1 /*
   2  * Copyright (c) 1999, 2017, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package java.awt;
  27 
  28 import java.awt.event.InputEvent;
  29 import java.awt.event.KeyEvent;
  30 import java.awt.geom.AffineTransform;
  31 import java.awt.image.BaseMultiResolutionImage;
  32 import java.awt.image.MultiResolutionImage;
  33 import java.awt.image.BufferedImage;
  34 import java.awt.image.DataBufferInt;
  35 import java.awt.image.DirectColorModel;
  36 import java.awt.image.Raster;
  37 import java.awt.image.WritableRaster;
  38 import java.awt.peer.RobotPeer;
  39 
  40 import sun.awt.AWTPermissions;
  41 import sun.awt.ComponentFactory;
  42 import sun.awt.SunToolkit;
  43 import sun.awt.image.SunWritableRaster;
  44 import sun.swing.SwingUtilities2;
  45 
  46 /**
  47  * This class is used to generate native system input events
  48  * for the purposes of test automation, self-running demos, and
  49  * other applications where control of the mouse and keyboard
  50  * is needed. The primary purpose of Robot is to facilitate
  51  * automated testing of Java platform implementations.
  52  * <p>
  53  * Using the class to generate input events differs from posting
  54  * events to the AWT event queue or AWT components in that the
  55  * events are generated in the platform's native input
  56  * queue. For example, {@code Robot.mouseMove} will actually move
  57  * the mouse cursor instead of just generating mouse move events.
  58  * <p>
  59  * Note that some platforms require special privileges or extensions
  60  * to access low-level input control. If the current platform configuration
  61  * does not allow input control, an {@code AWTException} will be thrown
  62  * when trying to construct Robot objects. For example, X-Window systems
  63  * will throw the exception if the XTEST 2.2 standard extension is not supported
  64  * (or not enabled) by the X server.
  65  * <p>
  66  * Applications that use Robot for purposes other than self-testing should
  67  * handle these error conditions gracefully.
  68  *
  69  * @author      Robi Khan
  70  * @since       1.3
  71  */
  72 public class Robot {
  73     private static final int MAX_DELAY = 60000;
  74     private RobotPeer peer;
  75     private boolean isAutoWaitForIdle = false;
  76     private int autoDelay = 0;
  77     private static int LEGAL_BUTTON_MASK = 0;
  78 
  79     private DirectColorModel screenCapCM = null;
  80 
  81     /**
  82      * Constructs a Robot object in the coordinate system of the primary screen.
  83      *
  84      * @throws  AWTException if the platform configuration does not allow
  85      * low-level input control.  This exception is always thrown when
  86      * GraphicsEnvironment.isHeadless() returns true
  87      * @throws  SecurityException if {@code createRobot} permission is not granted
  88      * @see     java.awt.GraphicsEnvironment#isHeadless
  89      * @see     SecurityManager#checkPermission
  90      * @see     AWTPermission
  91      */
  92     public Robot() throws AWTException {
  93         if (GraphicsEnvironment.isHeadless()) {
  94             throw new AWTException("headless environment");
  95         }
  96         init(GraphicsEnvironment.getLocalGraphicsEnvironment()
  97             .getDefaultScreenDevice());
  98     }
  99 
 100     /**
 101      * Creates a Robot for the given screen device. Coordinates passed
 102      * to Robot method calls like mouseMove, getPixelColor and
 103      * createScreenCapture will be interpreted as being in the same coordinate
 104      * system as the specified screen. Note that depending on the platform
 105      * configuration, multiple screens may either:
 106      * <ul>
 107      * <li>share the same coordinate system to form a combined virtual screen</li>
 108      * <li>use different coordinate systems to act as independent screens</li>
 109      * </ul>
 110      * <p>
 111      * If screen devices are reconfigured such that the coordinate system is
 112      * affected, the behavior of existing Robot objects is undefined.
 113      *
 114      * @param screen    A screen GraphicsDevice indicating the coordinate
 115      *                  system the Robot will operate in.
 116      * @throws  AWTException if the platform configuration does not allow
 117      * low-level input control.  This exception is always thrown when
 118      * GraphicsEnvironment.isHeadless() returns true.
 119      * @throws  IllegalArgumentException if {@code screen} is not a screen
 120      *          GraphicsDevice.
 121      * @throws  SecurityException if {@code createRobot} permission is not granted
 122      * @see     java.awt.GraphicsEnvironment#isHeadless
 123      * @see     GraphicsDevice
 124      * @see     SecurityManager#checkPermission
 125      * @see     AWTPermission
 126      */
 127     public Robot(GraphicsDevice screen) throws AWTException {
 128         checkIsScreenDevice(screen);
 129         init(screen);
 130     }
 131 
 132     private void init(GraphicsDevice screen) throws AWTException {
 133         checkRobotAllowed();
 134         Toolkit toolkit = Toolkit.getDefaultToolkit();
 135         if (toolkit instanceof ComponentFactory) {
 136             peer = ((ComponentFactory)toolkit).createRobot(this, screen);
 137             disposer = new RobotDisposer(peer);
 138             sun.java2d.Disposer.addRecord(anchor, disposer);
 139         }
 140         initLegalButtonMask();
 141     }
 142 
 143     @SuppressWarnings("deprecation")
 144     private static synchronized void initLegalButtonMask() {
 145         if (LEGAL_BUTTON_MASK != 0) return;
 146 
 147         int tmpMask = 0;
 148         if (Toolkit.getDefaultToolkit().areExtraMouseButtonsEnabled()){
 149             if (Toolkit.getDefaultToolkit() instanceof SunToolkit) {
 150                 final int buttonsNumber = ((SunToolkit)(Toolkit.getDefaultToolkit())).getNumberOfButtons();
 151                 for (int i = 0; i < buttonsNumber; i++){
 152                     tmpMask |= InputEvent.getMaskForButton(i+1);
 153                 }
 154             }
 155         }
 156         tmpMask |= InputEvent.BUTTON1_MASK|
 157             InputEvent.BUTTON2_MASK|
 158             InputEvent.BUTTON3_MASK|
 159             InputEvent.BUTTON1_DOWN_MASK|
 160             InputEvent.BUTTON2_DOWN_MASK|
 161             InputEvent.BUTTON3_DOWN_MASK;
 162         LEGAL_BUTTON_MASK = tmpMask;
 163     }
 164 
 165     /* determine if the security policy allows Robot's to be created */
 166     private void checkRobotAllowed() {
 167         SecurityManager security = System.getSecurityManager();
 168         if (security != null) {
 169             security.checkPermission(AWTPermissions.CREATE_ROBOT_PERMISSION);
 170         }
 171     }
 172 
 173     /* check if the given device is a screen device */
 174     private void checkIsScreenDevice(GraphicsDevice device) {
 175         if (device == null || device.getType() != GraphicsDevice.TYPE_RASTER_SCREEN) {
 176             throw new IllegalArgumentException("not a valid screen device");
 177         }
 178     }
 179 
 180     private transient Object anchor = new Object();
 181 
 182     static class RobotDisposer implements sun.java2d.DisposerRecord {
 183         private final RobotPeer peer;
 184         public RobotDisposer(RobotPeer peer) {
 185             this.peer = peer;
 186         }
 187         public void dispose() {
 188             if (peer != null) {
 189                 peer.dispose();
 190             }
 191         }
 192     }
 193 
 194     private transient RobotDisposer disposer;
 195 
 196     /**
 197      * Moves mouse pointer to given screen coordinates.
 198      * @param x         X position
 199      * @param y         Y position
 200      */
 201     public synchronized void mouseMove(int x, int y) {
 202         peer.mouseMove(x, y);
 203         afterEvent();
 204     }
 205 
 206     /**
 207      * Presses one or more mouse buttons.  The mouse buttons should
 208      * be released using the {@link #mouseRelease(int)} method.
 209      *
 210      * @param buttons the Button mask; a combination of one or more
 211      * mouse button masks.
 212      * <p>
 213      * It is allowed to use only a combination of valid values as a {@code buttons} parameter.
 214      * A valid combination consists of {@code InputEvent.BUTTON1_DOWN_MASK},
 215      * {@code InputEvent.BUTTON2_DOWN_MASK}, {@code InputEvent.BUTTON3_DOWN_MASK}
 216      * and values returned by the
 217      * {@link InputEvent#getMaskForButton(int) InputEvent.getMaskForButton(button)} method.
 218      *
 219      * The valid combination also depends on a
 220      * {@link Toolkit#areExtraMouseButtonsEnabled() Toolkit.areExtraMouseButtonsEnabled()} value as follows:
 221      * <ul>
 222      * <li> If support for extended mouse buttons is
 223      * {@link Toolkit#areExtraMouseButtonsEnabled() disabled} by Java
 224      * then it is allowed to use only the following standard button masks:
 225      * {@code InputEvent.BUTTON1_DOWN_MASK}, {@code InputEvent.BUTTON2_DOWN_MASK},
 226      * {@code InputEvent.BUTTON3_DOWN_MASK}.
 227      * <li> If support for extended mouse buttons is
 228      * {@link Toolkit#areExtraMouseButtonsEnabled() enabled} by Java
 229      * then it is allowed to use the standard button masks
 230      * and masks for existing extended mouse buttons, if the mouse has more then three buttons.
 231      * In that way, it is allowed to use the button masks corresponding to the buttons
 232      * in the range from 1 to {@link java.awt.MouseInfo#getNumberOfButtons() MouseInfo.getNumberOfButtons()}.
 233      * <br>
 234      * It is recommended to use the {@link InputEvent#getMaskForButton(int) InputEvent.getMaskForButton(button)}
 235      * method to obtain the mask for any mouse button by its number.
 236      * </ul>
 237      * <p>
 238      * The following standard button masks are also accepted:
 239      * <ul>
 240      * <li>{@code InputEvent.BUTTON1_MASK}
 241      * <li>{@code InputEvent.BUTTON2_MASK}
 242      * <li>{@code InputEvent.BUTTON3_MASK}
 243      * </ul>
 244      * However, it is recommended to use {@code InputEvent.BUTTON1_DOWN_MASK},
 245      * {@code InputEvent.BUTTON2_DOWN_MASK},  {@code InputEvent.BUTTON3_DOWN_MASK} instead.
 246      * Either extended {@code _DOWN_MASK} or old {@code _MASK} values
 247      * should be used, but both those models should not be mixed.
 248      * @throws IllegalArgumentException if the {@code buttons} mask contains the mask for extra mouse button
 249      *         and support for extended mouse buttons is {@link Toolkit#areExtraMouseButtonsEnabled() disabled} by Java
 250      * @throws IllegalArgumentException if the {@code buttons} mask contains the mask for extra mouse button
 251      *         that does not exist on the mouse and support for extended mouse buttons is {@link Toolkit#areExtraMouseButtonsEnabled() enabled} by Java
 252      * @see #mouseRelease(int)
 253      * @see InputEvent#getMaskForButton(int)
 254      * @see Toolkit#areExtraMouseButtonsEnabled()
 255      * @see java.awt.MouseInfo#getNumberOfButtons()
 256      * @see java.awt.event.MouseEvent
 257      */
 258     public synchronized void mousePress(int buttons) {
 259         checkButtonsArgument(buttons);
 260         peer.mousePress(buttons);
 261         afterEvent();
 262     }
 263 
 264     /**
 265      * Releases one or more mouse buttons.
 266      *
 267      * @param buttons the Button mask; a combination of one or more
 268      * mouse button masks.
 269      * <p>
 270      * It is allowed to use only a combination of valid values as a {@code buttons} parameter.
 271      * A valid combination consists of {@code InputEvent.BUTTON1_DOWN_MASK},
 272      * {@code InputEvent.BUTTON2_DOWN_MASK}, {@code InputEvent.BUTTON3_DOWN_MASK}
 273      * and values returned by the
 274      * {@link InputEvent#getMaskForButton(int) InputEvent.getMaskForButton(button)} method.
 275      *
 276      * The valid combination also depends on a
 277      * {@link Toolkit#areExtraMouseButtonsEnabled() Toolkit.areExtraMouseButtonsEnabled()} value as follows:
 278      * <ul>
 279      * <li> If the support for extended mouse buttons is
 280      * {@link Toolkit#areExtraMouseButtonsEnabled() disabled} by Java
 281      * then it is allowed to use only the following standard button masks:
 282      * {@code InputEvent.BUTTON1_DOWN_MASK}, {@code InputEvent.BUTTON2_DOWN_MASK},
 283      * {@code InputEvent.BUTTON3_DOWN_MASK}.
 284      * <li> If the support for extended mouse buttons is
 285      * {@link Toolkit#areExtraMouseButtonsEnabled() enabled} by Java
 286      * then it is allowed to use the standard button masks
 287      * and masks for existing extended mouse buttons, if the mouse has more then three buttons.
 288      * In that way, it is allowed to use the button masks corresponding to the buttons
 289      * in the range from 1 to {@link java.awt.MouseInfo#getNumberOfButtons() MouseInfo.getNumberOfButtons()}.
 290      * <br>
 291      * It is recommended to use the {@link InputEvent#getMaskForButton(int) InputEvent.getMaskForButton(button)}
 292      * method to obtain the mask for any mouse button by its number.
 293      * </ul>
 294      * <p>
 295      * The following standard button masks are also accepted:
 296      * <ul>
 297      * <li>{@code InputEvent.BUTTON1_MASK}
 298      * <li>{@code InputEvent.BUTTON2_MASK}
 299      * <li>{@code InputEvent.BUTTON3_MASK}
 300      * </ul>
 301      * However, it is recommended to use {@code InputEvent.BUTTON1_DOWN_MASK},
 302      * {@code InputEvent.BUTTON2_DOWN_MASK},  {@code InputEvent.BUTTON3_DOWN_MASK} instead.
 303      * Either extended {@code _DOWN_MASK} or old {@code _MASK} values
 304      * should be used, but both those models should not be mixed.
 305      * @throws IllegalArgumentException if the {@code buttons} mask contains the mask for extra mouse button
 306      *         and support for extended mouse buttons is {@link Toolkit#areExtraMouseButtonsEnabled() disabled} by Java
 307      * @throws IllegalArgumentException if the {@code buttons} mask contains the mask for extra mouse button
 308      *         that does not exist on the mouse and support for extended mouse buttons is {@link Toolkit#areExtraMouseButtonsEnabled() enabled} by Java
 309      * @see #mousePress(int)
 310      * @see InputEvent#getMaskForButton(int)
 311      * @see Toolkit#areExtraMouseButtonsEnabled()
 312      * @see java.awt.MouseInfo#getNumberOfButtons()
 313      * @see java.awt.event.MouseEvent
 314      */
 315     public synchronized void mouseRelease(int buttons) {
 316         checkButtonsArgument(buttons);
 317         peer.mouseRelease(buttons);
 318         afterEvent();
 319     }
 320 
 321     private void checkButtonsArgument(int buttons) {
 322         if ( (buttons|LEGAL_BUTTON_MASK) != LEGAL_BUTTON_MASK ) {
 323             throw new IllegalArgumentException("Invalid combination of button flags");
 324         }
 325     }
 326 
 327     /**
 328      * Rotates the scroll wheel on wheel-equipped mice.
 329      *
 330      * @param wheelAmt  number of "notches" to move the mouse wheel
 331      *                  Negative values indicate movement up/away from the user,
 332      *                  positive values indicate movement down/towards the user.
 333      *
 334      * @since 1.4
 335      */
 336     public synchronized void mouseWheel(int wheelAmt) {
 337         peer.mouseWheel(wheelAmt);
 338         afterEvent();
 339     }
 340 
 341     /**
 342      * Presses a given key.  The key should be released using the
 343      * {@code keyRelease} method.
 344      * <p>
 345      * Key codes that have more than one physical key associated with them
 346      * (e.g. {@code KeyEvent.VK_SHIFT} could mean either the
 347      * left or right shift key) will map to the left key.
 348      *
 349      * @param   keycode Key to press (e.g. {@code KeyEvent.VK_A})
 350      * @throws  IllegalArgumentException if {@code keycode} is not
 351      *          a valid key
 352      * @see     #keyRelease(int)
 353      * @see     java.awt.event.KeyEvent
 354      */
 355     public synchronized void keyPress(int keycode) {
 356         checkKeycodeArgument(keycode);
 357         peer.keyPress(keycode);
 358         afterEvent();
 359     }
 360 
 361     /**
 362      * Releases a given key.
 363      * <p>
 364      * Key codes that have more than one physical key associated with them
 365      * (e.g. {@code KeyEvent.VK_SHIFT} could mean either the
 366      * left or right shift key) will map to the left key.
 367      *
 368      * @param   keycode Key to release (e.g. {@code KeyEvent.VK_A})
 369      * @throws  IllegalArgumentException if {@code keycode} is not a
 370      *          valid key
 371      * @see  #keyPress(int)
 372      * @see     java.awt.event.KeyEvent
 373      */
 374     public synchronized void keyRelease(int keycode) {
 375         checkKeycodeArgument(keycode);
 376         peer.keyRelease(keycode);
 377         afterEvent();
 378     }
 379 
 380     /**
 381      * Presses a given unicode key.  The key should be released using the
 382      * {@code keyReleaseUnicode} method.
 383      * <p>
 384      *
 385      * @param   key unicode to press
 386      * @throws  IllegalArgumentException if {@code key} is not
 387      *          a valid unicode key
 388      * @see     #keyReleaseUnicode(int)
 389      * @see     java.awt.event.KeyEvent
 390      */
 391     public synchronized void keyPressUnicode(int key) {
 392         checkKeycodeArgument(key);
 393         peer.keyPressUnicode(key);
 394         afterEvent();
 395     }
 396 
 397     /**
 398      * Releases a given unicode key.
 399      * <p>
 400      *
 401      * @param   key unicode to release
 402      * @throws  IllegalArgumentException if {@code key} is not a
 403      *          valid key
 404      * @see     #keyPressUnicode(int)
 405      * @see     java.awt.event.KeyEvent
 406      */
 407     public synchronized void keyReleaseUnicode(int key) {
 408         checkKeycodeArgument(key);
 409         peer.keyReleaseUnicode(key);
 410         afterEvent();
 411     }
 412 
 413     private void checkKeycodeArgument(int keycode) {
 414         // rather than build a big table or switch statement here, we'll
 415         // just check that the key isn't VK_UNDEFINED and assume that the
 416         // peer implementations will throw an exception for other bogus
 417         // values e.g. -1, 999999
 418         if (keycode == KeyEvent.VK_UNDEFINED) {
 419             throw new IllegalArgumentException("Invalid key code");
 420         }
 421     }
 422 
 423     /**
 424      * Returns the color of a pixel at the given screen coordinates.
 425      * @param   x       X position of pixel
 426      * @param   y       Y position of pixel
 427      * @return  Color of the pixel
 428      */
 429     public synchronized Color getPixelColor(int x, int y) {
 430         AffineTransform tx = GraphicsEnvironment.
 431                 getLocalGraphicsEnvironment().getDefaultScreenDevice().
 432                 getDefaultConfiguration().getDefaultTransform();
 433         x = (int) (x * tx.getScaleX());
 434         y = (int) (y * tx.getScaleY());
 435         Color color = new Color(peer.getRGBPixel(x, y));
 436         return color;
 437     }
 438 
 439     /**
 440      * Creates an image containing pixels read from the screen.  This image does
 441      * not include the mouse cursor.
 442      * @param   screenRect      Rect to capture in screen coordinates
 443      * @return  The captured image
 444      * @throws  IllegalArgumentException if {@code screenRect} width and height are not greater than zero
 445      * @throws  SecurityException if {@code readDisplayPixels} permission is not granted
 446      * @see     SecurityManager#checkPermission
 447      * @see     AWTPermission
 448      */
 449     public synchronized BufferedImage createScreenCapture(Rectangle screenRect) {
 450         return createCompatibleImage(screenRect, false)[0];
 451     }
 452 
 453     /**
 454      * Creates an image containing pixels read from the screen.
 455      * This image does not include the mouse cursor.
 456      * This method can be used in case there is a scaling transform
 457      * from user space to screen (device) space.
 458      * Typically this means that the display is a high resolution screen,
 459      * although strictly it means any case in which there is such a transform.
 460      * Returns a {@link java.awt.image.MultiResolutionImage}.
 461      * <p>
 462      * For a non-scaled display, the {@code MultiResolutionImage}
 463      * will have one image variant:
 464      * <ul>
 465      * <li> Base Image with user specified size.
 466      * </ul>
 467      * <p>
 468      * For a high resolution display where there is a scaling transform,
 469      * the {@code MultiResolutionImage} will have two image variants:
 470      * <ul>
 471      * <li> Base Image with user specified size. This is scaled from the screen.
 472      * <li> Native device resolution image with device size pixels.
 473      * </ul>
 474      * <p>
 475      * Example:
 476      * <pre>{@code
 477      *      Image nativeResImage;
 478      *      MultiResolutionImage mrImage = robot.createMultiResolutionScreenCapture(frame.getBounds());
 479      *      List<Image> resolutionVariants = mrImage.getResolutionVariants();
 480      *      if (resolutionVariants.size() > 1) {
 481      *          nativeResImage = resolutionVariants.get(1);
 482      *      } else {
 483      *          nativeResImage = resolutionVariants.get(0);
 484      *      }
 485      * }</pre>
 486      * @param   screenRect     Rect to capture in screen coordinates
 487      * @return  The captured image
 488      * @throws  IllegalArgumentException if {@code screenRect} width and height are not greater than zero
 489      * @throws  SecurityException if {@code readDisplayPixels} permission is not granted
 490      * @see     SecurityManager#checkPermission
 491      * @see     AWTPermission
 492      *
 493      * @since 9
 494      */
 495     public synchronized MultiResolutionImage
 496             createMultiResolutionScreenCapture(Rectangle screenRect) {
 497 
 498         return new BaseMultiResolutionImage(
 499                 createCompatibleImage(screenRect, true));
 500     }
 501 
 502     private synchronized BufferedImage[]
 503             createCompatibleImage(Rectangle screenRect, boolean isHiDPI) {
 504 
 505         checkScreenCaptureAllowed();
 506 
 507         checkValidRect(screenRect);
 508 
 509         BufferedImage lowResolutionImage;
 510         BufferedImage highResolutionImage;
 511         DataBufferInt buffer;
 512         WritableRaster raster;
 513         BufferedImage[] imageArray;
 514 
 515         if (screenCapCM == null) {
 516             /*
 517              * Fix for 4285201
 518              * Create a DirectColorModel equivalent to the default RGB ColorModel,
 519              * except with no Alpha component.
 520              */
 521 
 522             screenCapCM = new DirectColorModel(24,
 523                     /* red mask */ 0x00FF0000,
 524                     /* green mask */ 0x0000FF00,
 525                     /* blue mask */ 0x000000FF);
 526         }
 527 
 528         int[] bandmasks = new int[3];
 529         bandmasks[0] = screenCapCM.getRedMask();
 530         bandmasks[1] = screenCapCM.getGreenMask();
 531         bandmasks[2] = screenCapCM.getBlueMask();
 532 
 533         // need to sync the toolkit prior to grabbing the pixels since in some
 534         // cases rendering to the screen may be delayed
 535         Toolkit.getDefaultToolkit().sync();
 536 
 537         GraphicsConfiguration gc = GraphicsEnvironment
 538                 .getLocalGraphicsEnvironment()
 539                 .getDefaultScreenDevice().
 540                 getDefaultConfiguration();
 541         gc = SwingUtilities2.getGraphicsConfigurationAtPoint(
 542                 gc, screenRect.getCenterX(), screenRect.getCenterY());
 543 
 544         AffineTransform tx = gc.getDefaultTransform();
 545         double uiScaleX = tx.getScaleX();
 546         double uiScaleY = tx.getScaleY();
 547         int pixels[];
 548 
 549         if (uiScaleX == 1 && uiScaleY == 1) {
 550 
 551             pixels = peer.getRGBPixels(screenRect);
 552             buffer = new DataBufferInt(pixels, pixels.length);
 553 
 554             bandmasks[0] = screenCapCM.getRedMask();
 555             bandmasks[1] = screenCapCM.getGreenMask();
 556             bandmasks[2] = screenCapCM.getBlueMask();
 557 
 558             raster = Raster.createPackedRaster(buffer, screenRect.width,
 559                     screenRect.height, screenRect.width, bandmasks, null);
 560             SunWritableRaster.makeTrackable(buffer);
 561 
 562             highResolutionImage = new BufferedImage(screenCapCM, raster,
 563                     false, null);
 564             imageArray = new BufferedImage[1];
 565             imageArray[0] = highResolutionImage;
 566 
 567         } else {
 568 
 569             int sX = (int) Math.floor(screenRect.x * uiScaleX);
 570             int sY = (int) Math.floor(screenRect.y * uiScaleY);
 571             int sWidth = (int) Math.ceil(screenRect.width * uiScaleX);
 572             int sHeight = (int) Math.ceil(screenRect.height * uiScaleY);
 573             int temppixels[];
 574             Rectangle scaledRect = new Rectangle(sX, sY, sWidth, sHeight);
 575             temppixels = peer.getRGBPixels(scaledRect);
 576 
 577             // HighResolutionImage
 578             pixels = temppixels;
 579             buffer = new DataBufferInt(pixels, pixels.length);
 580             raster = Raster.createPackedRaster(buffer, scaledRect.width,
 581                     scaledRect.height, scaledRect.width, bandmasks, null);
 582             SunWritableRaster.makeTrackable(buffer);
 583 
 584             highResolutionImage = new BufferedImage(screenCapCM, raster,
 585                     false, null);
 586 
 587 
 588             // LowResolutionImage
 589             lowResolutionImage = new BufferedImage(screenRect.width,
 590                     screenRect.height, highResolutionImage.getType());
 591             Graphics2D g = lowResolutionImage.createGraphics();
 592             g.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
 593                     RenderingHints.VALUE_INTERPOLATION_BILINEAR);
 594             g.setRenderingHint(RenderingHints.KEY_RENDERING,
 595                     RenderingHints.VALUE_RENDER_QUALITY);
 596             g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
 597                     RenderingHints.VALUE_ANTIALIAS_ON);
 598             g.drawImage(highResolutionImage, 0, 0,
 599                     screenRect.width, screenRect.height,
 600                     0, 0, scaledRect.width, scaledRect.height, null);
 601             g.dispose();
 602 
 603             if(!isHiDPI) {
 604                 imageArray = new BufferedImage[1];
 605                 imageArray[0] = lowResolutionImage;
 606             } else {
 607                 imageArray = new BufferedImage[2];
 608                 imageArray[0] = lowResolutionImage;
 609                 imageArray[1] = highResolutionImage;
 610             }
 611 
 612         }
 613 
 614         return imageArray;
 615     }
 616 
 617     private static void checkValidRect(Rectangle rect) {
 618         if (rect.width <= 0 || rect.height <= 0) {
 619             throw new IllegalArgumentException("Rectangle width and height must be > 0");
 620         }
 621     }
 622 
 623     private static void checkScreenCaptureAllowed() {
 624         SecurityManager security = System.getSecurityManager();
 625         if (security != null) {
 626             security.checkPermission(AWTPermissions.READ_DISPLAY_PIXELS_PERMISSION);
 627         }
 628     }
 629 
 630     /*
 631      * Called after an event is generated
 632      */
 633     private void afterEvent() {
 634         autoWaitForIdle();
 635         autoDelay();
 636     }
 637 
 638     /**
 639      * Returns whether this Robot automatically invokes {@code waitForIdle}
 640      * after generating an event.
 641      * @return Whether {@code waitForIdle} is automatically called
 642      */
 643     public synchronized boolean isAutoWaitForIdle() {
 644         return isAutoWaitForIdle;
 645     }
 646 
 647     /**
 648      * Sets whether this Robot automatically invokes {@code waitForIdle}
 649      * after generating an event.
 650      * @param   isOn    Whether {@code waitForIdle} is automatically invoked
 651      */
 652     public synchronized void setAutoWaitForIdle(boolean isOn) {
 653         isAutoWaitForIdle = isOn;
 654     }
 655 
 656     /*
 657      * Calls waitForIdle after every event if so desired.
 658      */
 659     private void autoWaitForIdle() {
 660         if (isAutoWaitForIdle) {
 661             waitForIdle();
 662         }
 663     }
 664 
 665     /**
 666      * Returns the number of milliseconds this Robot sleeps after generating an event.
 667      *
 668      * @return the delay duration in milliseconds
 669      */
 670     public synchronized int getAutoDelay() {
 671         return autoDelay;
 672     }
 673 
 674     /**
 675      * Sets the number of milliseconds this Robot sleeps after generating an event.
 676      *
 677      * @param  ms the delay duration in milliseconds
 678      * @throws IllegalArgumentException If {@code ms}
 679      *         is not between 0 and 60,000 milliseconds inclusive
 680      */
 681     public synchronized void setAutoDelay(int ms) {
 682         checkDelayArgument(ms);
 683         autoDelay = ms;
 684     }
 685 
 686     /*
 687      * Automatically sleeps for the specified interval after event generated.
 688      */
 689     private void autoDelay() {
 690         delay(autoDelay);
 691     }
 692 
 693     /**
 694      * Sleeps for the specified time.
 695      * To catch any {@code InterruptedException}s that occur,
 696      * {@code Thread.sleep()} may be used instead.
 697      *
 698      * @param  ms time to sleep in milliseconds
 699      * @throws IllegalArgumentException if {@code ms}
 700      *         is not between 0 and 60,000 milliseconds inclusive
 701      * @see java.lang.Thread#sleep
 702      */
 703     public synchronized void delay(int ms) {
 704         checkDelayArgument(ms);
 705         try {
 706             Thread.sleep(ms);
 707         } catch(InterruptedException ite) {
 708             ite.printStackTrace();
 709         }
 710     }
 711 
 712     private void checkDelayArgument(int ms) {
 713         if (ms < 0 || ms > MAX_DELAY) {
 714             throw new IllegalArgumentException("Delay must be to 0 to 60,000ms");
 715         }
 716     }
 717 
 718     /**
 719      * Waits until all events currently on the event queue have been processed.
 720      * @throws  IllegalThreadStateException if called on the AWT event dispatching thread
 721      */
 722     public synchronized void waitForIdle() {
 723         checkNotDispatchThread();
 724         SunToolkit.flushPendingEvents();
 725         ((SunToolkit) Toolkit.getDefaultToolkit()).realSync();
 726     }
 727 
 728     private void checkNotDispatchThread() {
 729         if (EventQueue.isDispatchThread()) {
 730             throw new IllegalThreadStateException("Cannot call method from the event dispatcher thread");
 731         }
 732     }
 733 
 734     /**
 735      * Returns a string representation of this Robot.
 736      *
 737      * @return  the string representation.
 738      */
 739     @Override
 740     public synchronized String toString() {
 741         String params = "autoDelay = "+getAutoDelay()+", "+"autoWaitForIdle = "+isAutoWaitForIdle();
 742         return getClass().getName() + "[ " + params + " ]";
 743     }
 744 }
--- EOF ---