diff --git a/jingle/extension/build/merge/javapng-2.0-rc6.jar b/jingle/extension/build/merge/javapng-2.0-rc6.jar new file mode 100644 index 000000000..f622b37c9 Binary files /dev/null and b/jingle/extension/build/merge/javapng-2.0-rc6.jar differ diff --git a/jingle/extension/source/org/jivesoftware/smackx/jingle/mediaimpl/sshare/ScreenShareMediaManager.java b/jingle/extension/source/org/jivesoftware/smackx/jingle/mediaimpl/sshare/ScreenShareMediaManager.java new file mode 100644 index 000000000..4e1270dd4 --- /dev/null +++ b/jingle/extension/source/org/jivesoftware/smackx/jingle/mediaimpl/sshare/ScreenShareMediaManager.java @@ -0,0 +1,106 @@ +/** + * $RCSfile$ + * $Revision: $ + * $Date: 25/12/2006 + *

+ * Copyright 2003-2006 Jive Software. + *

+ * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * http://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jivesoftware.smackx.jingle.mediaimpl.sshare; + +import org.jivesoftware.smackx.jingle.media.JingleMediaManager; +import org.jivesoftware.smackx.jingle.media.JingleMediaSession; +import org.jivesoftware.smackx.jingle.media.PayloadType; +import org.jivesoftware.smackx.jingle.mediaimpl.sshare.api.ImageEncoder; +import org.jivesoftware.smackx.jingle.mediaimpl.sshare.api.ImageDecoder; +import org.jivesoftware.smackx.jingle.nat.TransportCandidate; + +import java.util.ArrayList; +import java.util.List; + +/** + * Implements a JingleMediaManager for ScreenSharing. + * It currently uses an Audio payload Type. Which needs to be fixed in the next version. + * + * @author Thiago Camargo + */ + +public class ScreenShareMediaManager extends JingleMediaManager { + + private List payloads = new ArrayList(); + + private ImageDecoder decoder = null; + private ImageEncoder encoder = null; + + public ScreenShareMediaManager() { + setupPayloads(); + } + + /** + * Setup API supported Payloads + */ + private void setupPayloads() { + payloads.add(new PayloadType.Audio(30, "sshare")); + } + + /** + * Return all supported Payloads for this Manager. + * + * @return The Payload List + */ + public List getPayloads() { + return payloads; + } + + /** + * Returns a new JingleMediaSession + * + * @param payloadType payloadType + * @param remote remote Candidate + * @param local local Candidate + * @return JingleMediaSession JingleMediaSession + */ + public JingleMediaSession createMediaSession(PayloadType payloadType, final TransportCandidate remote, final TransportCandidate local) { + ScreenShareSession session = null; + session = new ScreenShareSession(payloadType, remote, local, "Screen"); + if (encoder != null) { + session.setEncoder(encoder); + } + if (decoder != null) { + session.setDecoder(decoder); + } + return session; + } + + public PayloadType getPreferredPayloadType() { + return super.getPreferredPayloadType(); + } + + public ImageDecoder getDecoder() { + return decoder; + } + + public void setDecoder(ImageDecoder decoder) { + this.decoder = decoder; + } + + public ImageEncoder getEncoder() { + return encoder; + } + + public void setEncoder(ImageEncoder encoder) { + this.encoder = encoder; + } +} diff --git a/jingle/extension/source/org/jivesoftware/smackx/jingle/mediaimpl/sshare/ScreenShareSession.java b/jingle/extension/source/org/jivesoftware/smackx/jingle/mediaimpl/sshare/ScreenShareSession.java new file mode 100644 index 000000000..b15bdd8fe --- /dev/null +++ b/jingle/extension/source/org/jivesoftware/smackx/jingle/mediaimpl/sshare/ScreenShareSession.java @@ -0,0 +1,184 @@ +/** + * $RCSfile$ + * $Revision: $ + * $Date: 08/11/2006 + *

+ * Copyright 2003-2006 Jive Software. + *

+ * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * http://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jivesoftware.smackx.jingle.mediaimpl.sshare; + +import org.jivesoftware.smackx.jingle.media.JingleMediaSession; +import org.jivesoftware.smackx.jingle.media.PayloadType; +import org.jivesoftware.smackx.jingle.mediaimpl.sshare.api.ImageDecoder; +import org.jivesoftware.smackx.jingle.mediaimpl.sshare.api.ImageEncoder; +import org.jivesoftware.smackx.jingle.mediaimpl.sshare.api.ImageReceiver; +import org.jivesoftware.smackx.jingle.mediaimpl.sshare.api.ImageTransmitter; +import org.jivesoftware.smackx.jingle.nat.TransportCandidate; + +import javax.swing.*; +import java.awt.*; +import java.io.IOException; +import java.net.InetAddress; +import java.net.ServerSocket; +import java.net.UnknownHostException; + +/** + * This Class implements a complete JingleMediaSession. + * It sould be used to transmit and receive captured images from the Display. + * This Class should be automaticly controlled by JingleSession. + * For better NAT Traversal support this implementation donīt support only receive or only transmit. + * To receive you MUST transmit. So the only implemented and functionally methods are startTransmit() and stopTransmit() + * + * @author Thiago Camargo + */ +public class ScreenShareSession extends JingleMediaSession { + + private ImageTransmitter transmitter = null; + private ImageReceiver receiver = null; + + /** + * Creates a org.jivesoftware.jingleaudio.jmf.AudioMediaSession with defined payload type, remote and local candidates + * + * @param payloadType Payload of the jmf + * @param remote the remote information. The candidate that the jmf will be sent to. + * @param local the local information. The candidate that will receive the jmf + * @param locator media locator + */ + public ScreenShareSession(final PayloadType payloadType, final TransportCandidate remote, + final TransportCandidate local, final String locator) { + super(payloadType, remote, local, "Screen"); + initialize(); + } + + /** + * Initialize the Audio Channel to make it able to send and receive audio + */ + public void initialize() { + + JFrame window = new JFrame(); + JPanel jp = new JPanel(); + window.add(jp); + + window.setLocation(0, 0); + window.setSize(400, 400); + + window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + + try { + receiver = new ImageReceiver(InetAddress.getByName("0.0.0.0"), getRemote().getPort(), getLocal().getPort(), 800, 600); + System.out.println("Receiving on:" + receiver.getLocalPort()); + } + catch (UnknownHostException e) { + e.printStackTrace(); + } + + jp.add(receiver); + receiver.setVisible(true); + window.setAlwaysOnTop(true); + window.setVisible(true); + + try { + InetAddress remote = InetAddress.getByName(getRemote().getIp()); + transmitter = new ImageTransmitter(receiver.getDatagramSocket(), remote, getRemote().getPort(), new Rectangle(0, 0, 800, 600)); + } + catch (Exception e) { + + } + + } + + /** + * Starts transmission and for NAT Traversal reasons start receiving also. + */ + public void startTrasmit() { + new Thread(transmitter).start(); + } + + /** + * Set transmit activity. If the active is true, the instance should trasmit. + * If it is set to false, the instance should pause transmit. + * + * @param active active state + */ + public void setTrasmit(boolean active) { + transmitter.setTransmit(true); + } + + /** + * For NAT Reasons this method does nothing. Use startTransmit() to start transmit and receive jmf + */ + public void startReceive() { + // Do nothing + } + + /** + * Stops transmission and for NAT Traversal reasons stop receiving also. + */ + public void stopTrasmit() { + + } + + /** + * For NAT Reasons this method does nothing. Use startTransmit() to start transmit and receive jmf + */ + public void stopReceive() { + // Do nothing + } + + /** + * Obtain a free port we can use. + * + * @return A free port number. + */ + protected int getFreePort() { + ServerSocket ss; + int freePort = 0; + + for (int i = 0; i < 10; i++) { + freePort = (int) (10000 + Math.round(Math.random() * 10000)); + freePort = freePort % 2 == 0 ? freePort : freePort + 1; + try { + ss = new ServerSocket(freePort); + freePort = ss.getLocalPort(); + ss.close(); + return freePort; + } + catch (IOException e) { + e.printStackTrace(); + } + } + try { + ss = new ServerSocket(0); + freePort = ss.getLocalPort(); + ss.close(); + } + catch (IOException e) { + e.printStackTrace(); + } + return freePort; + } + + public void setEncoder(ImageEncoder encoder) { + if (encoder != null) { + this.transmitter.setEncoder(encoder); + } + } + + public void setDecoder(ImageDecoder decoder) { + if (decoder != null) { + this.receiver.setDecoder(decoder); + } + } +} diff --git a/jingle/extension/source/org/jivesoftware/smackx/jingle/mediaimpl/sshare/api/AbstractBufferedImageOp.java b/jingle/extension/source/org/jivesoftware/smackx/jingle/mediaimpl/sshare/api/AbstractBufferedImageOp.java new file mode 100644 index 000000000..9b5c49fe7 --- /dev/null +++ b/jingle/extension/source/org/jivesoftware/smackx/jingle/mediaimpl/sshare/api/AbstractBufferedImageOp.java @@ -0,0 +1,98 @@ +/* +Copyright 2006 Jerry Huxtable + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package org.jivesoftware.smackx.jingle.mediaimpl.sshare.api; + +import java.awt.*; +import java.awt.geom.Point2D; +import java.awt.geom.Rectangle2D; +import java.awt.image.BufferedImage; +import java.awt.image.BufferedImageOp; +import java.awt.image.ColorModel; + +/** + * A convenience class which implements those methods of BufferedImageOp which are rarely changed. + */ +public abstract class AbstractBufferedImageOp implements BufferedImageOp, Cloneable { + + public BufferedImage createCompatibleDestImage(BufferedImage src, ColorModel dstCM) { + if ( dstCM == null ) + dstCM = src.getColorModel(); + return new BufferedImage(dstCM, dstCM.createCompatibleWritableRaster(src.getWidth(), src.getHeight()), dstCM.isAlphaPremultiplied(), null); + } + + public Rectangle2D getBounds2D( BufferedImage src ) { + return new Rectangle(0, 0, src.getWidth(), src.getHeight()); + } + + public Point2D getPoint2D( Point2D srcPt, Point2D dstPt ) { + if ( dstPt == null ) + dstPt = new Point2D.Double(); + dstPt.setLocation( srcPt.getX(), srcPt.getY() ); + return dstPt; + } + + public RenderingHints getRenderingHints() { + return null; + } + + /** + * A convenience method for getting ARGB pixels from an image. This tries to avoid the performance + * penalty of BufferedImage.getRGB unmanaging the image. + * @param image a BufferedImage object + * @param x the left edge of the pixel block + * @param y the right edge of the pixel block + * @param width the width of the pixel arry + * @param height the height of the pixel arry + * @param pixels the array to hold the returned pixels. May be null. + * @return the pixels + * @see #setRGB + */ + public int[] getRGB( BufferedImage image, int x, int y, int width, int height, int[] pixels ) { + int type = image.getType(); + if ( type == BufferedImage.TYPE_INT_ARGB || type == BufferedImage.TYPE_INT_RGB ) + return (int [])image.getRaster().getDataElements( x, y, width, height, pixels ); + return image.getRGB( x, y, width, height, pixels, 0, width ); + } + + /** + * A convenience method for setting ARGB pixels in an image. This tries to avoid the performance + * penalty of BufferedImage.setRGB unmanaging the image. + * @param image a BufferedImage object + * @param x the left edge of the pixel block + * @param y the right edge of the pixel block + * @param width the width of the pixel arry + * @param height the height of the pixel arry + * @param pixels the array of pixels to set + * @see #getRGB + */ + public void setRGB( BufferedImage image, int x, int y, int width, int height, int[] pixels ) { + int type = image.getType(); + if ( type == BufferedImage.TYPE_INT_ARGB || type == BufferedImage.TYPE_INT_RGB ) + image.getRaster().setDataElements( x, y, width, height, pixels ); + else + image.setRGB( x, y, width, height, pixels, 0, width ); + } + + public Object clone() { + try { + return super.clone(); + } + catch ( CloneNotSupportedException e ) { + return null; + } + } +} diff --git a/jingle/extension/source/org/jivesoftware/smackx/jingle/mediaimpl/sshare/api/DefaultDecoder.java b/jingle/extension/source/org/jivesoftware/smackx/jingle/mediaimpl/sshare/api/DefaultDecoder.java new file mode 100644 index 000000000..517e9cfa4 --- /dev/null +++ b/jingle/extension/source/org/jivesoftware/smackx/jingle/mediaimpl/sshare/api/DefaultDecoder.java @@ -0,0 +1,27 @@ +package org.jivesoftware.smackx.jingle.mediaimpl.sshare.api; + +import com.sixlegs.png.PngImage; + +import java.awt.image.BufferedImage; +import java.io.ByteArrayInputStream; +import java.io.IOException; + +/** + * Implements a default PNG Decoder + */ +public class DefaultDecoder implements ImageDecoder{ + + PngImage decoder = new PngImage(); + + public BufferedImage decode(ByteArrayInputStream stream) { + BufferedImage image = null; + try { + image = decoder.read(stream,true); + } + catch (IOException e) { + e.printStackTrace(); + // Do nothing + } + return image; + } +} diff --git a/jingle/extension/source/org/jivesoftware/smackx/jingle/mediaimpl/sshare/api/DefaultEncoder.java b/jingle/extension/source/org/jivesoftware/smackx/jingle/mediaimpl/sshare/api/DefaultEncoder.java new file mode 100644 index 000000000..fa118396e --- /dev/null +++ b/jingle/extension/source/org/jivesoftware/smackx/jingle/mediaimpl/sshare/api/DefaultEncoder.java @@ -0,0 +1,24 @@ +package org.jivesoftware.smackx.jingle.mediaimpl.sshare.api; + +import javax.imageio.ImageIO; +import java.awt.image.BufferedImage; +import java.io.ByteArrayOutputStream; +import java.io.IOException; + +/** + * Implements a default PNG Encoder + */ +public class DefaultEncoder implements ImageEncoder{ + + public ByteArrayOutputStream encode(BufferedImage bufferedImage) { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + try { + ImageIO.write(bufferedImage, "png", baos); + } + catch (IOException e) { + e.printStackTrace(); + baos = null; + } + return baos; + } +} diff --git a/jingle/extension/source/org/jivesoftware/smackx/jingle/mediaimpl/sshare/api/ImageDecoder.java b/jingle/extension/source/org/jivesoftware/smackx/jingle/mediaimpl/sshare/api/ImageDecoder.java new file mode 100644 index 000000000..1d71c02e4 --- /dev/null +++ b/jingle/extension/source/org/jivesoftware/smackx/jingle/mediaimpl/sshare/api/ImageDecoder.java @@ -0,0 +1,13 @@ +package org.jivesoftware.smackx.jingle.mediaimpl.sshare.api; + +import java.awt.image.BufferedImage; +import java.io.ByteArrayInputStream; + +/** + * Image Decoder Interface use this interface if you want to change the default decoder + * + * @author Thiago Rocha Camargo + */ +public interface ImageDecoder { + public BufferedImage decode(ByteArrayInputStream stream); +} diff --git a/jingle/extension/source/org/jivesoftware/smackx/jingle/mediaimpl/sshare/api/ImageEncoder.java b/jingle/extension/source/org/jivesoftware/smackx/jingle/mediaimpl/sshare/api/ImageEncoder.java new file mode 100644 index 000000000..eb8ee8fdf --- /dev/null +++ b/jingle/extension/source/org/jivesoftware/smackx/jingle/mediaimpl/sshare/api/ImageEncoder.java @@ -0,0 +1,13 @@ +package org.jivesoftware.smackx.jingle.mediaimpl.sshare.api; + +import java.awt.image.BufferedImage; +import java.io.ByteArrayOutputStream; + +/** + * Image Encoder Interface use this interface if you want to change the default encoder + * + * @author Thiago Rocha Camargo + */ +public interface ImageEncoder { + public ByteArrayOutputStream encode(BufferedImage bufferedImage); +} diff --git a/jingle/extension/source/org/jivesoftware/smackx/jingle/mediaimpl/sshare/api/ImageReceiver.java b/jingle/extension/source/org/jivesoftware/smackx/jingle/mediaimpl/sshare/api/ImageReceiver.java new file mode 100644 index 000000000..6e2e56d47 --- /dev/null +++ b/jingle/extension/source/org/jivesoftware/smackx/jingle/mediaimpl/sshare/api/ImageReceiver.java @@ -0,0 +1,145 @@ +package org.jivesoftware.smackx.jingle.mediaimpl.sshare.api; + +import java.awt.*; +import java.awt.image.BufferedImage; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.net.DatagramPacket; +import java.net.DatagramSocket; +import java.net.InetAddress; +import java.net.SocketException; + +/** + * UDP Image Receiver. + * It uses PNG Tiles into UDP packets. + * + * @author Thiago Rocha Camargo + */ +public class ImageReceiver extends Canvas { + + private boolean on = true; + private DatagramSocket socket; + private BufferedImage tiles[][]; + private static final int tileWidth = ImageTransmitter.tileWidth; + private InetAddress localHost; + private InetAddress remoteHost; + private int localPort; + private int remotePort; + private ImageDecoder decoder; + + public ImageReceiver(final InetAddress remoteHost, final int remotePort, final int localPort, int width, int height) { + tiles = new BufferedImage[width][height]; + + try { + + socket = new DatagramSocket(localPort); + localHost = socket.getLocalAddress(); + this.remoteHost = remoteHost; + this.remotePort = remotePort; + this.localPort = localPort; + this.decoder = new DefaultDecoder(); + + new Thread(new Runnable() { + public void run() { + byte buf[] = new byte[1024]; + DatagramPacket p = new DatagramPacket(buf, 1024); + try { + while (on) { + socket.receive(p); + + int length = p.getLength(); + + BufferedImage bufferedImage = decoder.decode(new ByteArrayInputStream(p.getData(), 0, length - 2)); + + if (bufferedImage != null) { + + int x = p.getData()[length - 2]; + int y = p.getData()[length - 1]; + + drawTile(x, y, bufferedImage); + + } + + } + } + catch (IOException e) { + e.printStackTrace(); + } + } + }).start(); + + new Thread(new Runnable() { + public void run() { + byte buf[] = new byte[1024]; + DatagramPacket p = new DatagramPacket(buf, 1024); + try { + while (on) { + + p.setAddress(remoteHost); + p.setPort(remotePort); + socket.send(p); + + try { + Thread.sleep(1000); + } + catch (InterruptedException e) { + e.printStackTrace(); + } + + } + } + catch (IOException e) { + e.printStackTrace(); + } + } + }).start(); + + } + catch (SocketException e) { + e.printStackTrace(); + } + this.setSize(width, height); + } + + public InetAddress getLocalHost() { + return localHost; + } + + public InetAddress getRemoteHost() { + return remoteHost; + } + + public int getLocalPort() { + return localPort; + } + + public int getRemotePort() { + return remotePort; + } + + public DatagramSocket getDatagramSocket() { + return socket; + } + + public void drawTile(int x, int y, BufferedImage bufferedImage) { + tiles[x][y] = bufferedImage; + //repaint(x * tileWidth, y * tileWidth, tileWidth, tileWidth); + this.getGraphics().drawImage(bufferedImage, tileWidth * x, tileWidth * y, this); + } + + public void paint(Graphics g) { + for (int i = 0; i < tiles.length; i++) { + for (int j = 0; j < tiles[0].length; j++) { + g.drawImage(tiles[i][j], tileWidth * i, tileWidth * j, this); + } + } + } + + public ImageDecoder getDecoder() { + return decoder; + } + + public void setDecoder(ImageDecoder decoder) { + this.decoder = decoder; + } +} diff --git a/jingle/extension/source/org/jivesoftware/smackx/jingle/mediaimpl/sshare/api/ImageTransmitter.java b/jingle/extension/source/org/jivesoftware/smackx/jingle/mediaimpl/sshare/api/ImageTransmitter.java new file mode 100644 index 000000000..7845e5866 --- /dev/null +++ b/jingle/extension/source/org/jivesoftware/smackx/jingle/mediaimpl/sshare/api/ImageTransmitter.java @@ -0,0 +1,239 @@ +package org.jivesoftware.smackx.jingle.mediaimpl.sshare.api; + +import java.awt.*; +import java.awt.image.BufferedImage; +import java.awt.image.PixelGrabber; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.net.DatagramPacket; +import java.net.DatagramSocket; +import java.net.InetAddress; +import java.util.Arrays; + +/** + * UDP Image Receiver. + * It uses PNG Tiles into UDP packets. + * + * @author Thiago Rocha Camargo + */ +public class ImageTransmitter implements Runnable { + + private Robot robot; + private InetAddress localHost; + private InetAddress remoteHost; + private int localPort; + private int remotePort; + public static final int tileWidth = 25; + private boolean on = true; + private boolean transmit = false; + private DatagramSocket socket; + private Rectangle area; + private int tiles[][][]; + private int maxI; + private int maxJ; + private boolean changed = false; + private final Object sync = new Object(); + private ImageEncoder encoder; + + public ImageTransmitter(DatagramSocket socket, InetAddress remoteHost, int remotePort, Rectangle area) { + + try { + robot = new Robot(); + + maxI = (int) Math.ceil(area.getWidth() / tileWidth); + maxJ = (int) Math.ceil(area.getHeight() / tileWidth); + + tiles = new int[maxI][maxJ][tileWidth * tileWidth]; + + this.area = area; + this.socket = socket; + localHost = socket.getLocalAddress(); + localPort = socket.getLocalPort(); + this.remoteHost = remoteHost; + this.remotePort = remotePort; + this.encoder = new DefaultEncoder(); + + transmit = true; + + } + catch (AWTException e) { + e.printStackTrace(); + } + + } + + public void start() { + byte buf[] = new byte[1024]; + final DatagramPacket p = new DatagramPacket(buf, 1024); + + /* + new Thread( + new Runnable() { + public void run() { + + int w = (int) area.getWidth(); + int h = (int) area.getHeight(); + + int tiles[][][] = new int[maxI][maxJ][tileWidth * tileWidth]; + + while (on) { + if (transmit) { + + boolean differ = false; + + BufferedImage capture = robot.createScreenCapture(area); + + //ColorSpace cs = ColorSpace.getInstance(ColorSpace.CS_GRAY); + //ColorConvertOp op = new ColorConvertOp(cs, null); + //capture = op.filter(capture, null); + + QuantizeFilter filter = new QuantizeFilter(); + capture = filter.filter(capture, null); + + long trace = System.currentTimeMillis(); + + for (int i = 0; i < maxI; i++) { + for (int j = 0; j < maxJ; j++) { + + final BufferedImage bufferedImage = capture.getSubimage(i * tileWidth, j * tileWidth, tileWidth, tileWidth); + + int pixels[] = new int[tileWidth * tileWidth]; + + PixelGrabber pg = new PixelGrabber(bufferedImage, 0, 0, tileWidth, tileWidth, pixels, 0, tileWidth); + + try { + if (pg.grabPixels()) { + if (!differ) { + if (!Arrays.equals(tiles[i][j], pixels)) { + differ = true; + } + } + tiles[i][j] = pixels; + } + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + } + if (differ) { + synchronized (sync) { + changed = true; + } + } + + trace = (System.currentTimeMillis() - trace); + System.err.println("Loop Time:" + trace); + + if (trace < 250) { + try { + Thread.sleep(250); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + } + + } + } + } + ).start(); + */ + + while (on) { + if (transmit) { + + BufferedImage capture = robot.createScreenCapture(area); + + //ColorSpace cs = ColorSpace.getInstance(ColorSpace.CS_GRAY); + //ColorConvertOp op = new ColorConvertOp(cs, null); + //capture = op.filter(capture, null); + + QuantizeFilter filter = new QuantizeFilter(); + capture = filter.filter(capture, null); + + long trace = System.currentTimeMillis(); + + for (int i = 0; i < maxI; i++) { + for (int j = 0; j < maxJ; j++) { + + final BufferedImage bufferedImage = capture.getSubimage(i * tileWidth, j * tileWidth, tileWidth, tileWidth); + + int pixels[] = new int[tileWidth * tileWidth]; + + PixelGrabber pg = new PixelGrabber(bufferedImage, 0, 0, tileWidth, tileWidth, pixels, 0, tileWidth); + + try { + if (pg.grabPixels()) { + + if (!Arrays.equals(tiles[i][j], pixels)) { + + ByteArrayOutputStream baos = encoder.encode(bufferedImage); + + if (baos != null) { + + Thread.sleep(1); + + baos.write(i); + baos.write(j); + + byte[] bytesOut = baos.toByteArray(); + + if (bytesOut.length > 400) + System.err.println(bytesOut.length); + + p.setData(bytesOut); + p.setAddress(remoteHost); + p.setPort(remotePort); + + try { + socket.send(p); + } + catch (IOException e) { + e.printStackTrace(); + } + + tiles[i][j] = pixels; + + } + + } + + } + } + catch (InterruptedException e) { + e.printStackTrace(); + } + } + } + + trace = (System.currentTimeMillis() - trace); + System.out.println("Loop Time:" + trace); + + if (trace < 1000) { + try { + Thread.sleep(1000 - trace); + } + catch (InterruptedException e) { + e.printStackTrace(); + } + } + } + } + } + + public void run() { + start(); + } + + public void setTransmit(boolean transmit) { + this.transmit = transmit; + } + + public ImageEncoder getEncoder() { + return encoder; + } + + public void setEncoder(ImageEncoder encoder) { + this.encoder = encoder; + } +} diff --git a/jingle/extension/source/org/jivesoftware/smackx/jingle/mediaimpl/sshare/api/OctTreeQuantizer.java b/jingle/extension/source/org/jivesoftware/smackx/jingle/mediaimpl/sshare/api/OctTreeQuantizer.java new file mode 100644 index 000000000..9d33eb187 --- /dev/null +++ b/jingle/extension/source/org/jivesoftware/smackx/jingle/mediaimpl/sshare/api/OctTreeQuantizer.java @@ -0,0 +1,282 @@ +/* +Copyright 2006 Jerry Huxtable + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package org.jivesoftware.smackx.jingle.mediaimpl.sshare.api; + +import java.io.PrintStream; +import java.util.Vector; + +/** + * An image Quantizer based on the Octree algorithm. This is a very basic implementation + * at present and could be much improved by picking the nodes to reduce more carefully + * (i.e. not completely at random) when I get the time. + */ +public class OctTreeQuantizer implements Quantizer { + + /** + * The greatest depth the tree is allowed to reach + */ + final static int MAX_LEVEL = 5; + + /** + * An Octtree node. + */ + class OctTreeNode { + int children; + int level; + OctTreeNode parent; + OctTreeNode leaf[] = new OctTreeNode[8]; + boolean isLeaf; + int count; + int totalRed; + int totalGreen; + int totalBlue; + int index; + + /** + * A debugging method which prints the tree out. + */ + public void list(PrintStream s, int level) { + for (int i = 0; i < level; i++) + System.out.print(' '); + if (count == 0) + System.out.println(index+": count="+count); + else + System.out.println(index+": count="+count+" red="+(totalRed/count)+" green="+(totalGreen/count)+" blue="+(totalBlue/count)); + for (int i = 0; i < 8; i++) + if (leaf[i] != null) + leaf[i].list(s, level+2); + } + } + + private int nodes = 0; + private OctTreeNode root; + private int reduceColors; + private int maximumColors; + private int colors = 0; + private Vector[] colorList; + + public OctTreeQuantizer() { + setup(256); + colorList = new Vector[MAX_LEVEL+1]; + for (int i = 0; i < MAX_LEVEL+1; i++) + colorList[i] = new Vector(); + root = new OctTreeNode(); + } + + /** + * Initialize the quantizer. This should be called before adding any pixels. + * @param numColors the number of colors we're quantizing to. + */ + public void setup(int numColors) { + maximumColors = numColors; + reduceColors = Math.max(512, numColors * 2); + } + + /** + * Add pixels to the quantizer. + * @param pixels the array of ARGB pixels + * @param offset the offset into the array + * @param count the count of pixels + */ + public void addPixels(int[] pixels, int offset, int count) { + for (int i = 0; i < count; i++) { + insertColor(pixels[i+offset]); + if (colors > reduceColors) + reduceTree(reduceColors); + } + } + + /** + * Get the color table index for a color. + * @param rgb the color + * @return the index + */ + public int getIndexForColor(int rgb) { + int red = (rgb >> 16) & 0xff; + int green = (rgb >> 8) & 0xff; + int blue = rgb & 0xff; + + OctTreeNode node = root; + + for (int level = 0; level <= MAX_LEVEL; level++) { + OctTreeNode child; + int bit = 0x80 >> level; + + int index = 0; + if ((red & bit) != 0) + index += 4; + if ((green & bit) != 0) + index += 2; + if ((blue & bit) != 0) + index += 1; + + child = node.leaf[index]; + + if (child == null) + return node.index; + else if (child.isLeaf) + return child.index; + else + node = child; + } + System.out.println("getIndexForColor failed"); + return 0; + } + + private void insertColor(int rgb) { + int red = (rgb >> 16) & 0xff; + int green = (rgb >> 8) & 0xff; + int blue = rgb & 0xff; + + OctTreeNode node = root; + +// System.out.println("insertColor="+Integer.toHexString(rgb)); + for (int level = 0; level <= MAX_LEVEL; level++) { + OctTreeNode child; + int bit = 0x80 >> level; + + int index = 0; + if ((red & bit) != 0) + index += 4; + if ((green & bit) != 0) + index += 2; + if ((blue & bit) != 0) + index += 1; + + child = node.leaf[index]; + + if (child == null) { + node.children++; + + child = new OctTreeNode(); + child.parent = node; + node.leaf[index] = child; + node.isLeaf = false; + nodes++; + colorList[level].addElement(child); + + if (level == MAX_LEVEL) { + child.isLeaf = true; + child.count = 1; + child.totalRed = red; + child.totalGreen = green; + child.totalBlue = blue; + child.level = level; + colors++; + return; + } + + node = child; + } else if (child.isLeaf) { + child.count++; + child.totalRed += red; + child.totalGreen += green; + child.totalBlue += blue; + return; + } else + node = child; + } + System.out.println("insertColor failed"); + } + + private void reduceTree(int numColors) { + for (int level = MAX_LEVEL-1; level >= 0; level--) { + Vector v = colorList[level]; + if (v != null && v.size() > 0) { + for (int j = 0; j < v.size(); j++) { + OctTreeNode node = (OctTreeNode)v.elementAt(j); + if (node.children > 0) { + for (int i = 0; i < 8; i++) { + OctTreeNode child = node.leaf[i]; + if (child != null) { + if (!child.isLeaf) + System.out.println("not a leaf!"); + node.count += child.count; + node.totalRed += child.totalRed; + node.totalGreen += child.totalGreen; + node.totalBlue += child.totalBlue; + node.leaf[i] = null; + node.children--; + colors--; + nodes--; + colorList[level+1].removeElement(child); + } + } + node.isLeaf = true; + colors++; + if (colors <= numColors) + return; + } + } + } + } + + System.out.println("Unable to reduce the OctTree"); + } + + /** + * Build the color table. + * @return the color table + */ + public int[] buildColorTable() { + int[] table = new int[colors]; + buildColorTable(root, table, 0); + return table; + } + + /** + * A quick way to use the quantizer. Just create a table the right size and pass in the pixels. + * @param inPixels the input colors + * @param table the output color table + */ + public void buildColorTable(int[] inPixels, int[] table) { + int count = inPixels.length; + maximumColors = table.length; + for (int i = 0; i < count; i++) { + insertColor(inPixels[i]); + if (colors > reduceColors) + reduceTree(reduceColors); + } + if (colors > maximumColors) + reduceTree(maximumColors); + buildColorTable(root, table, 0); + } + + private int buildColorTable(OctTreeNode node, int[] table, int index) { + if (colors > maximumColors) + reduceTree(maximumColors); + + if (node.isLeaf) { + int count = node.count; + table[index] = 0xff000000 | + ((node.totalRed/count) << 16) | + ((node.totalGreen/count) << 8) | + node.totalBlue/count; + node.index = index++; + } else { + for (int i = 0; i < 8; i++) { + if (node.leaf[i] != null) { + node.index = index; + index = buildColorTable(node.leaf[i], table, index); + } + } + } + return index; + } + +} + diff --git a/jingle/extension/source/org/jivesoftware/smackx/jingle/mediaimpl/sshare/api/PixelUtils.java b/jingle/extension/source/org/jivesoftware/smackx/jingle/mediaimpl/sshare/api/PixelUtils.java new file mode 100644 index 000000000..73ffed4f3 --- /dev/null +++ b/jingle/extension/source/org/jivesoftware/smackx/jingle/mediaimpl/sshare/api/PixelUtils.java @@ -0,0 +1,223 @@ +/* +Copyright 2006 Jerry Huxtable + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package org.jivesoftware.smackx.jingle.mediaimpl.sshare.api; + +import java.awt.*; +import java.util.Random; + +/** + * Some more useful math functions for image processing. + * These are becoming obsolete as we move to Java2D. Use MiscComposite instead. + */ +public class PixelUtils { + + public final static int REPLACE = 0; + public final static int NORMAL = 1; + public final static int MIN = 2; + public final static int MAX = 3; + public final static int ADD = 4; + public final static int SUBTRACT = 5; + public final static int DIFFERENCE = 6; + public final static int MULTIPLY = 7; + public final static int HUE = 8; + public final static int SATURATION = 9; + public final static int VALUE = 10; + public final static int COLOR = 11; + public final static int SCREEN = 12; + public final static int AVERAGE = 13; + public final static int OVERLAY = 14; + public final static int CLEAR = 15; + public final static int EXCHANGE = 16; + public final static int DISSOLVE = 17; + public final static int DST_IN = 18; + public final static int ALPHA = 19; + public final static int ALPHA_TO_GRAY = 20; + + private static Random randomGenerator = new Random(); + + /** + * Clamp a value to the range 0..255 + */ + public static int clamp(int c) { + if (c < 0) + return 0; + if (c > 255) + return 255; + return c; + } + + public static int interpolate(int v1, int v2, float f) { + return clamp((int)(v1+f*(v2-v1))); + } + + public static int brightness(int rgb) { + int r = (rgb >> 16) & 0xff; + int g = (rgb >> 8) & 0xff; + int b = rgb & 0xff; + return (r+g+b)/3; + } + + public static boolean nearColors(int rgb1, int rgb2, int tolerance) { + int r1 = (rgb1 >> 16) & 0xff; + int g1 = (rgb1 >> 8) & 0xff; + int b1 = rgb1 & 0xff; + int r2 = (rgb2 >> 16) & 0xff; + int g2 = (rgb2 >> 8) & 0xff; + int b2 = rgb2 & 0xff; + return Math.abs(r1-r2) <= tolerance && Math.abs(g1-g2) <= tolerance && Math.abs(b1-b2) <= tolerance; + } + + private final static float hsb1[] = new float[3];//FIXME-not thread safe + private final static float hsb2[] = new float[3];//FIXME-not thread safe + + // Return rgb1 painted onto rgb2 + public static int combinePixels(int rgb1, int rgb2, int op) { + return combinePixels(rgb1, rgb2, op, 0xff); + } + + public static int combinePixels(int rgb1, int rgb2, int op, int extraAlpha, int channelMask) { + return (rgb2 & ~channelMask) | combinePixels(rgb1 & channelMask, rgb2, op, extraAlpha); + } + + public static int combinePixels(int rgb1, int rgb2, int op, int extraAlpha) { + if (op == REPLACE) + return rgb1; + int a1 = (rgb1 >> 24) & 0xff; + int r1 = (rgb1 >> 16) & 0xff; + int g1 = (rgb1 >> 8) & 0xff; + int b1 = rgb1 & 0xff; + int a2 = (rgb2 >> 24) & 0xff; + int r2 = (rgb2 >> 16) & 0xff; + int g2 = (rgb2 >> 8) & 0xff; + int b2 = rgb2 & 0xff; + + switch (op) { + case NORMAL: + break; + case MIN: + r1 = Math.min(r1, r2); + g1 = Math.min(g1, g2); + b1 = Math.min(b1, b2); + break; + case MAX: + r1 = Math.max(r1, r2); + g1 = Math.max(g1, g2); + b1 = Math.max(b1, b2); + break; + case ADD: + r1 = clamp(r1+r2); + g1 = clamp(g1+g2); + b1 = clamp(b1+b2); + break; + case SUBTRACT: + r1 = clamp(r2-r1); + g1 = clamp(g2-g1); + b1 = clamp(b2-b1); + break; + case DIFFERENCE: + r1 = clamp(Math.abs(r1-r2)); + g1 = clamp(Math.abs(g1-g2)); + b1 = clamp(Math.abs(b1-b2)); + break; + case MULTIPLY: + r1 = clamp(r1*r2/255); + g1 = clamp(g1*g2/255); + b1 = clamp(b1*b2/255); + break; + case DISSOLVE: + if ((randomGenerator.nextInt() & 0xff) <= a1) { + r1 = r2; + g1 = g2; + b1 = b2; + } + break; + case AVERAGE: + r1 = (r1+r2)/2; + g1 = (g1+g2)/2; + b1 = (b1+b2)/2; + break; + case HUE: + case SATURATION: + case VALUE: + case COLOR: + Color.RGBtoHSB(r1, g1, b1, hsb1); + Color.RGBtoHSB(r2, g2, b2, hsb2); + switch (op) { + case HUE: + hsb2[0] = hsb1[0]; + break; + case SATURATION: + hsb2[1] = hsb1[1]; + break; + case VALUE: + hsb2[2] = hsb1[2]; + break; + case COLOR: + hsb2[0] = hsb1[0]; + hsb2[1] = hsb1[1]; + break; + } + rgb1 = Color.HSBtoRGB(hsb2[0], hsb2[1], hsb2[2]); + r1 = (rgb1 >> 16) & 0xff; + g1 = (rgb1 >> 8) & 0xff; + b1 = rgb1 & 0xff; + break; + case SCREEN: + r1 = 255 - ((255 - r1) * (255 - r2)) / 255; + g1 = 255 - ((255 - g1) * (255 - g2)) / 255; + b1 = 255 - ((255 - b1) * (255 - b2)) / 255; + break; + case OVERLAY: + int m, s; + s = 255 - ((255 - r1) * (255 - r2)) / 255; + m = r1 * r2 / 255; + r1 = (s * r1 + m * (255 - r1)) / 255; + s = 255 - ((255 - g1) * (255 - g2)) / 255; + m = g1 * g2 / 255; + g1 = (s * g1 + m * (255 - g1)) / 255; + s = 255 - ((255 - b1) * (255 - b2)) / 255; + m = b1 * b2 / 255; + b1 = (s * b1 + m * (255 - b1)) / 255; + break; + case CLEAR: + r1 = g1 = b1 = 0xff; + break; + case DST_IN: + r1 = clamp((r2*a1)/255); + g1 = clamp((g2*a1)/255); + b1 = clamp((b2*a1)/255); + a1 = clamp((a2*a1)/255); + return (a1 << 24) | (r1 << 16) | (g1 << 8) | b1; + case ALPHA: + a1 = a1*a2/255; + return (a1 << 24) | (r2 << 16) | (g2 << 8) | b2; + case ALPHA_TO_GRAY: + int na = 255-a1; + return (a1 << 24) | (na << 16) | (na << 8) | na; + } + if (extraAlpha != 0xff || a1 != 0xff) { + a1 = a1*extraAlpha/255; + int a3 = (255-a1)*a2/255; + r1 = clamp((r1*a1+r2*a3)/255); + g1 = clamp((g1*a1+g2*a3)/255); + b1 = clamp((b1*a1+b2*a3)/255); + a1 = clamp(a1+a3); + } + return (a1 << 24) | (r1 << 16) | (g1 << 8) | b1; + } + +} diff --git a/jingle/extension/source/org/jivesoftware/smackx/jingle/mediaimpl/sshare/api/QuantizeFilter.java b/jingle/extension/source/org/jivesoftware/smackx/jingle/mediaimpl/sshare/api/QuantizeFilter.java new file mode 100644 index 000000000..61de3ea4f --- /dev/null +++ b/jingle/extension/source/org/jivesoftware/smackx/jingle/mediaimpl/sshare/api/QuantizeFilter.java @@ -0,0 +1,178 @@ +/* +Copyright 2006 Jerry Huxtable + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package org.jivesoftware.smackx.jingle.mediaimpl.sshare.api; + +import java.awt.*; + +/** + * A filter which quantizes an image to a set number of colors - useful for producing + * images which are to be encoded using an index color model. The filter can perform + * Floyd-Steinberg error-diffusion dithering if required. At present, the quantization + * is done using an octtree algorithm but I eventually hope to add more quantization + * methods such as median cut. Note: at present, the filter produces an image which + * uses the RGB color model (because the application it was written for required it). + * I hope to extend it to produce an IndexColorModel by request. + */ +public class QuantizeFilter extends WholeImageFilter { + + /** + * Floyd-Steinberg dithering matrix. + */ + protected final static int[] matrix = { + 0, 0, 0, + 0, 0, 7, + 3, 5, 1, + }; + private int sum = 3+5+7+1; + + private boolean dither; + private int numColors = 256; + private boolean serpentine = true; + + /** + * Set the number of colors to quantize to. + * @param numColors the number of colors. The default is 256. + */ + public void setNumColors(int numColors) { + this.numColors = Math.min(Math.max(numColors, 8), 256); + } + + /** + * Get the number of colors to quantize to. + * @return the number of colors. + */ + public int getNumColors() { + return numColors; + } + + /** + * Set whether to use dithering or not. If not, the image is posterized. + * @param dither true to use dithering + */ + public void setDither(boolean dither) { + this.dither = dither; + } + + /** + * Return the dithering setting + * @return the current setting + */ + public boolean getDither() { + return dither; + } + + /** + * Set whether to use a serpentine pattern for return or not. This can reduce 'avalanche' artifacts in the output. + * @param serpentine true to use serpentine pattern + */ + public void setSerpentine(boolean serpentine) { + this.serpentine = serpentine; + } + + /** + * Return the serpentine setting + * @return the current setting + */ + public boolean getSerpentine() { + return serpentine; + } + + public void quantize(int[] inPixels, int[] outPixels, int width, int height, int numColors, boolean dither, boolean serpentine) { + int count = width*height; + Quantizer quantizer = new OctTreeQuantizer(); + quantizer.setup(numColors); + quantizer.addPixels(inPixels, 0, count); + int[] table = quantizer.buildColorTable(); + + if (!dither) { + for (int i = 0; i < count; i++) + outPixels[i] = table[quantizer.getIndexForColor(inPixels[i])]; + } else { + int index = 0; + for (int y = 0; y < height; y++) { + boolean reverse = serpentine && (y & 1) == 1; + int direction; + if (reverse) { + index = y*width+width-1; + direction = -1; + } else { + index = y*width; + direction = 1; + } + for (int x = 0; x < width; x++) { + int rgb1 = inPixels[index]; + int rgb2 = table[quantizer.getIndexForColor(rgb1)]; + + outPixels[index] = rgb2; + + int r1 = (rgb1 >> 16) & 0xff; + int g1 = (rgb1 >> 8) & 0xff; + int b1 = rgb1 & 0xff; + + int r2 = (rgb2 >> 16) & 0xff; + int g2 = (rgb2 >> 8) & 0xff; + int b2 = rgb2 & 0xff; + + int er = r1-r2; + int eg = g1-g2; + int eb = b1-b2; + + for (int i = -1; i <= 1; i++) { + int iy = i+y; + if (0 <= iy && iy < height) { + for (int j = -1; j <= 1; j++) { + int jx = j+x; + if (0 <= jx && jx < width) { + int w; + if (reverse) + w = matrix[(i+1)*3-j+1]; + else + w = matrix[(i+1)*3+j+1]; + if (w != 0) { + int k = reverse ? index - j : index + j; + rgb1 = inPixels[k]; + r1 = (rgb1 >> 16) & 0xff; + g1 = (rgb1 >> 8) & 0xff; + b1 = rgb1 & 0xff; + r1 += er * w/sum; + g1 += eg * w/sum; + b1 += eb * w/sum; + inPixels[k] = (PixelUtils.clamp(r1) << 16) | (PixelUtils.clamp(g1) << 8) | PixelUtils.clamp(b1); + } + } + } + } + } + index += direction; + } + } + } + } + + protected int[] filterPixels( int width, int height, int[] inPixels, Rectangle transformedSpace ) { + int[] outPixels = new int[width*height]; + + quantize(inPixels, outPixels, width, height, numColors, dither, serpentine); + + return outPixels; + } + + public String toString() { + return "Colors/Quantize..."; + } + +} diff --git a/jingle/extension/source/org/jivesoftware/smackx/jingle/mediaimpl/sshare/api/Quantizer.java b/jingle/extension/source/org/jivesoftware/smackx/jingle/mediaimpl/sshare/api/Quantizer.java new file mode 100644 index 000000000..dd5745dfa --- /dev/null +++ b/jingle/extension/source/org/jivesoftware/smackx/jingle/mediaimpl/sshare/api/Quantizer.java @@ -0,0 +1,53 @@ +/* +Copyright 2006 Jerry Huxtable + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package org.jivesoftware.smackx.jingle.mediaimpl.sshare.api; + +/** + * The interface for an image quantizer. The addColor method is called (repeatedly + * if necessary) with all the image pixels. A color table can then be returned by + * calling the buildColorTable method. + */ +public interface Quantizer { + /** + * Initialize the quantizer. This should be called before adding any pixels. + * @param numColors the number of colors we're quantizing to. + */ + public void setup(int numColors); + + /** + * Add pixels to the quantizer. + * @param pixels the array of ARGB pixels + * @param offset the offset into the array + * @param count the count of pixels + */ + public void addPixels(int[] pixels, int offset, int count); + + /** + * Build a color table from the added pixels. + * @return an array of ARGB pixels representing a color table + */ + public int[] buildColorTable(); + + /** + * Using the previously-built color table, return the index into that table for a pixel. + * This is guaranteed to return a valid index - returning the index of a color closer + * to that requested if necessary. + * @param rgb the pixel to find + * @return the pixel's index in the color table + */ + public int getIndexForColor(int rgb); +} diff --git a/jingle/extension/source/org/jivesoftware/smackx/jingle/mediaimpl/sshare/api/WholeImageFilter.java b/jingle/extension/source/org/jivesoftware/smackx/jingle/mediaimpl/sshare/api/WholeImageFilter.java new file mode 100644 index 000000000..ec5dc33e0 --- /dev/null +++ b/jingle/extension/source/org/jivesoftware/smackx/jingle/mediaimpl/sshare/api/WholeImageFilter.java @@ -0,0 +1,86 @@ +/* +Copyright 2006 Jerry Huxtable + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package org.jivesoftware.smackx.jingle.mediaimpl.sshare.api; + +import java.awt.*; +import java.awt.image.BufferedImage; +import java.awt.image.ColorModel; +import java.awt.image.WritableRaster; + +/** + * A filter which acts as a superclass for filters which need to have the whole image in memory + * to do their stuff. + */ +public abstract class WholeImageFilter extends AbstractBufferedImageOp { + + /** + * The output image bounds. + */ + protected Rectangle transformedSpace; + + /** + * The input image bounds. + */ + protected Rectangle originalSpace; + + /** + * Construct a WholeImageFilter. + */ + public WholeImageFilter() { + } + + public BufferedImage filter( BufferedImage src, BufferedImage dst ) { + int width = src.getWidth(); + int height = src.getHeight(); + int type = src.getType(); + WritableRaster srcRaster = src.getRaster(); + + originalSpace = new Rectangle(0, 0, width, height); + transformedSpace = new Rectangle(0, 0, width, height); + transformSpace(transformedSpace); + + if ( dst == null ) { + ColorModel dstCM = src.getColorModel(); + dst = new BufferedImage(dstCM, dstCM.createCompatibleWritableRaster(transformedSpace.width, transformedSpace.height), dstCM.isAlphaPremultiplied(), null); + } + WritableRaster dstRaster = dst.getRaster(); + + int[] inPixels = getRGB( src, 0, 0, width, height, null ); + inPixels = filterPixels( width, height, inPixels, transformedSpace ); + setRGB( dst, 0, 0, transformedSpace.width, transformedSpace.height, inPixels ); + + return dst; + } + + /** + * Calculate output bounds for given input bounds. + * @param rect input and output rectangle + */ + protected void transformSpace(Rectangle rect) { + } + + /** + * Actually filter the pixels. + * @param width the image width + * @param height the image height + * @param inPixels the image pixels + * @param transformedSpace the output bounds + * @return the output pixels + */ + protected abstract int[] filterPixels( int width, int height, int[] inPixels, Rectangle transformedSpace ); +} + diff --git a/jingle/extension/test/org/jivesoftware/smackx/jingle/JingleManagerTest.java b/jingle/extension/test/org/jivesoftware/smackx/jingle/JingleManagerTest.java index 3895f18ce..6308bba9c 100644 --- a/jingle/extension/test/org/jivesoftware/smackx/jingle/JingleManagerTest.java +++ b/jingle/extension/test/org/jivesoftware/smackx/jingle/JingleManagerTest.java @@ -909,7 +909,8 @@ public class JingleManagerTest extends SmackTestCase { System.out.println(valCounter()); assertTrue(valCounter() == 8); - //Thread.sleep(15000); + + Thread.sleep(15000); } catch (Exception e) { diff --git a/jingle/extension/test/org/jivesoftware/smackx/jingle/JingleMediaTest.java b/jingle/extension/test/org/jivesoftware/smackx/jingle/JingleMediaTest.java index 1a701bf3b..e640c6cb4 100644 --- a/jingle/extension/test/org/jivesoftware/smackx/jingle/JingleMediaTest.java +++ b/jingle/extension/test/org/jivesoftware/smackx/jingle/JingleMediaTest.java @@ -26,6 +26,7 @@ import org.jivesoftware.smackx.jingle.mediaimpl.jmf.JmfMediaManager; import org.jivesoftware.smackx.jingle.mediaimpl.jmf.AudioChannel; import org.jivesoftware.smackx.jingle.mediaimpl.jspeex.SpeexMediaManager; import org.jivesoftware.smackx.jingle.mediaimpl.multi.MultiMediaManager; +import org.jivesoftware.smackx.jingle.mediaimpl.sshare.ScreenShareMediaManager; import org.jivesoftware.smackx.jingle.listeners.JingleSessionRequestListener; import org.jivesoftware.smackx.jingle.listeners.JingleSessionStateListener; import org.jivesoftware.smackx.jingle.media.JingleMediaManager; @@ -270,6 +271,61 @@ public class JingleMediaTest extends SmackTestCase { e.printStackTrace(); } + } + + public void testCompleteScreenShare() { + + try { + + //XMPPConnection.DEBUG_ENABLED = true; + + XMPPConnection x0 = getConnection(0); + XMPPConnection x1 = getConnection(1); + + final JingleManager jm0 = new JingleManager( + x0, new ICETransportManager(x0,"stun.xten.net",3478)); + final JingleManager jm1 = new JingleManager( + x1, new ICETransportManager(x1,"stun.xten.net",3478)); + + JingleMediaManager jingleMediaManager0 = new ScreenShareMediaManager(); + JingleMediaManager jingleMediaManager1 = new ScreenShareMediaManager(); + + jm0.setMediaManager(jingleMediaManager0); + jm1.setMediaManager(jingleMediaManager1); + + jm1.addJingleSessionRequestListener(new JingleSessionRequestListener() { + public void sessionRequested(final JingleSessionRequest request) { + + try { + + IncomingJingleSession session = request.accept(jm1.getMediaManager().getPayloads()); + + session.start(request); + } + catch (XMPPException e) { + e.printStackTrace(); + } + + } + }); + + OutgoingJingleSession js0 = jm0.createOutgoingJingleSession(x1.getUser()); + + js0.start(); + + Thread.sleep(150000); + js0.terminate(); + + Thread.sleep(6000); + + x0.disconnect(); + x1.disconnect(); + + } + catch (Exception e) { + e.printStackTrace(); + } + } public void testCompleteWithBridge() {