mirror of
https://github.com/vanitasvitae/Smack.git
synced 2024-11-29 15:32:06 +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());
|
System.out.println(valCounter());
|
||||||
|
|
||||||
assertTrue(valCounter() == 8);
|
assertTrue(valCounter() == 8);
|
||||||
//Thread.sleep(15000);
|
|
||||||
|
Thread.sleep(15000);
|
||||||
|
|
||||||
}
|
}
|
||||||
catch (Exception e) {
|
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.jmf.AudioChannel;
|
||||||
import org.jivesoftware.smackx.jingle.mediaimpl.jspeex.SpeexMediaManager;
|
import org.jivesoftware.smackx.jingle.mediaimpl.jspeex.SpeexMediaManager;
|
||||||
import org.jivesoftware.smackx.jingle.mediaimpl.multi.MultiMediaManager;
|
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.JingleSessionRequestListener;
|
||||||
import org.jivesoftware.smackx.jingle.listeners.JingleSessionStateListener;
|
import org.jivesoftware.smackx.jingle.listeners.JingleSessionStateListener;
|
||||||
import org.jivesoftware.smackx.jingle.media.JingleMediaManager;
|
import org.jivesoftware.smackx.jingle.media.JingleMediaManager;
|
||||||
|
@ -270,6 +271,61 @@ public class JingleMediaTest extends SmackTestCase {
|
||||||
e.printStackTrace();
|
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() {
|
public void testCompleteWithBridge() {
|
||||||
|
|
Loading…
Reference in a new issue