1 /*
   2  * Copyright (c) 1999, 2016, 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.BufferedImage;
  32 import java.awt.image.DataBufferInt;
  33 import java.awt.image.DirectColorModel;
  34 import java.awt.image.Raster;
  35 import java.awt.image.WritableRaster;
  36 import java.awt.peer.RobotPeer;
  37 
  38 import sun.awt.AWTPermissions;
  39 import sun.awt.ComponentFactory;
  40 import sun.awt.SunToolkit;
  41 import sun.awt.image.SunWritableRaster;
  42 
  43 /**
  44  * This class is used to generate native system input events
  45  * for the purposes of test automation, self-running demos, and
  46  * other applications where control of the mouse and keyboard
  47  * is needed. The primary purpose of Robot is to facilitate
  48  * automated testing of Java platform implementations.
  49  * <p>
  50  * Using the class to generate input events differs from posting
  51  * events to the AWT event queue or AWT components in that the
  52  * events are generated in the platform's native input
  53  * queue. For example, {@code Robot.mouseMove} will actually move
  54  * the mouse cursor instead of just generating mouse move events.
  55  * <p>
  56  * Note that some platforms require special privileges or extensions
  57  * to access low-level input control. If the current platform configuration
  58  * does not allow input control, an {@code AWTException} will be thrown
  59  * when trying to construct Robot objects. For example, X-Window systems
  60  * will throw the exception if the XTEST 2.2 standard extension is not supported
  61  * (or not enabled) by the X server.
  62  * <p>
  63  * Applications that use Robot for purposes other than self-testing should
  64  * handle these error conditions gracefully.
  65  *
  66  * @author      Robi Khan
  67  * @since       1.3
  68  */
  69 public class Robot {
  70     private static final int MAX_DELAY = 60000;
  71     private RobotPeer peer;
  72     private boolean isAutoWaitForIdle = false;
  73     private int autoDelay = 0;
  74     private static int LEGAL_BUTTON_MASK = 0;
  75 
  76     private DirectColorModel screenCapCM = null;
  77 
  78     /**
  79      * Constructs a Robot object in the coordinate system of the primary screen.
  80      *
  81      * @throws  AWTException if the platform configuration does not allow
  82      * low-level input control.  This exception is always thrown when
  83      * GraphicsEnvironment.isHeadless() returns true
  84      * @throws  SecurityException if {@code createRobot} permission is not granted
  85      * @see     java.awt.GraphicsEnvironment#isHeadless
  86      * @see     SecurityManager#checkPermission
  87      * @see     AWTPermission
  88      */
  89     public Robot() throws AWTException {
  90         if (GraphicsEnvironment.isHeadless()) {
  91             throw new AWTException("headless environment");
  92         }
  93         init(GraphicsEnvironment.getLocalGraphicsEnvironment()
  94             .getDefaultScreenDevice());
  95     }
  96 
  97     /**
  98      * Creates a Robot for the given screen device. Coordinates passed
  99      * to Robot method calls like mouseMove and createScreenCapture will
 100      * be interpreted as being in the same coordinate system as the
 101      * specified screen. Note that depending on the platform configuration,
 102      * multiple screens may either:
 103      * <ul>
 104      * <li>share the same coordinate system to form a combined virtual screen</li>
 105      * <li>use different coordinate systems to act as independent screens</li>
 106      * </ul>
 107      * This constructor is meant for the latter case.
 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         checkIsScreenDevice(screen);
 127         init(screen);
 128     }
 129 
 130     private void init(GraphicsDevice screen) throws AWTException {
 131         checkRobotAllowed();
 132         Toolkit toolkit = Toolkit.getDefaultToolkit();
 133         if (toolkit instanceof ComponentFactory) {
 134             peer = ((ComponentFactory)toolkit).createRobot(this, screen);
 135             disposer = new RobotDisposer(peer);
 136             sun.java2d.Disposer.addRecord(anchor, disposer);
 137         }
 138         initLegalButtonMask();
 139     }
 140 
 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 void checkRobotAllowed() {
 164         SecurityManager security = System.getSecurityManager();
 165         if (security != null) {
 166             security.checkPermission(AWTPermissions.CREATE_ROBOT_PERMISSION);
 167         }
 168     }
 169 
 170     /* check if the given device is a screen device */
 171     private void checkIsScreenDevice(GraphicsDevice device) {
 172         if (device == null || device.getType() != GraphicsDevice.TYPE_RASTER_SCREEN) {
 173             throw new IllegalArgumentException("not a valid screen device");
 174         }
 175     }
 176 
 177     private transient Object anchor = new Object();
 178 
 179     static class RobotDisposer implements sun.java2d.DisposerRecord {
 180         private final RobotPeer peer;
 181         public RobotDisposer(RobotPeer peer) {
 182             this.peer = peer;
 183         }
 184         public void dispose() {
 185             if (peer != null) {
 186                 peer.dispose();
 187             }
 188         }
 189     }
 190 
 191     private transient RobotDisposer disposer;
 192 
 193     /**
 194      * Moves mouse pointer to given screen coordinates.
 195      * @param x         X position
 196      * @param y         Y position
 197      */
 198     public synchronized void mouseMove(int x, int y) {
 199         peer.mouseMove(x, y);
 200         afterEvent();
 201     }
 202 
 203     /**
 204      * Presses one or more mouse buttons.  The mouse buttons should
 205      * be released using the {@link #mouseRelease(int)} method.
 206      *
 207      * @param buttons the Button mask; a combination of one or more
 208      * mouse button masks.
 209      * <p>
 210      * It is allowed to use only a combination of valid values as a {@code buttons} parameter.
 211      * A valid combination consists of {@code InputEvent.BUTTON1_DOWN_MASK},
 212      * {@code InputEvent.BUTTON2_DOWN_MASK}, {@code InputEvent.BUTTON3_DOWN_MASK}
 213      * and values returned by the
 214      * {@link InputEvent#getMaskForButton(int) InputEvent.getMaskForButton(button)} method.
 215      *
 216      * The valid combination also depends on a
 217      * {@link Toolkit#areExtraMouseButtonsEnabled() Toolkit.areExtraMouseButtonsEnabled()} value as follows:
 218      * <ul>
 219      * <li> If support for extended mouse buttons is
 220      * {@link Toolkit#areExtraMouseButtonsEnabled() disabled} by Java
 221      * then it is allowed to use only the following standard button masks:
 222      * {@code InputEvent.BUTTON1_DOWN_MASK}, {@code InputEvent.BUTTON2_DOWN_MASK},
 223      * {@code InputEvent.BUTTON3_DOWN_MASK}.
 224      * <li> If support for extended mouse buttons is
 225      * {@link Toolkit#areExtraMouseButtonsEnabled() enabled} by Java
 226      * then it is allowed to use the standard button masks
 227      * and masks for existing extended mouse buttons, if the mouse has more then three buttons.
 228      * In that way, it is allowed to use the button masks corresponding to the buttons
 229      * in the range from 1 to {@link java.awt.MouseInfo#getNumberOfButtons() MouseInfo.getNumberOfButtons()}.
 230      * <br>
 231      * It is recommended to use the {@link InputEvent#getMaskForButton(int) InputEvent.getMaskForButton(button)}
 232      * method to obtain the mask for any mouse button by its number.
 233      * </ul>
 234      * <p>
 235      * The following standard button masks are also accepted:
 236      * <ul>
 237      * <li>{@code InputEvent.BUTTON1_MASK}
 238      * <li>{@code InputEvent.BUTTON2_MASK}
 239      * <li>{@code InputEvent.BUTTON3_MASK}
 240      * </ul>
 241      * However, it is recommended to use {@code InputEvent.BUTTON1_DOWN_MASK},
 242      * {@code InputEvent.BUTTON2_DOWN_MASK},  {@code InputEvent.BUTTON3_DOWN_MASK} instead.
 243      * Either extended {@code _DOWN_MASK} or old {@code _MASK} values
 244      * should be used, but both those models should not be mixed.
 245      * @throws IllegalArgumentException if the {@code buttons} mask contains the mask for extra mouse button
 246      *         and support for extended mouse buttons is {@link Toolkit#areExtraMouseButtonsEnabled() disabled} by Java
 247      * @throws IllegalArgumentException if the {@code buttons} mask contains the mask for extra mouse button
 248      *         that does not exist on the mouse and support for extended mouse buttons is {@link Toolkit#areExtraMouseButtonsEnabled() enabled} by Java
 249      * @see #mouseRelease(int)
 250      * @see InputEvent#getMaskForButton(int)
 251      * @see Toolkit#areExtraMouseButtonsEnabled()
 252      * @see java.awt.MouseInfo#getNumberOfButtons()
 253      * @see java.awt.event.MouseEvent
 254      */
 255     public synchronized void mousePress(int buttons) {
 256         checkButtonsArgument(buttons);
 257         peer.mousePress(buttons);
 258         afterEvent();
 259     }
 260 
 261     /**
 262      * Releases one or more mouse buttons.
 263      *
 264      * @param buttons the Button mask; a combination of one or more
 265      * mouse button masks.
 266      * <p>
 267      * It is allowed to use only a combination of valid values as a {@code buttons} parameter.
 268      * A valid combination consists of {@code InputEvent.BUTTON1_DOWN_MASK},
 269      * {@code InputEvent.BUTTON2_DOWN_MASK}, {@code InputEvent.BUTTON3_DOWN_MASK}
 270      * and values returned by the
 271      * {@link InputEvent#getMaskForButton(int) InputEvent.getMaskForButton(button)} method.
 272      *
 273      * The valid combination also depends on a
 274      * {@link Toolkit#areExtraMouseButtonsEnabled() Toolkit.areExtraMouseButtonsEnabled()} value as follows:
 275      * <ul>
 276      * <li> If the support for extended mouse buttons is
 277      * {@link Toolkit#areExtraMouseButtonsEnabled() disabled} by Java
 278      * then it is allowed to use only the following standard button masks:
 279      * {@code InputEvent.BUTTON1_DOWN_MASK}, {@code InputEvent.BUTTON2_DOWN_MASK},
 280      * {@code InputEvent.BUTTON3_DOWN_MASK}.
 281      * <li> If the support for extended mouse buttons is
 282      * {@link Toolkit#areExtraMouseButtonsEnabled() enabled} by Java
 283      * then it is allowed to use the standard button masks
 284      * and masks for existing extended mouse buttons, if the mouse has more then three buttons.
 285      * In that way, it is allowed to use the button masks corresponding to the buttons
 286      * in the range from 1 to {@link java.awt.MouseInfo#getNumberOfButtons() MouseInfo.getNumberOfButtons()}.
 287      * <br>
 288      * It is recommended to use the {@link InputEvent#getMaskForButton(int) InputEvent.getMaskForButton(button)}
 289      * method to obtain the mask for any mouse button by its number.
 290      * </ul>
 291      * <p>
 292      * The following standard button masks are also accepted:
 293      * <ul>
 294      * <li>{@code InputEvent.BUTTON1_MASK}
 295      * <li>{@code InputEvent.BUTTON2_MASK}
 296      * <li>{@code InputEvent.BUTTON3_MASK}
 297      * </ul>
 298      * However, it is recommended to use {@code InputEvent.BUTTON1_DOWN_MASK},
 299      * {@code InputEvent.BUTTON2_DOWN_MASK},  {@code InputEvent.BUTTON3_DOWN_MASK} instead.
 300      * Either extended {@code _DOWN_MASK} or old {@code _MASK} values
 301      * should be used, but both those models should not be mixed.
 302      * @throws IllegalArgumentException if the {@code buttons} mask contains the mask for extra mouse button
 303      *         and support for extended mouse buttons is {@link Toolkit#areExtraMouseButtonsEnabled() disabled} by Java
 304      * @throws IllegalArgumentException if the {@code buttons} mask contains the mask for extra mouse button
 305      *         that does not exist on the mouse and support for extended mouse buttons is {@link Toolkit#areExtraMouseButtonsEnabled() enabled} by Java
 306      * @see #mousePress(int)
 307      * @see InputEvent#getMaskForButton(int)
 308      * @see Toolkit#areExtraMouseButtonsEnabled()
 309      * @see java.awt.MouseInfo#getNumberOfButtons()
 310      * @see java.awt.event.MouseEvent
 311      */
 312     public synchronized void mouseRelease(int buttons) {
 313         checkButtonsArgument(buttons);
 314         peer.mouseRelease(buttons);
 315         afterEvent();
 316     }
 317 
 318     private void checkButtonsArgument(int buttons) {
 319         if ( (buttons|LEGAL_BUTTON_MASK) != LEGAL_BUTTON_MASK ) {
 320             throw new IllegalArgumentException("Invalid combination of button flags");
 321         }
 322     }
 323 
 324     /**
 325      * Rotates the scroll wheel on wheel-equipped mice.
 326      *
 327      * @param wheelAmt  number of "notches" to move the mouse wheel
 328      *                  Negative values indicate movement up/away from the user,
 329      *                  positive values indicate movement down/towards the user.
 330      *
 331      * @since 1.4
 332      */
 333     public synchronized void mouseWheel(int wheelAmt) {
 334         peer.mouseWheel(wheelAmt);
 335         afterEvent();
 336     }
 337 
 338     /**
 339      * Presses a given key.  The key should be released using the
 340      * {@code keyRelease} method.
 341      * <p>
 342      * Key codes that have more than one physical key associated with them
 343      * (e.g. {@code KeyEvent.VK_SHIFT} could mean either the
 344      * left or right shift key) will map to the left key.
 345      *
 346      * @param   keycode Key to press (e.g. {@code KeyEvent.VK_A})
 347      * @throws  IllegalArgumentException if {@code keycode} is not
 348      *          a valid key
 349      * @see     #keyRelease(int)
 350      * @see     java.awt.event.KeyEvent
 351      */
 352     public synchronized void keyPress(int keycode) {
 353         checkKeycodeArgument(keycode);
 354         peer.keyPress(keycode);
 355         afterEvent();
 356     }
 357 
 358     /**
 359      * Releases a given key.
 360      * <p>
 361      * Key codes that have more than one physical key associated with them
 362      * (e.g. {@code KeyEvent.VK_SHIFT} could mean either the
 363      * left or right shift key) will map to the left key.
 364      *
 365      * @param   keycode Key to release (e.g. {@code KeyEvent.VK_A})
 366      * @throws  IllegalArgumentException if {@code keycode} is not a
 367      *          valid key
 368      * @see  #keyPress(int)
 369      * @see     java.awt.event.KeyEvent
 370      */
 371     public synchronized void keyRelease(int keycode) {
 372         checkKeycodeArgument(keycode);
 373         peer.keyRelease(keycode);
 374         afterEvent();
 375     }
 376 
 377     private void checkKeycodeArgument(int keycode) {
 378         // rather than build a big table or switch statement here, we'll
 379         // just check that the key isn't VK_UNDEFINED and assume that the
 380         // peer implementations will throw an exception for other bogus
 381         // values e.g. -1, 999999
 382         if (keycode == KeyEvent.VK_UNDEFINED) {
 383             throw new IllegalArgumentException("Invalid key code");
 384         }
 385     }
 386 
 387     /**
 388      * Returns the color of a pixel at the given screen coordinates.
 389      * @param   x       X position of pixel
 390      * @param   y       Y position of pixel
 391      * @return  Color of the pixel
 392      */
 393     public synchronized Color getPixelColor(int x, int y) {
 394         Color color = new Color(peer.getRGBPixel(x, y));
 395         return color;
 396     }
 397 
 398     private static int interp(int pixels[], int x, int y, int w, int h, int fractx1, int fracty1) {
 399         int fractx0 = 256 - fractx1;
 400         int fracty0 = 256 - fracty1;
 401         int i = y * w + x;
 402         int rgb00 = (x < 0 || y < 0 || x >= w || y >= h) ? 0 : pixels[i];
 403         if (fracty1 == 0) {
 404             // No interplation with pixels[y+1]
 405             if (fractx1 == 0) {
 406                 // No interpolation with any neighbors
 407                 return rgb00;
 408             }
 409             int rgb10 = (y < 0 || x + 1 >= w || y >= h) ? 0 : pixels[i + 1];
 410             return interp(rgb00, rgb10, fractx0, fractx1);
 411         } else if (fractx1 == 0) {
 412             // No interpolation with pixels[x+1]
 413             int rgb01 = (x < 0 || x >= w || y + 1 >= h) ? 0 : pixels[i + w];
 414             return interp(rgb00, rgb01, fracty0, fracty1);
 415         } else {
 416             // All 4 neighbors must be interpolated
 417             int rgb10 = (y < 0 || x + 1 >= w || y >= h) ? 0 : pixels[i + 1];
 418             int rgb01 = (x < 0 || x >= w || y + 1 >= h) ? 0 : pixels[i + w];
 419             int rgb11 = (x + 1 >= w || y + 1 >= h) ? 0 : pixels[i + w + 1];
 420             return interp(interp(rgb00, rgb10, fractx0, fractx1),
 421                     interp(rgb01, rgb11, fractx0, fractx1),
 422                     fracty0, fracty1);
 423         }
 424     }
 425 
 426     private static int interp(int rgb0, int rgb1, int fract0, int fract1) {
 427         int a0 = (rgb0 >> 24) & 0xff;
 428         int r0 = (rgb0 >> 16) & 0xff;
 429         int g0 = (rgb0 >> 8) & 0xff;
 430         int b0 = (rgb0) & 0xff;
 431         int a1 = (rgb1 >> 24) & 0xff;
 432         int r1 = (rgb1 >> 16) & 0xff;
 433         int g1 = (rgb1 >> 8) & 0xff;
 434         int b1 = (rgb1) & 0xff;
 435         int a = (a0 * fract0 + a1 * fract1) >> 8;
 436         int r = (r0 * fract0 + r1 * fract1) >> 8;
 437         int g = (g0 * fract0 + g1 * fract1) >> 8;
 438         int b = (b0 * fract0 + b1 * fract1) >> 8;
 439         return (a << 24) | (r << 16) | (g << 8) | b;
 440     }
 441 
 442      /**
 443      * Creates an image containing pixels read from the screen.  This image does
 444      * not include the mouse cursor.
 445      * @param   screenRect      Rect to capture in screen coordinates
 446      * @return  The captured image
 447      * @throws  IllegalArgumentException if {@code screenRect} width and height are not greater than zero
 448      * @throws  SecurityException if {@code readDisplayPixels} permission is not granted
 449      * @see     SecurityManager#checkPermission
 450      * @see     AWTPermission
 451      */
 452     public synchronized BufferedImage createScreenCapture(Rectangle screenRect) {
 453           return createScreenCapture(screenRect, false);
 454     }
 455 
 456     /**
 457      * Creates an image containing pixels read from the screen.  This image does
 458      * not include the mouse cursor.
 459      * @param   screenRect  Rect to capture in screen coordinates
 460      * @param   isHiDPI Specifies if HiDPI
 461      * @return  The captured image
 462      * @throws  IllegalArgumentException if {@code screenRect} width and height are not greater than zero
 463      * @throws  SecurityException if {@code readDisplayPixels} permission is not granted
 464      * @see     SecurityManager#checkPermission
 465      * @see     AWTPermission
 466      */
 467     public synchronized BufferedImage createScreenCapture(Rectangle screenRect, 
 468             boolean isHiDPI) {
 469         checkScreenCaptureAllowed();
 470 
 471         checkValidRect(screenRect);
 472 
 473         BufferedImage image;
 474         DataBufferInt buffer;
 475         WritableRaster raster;
 476 
 477         if (screenCapCM == null) {
 478             /*
 479              * Fix for 4285201
 480              * Create a DirectColorModel equivalent to the default RGB ColorModel,
 481              * except with no Alpha component.
 482              */
 483 
 484             screenCapCM = new DirectColorModel(24,
 485                     /* red mask */ 0x00FF0000,
 486                     /* green mask */ 0x0000FF00,
 487                     /* blue mask */ 0x000000FF);
 488         }
 489 
 490         // need to sync the toolkit prior to grabbing the pixels since in some
 491         // cases rendering to the screen may be delayed
 492         Toolkit.getDefaultToolkit().sync();
 493         AffineTransform tx = GraphicsEnvironment.
 494                 getLocalGraphicsEnvironment().getDefaultScreenDevice().
 495                 getDefaultConfiguration().getDefaultTransform();
 496         double uiScaleX = tx.getScaleX();
 497         double uiScaleY = tx.getScaleY();
 498         int pixels[];
 499         int[] bandmasks = new int[3];
 500 
 501         if (uiScaleX == 1 && uiScaleY == 1) {
 502             pixels = peer.getRGBPixels(screenRect);
 503         } else {
 504             int x = screenRect.x;
 505             int y = screenRect.y;
 506             int width = screenRect.width;
 507             int height = screenRect.height;
 508             int pminx = (int) Math.floor(x * uiScaleX);
 509             int pminy = (int) Math.floor(y * uiScaleY);
 510             int pmaxx = (int) Math.ceil((x + width) * uiScaleX);
 511             int pmaxy = (int) Math.ceil((y + height) * uiScaleY);
 512             int pwidth = pmaxx - pminx;
 513             int pheight = pmaxy - pminy;
 514             int temppixels[];
 515             Rectangle rect = new Rectangle(pminx, pminy, pwidth, pheight);
 516             temppixels = peer.getRGBPixels(rect);
 517             if (isHiDPI) {
 518                 pixels = temppixels;
 519                 screenRect.width = pwidth;
 520                 screenRect.height = pheight;
 521             } else {
 522                 pixels = new int[width * height];
 523                 int index = 0;
 524                 for (int iy = 0; iy < height; iy++) {
 525                     float rely = (float) (((y + iy + 0.5f) * uiScaleY)
 526                             - (pminy + 0.5f));
 527                     int irely = (int) Math.floor(rely);
 528                     int fracty = (int) ((rely - irely) * 256);
 529                     for (int ix = 0; ix < width; ix++) {
 530                         float relx = (float) (((x + ix + 0.5f) * uiScaleX)
 531                                 - (pminx + 0.5f));
 532                         int irelx = (int) Math.floor(relx);
 533                         int fractx = (int) ((relx - irelx) * 256);
 534                         pixels[index++]
 535                                 = interp(temppixels, irelx, irely, pwidth,
 536                                         pheight, fractx, fracty);
 537                     }
 538                 }
 539                 screenRect.width = width;
 540                 screenRect.height = height;
 541             }
 542         }
 543 
 544         buffer = new DataBufferInt(pixels, pixels.length);
 545 
 546         bandmasks[0] = screenCapCM.getRedMask();
 547         bandmasks[1] = screenCapCM.getGreenMask();
 548         bandmasks[2] = screenCapCM.getBlueMask();
 549 
 550         raster = Raster.createPackedRaster(buffer, screenRect.width, screenRect.height, screenRect.width, bandmasks, null);
 551         SunWritableRaster.makeTrackable(buffer);
 552 
 553         image = new BufferedImage(screenCapCM, raster, false, null);
 554 
 555         return image;
 556     }
 557 
 558     private static void checkValidRect(Rectangle rect) {
 559         if (rect.width <= 0 || rect.height <= 0) {
 560             throw new IllegalArgumentException("Rectangle width and height must be > 0");
 561         }
 562     }
 563 
 564     private static void checkScreenCaptureAllowed() {
 565         SecurityManager security = System.getSecurityManager();
 566         if (security != null) {
 567             security.checkPermission(AWTPermissions.READ_DISPLAY_PIXELS_PERMISSION);
 568         }
 569     }
 570 
 571     /*
 572      * Called after an event is generated
 573      */
 574     private void afterEvent() {
 575         autoWaitForIdle();
 576         autoDelay();
 577     }
 578 
 579     /**
 580      * Returns whether this Robot automatically invokes {@code waitForIdle}
 581      * after generating an event.
 582      * @return Whether {@code waitForIdle} is automatically called
 583      */
 584     public synchronized boolean isAutoWaitForIdle() {
 585         return isAutoWaitForIdle;
 586     }
 587 
 588     /**
 589      * Sets whether this Robot automatically invokes {@code waitForIdle}
 590      * after generating an event.
 591      * @param   isOn    Whether {@code waitForIdle} is automatically invoked
 592      */
 593     public synchronized void setAutoWaitForIdle(boolean isOn) {
 594         isAutoWaitForIdle = isOn;
 595     }
 596 
 597     /*
 598      * Calls waitForIdle after every event if so desired.
 599      */
 600     private void autoWaitForIdle() {
 601         if (isAutoWaitForIdle) {
 602             waitForIdle();
 603         }
 604     }
 605 
 606     /**
 607      * Returns the number of milliseconds this Robot sleeps after generating an event.
 608      *
 609      * @return the delay duration in milliseconds
 610      */
 611     public synchronized int getAutoDelay() {
 612         return autoDelay;
 613     }
 614 
 615     /**
 616      * Sets the number of milliseconds this Robot sleeps after generating an event.
 617      *
 618      * @param  ms the delay duration in milliseconds
 619      * @throws IllegalArgumentException If {@code ms}
 620      *         is not between 0 and 60,000 milliseconds inclusive
 621      */
 622     public synchronized void setAutoDelay(int ms) {
 623         checkDelayArgument(ms);
 624         autoDelay = ms;
 625     }
 626 
 627     /*
 628      * Automatically sleeps for the specified interval after event generated.
 629      */
 630     private void autoDelay() {
 631         delay(autoDelay);
 632     }
 633 
 634     /**
 635      * Sleeps for the specified time.
 636      * To catch any {@code InterruptedException}s that occur,
 637      * {@code Thread.sleep()} may be used instead.
 638      *
 639      * @param  ms time to sleep in milliseconds
 640      * @throws IllegalArgumentException if {@code ms}
 641      *         is not between 0 and 60,000 milliseconds inclusive
 642      * @see java.lang.Thread#sleep
 643      */
 644     public synchronized void delay(int ms) {
 645         checkDelayArgument(ms);
 646         try {
 647             Thread.sleep(ms);
 648         } catch(InterruptedException ite) {
 649             ite.printStackTrace();
 650         }
 651     }
 652 
 653     private void checkDelayArgument(int ms) {
 654         if (ms < 0 || ms > MAX_DELAY) {
 655             throw new IllegalArgumentException("Delay must be to 0 to 60,000ms");
 656         }
 657     }
 658 
 659     /**
 660      * Waits until all events currently on the event queue have been processed.
 661      * @throws  IllegalThreadStateException if called on the AWT event dispatching thread
 662      */
 663     public synchronized void waitForIdle() {
 664         checkNotDispatchThread();
 665         SunToolkit.flushPendingEvents();
 666         ((SunToolkit) Toolkit.getDefaultToolkit()).realSync();
 667     }
 668 
 669     private void checkNotDispatchThread() {
 670         if (EventQueue.isDispatchThread()) {
 671             throw new IllegalThreadStateException("Cannot call method from the event dispatcher thread");
 672         }
 673     }
 674 
 675     /**
 676      * Returns a string representation of this Robot.
 677      *
 678      * @return  the string representation.
 679      */
 680     @Override
 681     public synchronized String toString() {
 682         String params = "autoDelay = "+getAutoDelay()+", "+"autoWaitForIdle = "+isAutoWaitForIdle();
 683         return getClass().getName() + "[ " + params + " ]";
 684     }
 685 }