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