From d3ce024c79704dd38dbb31f240ddf68163cec2de Mon Sep 17 00:00:00 2001 From: Thiago Camargo Date: Wed, 2 May 2007 19:55:59 +0000 Subject: [PATCH] [SMACK-220] - New Jingle Based Screen Share API git-svn-id: http://svn.igniterealtime.org/svn/repos/smack/trunk@8144 b35dd754-fafc-0310-a699-88a17e54d16e --- .../extension/build/merge/javapng-2.0-rc6.jar | Bin 0 -> 29128 bytes .../sshare/ScreenShareMediaManager.java | 106 +++++++ .../mediaimpl/sshare/ScreenShareSession.java | 184 ++++++++++++ .../sshare/api/AbstractBufferedImageOp.java | 98 ++++++ .../mediaimpl/sshare/api/DefaultDecoder.java | 27 ++ .../mediaimpl/sshare/api/DefaultEncoder.java | 24 ++ .../mediaimpl/sshare/api/ImageDecoder.java | 13 + .../mediaimpl/sshare/api/ImageEncoder.java | 13 + .../mediaimpl/sshare/api/ImageReceiver.java | 145 +++++++++ .../sshare/api/ImageTransmitter.java | 239 +++++++++++++++ .../sshare/api/OctTreeQuantizer.java | 282 ++++++++++++++++++ .../mediaimpl/sshare/api/PixelUtils.java | 223 ++++++++++++++ .../mediaimpl/sshare/api/QuantizeFilter.java | 178 +++++++++++ .../mediaimpl/sshare/api/Quantizer.java | 53 ++++ .../sshare/api/WholeImageFilter.java | 86 ++++++ .../smackx/jingle/JingleManagerTest.java | 3 +- .../smackx/jingle/JingleMediaTest.java | 56 ++++ 17 files changed, 1729 insertions(+), 1 deletion(-) create mode 100644 jingle/extension/build/merge/javapng-2.0-rc6.jar create mode 100644 jingle/extension/source/org/jivesoftware/smackx/jingle/mediaimpl/sshare/ScreenShareMediaManager.java create mode 100644 jingle/extension/source/org/jivesoftware/smackx/jingle/mediaimpl/sshare/ScreenShareSession.java create mode 100644 jingle/extension/source/org/jivesoftware/smackx/jingle/mediaimpl/sshare/api/AbstractBufferedImageOp.java create mode 100644 jingle/extension/source/org/jivesoftware/smackx/jingle/mediaimpl/sshare/api/DefaultDecoder.java create mode 100644 jingle/extension/source/org/jivesoftware/smackx/jingle/mediaimpl/sshare/api/DefaultEncoder.java create mode 100644 jingle/extension/source/org/jivesoftware/smackx/jingle/mediaimpl/sshare/api/ImageDecoder.java create mode 100644 jingle/extension/source/org/jivesoftware/smackx/jingle/mediaimpl/sshare/api/ImageEncoder.java create mode 100644 jingle/extension/source/org/jivesoftware/smackx/jingle/mediaimpl/sshare/api/ImageReceiver.java create mode 100644 jingle/extension/source/org/jivesoftware/smackx/jingle/mediaimpl/sshare/api/ImageTransmitter.java create mode 100644 jingle/extension/source/org/jivesoftware/smackx/jingle/mediaimpl/sshare/api/OctTreeQuantizer.java create mode 100644 jingle/extension/source/org/jivesoftware/smackx/jingle/mediaimpl/sshare/api/PixelUtils.java create mode 100644 jingle/extension/source/org/jivesoftware/smackx/jingle/mediaimpl/sshare/api/QuantizeFilter.java create mode 100644 jingle/extension/source/org/jivesoftware/smackx/jingle/mediaimpl/sshare/api/Quantizer.java create mode 100644 jingle/extension/source/org/jivesoftware/smackx/jingle/mediaimpl/sshare/api/WholeImageFilter.java 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 0000000000000000000000000000000000000000..f622b37c9d15ffcf633dd148cb48b099c2bdb295 GIT binary patch literal 29128 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 F{|7 + * 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() {