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 }