Skip to content

Commit ba58796

Browse files
committed
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.
1 parent e0b4fe9 commit ba58796

File tree

7 files changed

+301
-25
lines changed

7 files changed

+301
-25
lines changed

bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/CTabFolder.java

+18-19
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import org.eclipse.swt.accessibility.*;
1818
import org.eclipse.swt.events.*;
1919
import org.eclipse.swt.graphics.*;
20+
import org.eclipse.swt.internal.*;
2021
import org.eclipse.swt.widgets.*;
2122

2223
/**
@@ -717,26 +718,24 @@ public Rectangle computeTrim (int x, int y, int width, int height) {
717718
}
718719
return trim;
719720
}
721+
720722
Image createButtonImage(Display display, int button) {
721-
return new Image(display, (ImageDataProvider) zoom -> {
722-
GC tempGC = new GC (CTabFolder.this);
723-
Point size = renderer.computeSize(button, SWT.NONE, tempGC, SWT.DEFAULT, SWT.DEFAULT);
724-
tempGC.dispose();
725-
726-
Rectangle trim = renderer.computeTrim(button, SWT.NONE, 0, 0, 0, 0);
727-
Image image = new Image (display, size.x - trim.width, size.y - trim.height);
728-
GC gc = new GC (image);
729-
Color transColor = renderer.parent.getBackground();
730-
gc.setBackground(transColor);
731-
gc.fillRectangle(image.getBounds());
732-
renderer.draw(button, SWT.NONE, new Rectangle(trim.x, trim.y, size.x, size.y), gc);
733-
gc.dispose ();
734-
735-
final ImageData imageData = image.getImageData (zoom);
736-
imageData.transparentPixel = imageData.palette.getPixel(transColor.getRGB());
737-
image.dispose();
738-
return imageData;
739-
});
723+
final GC tempGC = new GC (CTabFolder.this);
724+
final Point size = renderer.computeSize(button, SWT.NONE, tempGC, SWT.DEFAULT, SWT.DEFAULT);
725+
tempGC.dispose();
726+
727+
final Rectangle trim = renderer.computeTrim(button, SWT.NONE, 0, 0, 0, 0);
728+
final Rectangle imageBounds = new Rectangle(0, 0, size.x - trim.width, size.y - trim.height);
729+
Color transColor = renderer.parent.getBackground();
730+
final ImageGcDrawer imageGcDrawer = new TransparancyColorImageGcDrawer(transColor) {
731+
@Override
732+
public void drawOn(GC gc) {
733+
gc.setBackground(transColor);
734+
gc.fillRectangle(imageBounds);
735+
renderer.draw(button, SWT.NONE, new Rectangle(trim.x, trim.y, size.x, size.y), gc);
736+
}
737+
};
738+
return new Image(display, imageGcDrawer, imageBounds.width, imageBounds.height);
740739
}
741740

742741
private void notifyItemCountChange() {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2025 Yatta and others.
3+
*
4+
* This program and the accompanying materials
5+
* are made available under the terms of the Eclipse Public License 2.0
6+
* which accompanies this distribution, and is available at
7+
* https://www.eclipse.org/legal/epl-2.0/
8+
*
9+
* SPDX-License-Identifier: EPL-2.0
10+
*
11+
* Contributors:
12+
* Yatta - initial API and implementation
13+
*******************************************************************************/
14+
package org.eclipse.swt.graphics;
15+
16+
/**
17+
* Interface to provide a callback mechanism to draw on different GC instances
18+
* depending on the zoom the image will be used for. A common use case is when the
19+
* application is moved from a low DPI monitor to a high DPI monitor.
20+
* This provides API which will be called by SWT during the image rendering.
21+
*
22+
* This interface needs to be implemented by client code to private logic that draws
23+
* on the empty GC on demand.
24+
*
25+
* @since 3.129
26+
*/
27+
public interface ImageGcDrawer {
28+
29+
30+
/**
31+
* Provides a GC to draw on for a requested zoom level.
32+
* <p>
33+
*
34+
* @param gc
35+
* The GC will draw on the underlying Image and is configured for the targeted zoom
36+
* @since 3.129
37+
*/
38+
void drawOn(GC gc);
39+
40+
/**
41+
* Implement this method if any post processing of the ImageData created by the operations on the
42+
* GC in <code>drawOn</code> is necessary.
43+
* <p>
44+
*
45+
* @param imageData
46+
* The resulting ImageData after <code>drawOn</code> was called
47+
* @since 3.129
48+
*/
49+
default void postProcess(ImageData imageData) {}
50+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package org.eclipse.swt.internal;
2+
3+
import org.eclipse.swt.graphics.*;
4+
5+
public abstract class TransparancyColorImageGcDrawer implements ImageGcDrawer {
6+
7+
private final Color transparancyColor;
8+
9+
public TransparancyColorImageGcDrawer(Color transparancyColor) {
10+
this.transparancyColor = transparancyColor;
11+
}
12+
13+
@Override
14+
public void postProcess(ImageData imageData) {
15+
imageData.transparentPixel = imageData.palette.getPixel(transparancyColor.getRGB());
16+
}
17+
18+
}

bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/graphics/Image.java

+139-6
Original file line numberDiff line numberDiff line change
@@ -172,8 +172,13 @@ private Image (Device device, int nativeZoom) {
172172
* @see #dispose()
173173
*/
174174
public Image(Device device, int width, int height) {
175+
this(device, width, height, DPIUtil.getNativeDeviceZoom());
176+
}
177+
178+
179+
private Image(Device device, int width, int height, int nativeZoom) {
175180
super(device);
176-
initialNativeZoom = DPIUtil.getNativeDeviceZoom();
181+
initialNativeZoom = nativeZoom;
177182
final int zoom = getZoom();
178183
width = DPIUtil.scaleUp (width, zoom);
179184
height = DPIUtil.scaleUp (height, zoom);
@@ -602,6 +607,32 @@ public Image(Device device, ImageDataProvider imageDataProvider) {
602607
this.device.registerResourceWithZoomSupport(this);
603608
}
604609

610+
/**
611+
* The provided ImageGcDrawer will be called on demand whenever a new variant of the
612+
* Image for an additional zoom is required. Depending on the OS specific implementation
613+
* these calls will be done during the instantiation or later when a new variant is
614+
* requested
615+
* <p>
616+
*
617+
* @param device the device on which to create the image
618+
* @param imageGcDrawer the ImageGcDrawer object to be called when a new image variant
619+
* for another zoom is required.
620+
* @param width the width of the new image in points
621+
* @param height the height of the new image in points
622+
*
623+
* @exception IllegalArgumentException <ul>
624+
* <li>ERROR_NULL_ARGUMENT - if device is null and there is no current device</li>
625+
* <li>ERROR_NULL_ARGUMENT - if the ImageGcDrawer is null</li>
626+
* </ul>
627+
* @since 3.129
628+
*/
629+
public Image(Device device, ImageGcDrawer imageGcDrawer, int width, int height) {
630+
super(device);
631+
this.imageProvider = new ImageGcDrawerWrapper(imageGcDrawer, width, height);
632+
initialNativeZoom = DPIUtil.getNativeDeviceZoom();
633+
init();
634+
}
635+
605636
private ImageData adaptImageDataIfDisabledOrGray(ImageData data) {
606637
ImageData returnImageData = null;
607638
switch (this.styleFlag) {
@@ -1140,6 +1171,9 @@ ImageHandle initNative(String filename, int zoom) {
11401171
void destroy () {
11411172
device.deregisterResourceWithZoomSupport(this);
11421173
if (memGC != null) memGC.dispose();
1174+
if (this.imageProvider != null) {
1175+
this.imageProvider.destroy();
1176+
}
11431177
destroyHandle();
11441178
memGC = null;
11451179
}
@@ -1282,14 +1316,17 @@ public Rectangle getBounds() {
12821316

12831317
Rectangle getBounds(int zoom) {
12841318
if (isDisposed()) SWT.error(SWT.ERROR_GRAPHIC_DISPOSED);
1285-
ImageHandle imageMetadata;
12861319
if (zoomLevelToImageHandle.containsKey(zoom)) {
1287-
imageMetadata = zoomLevelToImageHandle.get(zoom);
1320+
ImageHandle imageMetadata = zoomLevelToImageHandle.get(zoom);
1321+
Rectangle rectangle = new Rectangle(0, 0, imageMetadata.width, imageMetadata.height);
1322+
return DPIUtil.scaleBounds(rectangle, zoom, imageMetadata.zoom);
1323+
} else if (this.imageProvider != null) {
1324+
return this.imageProvider.getBounds(zoom);
12881325
} else {
1289-
imageMetadata = zoomLevelToImageHandle.values().iterator().next();
1326+
ImageHandle imageMetadata = zoomLevelToImageHandle.values().iterator().next();
1327+
Rectangle rectangle = new Rectangle(0, 0, imageMetadata.width, imageMetadata.height);
1328+
return DPIUtil.scaleBounds(rectangle, zoom, imageMetadata.zoom);
12901329
}
1291-
Rectangle rectangle = new Rectangle(0, 0, imageMetadata.width, imageMetadata.height);
1292-
return DPIUtil.scaleBounds(rectangle, zoom, imageMetadata.zoom);
12931330
}
12941331

12951332
/**
@@ -1932,6 +1969,9 @@ public void internal_dispose_GC (long hDC, GCData data) {
19321969
*/
19331970
@Override
19341971
public boolean isDisposed() {
1972+
if (this.imageProvider != null) {
1973+
return this.imageProvider.isDisposed();
1974+
}
19351975
return zoomLevelToImageHandle.isEmpty();
19361976
}
19371977

@@ -2043,9 +2083,11 @@ public static Image win32_new(Device device, int type, long handle, int nativeZo
20432083

20442084
private abstract class AbstractImageProviderWrapper {
20452085
abstract Object getProvider();
2086+
protected abstract Rectangle getBounds(int zoom);
20462087
abstract ImageData getImageData(int zoom);
20472088
abstract ImageHandle getImageMetadata(int zoom);
20482089
abstract AbstractImageProviderWrapper createCopy(Image image);
2090+
abstract boolean isDisposed();
20492091

20502092
protected void checkProvider(Object provider, Class<?> expectedClass) {
20512093
if (provider == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
@@ -2062,6 +2104,9 @@ public boolean equals(Object otherProvider) {
20622104
return otherProvider instanceof AbstractImageProviderWrapper aip //
20632105
&& getProvider().equals(aip.getProvider());
20642106
}
2107+
2108+
protected void destroy() {
2109+
}
20652110
}
20662111

20672112
private class ImageFileNameProviderWrapper extends AbstractImageProviderWrapper {
@@ -2076,6 +2121,13 @@ private class ImageFileNameProviderWrapper extends AbstractImageProviderWrapper
20762121
this.provider = provider;
20772122
}
20782123

2124+
@Override
2125+
protected Rectangle getBounds(int zoom) {
2126+
ImageHandle imageHandle = zoomLevelToImageHandle.values().iterator().next();
2127+
Rectangle rectangle = new Rectangle(0, 0, imageHandle.width, imageHandle.height);
2128+
return DPIUtil.scaleBounds(rectangle, zoom, imageHandle.zoom);
2129+
}
2130+
20792131
@Override
20802132
ImageData getImageData(int zoom) {
20812133
ElementAtZoom<String> fileName = DPIUtil.validateAndGetImagePathAtZoom (provider, zoom);
@@ -2099,6 +2151,11 @@ ImageHandle getImageMetadata(int zoom) {
20992151
return zoomLevelToImageHandle.get(zoom);
21002152
}
21012153

2154+
@Override
2155+
boolean isDisposed() {
2156+
return zoomLevelToImageHandle.isEmpty();
2157+
}
2158+
21022159
@Override
21032160
Object getProvider() {
21042161
return provider;
@@ -2127,6 +2184,13 @@ private class ImageDataProviderWrapper extends AbstractImageProviderWrapper {
21272184
this.provider = provider;
21282185
}
21292186

2187+
@Override
2188+
protected Rectangle getBounds(int zoom) {
2189+
ElementAtZoom<ImageData> data = DPIUtil.validateAndGetImageDataAtZoom (provider, zoom);
2190+
Rectangle rectangle = new Rectangle(0, 0, data.element().width, data.element().height);
2191+
return DPIUtil.scaleBounds(rectangle, zoom, data.zoom());
2192+
}
2193+
21302194
@Override
21312195
ImageData getImageData(int zoom) {
21322196
ElementAtZoom<ImageData> data = DPIUtil.validateAndGetImageDataAtZoom (provider, zoom);
@@ -2143,6 +2207,11 @@ ImageHandle getImageMetadata(int zoom) {
21432207
return zoomLevelToImageHandle.get(zoom);
21442208
}
21452209

2210+
@Override
2211+
boolean isDisposed() {
2212+
return zoomLevelToImageHandle.isEmpty();
2213+
}
2214+
21462215
@Override
21472216
Object getProvider() {
21482217
return provider;
@@ -2154,6 +2223,70 @@ ImageDataProviderWrapper createCopy(Image image) {
21542223
}
21552224
}
21562225

2226+
private class ImageGcDrawerWrapper extends AbstractImageProviderWrapper {
2227+
private ImageGcDrawer drawer;
2228+
private int width;
2229+
private int height;
2230+
private boolean isDestroyed = false;
2231+
2232+
public ImageGcDrawerWrapper(ImageGcDrawer imageGcDrawer, int width, int height) {
2233+
checkProvider(imageGcDrawer, ImageGcDrawer.class);
2234+
this.drawer = imageGcDrawer;
2235+
this.width = width;
2236+
this.height = height;
2237+
}
2238+
2239+
@Override
2240+
protected Rectangle getBounds(int zoom) {
2241+
Rectangle rectangle = new Rectangle(0, 0, width, height);
2242+
return DPIUtil.scaleBounds(rectangle, zoom, 100);
2243+
}
2244+
2245+
@Override
2246+
ImageData getImageData(int zoom) {
2247+
return getImageMetadata(zoom).getImageData();
2248+
}
2249+
2250+
@Override
2251+
ImageHandle getImageMetadata(int zoom) {
2252+
initialNativeZoom = zoom;
2253+
Image image = new Image(device, width, height, zoom);
2254+
GC gc = new GC(image);
2255+
try {
2256+
gc.data.nativeZoom = zoom;
2257+
drawer.drawOn(gc);
2258+
ImageData imageData = image.getImageMetadata(zoom).getImageData();
2259+
drawer.postProcess(imageData);
2260+
ImageData newData = adaptImageDataIfDisabledOrGray(imageData);
2261+
init(newData, zoom);
2262+
} finally {
2263+
gc.dispose();
2264+
image.dispose();
2265+
}
2266+
return zoomLevelToImageHandle.get(zoom);
2267+
}
2268+
2269+
@Override
2270+
protected void destroy() {
2271+
isDestroyed = true;
2272+
}
2273+
2274+
@Override
2275+
boolean isDisposed() {
2276+
return isDestroyed;
2277+
}
2278+
2279+
@Override
2280+
Object getProvider() {
2281+
return drawer;
2282+
}
2283+
2284+
@Override
2285+
ImageGcDrawerWrapper createCopy(Image image) {
2286+
return image.new ImageGcDrawerWrapper(drawer, width, height);
2287+
}
2288+
}
2289+
21572290
private class ImageHandle {
21582291
private final long handle;
21592292
private final int zoom;

examples/org.eclipse.swt.snippets/src/org/eclipse/swt/snippets/Snippet367.java

+8
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,10 @@ public static void main (String [] args) {
6060
return null;
6161
}
6262
};
63+
final ImageGcDrawer imageGcDrawer = gc -> {
64+
gc.drawRectangle(1, 1, 18, 18);
65+
gc.drawLine(3, 3, 17, 17);
66+
};
6367

6468
final Display display = new Display ();
6569
final Shell shell = new Shell (display);
@@ -98,6 +102,10 @@ public static void main (String [] args) {
98102
new Label (shell, SWT.NONE).setImage (new Image (display, imageDataProvider));
99103
new Button(shell, SWT.NONE).setImage (new Image (display, imageDataProvider));
100104

105+
new Label (shell, SWT.NONE).setText ("ImageGcDrawer:");
106+
new Label (shell, SWT.NONE).setImage (new Image (display, imageGcDrawer, 20, 20));
107+
new Button(shell, SWT.NONE).setImage (new Image (display, imageGcDrawer, 20, 20));
108+
101109
createSeparator(shell);
102110

103111
new Label (shell, SWT.NONE).setText ("1. Canvas\n(PaintListener)");

0 commit comments

Comments
 (0)