From a3749774cca3cbd2eb938b2b952a78ca7e62f54e Mon Sep 17 00:00:00 2001 From: Heiko Klare Date: Sun, 15 Jun 2025 18:16:55 +0200 Subject: [PATCH] [macOS] Initialize image data from ImageGcDrawer at device zoom only An Image based on an ImageGcDrawer currently initializes two image representations for 100% and 200% zoom, so that an appropriate representation for each of the two zoom levels can be retrieved. This, however, leads to issues when creating a GC on an Image created in that way, as only one of the two representations (the one fitting to the current device zoom) will be modified by the GC. If later the representation with the other zoom is used, it will not contain the changes performed by the GC. One such scenario is starting an application at a monitor with one zoom (used as the device zoom throughout the application lifecycle then) and moving the shell to a monitor with a different zoom. An image will then be modified only for the zoom of the first monitor, thus if using it inside a control, it will yield the unmodified version on the second monitor. To avoid this issue, this change ensures that an Image based on an ImageGcDrawer is only initialized for the device zoom. Fixes https://github.com/eclipse-platform/eclipse.platform.ui/issues/3039 --- .../cocoa/org/eclipse/swt/graphics/Image.java | 27 +++++++---------- .../Test_org_eclipse_swt_graphics_Image.java | 30 +++++++++++++++++++ 2 files changed, 40 insertions(+), 17 deletions(-) diff --git a/bundles/org.eclipse.swt/Eclipse SWT/cocoa/org/eclipse/swt/graphics/Image.java b/bundles/org.eclipse.swt/Eclipse SWT/cocoa/org/eclipse/swt/graphics/Image.java index 8ef4c8ff4bb..d1e95565b44 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT/cocoa/org/eclipse/swt/graphics/Image.java +++ b/bundles/org.eclipse.swt/Eclipse SWT/cocoa/org/eclipse/swt/graphics/Image.java @@ -572,7 +572,7 @@ public Image(Device device, ImageData data) { NSAutoreleasePool pool = null; if (!NSThread.isMainThread()) pool = (NSAutoreleasePool) new NSAutoreleasePool().alloc().init(); try { - init(data); + init(data, 100); init(); } finally { if (pool != null) pool.release(); @@ -623,7 +623,7 @@ public Image(Device device, ImageData source, ImageData mask) { ImageData image = new ImageData(source.width, source.height, source.depth, source.palette, source.scanlinePad, source.data); image.maskPad = mask.scanlinePad; image.maskData = mask.data; - init(image); + init(image, 100); } finally { if (pool != null) pool.release(); } @@ -789,7 +789,7 @@ public Image(Device device, ImageFileNameProvider imageFileNameProvider) { if (!NSThread.isMainThread()) pool = (NSAutoreleasePool) new NSAutoreleasePool().alloc().init(); try { initNative(filename); - if (this.handle == null) init(ImageDataLoader.load(filename, 100, 100).element()); + if (this.handle == null) init(ImageDataLoader.load(filename, 100, 100).element(), 100); init(); String filename2x = imageFileNameProvider.getImagePath(200); if (filename2x != null) { @@ -848,7 +848,7 @@ public Image(Device device, ImageDataProvider imageDataProvider) { NSAutoreleasePool pool = null; if (!NSThread.isMainThread()) pool = (NSAutoreleasePool) new NSAutoreleasePool().alloc().init(); try { - init (data); + init (data, 100); init (); ImageData data2x = imageDataProvider.getImageData (200); if (data2x != null) { @@ -886,20 +886,13 @@ public Image(Device device, ImageGcDrawer imageGcDrawer, int width, int height) this.imageGcDrawer = imageGcDrawer; this.width = width; this.height = height; - ImageData data = drawWithImageGcDrawer(imageGcDrawer, width, height, 100); + ImageData data = drawWithImageGcDrawer(imageGcDrawer, width, height, DPIUtil.getDeviceZoom()); if (data == null) SWT.error(SWT.ERROR_INVALID_ARGUMENT); NSAutoreleasePool pool = null; if (!NSThread.isMainThread()) pool = (NSAutoreleasePool) new NSAutoreleasePool().alloc().init(); try { - init (data); + init (data, DPIUtil.getDeviceZoom()); init (); - ImageData data2x = drawWithImageGcDrawer(imageGcDrawer, width, height, 200); - if (data2x != null) { - alphaInfo_200 = new AlphaInfo(); - NSBitmapImageRep rep = createRepresentation (data2x, alphaInfo_200); - handle.addRepresentation(rep); - rep.release(); - } } finally { if (pool != null) pool.release(); } @@ -1474,7 +1467,7 @@ void init(int width, int height) { if (alphaInfo_100 == null) alphaInfo_100 = new AlphaInfo(); } -void init(ImageData image) { +void init(ImageData image, int imageZoom) { if (image == null) SWT.error(SWT.ERROR_NULL_ARGUMENT); if (handle != null) handle.release(); @@ -1484,8 +1477,8 @@ void init(ImageData image) { size.width = width; size.height = height; handle = handle.initWithSize(size); - this.width = image.width; - this.height = image.height; + this.width = image.width * 100 / imageZoom; + this.height = image.height * 100 / imageZoom; if (alphaInfo_100 == null) alphaInfo_100 = new AlphaInfo(); NSBitmapImageRep rep = createRepresentation(image, alphaInfo_100); handle.addRepresentation(rep); @@ -1495,7 +1488,7 @@ void init(ImageData image) { private void initWithSupplier(Function canLoadAtZoom, Function zoomToImageData) { ImageData imageData = zoomToImageData.apply(100); - init(imageData); + init(imageData, 100); if (canLoadAtZoom.apply(200)) { ImageData imageData2x = zoomToImageData.apply(200); alphaInfo_200 = new AlphaInfo(); diff --git a/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_graphics_Image.java b/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_graphics_Image.java index c19b6e172b4..b4ff1bc0780 100644 --- a/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_graphics_Image.java +++ b/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_graphics_Image.java @@ -25,6 +25,7 @@ import static org.junit.Assert.assertTrue; import static org.junit.Assume.assumeFalse; import static org.junit.Assume.assumeTrue; +import static org.junit.jupiter.api.Assertions.assertNotEquals; import java.io.ByteArrayInputStream; import java.io.IOException; @@ -1075,6 +1076,34 @@ public void test_imageDataSameViaProviderAndSimpleData() { dataImage.dispose(); } +/** + * See https://github.com/eclipse-platform/eclipse.platform.ui/issues/3039 + */ +@Test +public void test_gcOnImageGcDrawer_imageDataAtNonDeviceZoom() { + int originalDeviceZoom = DPIUtil.getDeviceZoom(); + int deviceZoom = 200; + int nonDeviceZoom = 100; + DPIUtil.setDeviceZoom(deviceZoom); + + ImageGcDrawer blueDrawer = (gc, width, height) -> { + gc.setBackground(display.getSystemColor(SWT.COLOR_BLUE)); + gc.fillRectangle(0, 0, width, height); + }; + Image image = new Image(display, blueDrawer, 1, 1); + int bluePixelValue = image.getImageData(nonDeviceZoom).getPixel(0, 0); + + GC redOverwritingGc = new GC(image); + try { + redOverwritingGc.setBackground(display.getSystemColor(SWT.COLOR_RED)); + redOverwritingGc.fillRectangle(0, 0, 1, 1); + assertNotEquals(bluePixelValue, image.getImageData(nonDeviceZoom).getPixel(0, 0)); + } finally { + redOverwritingGc.dispose(); + image.dispose(); + DPIUtil.setDeviceZoom(originalDeviceZoom); + } +} private Comparator imageDataComparator() { return Comparator.comparingInt(d -> d.width) // @@ -1095,3 +1124,4 @@ private Comparator imageDataComparator() { } } +