From 02ee164b6fef112d3593109727b40e806c1b8176 Mon Sep 17 00:00:00 2001 From: Andreas Koch Date: Thu, 16 Jan 2025 15:07:23 +0100 Subject: [PATCH] Callback for dynamic drawing with a GC on images This commit contributes a new interface that can to used to initialize images with. The ImageGcDrawer interface should be used to replace the common use case of images to be used as the pane for a GC to draw on. This usecase leads to issues with the multi-zoom-support added to the win32 implementation, but can lead to scaling artifacts on other platforms as well, if the usages leads to scaling ofImageData. --- .../org/eclipse/swt/custom/CTabFolder.java | 38 ++--- .../cocoa/org/eclipse/swt/graphics/Image.java | 62 ++++++- .../eclipse/swt/graphics/ImageGcDrawer.java | 48 ++++++ .../TransparencyColorImageGcDrawer.java | 31 ++++ .../gtk/org/eclipse/swt/graphics/Image.java | 69 ++++++++ .../win32/org/eclipse/swt/graphics/Image.java | 155 +++++++++++++++++- .../org/eclipse/swt/snippets/Snippet367.java | 8 + .../org/eclipse/swt/snippets/Snippet382.java | 18 +- .../Test_org_eclipse_swt_graphics_Image.java | 52 ++++++ 9 files changed, 454 insertions(+), 27 deletions(-) create mode 100644 bundles/org.eclipse.swt/Eclipse SWT/common/org/eclipse/swt/graphics/ImageGcDrawer.java create mode 100644 bundles/org.eclipse.swt/Eclipse SWT/common/org/eclipse/swt/internal/TransparencyColorImageGcDrawer.java diff --git a/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/CTabFolder.java b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/CTabFolder.java index 4d29c190884..7e340642da0 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/CTabFolder.java +++ b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/CTabFolder.java @@ -17,6 +17,7 @@ import org.eclipse.swt.accessibility.*; import org.eclipse.swt.events.*; import org.eclipse.swt.graphics.*; +import org.eclipse.swt.internal.*; import org.eclipse.swt.widgets.*; /** @@ -717,26 +718,25 @@ public Rectangle computeTrim (int x, int y, int width, int height) { } return trim; } + Image createButtonImage(Display display, int button) { - return new Image(display, (ImageDataProvider) zoom -> { - GC tempGC = new GC (CTabFolder.this); - Point size = renderer.computeSize(button, SWT.NONE, tempGC, SWT.DEFAULT, SWT.DEFAULT); - tempGC.dispose(); - - Rectangle trim = renderer.computeTrim(button, SWT.NONE, 0, 0, 0, 0); - Image image = new Image (display, size.x - trim.width, size.y - trim.height); - GC gc = new GC (image); - Color transColor = renderer.parent.getBackground(); - gc.setBackground(transColor); - gc.fillRectangle(image.getBounds()); - renderer.draw(button, SWT.NONE, new Rectangle(trim.x, trim.y, size.x, size.y), gc); - gc.dispose (); - - final ImageData imageData = image.getImageData (zoom); - imageData.transparentPixel = imageData.palette.getPixel(transColor.getRGB()); - image.dispose(); - return imageData; - }); + final GC tempGC = new GC (CTabFolder.this); + final Point size = renderer.computeSize(button, SWT.NONE, tempGC, SWT.DEFAULT, SWT.DEFAULT); + tempGC.dispose(); + + final Rectangle trim = renderer.computeTrim(button, SWT.NONE, 0, 0, 0, 0); + final Point imageSize = new Point(size.x - trim.width, size.y - trim.height); + Color transColor = renderer.parent.getBackground(); + final ImageGcDrawer imageGcDrawer = new TransparencyColorImageGcDrawer(transColor) { + @Override + public void drawOn(GC gc, int imageWidth, int imageHeight) { + Rectangle imageBounds = new Rectangle(0, 0, imageWidth, imageHeight); + gc.setBackground(transColor); + gc.fillRectangle(imageBounds); + renderer.draw(button, SWT.NONE, imageBounds, gc); + } + }; + return new Image(display, imageGcDrawer, imageSize.x, imageSize.y); } private void notifyItemCountChange() { 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 f20ab4fc55c..97d16b8891f 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 @@ -15,6 +15,7 @@ import java.io.*; +import java.util.*; import org.eclipse.swt.*; import org.eclipse.swt.internal.*; @@ -135,6 +136,11 @@ public final class Image extends Resource implements Drawable { */ private ImageDataProvider imageDataProvider; + /** + * ImageGcDrawer to provide a callback to draw on a GC for various zoom levels + */ + private ImageGcDrawer imageGcDrawer; + /** * Style flag used to differentiate normal, gray-scale and disabled images based * on image data providers. Without this, a normal and a disabled image of the @@ -384,8 +390,9 @@ public Image(Device device, Image srcImage, int flag) { imageFileNameProvider = srcImage.imageFileNameProvider; imageDataProvider = srcImage.imageDataProvider; + imageGcDrawer = srcImage.imageGcDrawer; this.styleFlag = srcImage.styleFlag | flag; - if (imageFileNameProvider != null || imageDataProvider != null) { + if (imageFileNameProvider != null || imageDataProvider != null ||srcImage.imageGcDrawer != null) { /* If source image has 200% representation then create the 200% representation for the new image & apply flag */ NSBitmapImageRep rep200 = srcImage.getRepresentation (200); if (rep200 != null) createRepFromSourceAndApplyFlag(rep200, srcWidth * 2, srcHeight * 2, flag); @@ -843,6 +850,54 @@ public Image(Device device, ImageDataProvider imageDataProvider) { } } +/** + * The provided ImageGcDrawer will be called on demand whenever a new variant of the + * Image for an additional zoom is required. Depending on the OS specific implementation + * these calls will be done during the instantiation or later when a new variant is + * requested. + * + * @param device the device on which to create the image + * @param imageGcDrawer the ImageGcDrawer object to be called when a new image variant + * for another zoom is required. + * @param width the width of the new image in points + * @param height the height of the new image in points + * + * @exception IllegalArgumentException + * @since 3.129 + */ +public Image(Device device, ImageGcDrawer imageGcDrawer, int width, int height) { + super(device); + if (imageGcDrawer == null) SWT.error(SWT.ERROR_NULL_ARGUMENT); + this.imageGcDrawer = imageGcDrawer; + ImageData data = drawWithImageGcDrawer(imageGcDrawer, width, height, 100); + 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 (); + } finally { + if (pool != null) pool.release(); + } +} + +private ImageData drawWithImageGcDrawer(ImageGcDrawer imageGcDrawer, int width, int height, int zoom) { + Image image = new Image(device, width, height); + GC gc = new GC(image); + try { + imageGcDrawer.drawOn(gc, width, height); + ImageData imageData = image.getImageData(zoom); + imageGcDrawer.postProcess(imageData); + return imageData; + } finally { + gc.dispose(); + image.dispose(); + } +} + private AlphaInfo _getAlphaInfoAtCurrentZoom (NSBitmapImageRep rep) { int deviceZoom = DPIUtil.getDeviceZoom(); if (deviceZoom != 100 && (imageFileNameProvider != null || imageDataProvider != null)) { @@ -1121,6 +1176,9 @@ public boolean equals (Object object) { return styleFlag == image.styleFlag && imageDataProvider.equals (image.imageDataProvider); } else if (imageFileNameProvider != null && image.imageFileNameProvider != null) { return styleFlag == image.styleFlag && imageFileNameProvider.equals (image.imageFileNameProvider); + } else if (imageGcDrawer != null && image.imageGcDrawer != null) { + return styleFlag == image.styleFlag && imageGcDrawer.equals(image.imageGcDrawer) && width == image.width + && height == image.height; } else { return handle == image.handle; } @@ -1357,6 +1415,8 @@ public int hashCode () { return imageDataProvider.hashCode(); } else if (imageFileNameProvider != null) { return imageFileNameProvider.hashCode(); + } else if (imageGcDrawer != null) { + return Objects.hash(imageGcDrawer, height, width); } else { return handle != null ? (int)handle.id : 0; } diff --git a/bundles/org.eclipse.swt/Eclipse SWT/common/org/eclipse/swt/graphics/ImageGcDrawer.java b/bundles/org.eclipse.swt/Eclipse SWT/common/org/eclipse/swt/graphics/ImageGcDrawer.java new file mode 100644 index 00000000000..0400a0c2fd4 --- /dev/null +++ b/bundles/org.eclipse.swt/Eclipse SWT/common/org/eclipse/swt/graphics/ImageGcDrawer.java @@ -0,0 +1,48 @@ +/******************************************************************************* + * Copyright (c) 2025 Yatta and others. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Yatta - initial API and implementation + *******************************************************************************/ +package org.eclipse.swt.graphics; + +/** + * Interface to provide a callback mechanism to draw on different GC instances + * depending on the zoom the image will be used for. A common use case is when + * the application is moved from a low DPI monitor to a high DPI monitor. This + * provides API which will be called by SWT during the image rendering. + * + * This interface needs to be implemented by client code to provide logic that + * draws on the empty GC on demand. + * + * @since 3.129 + */ +public interface ImageGcDrawer { + + /** + * Draws an image on a GC for a requested zoom level. + * + * @param gc The GC will draw on the underlying Image and is configured + * for the targeted zoom + * @param imageWidth The width of the image in points to draw on + * @param imageHeight The height of the image in points to draw on + */ + void drawOn(GC gc, int imageWidth, int imageHeight); + + /** + * Executes post processing on ImageData. This method will always be called + * after drawOn and contain the resulting ImageData. + * + * @param imageData The resulting ImageData after drawOn was called + */ + default void postProcess(ImageData imageData) { + } + +} diff --git a/bundles/org.eclipse.swt/Eclipse SWT/common/org/eclipse/swt/internal/TransparencyColorImageGcDrawer.java b/bundles/org.eclipse.swt/Eclipse SWT/common/org/eclipse/swt/internal/TransparencyColorImageGcDrawer.java new file mode 100644 index 00000000000..e7ee43332e9 --- /dev/null +++ b/bundles/org.eclipse.swt/Eclipse SWT/common/org/eclipse/swt/internal/TransparencyColorImageGcDrawer.java @@ -0,0 +1,31 @@ +/******************************************************************************* + * Copyright (c) 2025 Yatta and others. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Yatta - initial API and implementation + *******************************************************************************/ +package org.eclipse.swt.internal; + +import org.eclipse.swt.graphics.*; + +public abstract class TransparencyColorImageGcDrawer implements ImageGcDrawer { + + private final Color transparencyColor; + + public TransparencyColorImageGcDrawer(Color transparencyColor) { + this.transparencyColor = transparencyColor; + } + + @Override + public void postProcess(ImageData imageData) { + imageData.transparentPixel = imageData.palette.getPixel(transparencyColor.getRGB()); + } + +} diff --git a/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/graphics/Image.java b/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/graphics/Image.java index 2b48e4b34a5..b51af22f7eb 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/graphics/Image.java +++ b/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/graphics/Image.java @@ -15,6 +15,7 @@ import java.io.*; +import java.util.*; import org.eclipse.swt.*; import org.eclipse.swt.internal.*; @@ -151,6 +152,11 @@ public final class Image extends Resource implements Drawable { */ private ImageDataProvider imageDataProvider; + /** + * ImageGcDrawer to provide a callback to draw on a GC for various zoom levels + */ + private ImageGcDrawer imageGcDrawer; + /** * Style flag used to differentiate normal, gray-scale and disabled images based * on image data providers. Without this, a normal and a disabled image of the @@ -263,6 +269,7 @@ public Image(Device device, Image srcImage, int flag) { this.type = srcImage.type; this.imageDataProvider = srcImage.imageDataProvider; this.imageFileNameProvider = srcImage.imageFileNameProvider; + this.imageGcDrawer = srcImage.imageGcDrawer; this.styleFlag = srcImage.styleFlag | flag; this.currentDeviceZoom = srcImage.currentDeviceZoom; @@ -661,6 +668,36 @@ public Image(Device device, ImageDataProvider imageDataProvider) { init (); } +/** + * The provided ImageGcDrawer will be called on demand whenever a new variant of the + * Image for an additional zoom is required. Depending on the OS specific implementation + * these calls will be done during the instantiation or later when a new variant is + * requested. + * + * @param device the device on which to create the image + * @param imageGcDrawer the ImageGcDrawer object to be called when a new image variant + * for another zoom is required. + * @param width the width of the new image in points + * @param height the height of the new image in points + * + * @exception IllegalArgumentException + * @since 3.129 + */ +public Image(Device device, ImageGcDrawer imageGcDrawer, int width, int height) { + super(device); + if (imageGcDrawer == null) { + SWT.error(SWT.ERROR_NULL_ARGUMENT); + } + this.imageGcDrawer = imageGcDrawer; + currentDeviceZoom = DPIUtil.getDeviceZoom(); + ImageData imageData = drawWithImageGcDrawer(width, height, currentDeviceZoom); + init (imageData); + init (); +} + /** * Refreshes the image for the current device scale factor. *

@@ -722,6 +759,17 @@ boolean refreshImageForZoom () { refreshed = true; currentDeviceZoom = deviceZoomLevel; } + } else if (imageGcDrawer != null) { + int deviceZoomLevel = deviceZoom; + if (deviceZoomLevel != currentDeviceZoom) { + ImageData data = drawWithImageGcDrawer(width, height, deviceZoomLevel); + /* Release current native resources */ + destroy (); + init(data); + init(); + refreshed = true; + currentDeviceZoom = deviceZoomLevel; + } } else { if (!DPIUtil.useCairoAutoScale()) { int deviceZoomLevel = deviceZoom; @@ -904,6 +952,9 @@ public boolean equals (Object object) { return (styleFlag == image.styleFlag) && imageDataProvider.equals (image.imageDataProvider); } else if (imageFileNameProvider != null && image.imageFileNameProvider != null) { return (styleFlag == image.styleFlag) && imageFileNameProvider.equals (image.imageFileNameProvider); + } else if (imageGcDrawer != null && image.imageGcDrawer != null) { + return styleFlag == image.styleFlag && imageGcDrawer.equals(image.imageGcDrawer) && width == image.width + && height == image.height; } else { return surface == image.surface; } @@ -1110,11 +1161,27 @@ public ImageData getImageData (int zoom) { } else if (imageFileNameProvider != null) { ElementAtZoom fileName = DPIUtil.validateAndGetImagePathAtZoom (imageFileNameProvider, zoom); return DPIUtil.scaleImageData (device, new ImageData (fileName.element()), zoom, fileName.zoom()); + } else if (imageGcDrawer != null) { + return drawWithImageGcDrawer(width, height, zoom); } else { return DPIUtil.scaleImageData (device, getImageDataAtCurrentZoom (), zoom, currentDeviceZoom); } } +private ImageData drawWithImageGcDrawer(int width, int height, int zoom) { + Image image = new Image(device, width, height); + GC gc = new GC(image); + try { + imageGcDrawer.drawOn(gc, width, height); + ImageData imageData = image.getImageData(zoom); + imageGcDrawer.postProcess(imageData); + return imageData; + } finally { + gc.dispose(); + image.dispose(); + } +} + /** * Invokes platform specific functionality to allocate a new image. *

@@ -1179,6 +1246,8 @@ public int hashCode () { return imageDataProvider.hashCode(); } else if (imageFileNameProvider != null) { return imageFileNameProvider.hashCode(); + } else if (imageGcDrawer != null) { + return Objects.hash(imageGcDrawer, width, height); } else { return (int)surface; } 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 ddd2aaeaee3..c27378b60da 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 @@ -172,8 +172,13 @@ private Image (Device device, int nativeZoom) { * @see #dispose() */ public Image(Device device, int width, int height) { + this(device, width, height, DPIUtil.getNativeDeviceZoom()); +} + + +private Image(Device device, int width, int height, int nativeZoom) { super(device); - initialNativeZoom = DPIUtil.getNativeDeviceZoom(); + initialNativeZoom = nativeZoom; final int zoom = getZoom(); width = DPIUtil.scaleUp (width, zoom); height = DPIUtil.scaleUp (height, zoom); @@ -602,6 +607,31 @@ public Image(Device device, ImageDataProvider imageDataProvider) { this.device.registerResourceWithZoomSupport(this); } +/** + * The provided ImageGcDrawer will be called on demand whenever a new variant of the + * Image for an additional zoom is required. Depending on the OS-specific implementation + * these calls will be done during the instantiation or later when a new variant is + * requested. + * + * @param device the device on which to create the image + * @param imageGcDrawer the ImageGcDrawer object to be called when a new image variant + * for another zoom is required. + * @param width the width of the new image in points + * @param height the height of the new image in points + * + * @exception IllegalArgumentException

+ * @since 3.129 + */ +public Image(Device device, ImageGcDrawer imageGcDrawer, int width, int height) { + super(device); + this.imageProvider = new ImageGcDrawerWrapper(imageGcDrawer, width, height); + initialNativeZoom = DPIUtil.getNativeDeviceZoom(); + init(); +} + private ImageData adaptImageDataIfDisabledOrGray(ImageData data) { ImageData returnImageData = null; switch (this.styleFlag) { @@ -1140,6 +1170,9 @@ ImageHandle initNative(String filename, int zoom) { void destroy () { device.deregisterResourceWithZoomSupport(this); if (memGC != null) memGC.dispose(); + if (this.imageProvider != null) { + this.imageProvider.destroy(); + } destroyHandle(); memGC = null; } @@ -1282,14 +1315,17 @@ public Rectangle getBounds() { Rectangle getBounds(int zoom) { if (isDisposed()) SWT.error(SWT.ERROR_GRAPHIC_DISPOSED); - ImageHandle imageMetadata; if (zoomLevelToImageHandle.containsKey(zoom)) { - imageMetadata = zoomLevelToImageHandle.get(zoom); + ImageHandle imageMetadata = zoomLevelToImageHandle.get(zoom); + Rectangle rectangle = new Rectangle(0, 0, imageMetadata.width, imageMetadata.height); + return DPIUtil.scaleBounds(rectangle, zoom, imageMetadata.zoom); + } else if (this.imageProvider != null) { + return this.imageProvider.getBounds(zoom); } else { - imageMetadata = zoomLevelToImageHandle.values().iterator().next(); + ImageHandle imageMetadata = zoomLevelToImageHandle.values().iterator().next(); + Rectangle rectangle = new Rectangle(0, 0, imageMetadata.width, imageMetadata.height); + return DPIUtil.scaleBounds(rectangle, zoom, imageMetadata.zoom); } - Rectangle rectangle = new Rectangle(0, 0, imageMetadata.width, imageMetadata.height); - return DPIUtil.scaleBounds(rectangle, zoom, imageMetadata.zoom); } /** @@ -1932,6 +1968,9 @@ public void internal_dispose_GC (long hDC, GCData data) { */ @Override public boolean isDisposed() { + if (this.imageProvider != null) { + return this.imageProvider.isDisposed(); + } return zoomLevelToImageHandle.isEmpty(); } @@ -2043,9 +2082,11 @@ public static Image win32_new(Device device, int type, long handle, int nativeZo private abstract class AbstractImageProviderWrapper { abstract Object getProvider(); + protected abstract Rectangle getBounds(int zoom); abstract ImageData getImageData(int zoom); abstract ImageHandle getImageMetadata(int zoom); abstract AbstractImageProviderWrapper createCopy(Image image); + abstract boolean isDisposed(); protected void checkProvider(Object provider, Class expectedClass) { if (provider == null) SWT.error(SWT.ERROR_NULL_ARGUMENT); @@ -2062,6 +2103,9 @@ public boolean equals(Object otherProvider) { return otherProvider instanceof AbstractImageProviderWrapper aip // && getProvider().equals(aip.getProvider()); } + + protected void destroy() { + } } private class ImageFileNameProviderWrapper extends AbstractImageProviderWrapper { @@ -2076,6 +2120,13 @@ private class ImageFileNameProviderWrapper extends AbstractImageProviderWrapper this.provider = provider; } + @Override + protected Rectangle getBounds(int zoom) { + ImageHandle imageHandle = zoomLevelToImageHandle.values().iterator().next(); + Rectangle rectangle = new Rectangle(0, 0, imageHandle.width, imageHandle.height); + return DPIUtil.scaleBounds(rectangle, zoom, imageHandle.zoom); + } + @Override ImageData getImageData(int zoom) { ElementAtZoom fileName = DPIUtil.validateAndGetImagePathAtZoom (provider, zoom); @@ -2099,6 +2150,11 @@ ImageHandle getImageMetadata(int zoom) { return zoomLevelToImageHandle.get(zoom); } + @Override + boolean isDisposed() { + return zoomLevelToImageHandle.isEmpty(); + } + @Override Object getProvider() { return provider; @@ -2127,6 +2183,13 @@ private class ImageDataProviderWrapper extends AbstractImageProviderWrapper { this.provider = provider; } + @Override + protected Rectangle getBounds(int zoom) { + ElementAtZoom data = DPIUtil.validateAndGetImageDataAtZoom (provider, zoom); + Rectangle rectangle = new Rectangle(0, 0, data.element().width, data.element().height); + return DPIUtil.scaleBounds(rectangle, zoom, data.zoom()); + } + @Override ImageData getImageData(int zoom) { ElementAtZoom data = DPIUtil.validateAndGetImageDataAtZoom (provider, zoom); @@ -2143,6 +2206,11 @@ ImageHandle getImageMetadata(int zoom) { return zoomLevelToImageHandle.get(zoom); } + @Override + boolean isDisposed() { + return zoomLevelToImageHandle.isEmpty(); + } + @Override Object getProvider() { return provider; @@ -2154,6 +2222,81 @@ ImageDataProviderWrapper createCopy(Image image) { } } +private class ImageGcDrawerWrapper extends AbstractImageProviderWrapper { + private ImageGcDrawer drawer; + private int width; + private int height; + private boolean isDestroyed; + + public ImageGcDrawerWrapper(ImageGcDrawer imageGcDrawer, int width, int height) { + checkProvider(imageGcDrawer, ImageGcDrawer.class); + this.drawer = imageGcDrawer; + this.width = width; + this.height = height; + } + + @Override + protected Rectangle getBounds(int zoom) { + Rectangle rectangle = new Rectangle(0, 0, width, height); + return DPIUtil.scaleBounds(rectangle, zoom, 100); + } + + @Override + ImageData getImageData(int zoom) { + return getImageMetadata(zoom).getImageData(); + } + + @Override + ImageHandle getImageMetadata(int zoom) { + initialNativeZoom = zoom; + Image image = new Image(device, width, height, zoom); + GC gc = new GC(image); + try { + gc.data.nativeZoom = zoom; + drawer.drawOn(gc, width, height); + ImageData imageData = image.getImageMetadata(zoom).getImageData(); + drawer.postProcess(imageData); + ImageData newData = adaptImageDataIfDisabledOrGray(imageData); + init(newData, zoom); + } finally { + gc.dispose(); + image.dispose(); + } + return zoomLevelToImageHandle.get(zoom); + } + + @Override + protected void destroy() { + isDestroyed = true; + } + + @Override + boolean isDisposed() { + return isDestroyed; + } + + @Override + Object getProvider() { + return drawer; + } + + @Override + ImageGcDrawerWrapper createCopy(Image image) { + return image.new ImageGcDrawerWrapper(drawer, width, height); + } + + @Override + public int hashCode() { + return Objects.hash(getProvider().hashCode(), width, height); + } + + @Override + public boolean equals(Object otherProvider) { + return otherProvider instanceof ImageGcDrawerWrapper aip && getProvider().equals(aip.getProvider()) + && width == aip.width && height == aip.height; + } +} + private class ImageHandle { private final long handle; private final int zoom; diff --git a/examples/org.eclipse.swt.snippets/src/org/eclipse/swt/snippets/Snippet367.java b/examples/org.eclipse.swt.snippets/src/org/eclipse/swt/snippets/Snippet367.java index d808c867fdd..4133afb1516 100644 --- a/examples/org.eclipse.swt.snippets/src/org/eclipse/swt/snippets/Snippet367.java +++ b/examples/org.eclipse.swt.snippets/src/org/eclipse/swt/snippets/Snippet367.java @@ -60,6 +60,10 @@ public static void main (String [] args) { return null; } }; + final ImageGcDrawer imageGcDrawer = gc -> { + gc.drawRectangle(1, 1, 18, 18); + gc.drawLine(3, 3, 17, 17); + }; final Display display = new Display (); final Shell shell = new Shell (display); @@ -98,6 +102,10 @@ public static void main (String [] args) { new Label (shell, SWT.NONE).setImage (new Image (display, imageDataProvider)); new Button(shell, SWT.NONE).setImage (new Image (display, imageDataProvider)); + new Label (shell, SWT.NONE).setText ("ImageGcDrawer:"); + new Label (shell, SWT.NONE).setImage (new Image (display, imageGcDrawer, 20, 20)); + new Button(shell, SWT.NONE).setImage (new Image (display, imageGcDrawer, 20, 20)); + createSeparator(shell); new Label (shell, SWT.NONE).setText ("1. Canvas\n(PaintListener)"); diff --git a/examples/org.eclipse.swt.snippets/src/org/eclipse/swt/snippets/Snippet382.java b/examples/org.eclipse.swt.snippets/src/org/eclipse/swt/snippets/Snippet382.java index 8a1b5273577..ff40443deea 100644 --- a/examples/org.eclipse.swt.snippets/src/org/eclipse/swt/snippets/Snippet382.java +++ b/examples/org.eclipse.swt.snippets/src/org/eclipse/swt/snippets/Snippet382.java @@ -63,6 +63,14 @@ public static void main (String [] args) { }; final Display display = new Display (); + + final ImageGcDrawer imageGcDrawer = gc -> { + gc.setBackground(display.getSystemColor(SWT.COLOR_RED)); + gc.fillRectangle(0, 0, 16, 16); + gc.setForeground(display.getSystemColor(SWT.COLOR_YELLOW)); + gc.drawRectangle(4, 4, 8, 8); + }; + final Shell shell = new Shell (display); shell.setText("Snippet382"); shell.setLayout (new GridLayout (3, false)); @@ -84,6 +92,10 @@ public void handleEvent(Event e) { final Image disabledImageWithData = new Image (display,imageWithData, SWT.IMAGE_DISABLE); final Image greyImageWithData = new Image (display,imageWithData, SWT.IMAGE_GRAY); + final Image imageWithGcDrawer = new Image (display, imageGcDrawer, 16, 16); + final Image disabledImageWithGcDrawer = new Image (display, imageWithGcDrawer, SWT.IMAGE_DISABLE); + final Image greyImageWithGcDrawer = new Image (display, imageWithGcDrawer, SWT.IMAGE_GRAY); + try { drawImages(mainGC, gcData, "Normal",40, imageWithFileNameProvider); drawImages(mainGC, gcData, "Disabled",80, disabledImageWithFileNameProvider); @@ -96,6 +108,10 @@ public void handleEvent(Event e) { drawImages(mainGC, gcData, "Normal",280, imageWithDataProvider); drawImages(mainGC, gcData, "Disabled",320, disabledImageWithData); drawImages(mainGC, gcData, "Greyed",360, greyImageWithData); + + drawImages(mainGC, gcData, "Normal", 400, imageWithGcDrawer); + drawImages(mainGC, gcData, "Disabled", 440, disabledImageWithGcDrawer); + drawImages(mainGC, gcData, "Greyed", 480, greyImageWithGcDrawer); } finally { mainGC.dispose (); } @@ -114,7 +130,7 @@ private void drawImages(GC mainGC, GCData gcData, String text, int y, final Imag }; shell.addListener(SWT.Paint, l); - shell.setSize(400, 500); + shell.setSize(400, 550); shell.open (); while (!shell.isDisposed ()) { if (!display.readAndDispatch ()) display.sleep (); 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 7e21ea728af..a9fbb03aef0 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 @@ -37,6 +37,7 @@ import org.eclipse.swt.graphics.ImageData; import org.eclipse.swt.graphics.ImageDataProvider; import org.eclipse.swt.graphics.ImageFileNameProvider; +import org.eclipse.swt.graphics.ImageGcDrawer; import org.eclipse.swt.graphics.PaletteData; import org.eclipse.swt.graphics.RGB; import org.eclipse.swt.graphics.Rectangle; @@ -97,6 +98,7 @@ public class Test_org_eclipse_swt_graphics_Image { } return new ImageData(getPath(fileName)); }; +ImageGcDrawer imageGcDrawer = (gc, width, height) -> {}; @Before public void setUp() { @@ -607,6 +609,23 @@ public void test_ConstructorLorg_eclipse_swt_graphics_Device_ImageDataProvider() image.dispose(); } +@Test +public void test_ConstructorLorg_eclipse_swt_graphics_Device_ImageGcDrawer() { + // Null provider + ImageGcDrawer drawer = null; + try { + Image image = new Image(display, drawer, 20, 20); + image.dispose(); + fail("No exception thrown for ImageGcDrawer == null"); + } catch (IllegalArgumentException e) { + assertSWTProblem("Incorrect exception thrown for ImageGcDrawer == null", SWT.ERROR_NULL_ARGUMENT, e); + } + + // Valid provider + Image image = new Image(display, imageGcDrawer, 20, 20); + image.dispose(); +} + @Test public void test_equalsLjava_lang_Object() { Image image = null; @@ -675,6 +694,22 @@ public void test_equalsLjava_lang_Object() { image.dispose(); image1.dispose(); } + + // ImageGcDrawer + try { + image = new Image(display, imageGcDrawer, 10, 10); + image1 = image; + + assertFalse(image.equals(null)); + + assertTrue(image.equals(image1)); + + image1 = new Image(display, imageGcDrawer, 10, 10); + assertTrue(image.equals(image1)); + } finally { + image.dispose(); + image1.dispose(); + } } @Test @@ -760,6 +795,13 @@ public void test_getBoundsInPixels() { bounds = image.getBounds(); image.dispose(); assertEquals(":d: Image.getBoundsInPixels method doesn't return bounds in Pixel values.", boundsInPixels, DPIUtil.autoScaleUp(bounds)); + + // create image with ImageGcDrawer + image = new Image(display, imageGcDrawer, bounds.width, bounds.height); + boundsInPixels = image.getBoundsInPixels(); + bounds = image.getBounds(); + image.dispose(); + assertEquals("Image.getBoundsInPixels method doesn't return bounds in Pixel values for ImageGcDrawer.", boundsInPixels, DPIUtil.autoScaleUp(bounds)); } @SuppressWarnings("deprecation") @@ -968,6 +1010,16 @@ public void test_hashCode() { image.dispose(); image1.dispose(); } + + // ImageGcDrawer + try { + image = new Image(display, imageGcDrawer, 10, 10); + image1 = new Image(display, imageGcDrawer, 10, 10); + assertEquals(image1.hashCode(), image.hashCode()); + } finally { + image.dispose(); + image1.dispose(); + } } @Test