/** * Copyright (c) 2009 - 2011 AppWork UG(haftungsbeschränkt) <e-mail@appwork.org> * * This file is part of org.appwork.screenshot * * This software is licensed under the Artistic License 2.0, * see the LICENSE file or http://www.opensource.org/licenses/artistic-license-2.0.php * for details */ package org.appwork.screenshot; import java.awt.AWTException; import java.awt.AlphaComposite; import java.awt.BasicStroke; import java.awt.Color; import java.awt.Composite; import java.awt.Cursor; import java.awt.DisplayMode; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.GraphicsDevice; import java.awt.GraphicsEnvironment; import java.awt.Image; import java.awt.MouseInfo; import java.awt.Point; import java.awt.PointerInfo; import java.awt.Rectangle; import java.awt.Robot; import java.awt.Toolkit; import java.awt.event.KeyEvent; import java.awt.event.KeyListener; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.awt.event.MouseMotionListener; import java.awt.geom.Area; import java.awt.geom.Rectangle2D; import java.awt.image.BufferStrategy; import java.awt.image.BufferedImage; import java.awt.image.MemoryImageSource; import java.awt.image.VolatileImage; import javax.swing.ImageIcon; import javax.swing.JFrame; import javax.swing.JWindow; import org.appwork.utils.locale.APPWORKUTILS; import org.appwork.utils.os.CrossSystem; import org.appwork.utils.swing.EDTHelper; import org.appwork.utils.swing.EDTRunner; import org.appwork.utils.swing.dialog.Dialog; import org.appwork.utils.swing.dialog.DialogCanceledException; import org.appwork.utils.swing.dialog.DialogClosedException; /** * @author thomas * */ public class ScreenShooter extends JWindow implements MouseListener, MouseMotionListener { private static final long serialVersionUID = 3184465232251321247L; /** * Size of the Mag Glass */ private static final int SIZE = 150; /** * Mag resize factor */ private static final int FACTOR = 5; private static final int SCALED_SIZE = ScreenShooter.SIZE / ScreenShooter.FACTOR; protected static final int FPS = 50; private static ScreenShooter layover; /** * Creates a screenshot of all available screens. and returns the * ScreenShooter * * @return * @throws AWTException */ public static ScreenShooter create() throws AWTException { final GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment(); final GraphicsDevice[] screens = ge.getScreenDevices(); // for (final GraphicsDevice screen : screens) { int xMax = 0; int xMin = 0; int yMax = 0; int yMin = 0; for (final GraphicsDevice screen : screens) { final Rectangle bounds = screen.getDefaultConfiguration().getBounds(); xMax = Math.max(xMax, bounds.x + bounds.width); yMax = Math.max(bounds.y + bounds.height, yMax); yMin = Math.min(yMin, bounds.y); xMin = Math.min(xMin, bounds.x); } // final BufferedImage complete = new BufferedImage(xMax - xMin, yMax - // yMin, Transparency.TRANSLUCENT); Image complete = null; Graphics2D g2 = null; if (CrossSystem.isLinux()) { final BufferedImage img = new BufferedImage(xMax - xMin, yMax - yMin, BufferedImage.TYPE_INT_RGB); g2 = img.createGraphics(); complete = img; } else { final VolatileImage img = ge.getDefaultScreenDevice().getDefaultConfiguration().createCompatibleVolatileImage(xMax - xMin, yMax - yMin); g2 = img.createGraphics(); complete = img; } // we create a normal screenshot and a grayed screenshot // final BufferedImage completeGrayed = new BufferedImage(xMax - xMin, // yMax - yMin, Transparency.TRANSLUCENT); final VolatileImage completeGrayed = ge.getDefaultScreenDevice().getDefaultConfiguration().createCompatibleVolatileImage(xMax - xMin, yMax - yMin); Graphics2D g2gray = completeGrayed.createGraphics(); for (final GraphicsDevice screen : screens) { final DisplayMode dm = screen.getDisplayMode(); // bounds are used to gete the position and size of this screen in // the complete screen configuration final Rectangle bounds = screen.getDefaultConfiguration().getBounds(); final int screenWidth = dm.getWidth(); final int screenHeight = dm.getHeight(); final Rectangle rect = new Rectangle(screenWidth, screenHeight); final Robot robot = new Robot(screen); final BufferedImage image = robot.createScreenCapture(rect); g2.drawImage(image, bounds.x - xMin, bounds.y - yMin, null); g2gray.drawImage(image, bounds.x - xMin, bounds.y - yMin, null); final Composite comp = g2gray.getComposite(); g2gray.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.3f)); g2gray.setColor(Color.BLACK); g2gray.fillRect(bounds.x - xMin, bounds.y - yMin, screenWidth, screenHeight); g2gray.drawImage(image, bounds.x - xMin, bounds.y - yMin, null); g2gray.setComposite(comp); } g2.dispose(); g2gray.dispose(); g2 = null; g2gray = null; final ScreenShooter layover = new ScreenShooter(); layover.setImage(complete, completeGrayed); return layover; } public static void main(final String[] args) throws AWTException, InterruptedException { new EDTHelper() { @Override public ScreenShooter edtRun() { try { ScreenShooter.layover = ScreenShooter.create(); } catch (final AWTException e) { // TODO Auto-generated catch block e.printStackTrace(); } ScreenShooter.layover.start(); return null; } }.waitForEDT(); final BufferedImage screenshot = ScreenShooter.layover.getScreenshot(); if (screenshot != null) { try { Dialog.getInstance().showConfirmDialog(0, "", "", new ImageIcon(screenshot), null, null); } catch (final DialogClosedException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (final DialogCanceledException e) { // TODO Auto-generated catch block e.printStackTrace(); } } System.exit(0); } private Image image; private boolean isDragging = false; private Point dragStart; private Point dragEnd; private Image grayedImage; private final Rectangle[] bounds; private BufferedImage screenshot; private final JFrame frame; private Point mouse; private boolean disposed = false; public ScreenShooter() { super(); // we extends from a JFrame because JWindow cannot get focus and this // cannot listen on key events // this.setUndecorated(true); this.addMouseListener(this); // we need an extra frame to listen for keyevents. jwindow cannot catch // key events this.frame = new JFrame(); this.frame.addKeyListener(new KeyListener() { @Override public void keyPressed(final KeyEvent e) { // TODO Auto-generated method stub } @Override public void keyReleased(final KeyEvent e) { if (e.getKeyCode() == KeyEvent.VK_ESCAPE) { ScreenShooter.this.cancel(); } } @Override public void keyTyped(final KeyEvent e) { // TODO Auto-generated method stub } }); this.frame.setUndecorated(false); // this.frame.setSize(0, 0); // see // http://www.javalobby.org/forums/thread.jspa?threadID=16867&tstart=0 final GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment(); final GraphicsDevice[] screens = ge.getScreenDevices(); // store screen device bounds to find current screen later easily this.bounds = new Rectangle[screens.length]; for (int i = 0; i < this.bounds.length; i++) { this.bounds[i] = screens[i].getDefaultConfiguration().getBounds(); } } /** * */ private void cancel() { if (this.isDragging) { this.stopDrag(); this.dragStart = null; this.dragEnd = null; } else { new EDTRunner() { @Override protected void runInEDT() { ScreenShooter.this.frame.setVisible(false); ScreenShooter.this.frame.dispose(); ScreenShooter.this.setVisible(false); ScreenShooter.this.dispose(); } }; } } /** * Converts yourPoint to a coresponding point in the mag glass * * @param mouselocation * @param mag * glass location * @param yourPoint * @return */ private Point convertToMagnifier(final Point mag, final Point p) { final int tx = (p.x - this.mouse.x) * ScreenShooter.FACTOR + this.mouse.x + mag.x + ScreenShooter.SIZE / 2 - this.mouse.x; final int ty = (p.y - this.mouse.y) * ScreenShooter.FACTOR + this.mouse.y + mag.y + ScreenShooter.SIZE / 2 - this.mouse.y; return new Point(tx, ty); } /** * Cuts the given range from the image(screenshot) * * @param x * @param y * @param x2 * @param y2 * @return */ private BufferedImage cut(final int x1, final int y1, final int x2, final int y2) { final int width = Math.abs(x1 - x2) + 1; final int height = Math.abs(y1 - y2) + 1; final int sX = Math.min(x1, x2); final int sY = Math.min(y1, y2); if (width <= 0 || height <= 0) { return null; } final BufferedImage ret = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); final Graphics2D gb = (Graphics2D) ret.getGraphics(); gb.drawImage(this.image, 0, 0, width, height, sX, sY, sX + width, sY + height, null); gb.dispose(); return ret; } @Override public void dispose() { super.dispose(); this.disposed = true; } /** * @param gb */ private void drawBigCross(final Graphics2D gb) { gb.setStroke(new BasicStroke(1)); gb.setColor(Color.GRAY); final Area clip = new Area(new Rectangle2D.Double(0, 0, this.getWidth(), this.getHeight())); final Area subClip = new Area(new Rectangle2D.Double(this.mouse.x - 15, this.mouse.y - 15, 30, 30)); clip.subtract(subClip); gb.setClip(clip); final float dash[] = { 10.0f }; gb.setStroke(new BasicStroke(1.0f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 10.0f, dash, System.currentTimeMillis() / ScreenShooter.FPS % 20)); gb.drawLine(0, this.mouse.y, this.image.getWidth(null), this.mouse.y); gb.drawLine(this.mouse.x, 0, this.mouse.x, this.image.getHeight(null)); gb.setClip(null); // draw tiny cross at mouse location gb.setColor(Color.BLACK); gb.setStroke(new BasicStroke(1)); // gb.drawLine(this.mouse.x - 10, this.mouse.y, this.mouse.x + 10, // this.mouse.y); // gb.drawLine(this.mouse.x, this.mouse.y - 10, this.mouse.x, // this.mouse.y + 10); gb.drawLine(this.mouse.x, this.mouse.y, this.mouse.x, this.mouse.y); } /** * get the device bounds of the device l is in. Use to find the currently * used screen * * * @return */ private Rectangle getDeviceBounds() { for (final Rectangle r : this.bounds) { if (this.mouse.x >= r.x && this.mouse.x <= r.x + r.width) { // x correct if (this.mouse.y >= r.y && this.mouse.y <= r.y + r.height) { // y correct return r; } } } return null; } /** * @return */ public BufferedImage getFullScreenShot() { if (this.image instanceof BufferedImage) { return (BufferedImage) this.image; } final BufferedImage img = new BufferedImage(this.getWidth(), this.getHeight(), BufferedImage.TYPE_INT_RGB); Graphics gd = img.getGraphics(); gd.drawImage(this.image, 0, 0, null); gd.dispose(); return img; } /** * calculates the position of the mag. mag position relative to the * mouseposition changes if we reach the screen devices bounds * * @param l * @return */ private Point getMagnifierPosition() { final Rectangle bounds = this.getDeviceBounds(); if (bounds == null) { return null; } int x = this.mouse.x + 20; if (x + ScreenShooter.SIZE > bounds.x + bounds.width) { x = this.mouse.x - ScreenShooter.SIZE - 20; } int y = this.mouse.y - ScreenShooter.SIZE - 20; if (y < bounds.y) { y = this.mouse.y + 20; } return new Point(x, y); } /** * gets the selected Screenshot. Blocks until a screenshot is available, or * the user canceled * * @return * @throws InterruptedException */ public BufferedImage getScreenshot() throws InterruptedException { while (this.screenshot == null && !this.disposed) { Thread.sleep(100); } return this.screenshot; } /* * (non-Javadoc) * * @see java.awt.event.MouseListener#mouseClicked(java.awt.event.MouseEvent) */ @Override public void mouseClicked(final MouseEvent e) { if (e.isPopupTrigger() || e.getButton() == MouseEvent.BUTTON3) { return; } if (this.isDragging) { this.stopDrag(); this.setVisible(false); this.frame.setVisible(false); new Thread() { @Override public void run() { ScreenShooter.this.screenshot = ScreenShooter.this.cut(ScreenShooter.this.dragStart.x, ScreenShooter.this.dragStart.y, ScreenShooter.this.dragEnd.x, ScreenShooter.this.dragEnd.y); ScreenShooter.this.frame.dispose(); ScreenShooter.this.dispose(); } }.start(); } else if (!this.isDragging) { this.startDrag(); } } /* * (non-Javadoc) * * @see * java.awt.event.MouseMotionListener#mouseDragged(java.awt.event.MouseEvent * ) */ @Override public void mouseDragged(final MouseEvent e) { this.mouse = e.getPoint(); } /* * (non-Javadoc) * * @see java.awt.event.MouseListener#mouseEntered(java.awt.event.MouseEvent) */ @Override public void mouseEntered(final MouseEvent e) { // TODO Auto-generated method stub } /* * (non-Javadoc) * * @see java.awt.event.MouseListener#mouseExited(java.awt.event.MouseEvent) */ @Override public void mouseExited(final MouseEvent e) { // TODO Auto-generated method stub } /* * (non-Javadoc) * * @see * java.awt.event.MouseMotionListener#mouseMoved(java.awt.event.MouseEvent) */ @Override public void mouseMoved(final MouseEvent e) { if (e != null) { this.mouse = e.getPoint(); } else { final PointerInfo pi = MouseInfo.getPointerInfo(); this.mouse = pi.getLocation(); } } /* * (non-Javadoc) * * @see java.awt.event.MouseListener#mousePressed(java.awt.event.MouseEvent) */ @Override public void mousePressed(final MouseEvent e) { if (e.isPopupTrigger() || e.getButton() == MouseEvent.BUTTON3) { this.cancel(); } } /* * (non-Javadoc) * * @see * java.awt.event.MouseListener#mouseReleased(java.awt.event.MouseEvent) */ @Override public void mouseReleased(final MouseEvent e) { if (e.isPopupTrigger() || e.getButton() == MouseEvent.BUTTON3) { return; } } /* * (non-Javadoc) * * @see java.awt.Window#paint(java.awt.Graphics) */ @Override public void paint(final Graphics g) { // if we do not override this, this might resuzlt in edt freezes for // some seconds } /** * Paints the mag, and the position values * * @param gb * @param l */ private void paintMagnifier(final Graphics2D gb) { final Point pos = this.getMagnifierPosition(); // draw and resize the mag image gb.drawImage(this.image, pos.x, pos.y, pos.x + ScreenShooter.SIZE, pos.y + ScreenShooter.SIZE, this.mouse.x - ScreenShooter.SCALED_SIZE / 2, this.mouse.y - ScreenShooter.SCALED_SIZE / 2, this.mouse.x + ScreenShooter.SCALED_SIZE / 2, this.mouse.y + ScreenShooter.SCALED_SIZE / 2, Color.BLACK, null); // Draws the black alpha cross Composite comp = gb.getComposite(); gb.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.3f)); gb.setColor(Color.BLACK); gb.setStroke(new BasicStroke(5)); gb.drawLine(pos.x + 2, pos.y + ScreenShooter.SIZE / 2 + 2, pos.x + ScreenShooter.SIZE - 2, pos.y + ScreenShooter.SIZE / 2 + 2); // gb.setColor(Color.RED); gb.drawLine(pos.x + ScreenShooter.SIZE / 2 + 2, pos.y + 2, pos.x + ScreenShooter.SIZE / 2 + 2, pos.y + ScreenShooter.SIZE / 2 - 3); gb.drawLine(pos.x + ScreenShooter.SIZE / 2 + 2, pos.y + ScreenShooter.SIZE / 2 + 7, pos.x + ScreenShooter.SIZE / 2 + 2, pos.y + ScreenShooter.SIZE - 2); gb.setComposite(comp); // if (this.isDragging) { // Paint the blue selection rectangle in the mag. final int startX = Math.min(this.dragStart.x, this.mouse.x); final int startY = Math.min(this.dragStart.y, this.mouse.y); final int endX = Math.max(this.mouse.x, this.dragStart.x) + 1; final int endY = Math.max(this.mouse.y, this.dragStart.y) + 1; final Point start = this.convertToMagnifier(pos, new Point(startX, startY)); final Point end = this.convertToMagnifier(pos, new Point(endX, endY)); gb.setStroke(new BasicStroke(1)); gb.setColor(Color.BLUE); final int x = Math.max(pos.x, start.x); final int y = Math.max(start.y, pos.y); final int width = Math.min(ScreenShooter.SIZE / 2 + (x == pos.x ? ScreenShooter.FACTOR : 0), end.x - start.x); final int height = Math.min(ScreenShooter.SIZE / 2 + (y == pos.y ? ScreenShooter.FACTOR : 0), end.y - start.y); gb.drawRect(x, y, width, height); comp = gb.getComposite(); gb.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.1f)); gb.fillRect(x, y, width, height); gb.setComposite(comp); // if we are dragging we paint a white area to paint the selected // are size gb.setStroke(new BasicStroke(1)); gb.setColor(Color.WHITE); gb.fillRect(pos.x + 1, pos.y + ScreenShooter.SIZE - gb.getFontMetrics().getHeight(), ScreenShooter.SIZE - 1, gb.getFontMetrics().getHeight()); gb.setColor(Color.GRAY); gb.drawLine(pos.x, pos.y + ScreenShooter.SIZE - gb.getFontMetrics().getHeight(), pos.x + ScreenShooter.SIZE, pos.y + ScreenShooter.SIZE - gb.getFontMetrics().getHeight()); final String dimension = APPWORKUTILS.T.Layover_size(Math.abs(this.mouse.x - this.dragStart.x) + 1, Math.abs(this.mouse.y - this.dragStart.y) + 1); gb.getFontMetrics().stringWidth(dimension); gb.drawString(dimension, pos.x + 5, pos.y + ScreenShooter.SIZE - 3); } // paint black border gb.setColor(Color.BLACK); gb.setStroke(new BasicStroke(1)); gb.drawRect(pos.x, pos.y, ScreenShooter.SIZE, ScreenShooter.SIZE); } /** * @param complete * @param completeGrayed */ private void setImage(final Image complete, final Image completeGrayed) { this.image = complete; this.grayedImage = completeGrayed; this.setSize(complete.getWidth(null), complete.getHeight(null)); } /** * */ public void start() { new EDTHelper<Object>() { @Override public Object edtRun() { // avoid that this frame shows up ScreenShooter.this.frame.setLocation(-10000, -10000); ScreenShooter.this.frame.setVisible(true); ScreenShooter.this.frame.requestFocus(); ScreenShooter.this.setVisible(true); ScreenShooter.this.setLocation(0, 0); ScreenShooter.this.setAlwaysOnTop(false); ScreenShooter.this.requestFocus(); ScreenShooter.this.requestFocusInWindow(); ScreenShooter.this.createBufferStrategy(2); // invisible cursor final int[] pixels = new int[16 * 16]; final Image image = Toolkit.getDefaultToolkit().createImage(new MemoryImageSource(16, 16, pixels, 0, 16)); final Cursor transparentCursor = Toolkit.getDefaultToolkit().createCustomCursor(image, new Point(0, 0), "invisibleCursor"); ScreenShooter.this.setCursor(transparentCursor); // it might happen, that our window looses focus and does not // lsten to mousevents any more. This timer kills the window if // there is no mousemove for >15 seconds ScreenShooter.this.addMouseMotionListener(ScreenShooter.this); ScreenShooter.this.mouseMoved(null); // this.timer.start(); new Thread("Asynchpainter") { @Override public void run() { long t = System.currentTimeMillis(); final int frame = 1000 / ScreenShooter.FPS; // Point oldMouse = ScreenShooter.this.mouse; ScreenShooter.this.updateGUI(ScreenShooter.this.getBufferStrategy()); try { Thread.sleep(Math.max(0, frame - System.currentTimeMillis() - t)); } catch (final InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } while (!ScreenShooter.this.disposed) { t = System.currentTimeMillis(); // if (ScreenShooter.this.mouse != oldMouse) { ScreenShooter.this.updateGUI(ScreenShooter.this.getBufferStrategy()); // } // oldMouse = ScreenShooter.this.mouse; try { final long wait = frame - (System.currentTimeMillis() - t); if (wait > 0) { Thread.sleep(wait); } } catch (final InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } }.start(); return null; } }.getReturnValue(); } /** * */ private void startDrag() { this.dragStart = this.mouse; this.isDragging = true; System.out.println("Start Drag " + this.dragStart); } /** * */ private void stopDrag() { this.isDragging = false; this.dragEnd = this.mouse; System.out.println("StopDrag "); } /** * Paints the complete screen * * @param bufferStrategy */ private void updateGUI(final BufferStrategy bufferStrategy) { Graphics2D gb = null; try { gb = (Graphics2D) bufferStrategy.getDrawGraphics(); final Point tempDrag = this.dragStart; if (this.isDragging && tempDrag != null) { final int startX = Math.min(tempDrag.x, this.mouse.x); final int startY = Math.min(tempDrag.y, this.mouse.y); // draw grayed image over full screen gb.drawImage(this.grayedImage, 0, 0, null); final int endX = Math.max(this.mouse.x, tempDrag.x); final int endY = Math.max(this.mouse.y, tempDrag.y); // draw ungrayed icon as selection gb.drawImage(this.image, startX, startY, endX, endY, startX, startY, endX, endY, null); gb.setColor(Color.GRAY); // draw BIG dashed hair cross this.drawBigCross(gb); // Draw selection Border gb.setColor(Color.BLACK); gb.setStroke(new BasicStroke(1)); gb.drawRect(startX, startY, endX - startX, endY - startY); gb.setStroke(new BasicStroke(1)); } else { // draw screenshot image gb.drawImage(this.image, 0, 0, null); // draw dashed cross this.drawBigCross(gb); } this.paintMagnifier(gb); // paint the position marker String str = this.mouse.y + " px"; final Rectangle db = this.getDeviceBounds(); int width = gb.getFontMetrics().stringWidth(str) + 10; int height = gb.getFontMetrics().getHeight() + 5; gb.setStroke(new BasicStroke(1)); gb.setColor(Color.white); int y = this.mouse.y - height / 2; if (y < db.y) { // dock marker on top y = db.y; } if (y + height + 5 > db.height + db.y) { y = db.height + db.y - height - 5; // dock marker on bottom } // paint marker gb.fillRect(db.x + db.width - width - 10, y, width, height); gb.setColor(Color.GRAY); gb.drawRect(db.x + db.width - width - 10, y, width, height); gb.drawString(str, db.x + db.width - width - 5, y + height - 5); // str = this.mouse.x + " px"; width = gb.getFontMetrics().stringWidth(str) + 10; height = gb.getFontMetrics().getHeight() + 5; gb.setStroke(new BasicStroke(1)); gb.setColor(Color.white); int x = this.mouse.x - width / 2; if (x < db.x + 5) { x = db.x + 5; // marker reached left margin we dock here } if (x + width + 5 > db.x + db.width) { // marker reached right margin. we dock here x = db.x + db.width - width - 5; } // avoid that marker overlap at the bottom right corner. set X // marker to // top if x and y markers share the same y position if (db.y + db.height - height - 5 - y <= height) { // on mac, we cannot override the topbar which is 22 px height gb.fillRect(x, db.y + (CrossSystem.isMac() ? 22 + 5 : 5), width, height); gb.setColor(Color.GRAY); gb.drawRect(x, +(CrossSystem.isMac() ? 22 + 5 : 5), width, height); gb.drawString(str, x + 5, (CrossSystem.isMac() ? 22 + height : height)); } else { gb.fillRect(x, db.y + db.height - height - 5, width, height); gb.setColor(Color.GRAY); gb.drawRect(x, db.y + db.height - height - 5, width, height); gb.drawString(str, x + 5, db.y + db.height - 10); } try { bufferStrategy.show(); } catch (final Exception e) { } } catch (final Exception e) { e.printStackTrace(); } finally { try { gb.dispose(); } catch (final Throwable e) { } } } }