Java tutorial
/** * Squidy Interaction Library is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of the License, * or (at your option) any later version. * * Squidy Interaction Library is distributed in the hope that it will be * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with Squidy Interaction Library. If not, see * <http://www.gnu.org/licenses/>. * * 2009 Human-Computer Interaction Group, University of Konstanz. * <http://hci.uni-konstanz.de> * * Please contact info@squidy-lib.de or visit our website * <http://www.squidy-lib.de> for further information. */ package org.squidy.nodes; import java.awt.AWTException; import java.awt.BorderLayout; import java.awt.Dimension; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.GraphicsConfiguration; import java.awt.GraphicsDevice; import java.awt.GraphicsEnvironment; import java.awt.Rectangle; import java.awt.Robot; import java.awt.geom.AffineTransform; import java.awt.image.BufferedImage; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.DataOutputStream; import java.io.IOException; import java.io.OutputStream; import java.net.Socket; import java.net.UnknownHostException; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import javax.imageio.IIOImage; import javax.imageio.ImageIO; import javax.imageio.ImageWriteParam; import javax.imageio.ImageWriter; import javax.imageio.stream.ImageOutputStream; import javax.swing.JComponent; import javax.swing.JFrame; import javax.xml.bind.annotation.XmlAttribute; import javax.xml.bind.annotation.XmlType; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.squidy.manager.ProcessException; import org.squidy.manager.controls.CheckBox; import org.squidy.manager.controls.Slider; import org.squidy.manager.controls.TextField; import org.squidy.manager.data.IData; import org.squidy.manager.data.Processor; import org.squidy.manager.data.Property; import org.squidy.manager.data.Throughput; import org.squidy.manager.data.Processor.Status; import org.squidy.manager.data.impl.DataObject; import org.squidy.manager.data.impl.DataPosition2D; import org.squidy.manager.model.AbstractNode; /** * <code>ScreenDispatcher</code>. * * <pre> * Date: Sep 1, 2008 * Time: 7:03:00 PM * </pre> * * @author Roman Rädle, <a * href="mailto:Roman.Raedle@uni-konstanz.de">Roman.Raedle@uni-konstanz.de</a>, University * of Konstanz * @version $Id: ScreenDispatcher.java 772 2011-09-16 15:39:44Z raedle $ * @since 1.1.0 */ @XmlType(name = "Screen Dispatcher") @Processor(name = "Screen Dispatcher", icon = "/org/squidy/nodes/image/48x48/screen-dispatcher.png", description = "/org/squidy/nodes/html/ScreenDispatcher.html", types = { Processor.Type.OUTPUT }, tags = { "screen", "dispatcher", "capture", "casting", "video" }, status = Status.UNSTABLE) public class ScreenDispatcher extends AbstractNode { // Logger to log info, error, debug,... messages. private static final Log LOG = LogFactory.getLog(ScreenDispatcher.class); // ################################################################################ // BEGIN OF ADJUSTABLES // ################################################################################ @XmlAttribute(name = "remote-address") @Property(name = "Remote address", description = "The address to the remote device.") @TextField private String remoteAddress = "141.14.248.74"; /** * @return the remoteAddress */ public final String getRemoteAddress() { return remoteAddress; } /** * @param remoteAddress * the remoteAddress to set */ public final void setRemoteAddress(String remoteAddress) { this.remoteAddress = remoteAddress; if (isProcessing()) { closeDispatcherSocket(); openDispatcherSocket(); } } @XmlAttribute(name = "remote-port") @Property(name = "Remote port", description = "The port to the remote device.") @TextField private int remotePort = 7777; /** * @return the remotePort */ public final int getRemotePort() { return remotePort; } /** * @param remotePort * the remotePort to set */ public final void setRemotePort(int remotePort) { this.remotePort = remotePort; if (isProcessing()) { closeDispatcherSocket(); openDispatcherSocket(); } } @XmlAttribute(name = "dispatch-position-x") @Property(name = "Dispatch position x", description = "The X position of the dispatched screen.") @TextField private int dispatchPositionX = 0; /** * @return the dispatchPositionX */ public final int getDispatchPositionX() { return dispatchPositionX; } /** * @param dispatchPositionX the dispatchPositionX to set */ public final void setDispatchPositionX(int dispatchPositionX) { this.dispatchPositionX = dispatchPositionX; } @XmlAttribute(name = "dispatch-position-y") @Property(name = "Dispatch position y", description = "The Y position of the dispatched screen.") @TextField private int dispatchPositionY = 0; /** * @return the dispatchPositionY */ public final int getDispatchPositionY() { return dispatchPositionY; } /** * @param dispatchPositionY the dispatchPositionY to set */ public final void setDispatchPositionY(int dispatchPositionY) { this.dispatchPositionY = dispatchPositionY; } @XmlAttribute(name = "dispatch-position-width") @Property(name = "Dispatch position width", description = "The width of the dispatched screen.") @TextField private int dispatchPositionWidth = 1280; /** * @return the dispatchPositionWidth */ public final int getDispatchPositionWidth() { return dispatchPositionWidth; } /** * @param dispatchPositionWidth the dispatchPositionWidth to set */ public final void setDispatchPositionWidth(int dispatchPositionWidth) { this.dispatchPositionWidth = dispatchPositionWidth; } @XmlAttribute(name = "dispatch-position-height") @Property(name = "Dispatch position height", description = "The height of the dispatched screen.") @TextField private int dispatchPositionHeight = 800; /** * @return the dispatchPositionHeight */ public final int getDispatchPositionHeight() { return dispatchPositionHeight; } /** * @param dispatchPositionHeight the dispatchPositionHeight to set */ public final void setDispatchPositionHeight(int dispatchPositionHeight) { this.dispatchPositionHeight = dispatchPositionHeight; } @XmlAttribute(name = "screen-width") @Property(name = "Screen width", description = "The screen width of the screen.") @TextField private int screenWidth = 1280; /** * @return the screenWidth */ public final int getScreenWidth() { return screenWidth; } /** * @param screenWidth the screenWidth to set */ public final void setScreenWidth(int screenWidth) { this.screenWidth = screenWidth; } @XmlAttribute(name = "screen-height") @Property(name = "Screen height", description = "The screen height of the screen.") @TextField private int screenHeight = 800; /** * @return the screenHeight */ public final int getScreenHeight() { return screenHeight; } /** * @param screenHeight the screenHeight to set */ public final void setScreenHeight(int screenHeight) { this.screenHeight = screenHeight; } @XmlAttribute(name = "remote-screen-width") @Property(name = "Remote screen width", description = "The screen width of the remote screen.") @TextField private int remoteScreenWidth = 320; /** * @return the remoteScreenWidth */ public final int getRemoteScreenWidth() { return remoteScreenWidth; } /** * @param remoteScreenWidth * the remoteScreenWidth to set */ public final void setRemoteScreenWidth(int remoteScreenWidth) { this.remoteScreenWidth = remoteScreenWidth; } @XmlAttribute(name = "remote-screen-height") @Property(name = "Remote screen height", description = "The screen height of the remote screen.") @TextField private int remoteScreenHeight = 480; /** * @return the remoteScreenHeight */ public final int getRemoteScreenHeight() { return remoteScreenHeight; } /** * @param remoteScreenHeight * the remoteScreenHeight to set */ public final void setRemoteScreenHeight(int remoteScreenHeight) { this.remoteScreenHeight = remoteScreenHeight; } @XmlAttribute(name = "scale-screen") @Property(name = "Scale screen", description = "Whether the screen should be scaled before transmitting the screen to the remote device..") @CheckBox private boolean scaleScreen = true; /** * @return the scaleScreen */ public final boolean isScaleScreen() { return scaleScreen; } /** * @param scaleScreen the scaleScreen to set */ public final void setScaleScreen(boolean scaleScreen) { this.scaleScreen = scaleScreen; } @XmlAttribute(name = "flip-screen") @Property(name = "Flip screen", description = "Whether the screen .") @CheckBox private boolean flipScreen = false; /** * @return the flipScreen */ public final boolean isFlipScreen() { return flipScreen; } /** * @param flipScreen * the flipScreen to set */ public final void setFlipScreen(boolean flipScreen) { this.flipScreen = flipScreen; } @XmlAttribute(name = "refresh-rate") @Property(name = "Refresh rate", description = "Indicates the refresh rate of the dispatcher.", suffix = "ms") @Slider(minimumValue = 0, maximumValue = 5000, minorTicks = 500, majorTicks = 2500, showTicks = true, showLabels = true) private int refreshRate = 500; /** * @return the refreshRate */ public final int getRefreshRate() { return refreshRate; } /** * @param refreshRate the refreshRate to set */ public final void setRefreshRate(int refreshRate) { this.refreshRate = refreshRate; } @XmlAttribute(name = "screen-capture-quality") @Property(name = "Screen capture quality", description = "Defines the quality a screen capture will be sent.") @Slider(minimumValue = 0, maximumValue = 100, minorTicks = 10, majorTicks = 25, showTicks = true, showLabels = true) private int screenCaptureQuality = 10; /** * @return the screenCaptureQuality */ public final int getScreenCaptureQuality() { return screenCaptureQuality; } /** * @param screenCaptureQuality the screenCaptureQuality to set */ public final void setScreenCaptureQuality(int screenCaptureQuality) { this.screenCaptureQuality = screenCaptureQuality; } @XmlAttribute(name = "show-dispatching-debug-window") @Property(name = "Show dispatching debug window", description = "Whether the dispatching debug window should be visible or not.") @CheckBox private boolean showDispatchingDebugWindow = false; /** * @return the showDispatchingDebugWindow */ public final boolean isShowDispatchingDebugWindow() { return showDispatchingDebugWindow; } /** * @param showDispatchingDebugWindow * the showDispatchingDebugWindow to set */ public final void setShowDispatchingDebugWindow(boolean showDispatchingDebugWindow) { this.showDispatchingDebugWindow = showDispatchingDebugWindow; if (isProcessing()) { if (showDispatchingDebugWindow) { showDispatchingDebugWindow(); } else { hideDispatchingDebugWindow(); } } } // ################################################################################ // END OF ADJUSTABLES // ################################################################################ private JFrame dispatchingDebugWindow; private ScreenCaptureDebugging screenCaptureDebugging; private Socket socket; private CaptureScreen captureScreen; /* (non-Javadoc) * @see org.squidy.manager.ReflectionProcessable#onStart() */ @Override public void onStart() throws ProcessException { if (showDispatchingDebugWindow) { showDispatchingDebugWindow(); } createCaptureScreen( new Rectangle(dispatchPositionX, dispatchPositionY, dispatchPositionWidth, dispatchPositionHeight)); openDispatcherSocket(); new Thread() { /* (non-Javadoc) * @see java.lang.Thread#run() */ @Override public void run() { while (isProcessing()) { try { process2(null); Thread.sleep(refreshRate); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } }.start(); } /* (non-Javadoc) * @see org.squidy.manager.ReflectionProcessable#onStop() */ @Override public void onStop() throws ProcessException { closeDispatcherSocket(); hideDispatchingDebugWindow(); disposeCaptureScreen(); } /** * @param bounds */ private void createCaptureScreen(Rectangle bounds) throws ProcessException { try { captureScreen = new CaptureScreen(bounds); } catch (AWTException e) { throw new ProcessException(e.getMessage(), e); } } /** * */ private void disposeCaptureScreen() { if (captureScreen != null) { captureScreen.dispose(); } } /** * */ private void openDispatcherSocket() { try { socket = new Socket(remoteAddress, remotePort); } catch (UnknownHostException e) { // throw new ProcessException("Could not initialize connection to screen capture receiver.", e); } catch (IOException e) { // throw new ProcessException("Could not initialize connection to screen capture receiver.", e); } } /** * */ private void closeDispatcherSocket() { if (socket != null) { try { socket.close(); } catch (IOException e) { throw new ProcessException("Could not close socket.", e); } socket = null; } } /** * */ private void reopenDispatcherSocket() { closeDispatcherSocket(); openDispatcherSocket(); } /** * */ private void showDispatchingDebugWindow() { if (dispatchingDebugWindow == null) { dispatchingDebugWindow = new JFrame("Dispatching Debug Window"); dispatchingDebugWindow.setLayout(new BorderLayout()); dispatchingDebugWindow.setSize(new Dimension(320, 480)); dispatchingDebugWindow.setPreferredSize(new Dimension(320, 480)); screenCaptureDebugging = new ScreenCaptureDebugging(); dispatchingDebugWindow.add(screenCaptureDebugging, BorderLayout.CENTER); dispatchingDebugWindow.setVisible(true); } } /** * */ private void hideDispatchingDebugWindow() { if (dispatchingDebugWindow != null) { dispatchingDebugWindow.setVisible(false); dispatchingDebugWindow.dispose(); dispatchingDebugWindow = null; } } /** * @param dataPosition2D * @return */ public IData process(DataPosition2D dataPosition2D) { double x = dataPosition2D.getX(); double y = dataPosition2D.getY(); double scaleX = 1.0 / screenWidth; double scaleY = 1.0 / screenHeight; x = (scaleX * dispatchPositionX) + (x * scaleX * dispatchPositionWidth); y = (scaleY * dispatchPositionY) + (y * scaleY * dispatchPositionHeight); dataPosition2D.setX(x); dataPosition2D.setY(y); return dataPosition2D; } /** * @param data * @return */ public IData process(IData data) { return data; } /** * @param data * @return * @throws ProcessException */ public IData process2(IData data) throws ProcessException { // TODO [RR]: This may causes an exception if squidy will be stopped but dispatching thread is still alive. if (captureScreen == null) { throw new ProcessException("Capture screen to capture a screenshot is null", data); } BufferedImage image = captureScreen.capture(); // Scale screen if set. if (scaleScreen) { image = scaleImageTo(image, remoteScreenWidth, remoteScreenHeight); } if (dispatchingDebugWindow != null) { screenCaptureDebugging.setImage(image); dispatchingDebugWindow.repaint(); } // Reopen dispatcher socket if connection has been closed. if (socket == null || socket.isClosed() || !socket.isConnected()) { if (LOG.isDebugEnabled()) { LOG.debug("Connection is closed. Trying to reconnect in 5000 ms."); } try { Thread.sleep(5000); } catch (InterruptedException e1) { throw new ProcessException(e1.getMessage(), e1); } reopenDispatcherSocket(); } else { try { float tmpScreenCaptureQuality = screenCaptureQuality / (float) 100; // System.out.println("QUAL: " + tmpScreenCaptureQuality); byte[] picture = ImageKit.getBytes(image, tmpScreenCaptureQuality); DataOutputStream stream = new DataOutputStream(socket.getOutputStream()); // byte[] picture = pictureStream.toByteArray(); // int v = picture.length; // System.out.println("LENGTH: " + v); // System.out.println((v >>> 24) & 0xFF); // System.out.println((v >>> 16) & 0xFF); // System.out.println((v >>> 8) & 0xFF); // System.out.println((v >>> 0) & 0xFF); // System.out.println("SIZE: " + picture.length); stream.writeInt(picture.length); stream.write(picture); } catch (IOException e) { closeDispatcherSocket(); if (LOG.isErrorEnabled()) { LOG.error(e.getMessage(), e); } } } // else { // if (LOG.isErrorEnabled()) { // LOG.error("Dispatching socket is not open or have been closed already."); // } //// throw new ProcessException("Dispatching socket is not open or have been closed already.", data); // } return data; } /** * Scales an image with pixel ratio awareness. * * @param image * The source image that will be used to scale the image. * @param width * The maximum width of the scaled image. * @param height * The maximum height of the scaled image. * @return Either the scaled image if its bigger than the source's width AND height or the * source image. */ /** * @param image * @param width * @param height * @return */ private BufferedImage scaleImageTo(BufferedImage image, int width, int height) { int imageWidth = image.getWidth(null); int imageHeight = image.getHeight(null); double scaleX = (double) width / imageWidth; double scaleY = (double) height / imageHeight; if (flipScreen) { scaleX = (double) width / imageHeight; scaleY = (double) height / imageWidth; } AffineTransform at = AffineTransform.getScaleInstance(scaleX, scaleY); if (flipScreen) { at.rotate(Math.PI / 2); at.translate(0, -imageHeight); } BufferedImage scaledImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); Graphics2D g2d = scaledImage.createGraphics(); g2d.drawImage(image, at, null); g2d.dispose(); return scaledImage; } @SuppressWarnings("serial") private class ScreenCaptureDebugging extends JComponent { private BufferedImage image; public void setImage(BufferedImage image) { if (this.image != null) { } this.image = image; } /* * (non-Javadoc) * * @see javax.swing.JComponent#paintComponent(java.awt.Graphics) */ @Override protected void paintComponent(Graphics g) { super.paintComponent(g); if (image != null) { g.drawImage(image, 0, 0, null); } } } /** * <code>CaptureScreen</code>. * * <pre> * Date: Oct 29, 2008 * Time: 4:24:14 PM * </pre> * * @author Roman Rädle, <a * href="mailto:Roman.Raedle@uni-konstanz.de">Roman.Raedle@uni-konstanz.de</a>, * University of Konstanz * @version $Id: ScreenDispatcher.java 772 2011-09-16 15:39:44Z raedle $ * @since 1.1.0 */ class CaptureScreen { private CaptureData[] captureData; private Rectangle bounds; public CaptureScreen(Rectangle bounds) throws AWTException { this.bounds = bounds; initializeCaptureData(bounds); } /** * @param bounds * @throws AWTException */ private void initializeCaptureData(Rectangle bounds) throws AWTException { List<CaptureData> data = new ArrayList<CaptureData>(); GraphicsEnvironment graphicsEnvironment = GraphicsEnvironment.getLocalGraphicsEnvironment(); GraphicsDevice[] graphicsDevices = graphicsEnvironment.getScreenDevices(); for (GraphicsDevice graphicsDevice : graphicsDevices) { GraphicsConfiguration graphicsConfiguration = graphicsDevice.getDefaultConfiguration(); Rectangle screenBounds = graphicsConfiguration.getBounds(); if (screenBounds.intersects(bounds)) { Rectangle intersectionBounds = screenBounds.intersection(bounds); data.add(new CaptureData(new Robot(graphicsDevice), intersectionBounds)); } } captureData = data.toArray(new CaptureData[data.size()]); } /** * @return */ public BufferedImage capture() { if (captureData != null) { BufferedImage image = new BufferedImage((int) bounds.getWidth(), (int) bounds.getHeight(), BufferedImage.TYPE_INT_RGB); for (CaptureData data : captureData) { data.captureToImage(image); } return image; } return null; } /** * */ public void dispose() { if (captureData != null) { for (CaptureData data : captureData) { data.dispose(); } } } } /** * <code>CaptureScreen</code>. * * <pre> * Date: Oct 29, 2008 * Time: 4:11:21 PM * </pre> * * @author Roman Rädle, <a * href="mailto:Roman.Raedle@uni-konstanz.de">Roman.Raedle@uni-konstanz.de</a>, * University of Konstanz * @version $Id: ScreenDispatcher.java 772 2011-09-16 15:39:44Z raedle $ * @since 1.1.0 */ class CaptureData { private Robot robot; private Rectangle bounds; /** * @param robot * @param bounds */ public CaptureData(Robot robot, Rectangle bounds) { this.robot = robot; this.bounds = bounds; } /** * @param image */ public void captureToImage(BufferedImage image) { if (robot == null) { return; } // Rectangle adjustBounds = new Rectangle(0, 0, (int) bounds.getWidth(), (int) bounds.getHeight()); // BufferedImage screenCapture = robot.createScreenCapture(adjustBounds); BufferedImage screenCapture = robot.createScreenCapture(bounds); Graphics2D g2d = image.createGraphics(); g2d.drawImage(screenCapture, (int) bounds.getX() - dispatchPositionX, (int) bounds.getY() - dispatchPositionY, null); g2d.dispose(); } /** * */ public void dispose() { if (robot != null) { robot = null; } } } /** * <code>ImageKit</code>. * * <pre> * Date: Oct 29, 2008 * Time: 4:11:25 PM * </pre> * * @author Roman Rädle, <a * href="mailto:Roman.Raedle@uni-konstanz.de">Roman.Raedle@uni-konstanz.de</a>, * University of Konstanz * @version $Id: ScreenDispatcher.java 772 2011-09-16 15:39:44Z raedle $ * @since 1.1.0 */ static public class ImageKit { //quality means jpeg output, if quality is < 0 ==> use default quality public static void write(BufferedImage image, float quality, OutputStream out) throws IOException { Iterator writers = ImageIO.getImageWritersBySuffix("jpeg"); if (!writers.hasNext()) throw new IllegalStateException("No writers found"); ImageWriter writer = (ImageWriter) writers.next(); ImageOutputStream ios = ImageIO.createImageOutputStream(out); writer.setOutput(ios); ImageWriteParam param = writer.getDefaultWriteParam(); if (quality >= 0) { param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT); param.setCompressionQuality(quality); } writer.write(null, new IIOImage(image, null, null), param); } public static BufferedImage read(byte[] bytes) { try { return ImageIO.read(new ByteArrayInputStream(bytes)); } catch (IOException e) { throw new RuntimeException(e); } } public static byte[] getBytes(BufferedImage image, float quality) { try { ByteArrayOutputStream out = new ByteArrayOutputStream(50000); write(image, quality, out); return out.toByteArray(); } catch (IOException e) { throw new RuntimeException(e); } } public static BufferedImage compress(BufferedImage image, float quality) { try { ByteArrayOutputStream out = new ByteArrayOutputStream(50000); write(image, quality, out); return ImageIO.read(new ByteArrayInputStream(out.toByteArray())); } catch (IOException e) { throw new RuntimeException(e); } } } /** * @param args */ public static void main(String[] args) { ScreenDispatcher sd = new ScreenDispatcher(); sd.setRemoteAddress("192.168.1.101"); sd.setScreenCaptureQuality(100); sd.setDispatchPositionX(0); sd.setDispatchPositionWidth(1440); sd.setDispatchPositionHeight(900); // sd.setFlipScreen(true); sd.setRefreshRate(350); sd.setShowDispatchingDebugWindow(true); sd.start(); } }