mirror of
https://codeberg.org/Mercury-IM/Smack
synced 2024-12-23 04:57:58 +01:00
[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
This commit is contained in:
parent
8e08a8ba4a
commit
d3ce024c79
17 changed files with 1729 additions and 1 deletions
BIN
jingle/extension/build/merge/javapng-2.0-rc6.jar
Normal file
BIN
jingle/extension/build/merge/javapng-2.0-rc6.jar
Normal file
Binary file not shown.
|
@ -0,0 +1,106 @@
|
|||
/**
|
||||
* $RCSfile$
|
||||
* $Revision: $
|
||||
* $Date: 25/12/2006
|
||||
* <p/>
|
||||
* Copyright 2003-2006 Jive Software.
|
||||
* <p/>
|
||||
* 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
|
||||
* <p/>
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* <p/>
|
||||
* 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<PayloadType> payloads = new ArrayList<PayloadType>();
|
||||
|
||||
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<PayloadType> 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;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,184 @@
|
|||
/**
|
||||
* $RCSfile$
|
||||
* $Revision: $
|
||||
* $Date: 08/11/2006
|
||||
* <p/>
|
||||
* Copyright 2003-2006 Jive Software.
|
||||
* <p/>
|
||||
* 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
|
||||
* <p/>
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* <p/>
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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...";
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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 );
|
||||
}
|
||||
|
|
@ -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) {
|
||||
|
|
|
@ -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() {
|
||||
|
|
Loading…
Reference in a new issue