From c36d6360f5e2b9e0e002298dca06452cc501b623 Mon Sep 17 00:00:00 2001 From: Alexey Ushakov Date: Wed, 4 Dec 2024 11:49:13 +0100 Subject: [PATCH] JBR-7990 Vulkan: Robot pixel grabbing for Vulkan surfaces Implemented grabbing pixels via partly supported SurfaceToSwBlit, fixed multi-monitor scenario --- .../native/common/java2d/vulkan/VKBase.c | 1 + .../native/common/java2d/vulkan/VKBase.h | 1 + .../native/common/java2d/vulkan/VKBlitLoops.c | 118 ++++++++++++++++ .../native/common/java2d/vulkan/VKBlitLoops.h | 5 + .../common/java2d/vulkan/VKRenderQueue.c | 1 + .../unix/classes/sun/awt/wl/WLRobotPeer.java | 16 ++- .../sun/java2d/vulkan/WLVKSurfaceData.java | 53 +++++++- .../sun/java2d/wl/WLPixelGrabberExt.java | 33 +++++ .../sun/java2d/wl/WLSMSurfaceData.java | 2 +- .../awt/wayland/RobotGetOOBPixelsTest.java | 100 ++++++++++++++ .../java/awt/wayland/RobotGetPixelTest.java | 128 ++++++++++++++++++ .../java/awt/wayland/RobotGetPixelsTest.java | 119 ++++++++++++++++ 12 files changed, 569 insertions(+), 8 deletions(-) create mode 100644 src/java.desktop/unix/classes/sun/java2d/wl/WLPixelGrabberExt.java create mode 100644 test/jdk/jb/java/awt/wayland/RobotGetOOBPixelsTest.java create mode 100644 test/jdk/jb/java/awt/wayland/RobotGetPixelTest.java create mode 100644 test/jdk/jb/java/awt/wayland/RobotGetPixelsTest.java diff --git a/src/java.desktop/share/native/common/java2d/vulkan/VKBase.c b/src/java.desktop/share/native/common/java2d/vulkan/VKBase.c index 3b8670f3ef6f..912f8c4decbd 100644 --- a/src/java.desktop/share/native/common/java2d/vulkan/VKBase.c +++ b/src/java.desktop/share/native/common/java2d/vulkan/VKBase.c @@ -638,6 +638,7 @@ static jboolean VK_InitDevice(VKDevice* device) { DEVICE_PROC(vkInvalidateMappedMemoryRanges); DEVICE_PROC(vkCmdPushConstants); DEVICE_PROC(vkCmdCopyBufferToImage); + DEVICE_PROC(vkCmdCopyImageToBuffer); device->vkGetDeviceQueue(device->handle, device->queueFamily, 0, &device->queue); if (device->queue == NULL) { diff --git a/src/java.desktop/share/native/common/java2d/vulkan/VKBase.h b/src/java.desktop/share/native/common/java2d/vulkan/VKBase.h index 5321c5a03683..39342e2090b6 100644 --- a/src/java.desktop/share/native/common/java2d/vulkan/VKBase.h +++ b/src/java.desktop/share/native/common/java2d/vulkan/VKBase.h @@ -115,6 +115,7 @@ struct VKDevice { PFN_vkInvalidateMappedMemoryRanges vkInvalidateMappedMemoryRanges; PFN_vkCmdPushConstants vkCmdPushConstants; PFN_vkCmdCopyBufferToImage vkCmdCopyBufferToImage; + PFN_vkCmdCopyImageToBuffer vkCmdCopyImageToBuffer; }; struct VKGraphicsEnvironment { diff --git a/src/java.desktop/share/native/common/java2d/vulkan/VKBlitLoops.c b/src/java.desktop/share/native/common/java2d/vulkan/VKBlitLoops.c index 94fe8d2cd3f2..86df4926c025 100644 --- a/src/java.desktop/share/native/common/java2d/vulkan/VKBlitLoops.c +++ b/src/java.desktop/share/native/common/java2d/vulkan/VKBlitLoops.c @@ -31,6 +31,7 @@ #include "VKBlitLoops.h" #include "VKSurfaceData.h" #include "VKRenderer.h" +#include "GraphicsPrimitiveMgr.h" #include "Trace.h" @@ -316,4 +317,121 @@ void VKBlitLoops_Blit(JNIEnv *env, SurfaceData_InvokeRelease(env, srcOps, &srcInfo); } SurfaceData_InvokeUnlock(env, srcOps, &srcInfo); +} + +/** + * Specialized blit method for copying a native Vulkan "Surface" to a system + * memory ("Sw") surface. + */ +void +VKBlitLoops_SurfaceToSwBlit(JNIEnv *env, VKRenderingContext* context, + jlong pSrcOps, jlong pDstOps, jint dsttype, + jint srcx, jint srcy, jint dstx, jint dsty, + jint width, jint height) +{ + VKSDOps *srcOps = (VKSDOps *)jlong_to_ptr(pSrcOps); + SurfaceDataOps *dstOps = (SurfaceDataOps *)jlong_to_ptr(pDstOps); + SurfaceDataRasInfo srcInfo, dstInfo; + + J2dTraceLn6(J2D_TRACE_INFO, "VKBlitLoops_SurfaceToSwBlit: (%d %d %d %d) -> (%d %d)", + srcx, srcy, width, height, dstx, dsty); + + if (width <= 0 || height <= 0) { + J2dTraceLn(J2D_TRACE_WARNING, + "VKBlitLoops_SurfaceToSwBlit: dimensions are non-positive"); + return; + } + + RETURN_IF_NULL(srcOps); + RETURN_IF_NULL(dstOps); + RETURN_IF_NULL(context); + + VKDevice* device = srcOps->device; + VKImage* image = srcOps->image; + + if (image == NULL || device == NULL) { + J2dRlsTraceLn2(J2D_TRACE_ERROR, + "VKBlitLoops_SurfaceToSwBlit: image(%p) or device(%p) is NULL", image, device) + return; + } + + srcInfo.bounds.x1 = srcx; + srcInfo.bounds.y1 = srcy; + srcInfo.bounds.x2 = srcx + width; + srcInfo.bounds.y2 = srcy + height; + dstInfo.bounds.x1 = dstx; + dstInfo.bounds.y1 = dsty; + dstInfo.bounds.x2 = dstx + width; + dstInfo.bounds.y2 = dsty + height; + + SurfaceData_IntersectBoundsXYXY(&srcInfo.bounds, + 0, 0, srcOps->image->extent.width, srcOps->image->extent.height); + SurfaceData_IntersectBlitBounds(&dstInfo.bounds, &srcInfo.bounds, + srcx - dstx, srcy - dsty); + + if (dstOps->Lock(env, dstOps, &dstInfo, SD_LOCK_WRITE) != SD_SUCCESS) { + J2dTraceLn(J2D_TRACE_WARNING, + "VKBlitLoops_SurfaceToSwBlit: could not acquire dst lock"); + return; + } + + if (srcInfo.bounds.x2 > srcInfo.bounds.x1 && + srcInfo.bounds.y2 > srcInfo.bounds.y1) + { + dstOps->GetRasInfo(env, dstOps, &dstInfo); + if (dstInfo.rasBase) { + void *pDst = dstInfo.rasBase; + srcx = srcInfo.bounds.x1; + srcy = srcInfo.bounds.y1; + dstx = dstInfo.bounds.x1; + dsty = dstInfo.bounds.y1; + width = srcInfo.bounds.x2 - srcInfo.bounds.x1; + height = srcInfo.bounds.y2 - srcInfo.bounds.y1; + jsize bufferSizeInPixels = width * height; + + pDst = PtrAddBytes(pDst, dstx * dstInfo.pixelStride); + pDst = PtrPixelsRow(pDst, dsty, dstInfo.scanStride); + + VKRenderer_FlushRenderPass(srcOps); + VkCommandBuffer cb = VKRenderer_Record(device->renderer); + + VKBuffer* buffer = VKBuffer_Create(device, bufferSizeInPixels * sizeof(jint), + VK_BUFFER_USAGE_TRANSFER_DST_BIT, + VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | + VK_MEMORY_PROPERTY_HOST_COHERENT_BIT); + + VkBufferImageCopy region = { + .bufferOffset = 0, + .bufferRowLength = 0, + .bufferImageHeight = 0, + .imageSubresource= { + .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .mipLevel = 0, + .baseArrayLayer = 0, + .layerCount = 1 + }, + .imageOffset = {srcx, srcy, 0}, + .imageExtent = {width, height, 1} + }; + + device->vkCmdCopyImageToBuffer(cb, image->handle, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + buffer->handle, + 1, ®ion); + VKRenderer_Flush(device->renderer); + VKRenderer_Sync(device->renderer); + void* pixelData; + VK_IF_ERROR(device->vkMapMemory(device->handle, buffer->range.memory, + 0, VK_WHOLE_SIZE, 0, &pixelData)) + { + J2dRlsTraceLn(J2D_TRACE_ERROR, "VKBlitLoops_SurfaceToSwBlit: could not map buffer memory"); + VKBuffer_Destroy(device, buffer); + return; + } + memcpy(pDst, pixelData, bufferSizeInPixels * sizeof(jint)); + device->vkUnmapMemory(device->handle, buffer->range.memory); + VKBuffer_Destroy(device, buffer); + } + SurfaceData_InvokeRelease(env, dstOps, &dstInfo); + } + SurfaceData_InvokeUnlock(env, dstOps, &dstInfo); } \ No newline at end of file diff --git a/src/java.desktop/share/native/common/java2d/vulkan/VKBlitLoops.h b/src/java.desktop/share/native/common/java2d/vulkan/VKBlitLoops.h index 40968e844b02..b655b599eef0 100644 --- a/src/java.desktop/share/native/common/java2d/vulkan/VKBlitLoops.h +++ b/src/java.desktop/share/native/common/java2d/vulkan/VKBlitLoops.h @@ -49,5 +49,10 @@ void VKBlitLoops_Blit(JNIEnv *env, jdouble dx1, jdouble dy1, jdouble dx2, jdouble dy2); +void +VKBlitLoops_SurfaceToSwBlit(JNIEnv *env, VKRenderingContext* context, + jlong pSrcOps, jlong pDstOps, jint dsttype, + jint srcx, jint srcy, jint dstx, jint dsty, + jint width, jint height); #endif /* VKBlitLoops_h_Included */ diff --git a/src/java.desktop/share/native/common/java2d/vulkan/VKRenderQueue.c b/src/java.desktop/share/native/common/java2d/vulkan/VKRenderQueue.c index fddf193a3fa4..2178b791f5f0 100644 --- a/src/java.desktop/share/native/common/java2d/vulkan/VKRenderQueue.c +++ b/src/java.desktop/share/native/common/java2d/vulkan/VKRenderQueue.c @@ -395,6 +395,7 @@ JNIEXPORT void JNICALL Java_sun_java2d_vulkan_VKRenderQueue_flushBuffer jlong pSrc = NEXT_LONG(b); jlong pDst = NEXT_LONG(b); J2dRlsTraceLn(J2D_TRACE_VERBOSE, "VKRenderQueue_flushBuffer: SURFACE_TO_SW_BLIT"); + VKBlitLoops_SurfaceToSwBlit(env, &context, pSrc, pDst, dsttype, sx, sy, dx, dy, w, h); } break; case sun_java2d_pipe_BufferedOpCodes_MASK_FILL: diff --git a/src/java.desktop/unix/classes/sun/awt/wl/WLRobotPeer.java b/src/java.desktop/unix/classes/sun/awt/wl/WLRobotPeer.java index 5ce9f416680f..779648e3afb8 100644 --- a/src/java.desktop/unix/classes/sun/awt/wl/WLRobotPeer.java +++ b/src/java.desktop/unix/classes/sun/awt/wl/WLRobotPeer.java @@ -27,7 +27,7 @@ package sun.awt.wl; import sun.java2d.SurfaceData; -import sun.java2d.wl.WLSMSurfaceData; +import sun.java2d.wl.WLPixelGrabberExt; import java.awt.*; import java.awt.peer.RobotPeer; @@ -121,7 +121,11 @@ public int getRGBPixel(int x, int y) { } else { // Can get pixels from the singular window's surface data, // not necessarily the true value that the user observes. - return getRGBPixelsOfSingularWindow(bounds); + Rectangle deviceBounds = wgc.getDefaultTransform().createTransformedShape(wgc.getBounds()).getBounds(); + + Rectangle grabBounds = new Rectangle(bounds.x - deviceBounds.x, bounds.y - deviceBounds.y, + bounds.width, bounds.height); + return getRGBPixelsOfSingularWindow(grabBounds); } } @@ -130,7 +134,7 @@ private int getRGBPixelOfSingularWindow(int x, int y) { WLToolkit.awtLock(); try { checkPeerForPixelGrab(peer); - return SurfaceData.convertTo(WLSMSurfaceData.class, peer.surfaceData).getRGBPixelAt(x, y); + return SurfaceData.convertTo(WLPixelGrabberExt.class, peer.surfaceData).getRGBPixelAt(x, y); } finally { WLToolkit.awtUnlock(); } @@ -141,7 +145,7 @@ private int getRGBPixelOfSingularWindow(int x, int y) { WLToolkit.awtLock(); try { checkPeerForPixelGrab(peer); - return SurfaceData.convertTo(WLSMSurfaceData.class, peer.surfaceData).getRGBPixelsAt(bounds); + return SurfaceData.convertTo(WLPixelGrabberExt.class, peer.surfaceData).getRGBPixelsAt(bounds); } finally { WLToolkit.awtUnlock(); } @@ -151,8 +155,8 @@ private static void checkPeerForPixelGrab(WLComponentPeer peer) { if (!peer.hasSurface()) { throw new UnsupportedOperationException("The window has no backing buffer to read pixels from"); } - if (! (peer.surfaceData instanceof WLSMSurfaceData)) { - throw new UnsupportedOperationException("Reading pixels of a window is only supported for memory-mapped buffers"); + if (! (peer.surfaceData instanceof WLPixelGrabberExt)) { + throw new UnsupportedOperationException("WLPixelGrabberExt is required to read pixels from a Wayland surface"); } } diff --git a/src/java.desktop/unix/classes/sun/java2d/vulkan/WLVKSurfaceData.java b/src/java.desktop/unix/classes/sun/java2d/vulkan/WLVKSurfaceData.java index 441d93bc7a61..ab361c126ae4 100644 --- a/src/java.desktop/unix/classes/sun/java2d/vulkan/WLVKSurfaceData.java +++ b/src/java.desktop/unix/classes/sun/java2d/vulkan/WLVKSurfaceData.java @@ -26,23 +26,29 @@ package sun.java2d.vulkan; +import java.awt.AlphaComposite; import java.awt.GraphicsConfiguration; import java.awt.GraphicsDevice; import java.awt.GraphicsEnvironment; import java.awt.Rectangle; +import java.awt.image.BufferedImage; import java.awt.image.ColorModel; +import sun.awt.image.BufImgSurfaceData; import sun.awt.wl.WLComponentPeer; import sun.java2d.SurfaceData; +import sun.java2d.loops.Blit; +import sun.java2d.loops.CompositeType; import sun.java2d.loops.SurfaceType; import sun.java2d.pipe.BufferedContext; import sun.java2d.pipe.RenderBuffer; +import sun.java2d.wl.WLPixelGrabberExt; import sun.java2d.wl.WLSurfaceDataExt; import sun.util.logging.PlatformLogger; import static sun.java2d.pipe.BufferedOpCodes.FLUSH_BUFFER; import static sun.java2d.pipe.BufferedOpCodes.CONFIGURE_SURFACE; -public abstract class WLVKSurfaceData extends VKSurfaceData implements WLSurfaceDataExt { +public abstract class WLVKSurfaceData extends VKSurfaceData implements WLSurfaceDataExt, WLPixelGrabberExt { private static final PlatformLogger log = PlatformLogger.getLogger("sun.java2d.vulkan.WLVKSurfaceData"); protected WLComponentPeer peer; @@ -145,6 +151,51 @@ public static WLVKGraphicsConfig getGC(WLComponentPeer peer) { } } + public int getRGBPixelAt(int x, int y) { + Rectangle r = peer.getBufferBounds(); + if (x < r.x || x >= r.x + r.width || y < r.y || y >= r.y + r.height) { + throw new ArrayIndexOutOfBoundsException("x,y outside of buffer bounds"); + } + + BufferedImage resImg = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB); + SurfaceData resData = BufImgSurfaceData.createData(resImg); + + Blit blit = Blit.getFromCache(getSurfaceType(), CompositeType.SrcNoEa, + resData.getSurfaceType()); + blit.Blit(this, resData, AlphaComposite.Src, null, + x, y, 0, 0, 1, 1); + + return resImg.getRGB(0, 0); + } + + public int [] getRGBPixelsAt(Rectangle bounds) { + Rectangle r = peer.getBufferBounds(); + + if ((long)bounds.width * (long)bounds.height > Integer.MAX_VALUE) { + throw new IndexOutOfBoundsException("Dimensions (width=" + bounds.width + + " height=" + bounds.height + ") are too large"); + } + + Rectangle b = bounds.intersection(r); + + if (b.isEmpty()) { + throw new IndexOutOfBoundsException("Requested bounds are outside of surface bounds"); + } + + BufferedImage resImg = new BufferedImage(b.width, b.height, BufferedImage.TYPE_INT_ARGB); + SurfaceData resData = BufImgSurfaceData.createData(resImg); + + Blit blit = Blit.getFromCache(getSurfaceType(), CompositeType.SrcNoEa, + resData.getSurfaceType()); + blit.Blit(this, resData, AlphaComposite.Src, null, + b.x, b.y, 0, 0, b.width, b.height); + + int [] pixels = new int[b.width * b.height]; + resImg.getRGB(0, 0, b.width, b.height, pixels, 0, b.width); + return pixels; + } + + public static class WLVKWindowSurfaceData extends WLVKSurfaceData { public WLVKWindowSurfaceData(WLComponentPeer peer, WLVKGraphicsConfig gc) { diff --git a/src/java.desktop/unix/classes/sun/java2d/wl/WLPixelGrabberExt.java b/src/java.desktop/unix/classes/sun/java2d/wl/WLPixelGrabberExt.java new file mode 100644 index 000000000000..d79920739fad --- /dev/null +++ b/src/java.desktop/unix/classes/sun/java2d/wl/WLPixelGrabberExt.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2024, JetBrains s.r.o.. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package sun.java2d.wl; +import java.awt.Rectangle; + +public interface WLPixelGrabberExt { + int getRGBPixelAt(int x, int y); + int [] getRGBPixelsAt(Rectangle bounds); +} diff --git a/src/java.desktop/unix/classes/sun/java2d/wl/WLSMSurfaceData.java b/src/java.desktop/unix/classes/sun/java2d/wl/WLSMSurfaceData.java index 70d5406c9179..c9de67614eff 100644 --- a/src/java.desktop/unix/classes/sun/java2d/wl/WLSMSurfaceData.java +++ b/src/java.desktop/unix/classes/sun/java2d/wl/WLSMSurfaceData.java @@ -43,7 +43,7 @@ /** * SurfaceData for shared memory buffers. */ -public class WLSMSurfaceData extends SurfaceData implements WLSurfaceDataExt { +public class WLSMSurfaceData extends SurfaceData implements WLSurfaceDataExt, WLPixelGrabberExt { private static final PlatformLogger log = PlatformLogger.getLogger("sun.java2d.wl.WLSMSurfaceData"); private final WLComponentPeer peer; diff --git a/test/jdk/jb/java/awt/wayland/RobotGetOOBPixelsTest.java b/test/jdk/jb/java/awt/wayland/RobotGetOOBPixelsTest.java new file mode 100644 index 000000000000..dfb6cdc98cdd --- /dev/null +++ b/test/jdk/jb/java/awt/wayland/RobotGetOOBPixelsTest.java @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2024, JetBrains s.r.o.. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +import javax.swing.*; +import java.awt.*; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; +import java.util.concurrent.CountDownLatch; + +/* + * @test + * @requires os.family == "linux" + * @summary Verifies that robot correctly pick color + * @run main/othervm -Dawt.toolkit.name=WLToolkit -Dsun.java2d.vulkan=True RobotGetOOBPixelsTest + * @run main/othervm -Dawt.toolkit.name=WLToolkit -Dsun.java2d.vulkan=False RobotGetOOBPixelsTest + */ + + +public class RobotGetOOBPixelsTest { + final static int W = 600; + final static int H = 600; + + final static CountDownLatch latchShownFrame = new CountDownLatch(1); + static volatile boolean failed = false; + + public static void main(String[] args) throws InterruptedException, AWTException { + final Robot robot = new Robot(); + SwingUtilities.invokeLater(() -> { + JFrame frame = new JFrame("Robot Test"); + frame.setSize(W, H); + frame.add(new JPanel(){ + @Override + protected void paintComponent(Graphics g) { + super.paintComponent(g); + g.setColor(Color.RED); + g.fillRect(0, 0, W/3, H/3); + g.setColor(Color.GREEN); + g.fillRect(W/3, H/3, W/3, H/3); + g.setColor(Color.BLUE); + g.fillRect((2*W)/3, (2*H)/3, W/3, H/3); + } + }); + + frame.addWindowListener(new WindowAdapter() { + @Override + public void windowActivated(WindowEvent e) { + int [] extremeValues = {Integer.MIN_VALUE, Integer.MIN_VALUE + 1, Integer.MAX_VALUE - 1, + Integer.MAX_VALUE}; + for (int val1 : extremeValues) { + for (int val2 : extremeValues) { + try { + robot.createScreenCapture( + new Rectangle(val1, val2, frame.getWidth(), frame.getHeight())); + failed = true; + } catch (IndexOutOfBoundsException ex) { + // Expected exception + } + } + } + + try { + robot.createScreenCapture( + new Rectangle(0, 0, Integer.MAX_VALUE, Integer.MAX_VALUE)); + failed = true; + } catch (IndexOutOfBoundsException ex) { + // Expected exception + } + + latchShownFrame.countDown(); + } + }); + frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + frame.setVisible(true); + }); + + latchShownFrame.await(); + if (failed) throw new RuntimeException("Test failed"); + } +} \ No newline at end of file diff --git a/test/jdk/jb/java/awt/wayland/RobotGetPixelTest.java b/test/jdk/jb/java/awt/wayland/RobotGetPixelTest.java new file mode 100644 index 000000000000..37142f696384 --- /dev/null +++ b/test/jdk/jb/java/awt/wayland/RobotGetPixelTest.java @@ -0,0 +1,128 @@ +/* + * Copyright 2024 JetBrains s.r.o. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +import javax.swing.*; +import java.awt.*; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; +import java.util.concurrent.CountDownLatch; + +/* + * @test + * @requires os.family == "linux" + * @summary Verifies that robot correctly pick color + * @run main/othervm -Dawt.toolkit.name=WLToolkit -Dsun.java2d.vulkan=True RobotGetPixelTest + * @run main/othervm -Dawt.toolkit.name=WLToolkit -Dsun.java2d.vulkan=False RobotGetPixelTest + */ + + +public class RobotGetPixelTest { + final static int W = 600; + final static int H = 600; + + final static CountDownLatch latchShownFrame = new CountDownLatch(1); + static volatile boolean failed = false; + + static boolean compareColors(Color c1, Color c2, double tolerance) { + return Math.abs(c1.getRed() - c2.getRed()) < tolerance && + Math.abs(c1.getGreen() - c2.getGreen()) < tolerance && + Math.abs(c1.getBlue() - c2.getBlue()) < tolerance; + } + + public static void main(String[] args) throws InterruptedException, AWTException { + final Robot robot = new Robot(); + SwingUtilities.invokeLater(() -> { + JFrame frame = new JFrame("Robot Test"); + frame.setSize(W, H); + frame.add(new JPanel(){ + @Override + protected void paintComponent(Graphics g) { + super.paintComponent(g); + g.setColor(Color.RED); + g.fillRect(0, 0, W/3, H/3); + g.setColor(Color.GREEN); + g.fillRect(W/3, H/3, W/3, H/3); + g.setColor(Color.BLUE); + g.fillRect((2*W)/3, (2*H)/3, W/3, H/3); + } + }); + + frame.addWindowListener(new WindowAdapter() { + @Override + public void windowActivated(WindowEvent e) { + int x = frame.getX() + frame.getInsets().bottom + W/6; + int y = frame.getY() + frame.getInsets().left + H/6; + try { + Color c = robot.getPixelColor(x, y); + if (!compareColors(c, Color.RED, 10)) { + System.out.println("Unexpected color: " + c + " at (" + x + ", " + y + ")"); + failed = true; + } + + x += W / 3; + y += H / 3; + + c = robot.getPixelColor(x, y); + if (!compareColors(c, Color.GREEN, 10)) { + System.out.println("Unexpected color: " + c + " at (" + x + ", " + y + ")"); + failed = true; + } + + x += W / 3; + y += H / 3; + + c = robot.getPixelColor(x, y); + if (!compareColors(c, Color.BLUE, 10)) { + System.out.println("Unexpected color: " + c + " at (" + x + ", " + y + ")"); + failed = true; + } + + int[] extremeValues = {Integer.MIN_VALUE, Integer.MIN_VALUE + 1, Integer.MAX_VALUE - 1, + Integer.MAX_VALUE}; + + for (int i = 0; i < extremeValues.length; i++) { + for (int j = 0; j < extremeValues.length; j++) { + try { + c = robot.getPixelColor(extremeValues[i], extremeValues[j]); + failed = true; + } catch (ArrayIndexOutOfBoundsException ex) { + // Expected + } + } + } + } catch (Throwable t) { + failed = true; + System.out.println("Unexpected exception:"); + t.printStackTrace(System.out); + } + latchShownFrame.countDown(); + } + }); + frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + frame.setVisible(true); + }); + + latchShownFrame.await(); + if (failed) throw new RuntimeException("Test failed"); + } +} \ No newline at end of file diff --git a/test/jdk/jb/java/awt/wayland/RobotGetPixelsTest.java b/test/jdk/jb/java/awt/wayland/RobotGetPixelsTest.java new file mode 100644 index 000000000000..312b24dc567e --- /dev/null +++ b/test/jdk/jb/java/awt/wayland/RobotGetPixelsTest.java @@ -0,0 +1,119 @@ +/* + * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2024, JetBrains s.r.o.. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +import javax.swing.*; +import java.awt.*; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; +import java.awt.image.BufferedImage; +import java.util.concurrent.CountDownLatch; + +/* + * @test + * @requires os.family == "linux" + * @summary Verifies that robot correctly pick color + * @run main/othervm -Dawt.toolkit.name=WLToolkit -Dsun.java2d.vulkan=True RobotGetPixelsTest + * @run main/othervm -Dawt.toolkit.name=WLToolkit -Dsun.java2d.vulkan=False RobotGetPixelsTest + */ + + +public class RobotGetPixelsTest { + final static int W = 600; + final static int H = 600; + + final static CountDownLatch latchShownFrame = new CountDownLatch(1); + static volatile boolean failed = false; + + static boolean compareColors(Color c1, Color c2, double tolerance) { + return Math.abs(c1.getRed() - c2.getRed()) < tolerance && + Math.abs(c1.getGreen() - c2.getGreen()) < tolerance && + Math.abs(c1.getBlue() - c2.getBlue()) < tolerance; + } + + public static void main(String[] args) throws InterruptedException, AWTException { + final Robot robot = new Robot(); + SwingUtilities.invokeLater(() -> { + JFrame frame = new JFrame("Robot Test"); + frame.setSize(W, H); + frame.add(new JPanel(){ + @Override + protected void paintComponent(Graphics g) { + super.paintComponent(g); + g.setColor(Color.RED); + g.fillRect(0, 0, W/3, H/3); + g.setColor(Color.GREEN); + g.fillRect(W/3, H/3, W/3, H/3); + g.setColor(Color.BLUE); + g.fillRect((2*W)/3, (2*H)/3, W/3, H/3); + } + }); + + frame.addWindowListener(new WindowAdapter() { + @Override + public void windowActivated(WindowEvent e) { + try { + BufferedImage bi = robot.createScreenCapture( + new Rectangle(frame.getX(), frame.getY(), frame.getWidth(), frame.getHeight())); + int x = frame.getInsets().bottom + W / 6; + int y = frame.getInsets().left + H / 6; + + Color c = new Color(bi.getRGB(x, y)); + if (!compareColors(c, Color.RED, 10)) { + System.out.println("Unexpected color: " + c + " at (" + x + ", " + y + ")"); + failed = true; + } + + x += W / 3; + y += H / 3; + + c = new Color(bi.getRGB(x, y)); + if (!compareColors(c, Color.GREEN, 10)) { + System.out.println("Unexpected color: " + c + " at (" + x + ", " + y + ")"); + failed = true; + } + + x += W / 3; + y += H / 3; + + c = new Color(bi.getRGB(x, y)); + if (!compareColors(c, Color.BLUE, 10)) { + System.out.println("Unexpected color: " + c + " at (" + x + ", " + y + ")"); + failed = true; + } + } catch (Throwable t) { + System.out.println("Unexpected exception:"); + t.printStackTrace(System.out); + failed = true; + } + latchShownFrame.countDown(); + } + }); + frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + frame.setVisible(true); + }); + + latchShownFrame.await(); + if (failed) throw new RuntimeException("Test failed"); + } +} \ No newline at end of file