1 /* 2 * Copyright (c) 2010, 2017, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 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 }