001 /* 002 * Created on May 6, 2007 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 005 * in compliance with the License. You may obtain a copy of the License at 006 * 007 * http://www.apache.org/licenses/LICENSE-2.0 008 * 009 * Unless required by applicable law or agreed to in writing, software distributed under the License 010 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 011 * or implied. See the License for the specific language governing permissions and limitations under 012 * the License. 013 * 014 * Copyright @2007-2010 the original author or authors. 015 */ 016 package org.fest.swing.image; 017 018 import static org.fest.swing.core.FocusOwnerFinder.focusOwner; 019 import static org.fest.swing.edt.GuiActionRunner.execute; 020 import static org.fest.swing.image.ImageFileExtensions.PNG; 021 import static org.fest.swing.query.ComponentLocationOnScreenQuery.locationOnScreen; 022 import static org.fest.swing.query.ComponentSizeQuery.sizeOf; 023 import static org.fest.util.Strings.*; 024 025 import java.awt.*; 026 import java.awt.image.BufferedImage; 027 import java.util.Locale; 028 029 import javax.swing.text.Caret; 030 import javax.swing.text.JTextComponent; 031 032 import org.fest.swing.annotation.RunsInEDT; 033 import org.fest.swing.edt.GuiQuery; 034 import org.fest.swing.edt.GuiTask; 035 import org.fest.swing.util.RobotFactory; 036 import org.fest.util.VisibleForTesting; 037 038 /** 039 * Understands taking screenshots of the desktop and GUI components. 040 * 041 * @author Alex Ruiz 042 * @author Yvonne Wang 043 */ 044 public class ScreenshotTaker { 045 046 /** 047 * Extension of the image files containing the screenshots taken by instances of this class (png). 048 * @deprecated use <code>{@link ImageFileExtensions#PNG}</code> instead. 049 */ 050 @Deprecated public static final String PNG_EXTENSION = "png"; 051 052 private final Robot robot; 053 private final ImageFileWriter writer; 054 055 /** 056 * Creates a new <code>{@link ScreenshotTaker}</code>. 057 * @throws ImageException if a AWT Robot (the responsible for taking screenshots) cannot be instantiated. 058 */ 059 public ScreenshotTaker() { 060 this(new ImageFileWriter(), new RobotFactory()); 061 } 062 063 @VisibleForTesting 064 ScreenshotTaker(ImageFileWriter writer, RobotFactory robotFactory) { 065 this.writer = writer; 066 try { 067 robot = robotFactory.newRobotInPrimaryScreen(); 068 } catch (AWTException e) { 069 throw new ImageException("Unable to create AWT Robot", e); 070 } 071 } 072 073 /** 074 * Takes a screenshot of the desktop and saves it as a PNG file. 075 * @param imageFilePath the path of the file to save the screenshot to. 076 * @throws ImageException if the given file path is <code>null</code> or empty. 077 * @throws ImageException if the given file path does not end with ".png". 078 * @throws ImageException if the given file path belongs to a non-empty directory. 079 * @throws ImageException if an I/O error prevents the image from being saved as a file. 080 */ 081 public void saveDesktopAsPng(String imageFilePath) { 082 saveImage(takeDesktopScreenshot(), imageFilePath); 083 } 084 085 /** 086 * Takes a screenshot of the desktop. 087 * @return the screenshot of the desktop. 088 * @throws SecurityException if <code>readDisplayPixels</code> permission is not granted. 089 */ 090 public BufferedImage takeDesktopScreenshot() { 091 Rectangle r = new Rectangle(Toolkit.getDefaultToolkit().getScreenSize()); 092 return takeScreenshot(r); 093 } 094 095 /** 096 * Takes a screenshot of the given <code>{@link java.awt.Component}</code> and saves it as a PNG file. 097 * @param c the given component. 098 * @param imageFilePath the path of the file to save the screenshot to. 099 * @throws ImageException if the given file path is <code>null</code> or empty. 100 * @throws ImageException if the given file path does not end with ".png". 101 * @throws ImageException if the given file path belongs to a non-empty directory. 102 * @throws ImageException if an I/O error prevents the image from being saved as a file. 103 */ 104 public void saveComponentAsPng(Component c, String imageFilePath) { 105 saveImage(takeScreenshotOf(c), imageFilePath); 106 } 107 108 /** 109 * Takes a screenshot of the given <code>{@link java.awt.Component}</code>. 110 * @param c the given component. 111 * @return a screenshot of the given component. 112 * @throws SecurityException if <code>readDisplayPixels</code> permission is not granted. 113 */ 114 public BufferedImage takeScreenshotOf(Component c) { 115 Point locationOnScreen = locationOnScreen(c); 116 Dimension size = sizeOf(c); 117 Rectangle r = new Rectangle(locationOnScreen.x, locationOnScreen.y, size.width, size.height); 118 return takeScreenshot(r); 119 } 120 121 private BufferedImage takeScreenshot(Rectangle r) { 122 JTextComponent textComponent = findFocusOwnerAndHideItsCaret(); 123 robot.waitForIdle(); 124 try { 125 return takeScreenshot(robot, r); 126 } finally { 127 showCaretIfPossible(textComponent); 128 } 129 } 130 131 @RunsInEDT 132 private static JTextComponent findFocusOwnerAndHideItsCaret() { 133 return execute(new GuiQuery<JTextComponent>() { 134 protected JTextComponent executeInEDT() { 135 Component focusOwner = focusOwner(); 136 if (!(focusOwner instanceof JTextComponent)) return null; 137 JTextComponent textComponent = (JTextComponent)focusOwner; 138 Caret caret = textComponent.getCaret(); 139 if (caret == null || !caret.isVisible()) return null; 140 caret.setVisible(false); 141 return textComponent; 142 } 143 }); 144 } 145 146 private static BufferedImage takeScreenshot(final Robot robot, final Rectangle r) { 147 return execute(new GuiQuery<BufferedImage>() { 148 protected BufferedImage executeInEDT() { 149 return robot.createScreenCapture(r); 150 } 151 }); 152 } 153 154 private void showCaretIfPossible(JTextComponent textComponent) { 155 if (textComponent == null) return; 156 showCaretOf(textComponent); 157 robot.waitForIdle(); 158 } 159 160 @RunsInEDT 161 private static void showCaretOf(final JTextComponent textComponent) { 162 execute(new GuiTask() { 163 protected void executeInEDT() { 164 Caret caret = textComponent.getCaret(); 165 if (caret != null) caret.setVisible(true); 166 } 167 }); 168 } 169 170 /** 171 * Save the given image as a PNG file. 172 * @param image the image to save. 173 * @param filePath the path of the file to save the image to. 174 * @throws ImageException if the given file path is <code>null</code> or empty. 175 * @throws ImageException if the given file path does not end with ".png". 176 * @throws ImageException if the given file path belongs to a non-empty directory. 177 * @throws ImageException if an I/O error prevents the image from being saved as a file. 178 */ 179 public void saveImage(BufferedImage image, String filePath) { 180 validate(filePath); 181 try { 182 writer.writeAsPng(image, filePath); 183 } catch (Exception e) { 184 throw new ImageException(concat("Unable to save image as ", quote(filePath)), e); 185 } 186 } 187 188 private void validate(String imageFilePath) { 189 if (isEmpty(imageFilePath)) throw new ImageException("The image path cannot be empty"); 190 if (!imageFilePath.endsWith(PNG)) 191 throw new ImageException(concat("The image file should be a ", PNG.toUpperCase(Locale.getDefault()))); 192 } 193 }