1 /*
   2  * Copyright (c) 2010, 2018, 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 package com.sun.glass.ui;
  26 
  27 import java.lang.annotation.Native;
  28 import java.nio.ByteBuffer;
  29 import java.nio.IntBuffer;
  30 import java.util.Objects;
  31 
  32 import javafx.scene.image.Image;
  33 import javafx.scene.image.PixelWriter;
  34 import javafx.scene.image.WritableImage;
  35 import javafx.scene.input.KeyCode;
  36 import javafx.scene.input.MouseButton;
  37 import javafx.scene.paint.Color;
  38 import javafx.stage.Screen;
  39 
  40 import com.sun.javafx.image.PixelUtils;
  41 
  42 public abstract class GlassRobot {
  43 
  44     @Native public static final int MOUSE_LEFT_BTN    = 1 << 0;
  45     @Native public static final int MOUSE_RIGHT_BTN   = 1 << 1;
  46     @Native public static final int MOUSE_MIDDLE_BTN  = 1 << 2;
  47 
  48     /**
  49      * Initializes any state necessary for this {@code Robot}. Called by
  50      * the {@code Robot} constructor.
  51      */
  52     public abstract void create();
  53 
  54     /**
  55      * Frees any resources allocated by this {@code Robot}.
  56      */
  57     public abstract void destroy();
  58 
  59     /**
  60      * Presses the specified {@link KeyCode} key.
  61      *
  62      * @param keyCode the key to press
  63      */
  64     public abstract void keyPress(KeyCode keyCode);
  65 
  66     /**
  67      * Releases the specified {@link KeyCode} key.
  68      *
  69      * @param keyCode the key to release
  70      */
  71     public abstract void keyRelease(KeyCode keyCode);
  72 
  73     /**
  74      * Returns the current mouse x-position.
  75      *
  76      * @return the current mouse x-position
  77      */
  78     public abstract double getMouseX();
  79 
  80     /**
  81      * Returns the current mouse y-position.
  82      *
  83      * @return the current mouse y-position
  84      */
  85     public abstract double getMouseY();
  86 
  87     /**
  88      * Moves the mouse to the specified (x,y) screen coordinates relative to
  89      * the primary screen.
  90      *
  91      * @param x screen coordinate x to move the mouse to
  92      * @param y screen coordinate y to move the mouse to
  93      */
  94     public abstract void mouseMove(double x, double y);
  95 
  96     /**
  97      * Presses the specified {@link MouseButton}s.
  98      *
  99      * @param buttons the mouse buttons to press
 100      */
 101     public abstract void mousePress(MouseButton... buttons);
 102 
 103     /**
 104      * Releases the specified {@link MouseButton}s.
 105      *
 106      * @param buttons the mouse buttons to release
 107      */
 108     public abstract void mouseRelease(MouseButton... buttons);
 109 
 110     /**
 111      * Scrolls the mouse wheel by the specified amount of wheel clicks. A positive
 112      * {@code wheelAmt} scrolls the wheel towards the user (down) whereas negative
 113      * amounts scrolls the wheel away from the user (up).
 114      *
 115      * @param wheelAmt the (signed) amount of clicks to scroll the wheel
 116      */
 117     public abstract void mouseWheel(int wheelAmt);
 118 
 119     /**
 120      * Returns the {@link Color} of the pixel at the screen coordinates relative to the
 121      * primary screen specified by {@code location}. Regardless of the scale of the screen
 122      * ({@link Screen#getOutputScaleX()}, {@link Screen#getOutputScaleY()}), this method only
 123      * samples a single pixel. For example, on a HiDPI screen with output scale 2, the screen
 124      * unit at the point (x,y) may have 4 pixels. In this case the color returned is the color
 125      * of the top, left pixel. Color values are <em>not</em> averaged when a screen unit is
 126      * made up of more than one pixel.
 127      *
 128      * @param x the x coordinate to get the pixel color from
 129      * @param y the y coordinate to get the pixel color from
 130      * @return the pixel color at the specified screen coordinates
 131      */
 132     public abstract Color getPixelColor(double x, double y);
 133 
 134     /**
 135      * Captures the specified rectangular area of the screen and uses it to fill the given
 136      * {@code data} array with the raw pixel data. The data is in RGBA format where each
 137      * pixel in the image is encoded as 4 bytes - one for each color component of each
 138      * pixel. If this method is not overridden by subclasses then
 139      * {@link #getScreenCapture(WritableImage, double, double, double, double, boolean)}
 140      * must be overridden to not call this method.
 141      *
 142      * @param x the starting x-position of the rectangular area to capture
 143      * @param y the starting y-position of the rectangular area to capture
 144      * @param width the width of the rectangular area to capture
 145      * @param height the height of the rectangular area to capture
 146      * @param data the array to fill with the raw pixel data corresponding to
 147      * the captured region
 148      * @param scaleToFit If {@literal true} the returned {@code Image} will be
 149      * scaled to fit the request dimensions, if necessary. Otherwise the size
 150      * of the returned image will depend on the output scale (DPI) of the primary
 151      * screen.
 152      */
 153     public void getScreenCapture(int x, int y, int width, int height, int[] data, boolean scaleToFit) {
 154         throw new InternalError("not implemented");
 155     }
 156 
 157     /**
 158      * Returns an {@code Image} containing the specified rectangular area of the screen.
 159      * <p>
 160      * If the {@code scaleToFit} argument is {@literal false}, the returned
 161      * {@code Image} object dimensions may differ from the requested {@code width}
 162      * and {@code height} depending on how many physical pixels the area occupies
 163      * on the screen. E.g. in HiDPI mode on the Mac (aka Retina display) the pixels
 164      * are doubled, and thus a screen capture of an area of size (10x10) pixels
 165      * will result in an {@code Image} with dimensions (20x20). Calling code should
 166      * use the returned images's {@link Image#getWidth() and {@link Image#getHeight()
 167      * methods to determine the actual image size.
 168      * <p>
 169      * If {@code scaleToFit} is {@literal true}, the returned {@code Image} is of
 170      * the requested size. Note that in this case the image will be scaled in
 171      * order to fit to the requested dimensions if necessary such as when running
 172      * on a HiDPI display.
 173      *
 174      * @param x the starting x-position of the rectangular area to capture
 175      * @param y the starting y-position of the rectangular area to capture
 176      * @param width the width of the rectangular area to capture
 177      * @param height the height of the rectangular area to capture
 178      * @param scaleToFit If {@literal true} the returned {@code Image} will be
 179      * scaled to fit the request dimensions, if necessary. Otherwise the size
 180      * of the returned image will depend on the output scale (DPI) of the primary
 181      * screen.
 182      */
 183     public WritableImage getScreenCapture(WritableImage image, double x, double y, double width,
 184                                           double height, boolean scaleToFit) {
 185         if (width <= 0) {
 186             throw new IllegalArgumentException("width must be > 0");
 187         }
 188         if (height <= 0) {
 189             throw new IllegalArgumentException("height must be > 0");
 190         }
 191         Screen primaryScreen = Screen.getPrimary();
 192         Objects.requireNonNull(primaryScreen);
 193         double outputScaleX = primaryScreen.getOutputScaleX();
 194         double outputScaleY = primaryScreen.getOutputScaleY();
 195         int data[];
 196         int dw, dh;
 197         if (outputScaleX == 1.0f && outputScaleY == 1.0f) {
 198             // No scaling will be necessary regardless of if "scaleToFit" is set or not.
 199             data = new int[(int) (width * height)];
 200             getScreenCapture((int) x, (int) y, (int) width, (int) height, data, scaleToFit);
 201             dw = (int) width;
 202             dh = (int) height;
 203         } else {
 204             // Compute the absolute pixel bounds that the requested size will fill given
 205             // the display's scale.
 206             int pminx = (int) Math.floor(x * outputScaleX);
 207             int pminy = (int) Math.floor(y * outputScaleY);
 208             int pmaxx = (int) Math.ceil((x + width) * outputScaleX);
 209             int pmaxy = (int) Math.ceil((y + height) * outputScaleY);
 210             int pwidth = pmaxx - pminx;
 211             int pheight = pmaxy - pminy;
 212             int tmpdata[] = new int[pwidth * pheight];
 213             getScreenCapture(pminx, pminy, pwidth, pheight, tmpdata, scaleToFit);
 214             dw = pwidth;
 215             dh = pheight;
 216             if (!scaleToFit) {
 217                 data = tmpdata;
 218             } else {
 219                 // We must resize the image to fit the requested bounds. This means
 220                 // resizing the pixel data array which we accomplish using bilinear (?)
 221                 // interpolation.
 222                 data = new int[(int) (width * height)];
 223                 int index = 0;
 224                 for (int iy = 0; iy < height; iy++) {
 225                     double rely = ((y + iy + 0.5f) * outputScaleY) - (pminy + 0.5f);
 226                     int irely = (int) Math.floor(rely);
 227                     int fracty = (int) ((rely - irely) * 256);
 228                     for (int ix = 0; ix < width; ix++) {
 229                         double relx = ((x + ix + 0.5f) * outputScaleX) - (pminx + 0.5f);
 230                         int irelx = (int) Math.floor(relx);
 231                         int fractx = (int) ((relx - irelx) * 256);
 232                         data[index++] = interp(tmpdata, irelx, irely, pwidth, pheight, fractx, fracty);
 233                     }
 234                 }
 235                 dw = (int) width;
 236                 dh = (int) height;
 237             }
 238         }
 239 
 240         return convertFromPixels(image, Application.GetApplication().createPixels(dw, dh, IntBuffer.wrap(data)));
 241     }
 242 
 243     public static int convertToRobotMouseButton(MouseButton[] buttons) {
 244         int ret = 0;
 245         for (MouseButton button : buttons) {
 246             switch (button) {
 247                 case PRIMARY: ret |= MOUSE_LEFT_BTN; break;
 248                 case SECONDARY: ret |= MOUSE_RIGHT_BTN; break;
 249                 case MIDDLE: ret |= MOUSE_MIDDLE_BTN; break;
 250                 default: throw new IllegalArgumentException("MouseButton: " + button + " not supported by Robot");
 251             }
 252         }
 253         return ret;
 254     }
 255 
 256     public static Color convertFromIntArgb(int color) {
 257         int alpha = (color >> 24) & 0xFF;
 258         int red   = (color >> 16) & 0xFF;
 259         int green = (color >>  8) & 0xFF;
 260         int blue  =  color        & 0xFF;
 261         return new Color(red / 255d, green / 255d, blue / 255d, alpha / 255d);
 262     }
 263 
 264     protected static WritableImage convertFromPixels(WritableImage image, Pixels pixels) {
 265         Objects.requireNonNull(pixels);
 266         int width = pixels.getWidth();
 267         int height = pixels.getHeight();
 268         if (image == null || image.getWidth() != width || image.getHeight() != height) {
 269             image = new WritableImage(width, height);
 270         }
 271 
 272         int bytesPerComponent = pixels.getBytesPerComponent();
 273         if (bytesPerComponent == 4) {
 274             IntBuffer intBuffer = (IntBuffer) pixels.getPixels();
 275             writeIntBufferToImage(intBuffer, image);
 276         } else if (bytesPerComponent == 1) {
 277             ByteBuffer byteBuffer = (ByteBuffer) pixels.getPixels();
 278             writeByteBufferToImage(byteBuffer, image);
 279         } else {
 280             throw new IllegalArgumentException("bytesPerComponent must be either 4 or 1 but was: " +
 281                     bytesPerComponent);
 282         }
 283 
 284         return image;
 285     }
 286 
 287     private static void writeIntBufferToImage(IntBuffer intBuffer, WritableImage image) {
 288         Objects.requireNonNull(image);
 289         PixelWriter pixelWriter = image.getPixelWriter();
 290         double width = image.getWidth();
 291         double height = image.getHeight();
 292 
 293         for (int y = 0; y < height; y++) {
 294             for (int x = 0; x < width; x++) {
 295                 int argb = intBuffer.get();
 296                 pixelWriter.setArgb(x, y, argb);
 297             }
 298         }
 299     }
 300 
 301     private static void writeByteBufferToImage(ByteBuffer byteBuffer, WritableImage image) {
 302         Objects.requireNonNull(image);
 303         PixelWriter pixelWriter = image.getPixelWriter();
 304         double width = image.getWidth();
 305         double height = image.getHeight();
 306 
 307         int format = Pixels.getNativeFormat();
 308 
 309         for (int y = 0; y < height; y++) {
 310             for (int x = 0; x < width; x++) {
 311                 if (format == Pixels.Format.BYTE_BGRA_PRE) {
 312                     pixelWriter.setArgb(x, y, PixelUtils.PretoNonPre(bgraPreToRgbaPre(byteBuffer.getInt())));
 313                 } else if (format == Pixels.Format.BYTE_ARGB) {
 314                     pixelWriter.setArgb(x, y, byteBuffer.getInt());
 315                 } else {
 316                     throw new IllegalArgumentException("format must be either BYTE_BGRA_PRE or BYTE_ARGB");
 317                 }
 318             }
 319         }
 320     }
 321 
 322     private static int bgraPreToRgbaPre(int bgraPre) {
 323         return Integer.reverseBytes(bgraPre);
 324     }
 325 
 326     private static int interp(int pixels[], int x, int y, int w, int h, int fractx1, int fracty1) {
 327         int fractx0 = 256 - fractx1;
 328         int fracty0 = 256 - fracty1;
 329         int i = y * w + x;
 330         int rgb00 = (x < 0 || y < 0 || x >= w || y >= h) ? 0 : pixels[i];
 331         if (fracty1 == 0) {
 332             // No interpolation with pixels[y+1]
 333             if (fractx1 == 0) {
 334                 // No interpolation with any neighbors
 335                 return rgb00;
 336             }
 337             int rgb10 = (y < 0 || x+1 >= w || y >= h) ? 0 : pixels[i+1];
 338             return interp(rgb00, rgb10, fractx0, fractx1);
 339         } else if (fractx1 == 0) {
 340             // No interpolation with pixels[x+1]
 341             int rgb01 = (x < 0 || x >= w || y+1 >= h) ? 0 : pixels[i+w];
 342             return interp(rgb00, rgb01, fracty0, fracty1);
 343         } else {
 344             // All 4 neighbors must be interpolated
 345             int rgb10 = (y < 0 || x+1 >= w || y >= h) ? 0 : pixels[i+1];
 346             int rgb01 = (x < 0 || x >= w || y+1 >= h) ? 0 : pixels[i+w];
 347             int rgb11 = (x+1 >= w || y+1 >= h) ? 0 : pixels[i+w+1];
 348             return interp(interp(rgb00, rgb10, fractx0, fractx1),
 349                     interp(rgb01, rgb11, fractx0, fractx1),
 350                     fracty0, fracty1);
 351         }
 352     }
 353 
 354     private static int interp(int rgb0, int rgb1, int fract0, int fract1) {
 355         int a0 = (rgb0 >> 24) & 0xff;
 356         int r0 = (rgb0 >> 16) & 0xff;
 357         int g0 = (rgb0 >>  8) & 0xff;
 358         int b0 = (rgb0      ) & 0xff;
 359         int a1 = (rgb1 >> 24) & 0xff;
 360         int r1 = (rgb1 >> 16) & 0xff;
 361         int g1 = (rgb1 >>  8) & 0xff;
 362         int b1 = (rgb1      ) & 0xff;
 363         int a = (a0 * fract0 + a1 * fract1) >> 8;
 364         int r = (r0 * fract0 + r1 * fract1) >> 8;
 365         int g = (g0 * fract0 + g1 * fract1) >> 8;
 366         int b = (b0 * fract0 + b1 * fract1) >> 8;
 367         return (a << 24) | (r << 16) | (g << 8) | b;
 368     }
 369 
 370 }