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() {