diff --git a/bundles/org.eclipse.swt/Eclipse SWT Tests/win32/org/eclipse/swt/graphics/ImagesWin32Tests.java b/bundles/org.eclipse.swt/Eclipse SWT Tests/win32/org/eclipse/swt/graphics/ImagesWin32Tests.java index cf48800de3..33f7e3fac2 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT Tests/win32/org/eclipse/swt/graphics/ImagesWin32Tests.java +++ b/bundles/org.eclipse.swt/Eclipse SWT Tests/win32/org/eclipse/swt/graphics/ImagesWin32Tests.java @@ -134,6 +134,85 @@ public void testHandlesAreDifferentForTwoZoomImageAtDifferentNearestZooms() { } } + /** + * Tests that a GC.drawImage() handle at the exact imageZoom is returned in + * preference to a handle at nearestAvailableZoom when both are present in the + * image's handle manager. + *
+ * Explicitly creating a persistent handle at 200% via + * {@link Image#win32_getHandle(Image, int)} places it in the handle manager. + * When GC.drawImage() then targets a pixel size that maps to imageZoom=200 + * (while nearestAvailableZoom stays 100% because the provider only has 100% + * data), the 200% handle must be found first via the imageZoom lookup and + * returned instead of the 100% one. + *
+ * See https://github.com/eclipse-platform/eclipse.platform.swt/issues/3419 + */ + @Test + public void testDrawImagePrefersExistingHandleAtExactImageZoom() { + PaletteData palette = new PaletteData(0xFF0000, 0xFF00, 0xFF); + ImageData imageData = new ImageData(10, 10, 24, palette); + Image image = new Image(Display.getDefault(), (ImageDataProvider) zoom -> zoom == 100 ? imageData : null); + try { + long handle100 = Image.win32_getHandle(image, 100); + long handle200 = Image.win32_getHandle(image, 200); + assertNotEquals(0L, handle100, "100% handle should be non-zero"); + assertNotEquals(0L, handle200, "200% handle should be non-zero"); + assertNotEquals(handle100, handle200, "Handles for different zooms should be distinct native objects"); + long[] drawHandle = {0}; + // 20x20 pixels → imageZoom=200 for a 10x10 base; nearestAvailableZoom=100 + // The 200% handle already in the handle manager must be found and preferred + image.executeOnImageHandleAtBestFittingSize(h -> drawHandle[0] = h.handle(), 20, 20); + assertEquals(handle200, drawHandle[0], + "GC.drawImage() should prefer the existing handle at imageZoom=200 " + + "over the nearestAvailableZoom=100 handle"); + } finally { + image.dispose(); + } + } + + /** + * Tests that when the pixel size requested by GC.drawImage() maps to a zoom + * equal to a current monitor's zoom, a persistent handle is created for that + * zoom level and is subsequently reusable via + * {@link Image#win32_getHandle(Image, int)}. + *
+ * A shell is created so that {@code Display.getShells()} is non-empty and + * the monitor zoom is visible to the handle-selection logic. Drawing at a + * pixel size whose imageZoom equals the shell's zoom triggers the + * monitor-zoom persistence path: the handle is stored in the image's handle + * manager and can be retrieved without allocating a second native object. + * On 100%-DPI machines the new {@code imageZoom == monitorZoom} branch + * overlaps with the existing {@code nearestAvailableZoom == 100} fallback; + * on HiDPI machines the new branch is exercised in isolation. + *
+ * See https://github.com/eclipse-platform/eclipse.platform.swt/issues/3419
+ */
+ @Test
+ public void testDrawImageCreatesAndReusesPersistentHandleForMonitorZoomImageZoom() {
+ PaletteData palette = new PaletteData(0xFF0000, 0xFF00, 0xFF);
+ ImageData imageData = new ImageData(10, 10, 24, palette);
+ Image image = new Image(Display.getDefault(), (ImageDataProvider) zoom -> zoom == 100 ? imageData : null);
+ Shell shell = new Shell(Display.getDefault());
+ try {
+ int shellZoom = shell.getZoom();
+ int pixelSize = 10 * shellZoom / 100;
+ long[] drawHandle = {0};
+ // Drawing at shellZoom's pixel size → imageZoom == shellZoom == monitorZoom
+ // A persistent handle must be created and stored in the handle manager
+ image.executeOnImageHandleAtBestFittingSize(h -> drawHandle[0] = h.handle(), pixelSize, pixelSize);
+ // If persisted, win32_getHandle returns the same native handle without a new allocation
+ long persistedHandle = Image.win32_getHandle(image, shellZoom);
+ assertNotEquals(0L, drawHandle[0], "Draw handle should be non-zero");
+ assertEquals(persistedHandle, drawHandle[0],
+ "GC.drawImage() at the monitor zoom should create a persistent handle "
+ + "so that win32_getHandle returns the same native object");
+ } finally {
+ image.dispose();
+ shell.dispose();
+ }
+ }
+
/**
* Tests that a persistent native handle already created via
* {@link Image#win32_getHandle(Image, int)} is found and reused by
diff --git a/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/graphics/Image.java b/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/graphics/Image.java
index 29e6fc94ae..962da6ee71 100644
--- a/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/graphics/Image.java
+++ b/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/graphics/Image.java
@@ -18,6 +18,7 @@
import java.io.*;
import java.util.*;
+import java.util.List;
import java.util.concurrent.*;
import java.util.concurrent.atomic.*;
import java.util.function.*;
@@ -29,6 +30,7 @@
import org.eclipse.swt.internal.gdip.*;
import org.eclipse.swt.internal.image.*;
import org.eclipse.swt.internal.win32.*;
+import org.eclipse.swt.widgets.*;
/**
* Instances of this class are graphics which have been prepared
@@ -261,13 +263,10 @@ private InternalImageHandle getOrCreateImageHandleAtClosestSize(int widthHint, i
int imageZoomForHeight = 100 * heightHint / bounds.height;
int imageZoom = DPIUtil.getZoomForAutoscaleProperty(Math.max(imageZoomForWidth, imageZoomForHeight));
int nearestAvailableZoom = imageProvider.nearestAvailableZoom(imageZoom);
- InternalImageHandle bestFittingHandle = imageHandleManager.get(nearestAvailableZoom);
+ InternalImageHandle bestFittingHandle = getPersistentHandle(imageZoom, nearestAvailableZoom);
if (bestFittingHandle != null) {
return bestFittingHandle;
}
- if (nearestAvailableZoom == 100) {
- return getHandleInternal(100, 100);
- }
if (previousHandle != null && previousHandle.zoom() == nearestAvailableZoom) {
temporaryHandleContainer = previousHandle;
return previousHandle.handle();
@@ -277,6 +276,35 @@ private InternalImageHandle getOrCreateImageHandleAtClosestSize(int widthHint, i
return temporaryHandleContainer.handle();
}
+ private InternalImageHandle getPersistentHandle(int imageZoom, int nearestAvailableZoom) {
+ InternalImageHandle bestFittingHandle = imageHandleManager.get(imageZoom);
+ if (bestFittingHandle != null) {
+ return bestFittingHandle;
+ }
+ if (getShellZooms().contains(imageZoom)) {
+ return getHandleInternal(imageZoom, imageZoom);
+ }
+ bestFittingHandle = imageHandleManager.get(nearestAvailableZoom);
+ if (bestFittingHandle != null) {
+ return bestFittingHandle;
+ }
+ if (nearestAvailableZoom == 100) {
+ return getHandleInternal(100, 100);
+ }
+ return null;
+ }
+
+ private Set