rev 60071 : 8211999: Window positioning bugs due to overlapping GraphicsDevice bounds (Windows/HiDPI)
Reviewed-by: XXX

   1 /*
   2  * Copyright (c) 1999, 2020, 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.BufferedImage;
  33 import java.awt.image.DataBufferInt;
  34 import java.awt.image.DirectColorModel;
  35 import java.awt.image.MultiResolutionImage;
  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.java2d.SunGraphicsEnvironment;
  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         checkHeadless();
  94         init(GraphicsEnvironment.getLocalGraphicsEnvironment()
  95             .getDefaultScreenDevice());
  96     }
  97 
  98     /**
  99      * Creates a Robot for the given screen device. Coordinates passed
 100      * to Robot method calls like mouseMove, getPixelColor and
 101      * createScreenCapture will be interpreted as being in the same coordinate
 102      * system as the specified screen. Note that depending on the platform
 103      * configuration, multiple screens may either:
 104      * <ul>
 105      * <li>share the same coordinate system to form a combined virtual screen</li>
 106      * <li>use different coordinate systems to act as independent screens</li>
 107      * </ul>
 108      * <p>
 109      * If screen devices are reconfigured such that the coordinate system is
 110      * affected, the behavior of existing Robot objects is undefined.
 111      *
 112      * @param screen    A screen GraphicsDevice indicating the coordinate
 113      *                  system the Robot will operate in.
 114      * @throws  AWTException if the platform configuration does not allow
 115      * low-level input control.  This exception is always thrown when
 116      * GraphicsEnvironment.isHeadless() returns true.
 117      * @throws  IllegalArgumentException if {@code screen} is not a screen
 118      *          GraphicsDevice.
 119      * @throws  SecurityException if {@code createRobot} permission is not granted
 120      * @see     java.awt.GraphicsEnvironment#isHeadless
 121      * @see     GraphicsDevice
 122      * @see     SecurityManager#checkPermission
 123      * @see     AWTPermission
 124      */
 125     public Robot(GraphicsDevice screen) throws AWTException {
 126         checkHeadless();
 127         checkIsScreenDevice(screen);
 128         init(screen);
 129     }
 130 
 131     private void init(GraphicsDevice screen) throws AWTException {
 132         checkRobotAllowed();
 133         Toolkit toolkit = Toolkit.getDefaultToolkit();
 134         if (toolkit instanceof ComponentFactory) {
 135             peer = ((ComponentFactory)toolkit).createRobot(screen);
 136         }
 137         initLegalButtonMask();
 138     }
 139 
 140     @SuppressWarnings("deprecation")
 141     private static synchronized void initLegalButtonMask() {
 142         if (LEGAL_BUTTON_MASK != 0) return;
 143 
 144         int tmpMask = 0;
 145         if (Toolkit.getDefaultToolkit().areExtraMouseButtonsEnabled()){
 146             if (Toolkit.getDefaultToolkit() instanceof SunToolkit) {
 147                 final int buttonsNumber = ((SunToolkit)(Toolkit.getDefaultToolkit())).getNumberOfButtons();
 148                 for (int i = 0; i < buttonsNumber; i++){
 149                     tmpMask |= InputEvent.getMaskForButton(i+1);
 150                 }
 151             }
 152         }
 153         tmpMask |= InputEvent.BUTTON1_MASK|
 154             InputEvent.BUTTON2_MASK|
 155             InputEvent.BUTTON3_MASK|
 156             InputEvent.BUTTON1_DOWN_MASK|
 157             InputEvent.BUTTON2_DOWN_MASK|
 158             InputEvent.BUTTON3_DOWN_MASK;
 159         LEGAL_BUTTON_MASK = tmpMask;
 160     }
 161 
 162     /* determine if the security policy allows Robot's to be created */
 163     private static void checkRobotAllowed() {
 164         SecurityManager security = System.getSecurityManager();
 165         if (security != null) {
 166             security.checkPermission(AWTPermissions.CREATE_ROBOT_PERMISSION);
 167         }
 168     }
 169 
 170     /**
 171      * Check for headless state and throw {@code AWTException} if headless.
 172      */
 173     private static void checkHeadless() throws AWTException {
 174         if (GraphicsEnvironment.isHeadless()) {
 175             throw new AWTException("headless environment");
 176         }
 177     }
 178 
 179     /* check if the given device is a screen device */
 180     private static void checkIsScreenDevice(GraphicsDevice device) {
 181         if (device == null || device.getType() != GraphicsDevice.TYPE_RASTER_SCREEN) {
 182             throw new IllegalArgumentException("not a valid screen device");
 183         }
 184     }
 185 
 186     /**
 187      * Moves mouse pointer to given screen coordinates.
 188      * @param x         X position
 189      * @param y         Y position
 190      */
 191     public synchronized void mouseMove(int x, int y) {
 192         peer.mouseMove(x, y);
 193         afterEvent();
 194     }
 195 
 196     /**
 197      * Presses one or more mouse buttons.  The mouse buttons should
 198      * be released using the {@link #mouseRelease(int)} method.
 199      *
 200      * @param buttons the Button mask; a combination of one or more
 201      * mouse button masks.
 202      * <p>
 203      * It is allowed to use only a combination of valid values as a {@code buttons} parameter.
 204      * A valid combination consists of {@code InputEvent.BUTTON1_DOWN_MASK},
 205      * {@code InputEvent.BUTTON2_DOWN_MASK}, {@code InputEvent.BUTTON3_DOWN_MASK}
 206      * and values returned by the
 207      * {@link InputEvent#getMaskForButton(int) InputEvent.getMaskForButton(button)} method.
 208      *
 209      * The valid combination also depends on a
 210      * {@link Toolkit#areExtraMouseButtonsEnabled() Toolkit.areExtraMouseButtonsEnabled()} value as follows:
 211      * <ul>
 212      * <li> If support for extended mouse buttons is
 213      * {@link Toolkit#areExtraMouseButtonsEnabled() disabled} by Java
 214      * then it is allowed to use only the following standard button masks:
 215      * {@code InputEvent.BUTTON1_DOWN_MASK}, {@code InputEvent.BUTTON2_DOWN_MASK},
 216      * {@code InputEvent.BUTTON3_DOWN_MASK}.
 217      * <li> If support for extended mouse buttons is
 218      * {@link Toolkit#areExtraMouseButtonsEnabled() enabled} by Java
 219      * then it is allowed to use the standard button masks
 220      * and masks for existing extended mouse buttons, if the mouse has more then three buttons.
 221      * In that way, it is allowed to use the button masks corresponding to the buttons
 222      * in the range from 1 to {@link java.awt.MouseInfo#getNumberOfButtons() MouseInfo.getNumberOfButtons()}.
 223      * <br>
 224      * It is recommended to use the {@link InputEvent#getMaskForButton(int) InputEvent.getMaskForButton(button)}
 225      * method to obtain the mask for any mouse button by its number.
 226      * </ul>
 227      * <p>
 228      * The following standard button masks are also accepted:
 229      * <ul>
 230      * <li>{@code InputEvent.BUTTON1_MASK}
 231      * <li>{@code InputEvent.BUTTON2_MASK}
 232      * <li>{@code InputEvent.BUTTON3_MASK}
 233      * </ul>
 234      * However, it is recommended to use {@code InputEvent.BUTTON1_DOWN_MASK},
 235      * {@code InputEvent.BUTTON2_DOWN_MASK},  {@code InputEvent.BUTTON3_DOWN_MASK} instead.
 236      * Either extended {@code _DOWN_MASK} or old {@code _MASK} values
 237      * should be used, but both those models should not be mixed.
 238      * @throws IllegalArgumentException if the {@code buttons} mask contains the mask for extra mouse button
 239      *         and support for extended mouse buttons is {@link Toolkit#areExtraMouseButtonsEnabled() disabled} by Java
 240      * @throws IllegalArgumentException if the {@code buttons} mask contains the mask for extra mouse button
 241      *         that does not exist on the mouse and support for extended mouse buttons is {@link Toolkit#areExtraMouseButtonsEnabled() enabled} by Java
 242      * @see #mouseRelease(int)
 243      * @see InputEvent#getMaskForButton(int)
 244      * @see Toolkit#areExtraMouseButtonsEnabled()
 245      * @see java.awt.MouseInfo#getNumberOfButtons()
 246      * @see java.awt.event.MouseEvent
 247      */
 248     public synchronized void mousePress(int buttons) {
 249         checkButtonsArgument(buttons);
 250         peer.mousePress(buttons);
 251         afterEvent();
 252     }
 253 
 254     /**
 255      * Releases one or more mouse buttons.
 256      *
 257      * @param buttons the Button mask; a combination of one or more
 258      * mouse button masks.
 259      * <p>
 260      * It is allowed to use only a combination of valid values as a {@code buttons} parameter.
 261      * A valid combination consists of {@code InputEvent.BUTTON1_DOWN_MASK},
 262      * {@code InputEvent.BUTTON2_DOWN_MASK}, {@code InputEvent.BUTTON3_DOWN_MASK}
 263      * and values returned by the
 264      * {@link InputEvent#getMaskForButton(int) InputEvent.getMaskForButton(button)} method.
 265      *
 266      * The valid combination also depends on a
 267      * {@link Toolkit#areExtraMouseButtonsEnabled() Toolkit.areExtraMouseButtonsEnabled()} value as follows:
 268      * <ul>
 269      * <li> If the support for extended mouse buttons is
 270      * {@link Toolkit#areExtraMouseButtonsEnabled() disabled} by Java
 271      * then it is allowed to use only the following standard button masks:
 272      * {@code InputEvent.BUTTON1_DOWN_MASK}, {@code InputEvent.BUTTON2_DOWN_MASK},
 273      * {@code InputEvent.BUTTON3_DOWN_MASK}.
 274      * <li> If the support for extended mouse buttons is
 275      * {@link Toolkit#areExtraMouseButtonsEnabled() enabled} by Java
 276      * then it is allowed to use the standard button masks
 277      * and masks for existing extended mouse buttons, if the mouse has more then three buttons.
 278      * In that way, it is allowed to use the button masks corresponding to the buttons
 279      * in the range from 1 to {@link java.awt.MouseInfo#getNumberOfButtons() MouseInfo.getNumberOfButtons()}.
 280      * <br>
 281      * It is recommended to use the {@link InputEvent#getMaskForButton(int) InputEvent.getMaskForButton(button)}
 282      * method to obtain the mask for any mouse button by its number.
 283      * </ul>
 284      * <p>
 285      * The following standard button masks are also accepted:
 286      * <ul>
 287      * <li>{@code InputEvent.BUTTON1_MASK}
 288      * <li>{@code InputEvent.BUTTON2_MASK}
 289      * <li>{@code InputEvent.BUTTON3_MASK}
 290      * </ul>
 291      * However, it is recommended to use {@code InputEvent.BUTTON1_DOWN_MASK},
 292      * {@code InputEvent.BUTTON2_DOWN_MASK},  {@code InputEvent.BUTTON3_DOWN_MASK} instead.
 293      * Either extended {@code _DOWN_MASK} or old {@code _MASK} values
 294      * should be used, but both those models should not be mixed.
 295      * @throws IllegalArgumentException if the {@code buttons} mask contains the mask for extra mouse button
 296      *         and support for extended mouse buttons is {@link Toolkit#areExtraMouseButtonsEnabled() disabled} by Java
 297      * @throws IllegalArgumentException if the {@code buttons} mask contains the mask for extra mouse button
 298      *         that does not exist on the mouse and support for extended mouse buttons is {@link Toolkit#areExtraMouseButtonsEnabled() enabled} by Java
 299      * @see #mousePress(int)
 300      * @see InputEvent#getMaskForButton(int)
 301      * @see Toolkit#areExtraMouseButtonsEnabled()
 302      * @see java.awt.MouseInfo#getNumberOfButtons()
 303      * @see java.awt.event.MouseEvent
 304      */
 305     public synchronized void mouseRelease(int buttons) {
 306         checkButtonsArgument(buttons);
 307         peer.mouseRelease(buttons);
 308         afterEvent();
 309     }
 310 
 311     private static void checkButtonsArgument(int buttons) {
 312         if ( (buttons|LEGAL_BUTTON_MASK) != LEGAL_BUTTON_MASK ) {
 313             throw new IllegalArgumentException("Invalid combination of button flags");
 314         }
 315     }
 316 
 317     /**
 318      * Rotates the scroll wheel on wheel-equipped mice.
 319      *
 320      * @param wheelAmt  number of "notches" to move the mouse wheel
 321      *                  Negative values indicate movement up/away from the user,
 322      *                  positive values indicate movement down/towards the user.
 323      *
 324      * @since 1.4
 325      */
 326     public synchronized void mouseWheel(int wheelAmt) {
 327         peer.mouseWheel(wheelAmt);
 328         afterEvent();
 329     }
 330 
 331     /**
 332      * Presses a given key.  The key should be released using the
 333      * {@code keyRelease} method.
 334      * <p>
 335      * Key codes that have more than one physical key associated with them
 336      * (e.g. {@code KeyEvent.VK_SHIFT} could mean either the
 337      * left or right shift key) will map to the left key.
 338      *
 339      * @param   keycode Key to press (e.g. {@code KeyEvent.VK_A})
 340      * @throws  IllegalArgumentException if {@code keycode} is not
 341      *          a valid key
 342      * @see     #keyRelease(int)
 343      * @see     java.awt.event.KeyEvent
 344      */
 345     public synchronized void keyPress(int keycode) {
 346         checkKeycodeArgument(keycode);
 347         peer.keyPress(keycode);
 348         afterEvent();
 349     }
 350 
 351     /**
 352      * Releases a given key.
 353      * <p>
 354      * Key codes that have more than one physical key associated with them
 355      * (e.g. {@code KeyEvent.VK_SHIFT} could mean either the
 356      * left or right shift key) will map to the left key.
 357      *
 358      * @param   keycode Key to release (e.g. {@code KeyEvent.VK_A})
 359      * @throws  IllegalArgumentException if {@code keycode} is not a
 360      *          valid key
 361      * @see  #keyPress(int)
 362      * @see     java.awt.event.KeyEvent
 363      */
 364     public synchronized void keyRelease(int keycode) {
 365         checkKeycodeArgument(keycode);
 366         peer.keyRelease(keycode);
 367         afterEvent();
 368     }
 369 
 370     private static void checkKeycodeArgument(int keycode) {
 371         // rather than build a big table or switch statement here, we'll
 372         // just check that the key isn't VK_UNDEFINED and assume that the
 373         // peer implementations will throw an exception for other bogus
 374         // values e.g. -1, 999999
 375         if (keycode == KeyEvent.VK_UNDEFINED) {
 376             throw new IllegalArgumentException("Invalid key code");
 377         }
 378     }
 379 
 380     /**
 381      * Returns the color of a pixel at the given screen coordinates.
 382      * @param   x       X position of pixel
 383      * @param   y       Y position of pixel
 384      * @return  Color of the pixel
 385      */
 386     public synchronized Color getPixelColor(int x, int y) {
 387         checkScreenCaptureAllowed();
 388         AffineTransform tx = GraphicsEnvironment.
 389                 getLocalGraphicsEnvironment().getDefaultScreenDevice().
 390                 getDefaultConfiguration().getDefaultTransform();
 391         x = (int) (x * tx.getScaleX());
 392         y = (int) (y * tx.getScaleY());
 393         Color color = new Color(peer.getRGBPixel(x, y));
 394         return color;
 395     }
 396 
 397     /**
 398      * Creates an image containing pixels read from the screen.  This image does
 399      * not include the mouse cursor.
 400      * @param   screenRect      Rect to capture in screen coordinates
 401      * @return  The captured image
 402      * @throws  IllegalArgumentException if {@code screenRect} width and height are not greater than zero
 403      * @throws  SecurityException if {@code readDisplayPixels} permission is not granted
 404      * @see     SecurityManager#checkPermission
 405      * @see     AWTPermission
 406      */
 407     public synchronized BufferedImage createScreenCapture(Rectangle screenRect) {
 408         return createCompatibleImage(screenRect, false)[0];
 409     }
 410 
 411     /**
 412      * Creates an image containing pixels read from the screen.
 413      * This image does not include the mouse cursor.
 414      * This method can be used in case there is a scaling transform
 415      * from user space to screen (device) space.
 416      * Typically this means that the display is a high resolution screen,
 417      * although strictly it means any case in which there is such a transform.
 418      * Returns a {@link java.awt.image.MultiResolutionImage}.
 419      * <p>
 420      * For a non-scaled display, the {@code MultiResolutionImage}
 421      * will have one image variant:
 422      * <ul>
 423      * <li> Base Image with user specified size.
 424      * </ul>
 425      * <p>
 426      * For a high resolution display where there is a scaling transform,
 427      * the {@code MultiResolutionImage} will have two image variants:
 428      * <ul>
 429      * <li> Base Image with user specified size. This is scaled from the screen.
 430      * <li> Native device resolution image with device size pixels.
 431      * </ul>
 432      * <p>
 433      * Example:
 434      * <pre>{@code
 435      *      Image nativeResImage;
 436      *      MultiResolutionImage mrImage = robot.createMultiResolutionScreenCapture(frame.getBounds());
 437      *      List<Image> resolutionVariants = mrImage.getResolutionVariants();
 438      *      if (resolutionVariants.size() > 1) {
 439      *          nativeResImage = resolutionVariants.get(1);
 440      *      } else {
 441      *          nativeResImage = resolutionVariants.get(0);
 442      *      }
 443      * }</pre>
 444      * @param   screenRect     Rect to capture in screen coordinates
 445      * @return  The captured image
 446      * @throws  IllegalArgumentException if {@code screenRect} width and height are not greater than zero
 447      * @throws  SecurityException if {@code readDisplayPixels} permission is not granted
 448      * @see     SecurityManager#checkPermission
 449      * @see     AWTPermission
 450      *
 451      * @since 9
 452      */
 453     public synchronized MultiResolutionImage
 454             createMultiResolutionScreenCapture(Rectangle screenRect) {
 455 
 456         return new BaseMultiResolutionImage(
 457                 createCompatibleImage(screenRect, true));
 458     }
 459 
 460     private synchronized BufferedImage[]
 461             createCompatibleImage(Rectangle screenRect, boolean isHiDPI) {
 462 
 463         checkScreenCaptureAllowed();
 464 
 465         checkValidRect(screenRect);
 466 
 467         BufferedImage lowResolutionImage;
 468         BufferedImage highResolutionImage;
 469         DataBufferInt buffer;
 470         WritableRaster raster;
 471         BufferedImage[] imageArray;
 472 
 473         if (screenCapCM == null) {
 474             /*
 475              * Fix for 4285201
 476              * Create a DirectColorModel equivalent to the default RGB ColorModel,
 477              * except with no Alpha component.
 478              */
 479 
 480             screenCapCM = new DirectColorModel(24,
 481                     /* red mask */ 0x00FF0000,
 482                     /* green mask */ 0x0000FF00,
 483                     /* blue mask */ 0x000000FF);
 484         }
 485 
 486         int[] bandmasks = new int[3];
 487         bandmasks[0] = screenCapCM.getRedMask();
 488         bandmasks[1] = screenCapCM.getGreenMask();
 489         bandmasks[2] = screenCapCM.getBlueMask();
 490 
 491         // need to sync the toolkit prior to grabbing the pixels since in some
 492         // cases rendering to the screen may be delayed
 493         Toolkit.getDefaultToolkit().sync();
 494 
 495         GraphicsConfiguration gc = GraphicsEnvironment
 496                 .getLocalGraphicsEnvironment()
 497                 .getDefaultScreenDevice().
 498                 getDefaultConfiguration();
 499         gc = SunGraphicsEnvironment.getGraphicsConfigurationAtPoint(
 500                 gc, screenRect.getCenterX(), screenRect.getCenterY());
 501 
 502         AffineTransform tx = gc.getDefaultTransform();
 503         double uiScaleX = tx.getScaleX();
 504         double uiScaleY = tx.getScaleY();
 505         int[] pixels;
 506 
 507         if (uiScaleX == 1 && uiScaleY == 1) {
 508 
 509             pixels = peer.getRGBPixels(screenRect);
 510             buffer = new DataBufferInt(pixels, pixels.length);
 511 
 512             bandmasks[0] = screenCapCM.getRedMask();
 513             bandmasks[1] = screenCapCM.getGreenMask();
 514             bandmasks[2] = screenCapCM.getBlueMask();
 515 
 516             raster = Raster.createPackedRaster(buffer, screenRect.width,
 517                     screenRect.height, screenRect.width, bandmasks, null);
 518             SunWritableRaster.makeTrackable(buffer);
 519 
 520             highResolutionImage = new BufferedImage(screenCapCM, raster,
 521                     false, null);
 522             imageArray = new BufferedImage[1];
 523             imageArray[0] = highResolutionImage;
 524 
 525         } else {
 526 
 527             int sX = (int) Math.floor(screenRect.x * uiScaleX);
 528             int sY = (int) Math.floor(screenRect.y * uiScaleY);
 529             int sWidth = (int) Math.ceil(screenRect.width * uiScaleX);
 530             int sHeight = (int) Math.ceil(screenRect.height * uiScaleY);
 531             int[] temppixels;
 532             Rectangle scaledRect = new Rectangle(sX, sY, sWidth, sHeight);
 533             temppixels = peer.getRGBPixels(scaledRect);
 534 
 535             // HighResolutionImage
 536             pixels = temppixels;
 537             buffer = new DataBufferInt(pixels, pixels.length);
 538             raster = Raster.createPackedRaster(buffer, scaledRect.width,
 539                     scaledRect.height, scaledRect.width, bandmasks, null);
 540             SunWritableRaster.makeTrackable(buffer);
 541 
 542             highResolutionImage = new BufferedImage(screenCapCM, raster,
 543                     false, null);
 544 
 545 
 546             // LowResolutionImage
 547             lowResolutionImage = new BufferedImage(screenRect.width,
 548                     screenRect.height, highResolutionImage.getType());
 549             Graphics2D g = lowResolutionImage.createGraphics();
 550             g.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
 551                     RenderingHints.VALUE_INTERPOLATION_BILINEAR);
 552             g.setRenderingHint(RenderingHints.KEY_RENDERING,
 553                     RenderingHints.VALUE_RENDER_QUALITY);
 554             g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
 555                     RenderingHints.VALUE_ANTIALIAS_ON);
 556             g.drawImage(highResolutionImage, 0, 0,
 557                     screenRect.width, screenRect.height,
 558                     0, 0, scaledRect.width, scaledRect.height, null);
 559             g.dispose();
 560 
 561             if(!isHiDPI) {
 562                 imageArray = new BufferedImage[1];
 563                 imageArray[0] = lowResolutionImage;
 564             } else {
 565                 imageArray = new BufferedImage[2];
 566                 imageArray[0] = lowResolutionImage;
 567                 imageArray[1] = highResolutionImage;
 568             }
 569 
 570         }
 571 
 572         return imageArray;
 573     }
 574 
 575     private static void checkValidRect(Rectangle rect) {
 576         if (rect.width <= 0 || rect.height <= 0) {
 577             throw new IllegalArgumentException("Rectangle width and height must be > 0");
 578         }
 579     }
 580 
 581     private static void checkScreenCaptureAllowed() {
 582         SecurityManager security = System.getSecurityManager();
 583         if (security != null) {
 584             security.checkPermission(AWTPermissions.READ_DISPLAY_PIXELS_PERMISSION);
 585         }
 586     }
 587 
 588     /*
 589      * Called after an event is generated
 590      */
 591     private void afterEvent() {
 592         autoWaitForIdle();
 593         autoDelay();
 594     }
 595 
 596     /**
 597      * Returns whether this Robot automatically invokes {@code waitForIdle}
 598      * after generating an event.
 599      * @return Whether {@code waitForIdle} is automatically called
 600      */
 601     public synchronized boolean isAutoWaitForIdle() {
 602         return isAutoWaitForIdle;
 603     }
 604 
 605     /**
 606      * Sets whether this Robot automatically invokes {@code waitForIdle}
 607      * after generating an event.
 608      * @param   isOn    Whether {@code waitForIdle} is automatically invoked
 609      */
 610     public synchronized void setAutoWaitForIdle(boolean isOn) {
 611         isAutoWaitForIdle = isOn;
 612     }
 613 
 614     /*
 615      * Calls waitForIdle after every event if so desired.
 616      */
 617     private void autoWaitForIdle() {
 618         if (isAutoWaitForIdle) {
 619             waitForIdle();
 620         }
 621     }
 622 
 623     /**
 624      * Returns the number of milliseconds this Robot sleeps after generating an event.
 625      *
 626      * @return the delay duration in milliseconds
 627      */
 628     public synchronized int getAutoDelay() {
 629         return autoDelay;
 630     }
 631 
 632     /**
 633      * Sets the number of milliseconds this Robot sleeps after generating an event.
 634      *
 635      * @param  ms the delay duration in milliseconds
 636      * @throws IllegalArgumentException If {@code ms}
 637      *         is not between 0 and 60,000 milliseconds inclusive
 638      */
 639     public synchronized void setAutoDelay(int ms) {
 640         checkDelayArgument(ms);
 641         autoDelay = ms;
 642     }
 643 
 644     /*
 645      * Automatically sleeps for the specified interval after event generated.
 646      */
 647     private void autoDelay() {
 648         delay(autoDelay);
 649     }
 650 
 651     /**
 652      * Sleeps for the specified time.
 653      * <p>
 654      * If the invoking thread is interrupted while waiting, then it will return
 655      * immediately with the interrupt status set. If the interrupted status is
 656      * already set, this method returns immediately with the interrupt status
 657      * set.
 658      *
 659      * @param  ms time to sleep in milliseconds
 660      * @throws IllegalArgumentException if {@code ms} is not between {@code 0}
 661      *         and {@code 60,000} milliseconds inclusive
 662      */
 663     public void delay(int ms) {
 664         checkDelayArgument(ms);
 665         Thread thread = Thread.currentThread();
 666         if (!thread.isInterrupted()) {
 667             try {
 668                 Thread.sleep(ms);
 669             } catch (final InterruptedException ignored) {
 670                 thread.interrupt(); // Preserve interrupt status
 671             }
 672         }
 673     }
 674 
 675     private static void checkDelayArgument(int ms) {
 676         if (ms < 0 || ms > MAX_DELAY) {
 677             throw new IllegalArgumentException("Delay must be to 0 to 60,000ms");
 678         }
 679     }
 680 
 681     /**
 682      * Waits until all events currently on the event queue have been processed.
 683      * @throws  IllegalThreadStateException if called on the AWT event dispatching thread
 684      */
 685     public synchronized void waitForIdle() {
 686         checkNotDispatchThread();
 687         SunToolkit.flushPendingEvents();
 688         ((SunToolkit) Toolkit.getDefaultToolkit()).realSync();
 689     }
 690 
 691     private static void checkNotDispatchThread() {
 692         if (EventQueue.isDispatchThread()) {
 693             throw new IllegalThreadStateException("Cannot call method from the event dispatcher thread");
 694         }
 695     }
 696 
 697     /**
 698      * Returns a string representation of this Robot.
 699      *
 700      * @return  the string representation.
 701      */
 702     @Override
 703     public synchronized String toString() {
 704         String params = "autoDelay = "+getAutoDelay()+", "+"autoWaitForIdle = "+isAutoWaitForIdle();
 705         return getClass().getName() + "[ " + params + " ]";
 706     }
 707 }
--- EOF ---