Skip to content

Commit 0b2e934

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 0b2e934

File tree

5 files changed

+241
-6
lines changed

5 files changed

+241
-6
lines changed
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
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+
}

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

Lines changed: 126 additions & 6 deletions
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) {
@@ -1282,14 +1313,17 @@ public Rectangle getBounds() {
12821313

12831314
Rectangle getBounds(int zoom) {
12841315
if (isDisposed()) SWT.error(SWT.ERROR_GRAPHIC_DISPOSED);
1285-
ImageHandle imageMetadata;
12861316
if (zoomLevelToImageHandle.containsKey(zoom)) {
1287-
imageMetadata = zoomLevelToImageHandle.get(zoom);
1317+
ImageHandle imageMetadata = zoomLevelToImageHandle.get(zoom);
1318+
Rectangle rectangle = new Rectangle(0, 0, imageMetadata.width, imageMetadata.height);
1319+
return DPIUtil.scaleBounds(rectangle, zoom, imageMetadata.zoom);
1320+
} else if (this.imageProvider != null) {
1321+
return this.imageProvider.getBounds(zoom);
12881322
} else {
1289-
imageMetadata = zoomLevelToImageHandle.values().iterator().next();
1323+
ImageHandle imageMetadata = zoomLevelToImageHandle.values().iterator().next();
1324+
Rectangle rectangle = new Rectangle(0, 0, imageMetadata.width, imageMetadata.height);
1325+
return DPIUtil.scaleBounds(rectangle, zoom, imageMetadata.zoom);
12901326
}
1291-
Rectangle rectangle = new Rectangle(0, 0, imageMetadata.width, imageMetadata.height);
1292-
return DPIUtil.scaleBounds(rectangle, zoom, imageMetadata.zoom);
12931327
}
12941328

12951329
/**
@@ -1932,6 +1966,9 @@ public void internal_dispose_GC (long hDC, GCData data) {
19321966
*/
19331967
@Override
19341968
public boolean isDisposed() {
1969+
if (this.imageProvider != null) {
1970+
return this.imageProvider.isDisposed();
1971+
}
19351972
return zoomLevelToImageHandle.isEmpty();
19361973
}
19371974

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

20442081
private abstract class AbstractImageProviderWrapper {
20452082
abstract Object getProvider();
2083+
protected abstract Rectangle getBounds(int zoom);
20462084
abstract ImageData getImageData(int zoom);
20472085
abstract ImageHandle getImageMetadata(int zoom);
20482086
abstract AbstractImageProviderWrapper createCopy(Image image);
2087+
abstract boolean isDisposed();
20492088

20502089
protected void checkProvider(Object provider, Class<?> expectedClass) {
20512090
if (provider == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
@@ -2076,6 +2115,13 @@ private class ImageFileNameProviderWrapper extends AbstractImageProviderWrapper
20762115
this.provider = provider;
20772116
}
20782117

2118+
@Override
2119+
protected Rectangle getBounds(int zoom) {
2120+
ImageHandle imageHandle = zoomLevelToImageHandle.values().iterator().next();
2121+
Rectangle rectangle = new Rectangle(0, 0, imageHandle.width, imageHandle.height);
2122+
return DPIUtil.scaleBounds(rectangle, zoom, imageHandle.zoom);
2123+
}
2124+
20792125
@Override
20802126
ImageData getImageData(int zoom) {
20812127
ElementAtZoom<String> fileName = DPIUtil.validateAndGetImagePathAtZoom (provider, zoom);
@@ -2099,6 +2145,11 @@ ImageHandle getImageMetadata(int zoom) {
20992145
return zoomLevelToImageHandle.get(zoom);
21002146
}
21012147

2148+
@Override
2149+
boolean isDisposed() {
2150+
return zoomLevelToImageHandle.isEmpty();
2151+
}
2152+
21022153
@Override
21032154
Object getProvider() {
21042155
return provider;
@@ -2127,6 +2178,13 @@ private class ImageDataProviderWrapper extends AbstractImageProviderWrapper {
21272178
this.provider = provider;
21282179
}
21292180

2181+
@Override
2182+
protected Rectangle getBounds(int zoom) {
2183+
ElementAtZoom<ImageData> data = DPIUtil.validateAndGetImageDataAtZoom (provider, zoom);
2184+
Rectangle rectangle = new Rectangle(0, 0, data.element().width, data.element().height);
2185+
return DPIUtil.scaleBounds(rectangle, zoom, data.zoom());
2186+
}
2187+
21302188
@Override
21312189
ImageData getImageData(int zoom) {
21322190
ElementAtZoom<ImageData> data = DPIUtil.validateAndGetImageDataAtZoom (provider, zoom);
@@ -2143,6 +2201,11 @@ ImageHandle getImageMetadata(int zoom) {
21432201
return zoomLevelToImageHandle.get(zoom);
21442202
}
21452203

2204+
@Override
2205+
boolean isDisposed() {
2206+
return zoomLevelToImageHandle.isEmpty();
2207+
}
2208+
21462209
@Override
21472210
Object getProvider() {
21482211
return provider;
@@ -2154,6 +2217,63 @@ ImageDataProviderWrapper createCopy(Image image) {
21542217
}
21552218
}
21562219

2220+
private class ImageGcDrawerWrapper extends AbstractImageProviderWrapper {
2221+
private ImageGcDrawer drawer;
2222+
private int width;
2223+
private int height;
2224+
2225+
public ImageGcDrawerWrapper(ImageGcDrawer imageGcDrawer, int width, int height) {
2226+
checkProvider(imageGcDrawer, ImageGcDrawer.class);
2227+
this.drawer = imageGcDrawer;
2228+
this.width = width;
2229+
this.height = height;
2230+
}
2231+
2232+
@Override
2233+
protected Rectangle getBounds(int zoom) {
2234+
Rectangle rectangle = new Rectangle(0, 0, width, height);
2235+
return DPIUtil.scaleBounds(rectangle, zoom, 100);
2236+
}
2237+
2238+
@Override
2239+
ImageData getImageData(int zoom) {
2240+
return getImageMetadata(zoom).getImageData();
2241+
}
2242+
2243+
@Override
2244+
ImageHandle getImageMetadata(int zoom) {
2245+
initialNativeZoom = zoom;
2246+
Image image = new Image(device, width, height, zoom);
2247+
GC gc = new GC(image);
2248+
try {
2249+
gc.data.nativeZoom = zoom;
2250+
drawer.drawOn(gc);
2251+
ImageData id = image.getImageMetadata(zoom).getImageData();
2252+
ImageData newData = adaptImageDataIfDisabledOrGray(id);
2253+
init(newData, zoom);
2254+
} finally {
2255+
gc.dispose();
2256+
image.dispose();
2257+
}
2258+
return zoomLevelToImageHandle.get(zoom);
2259+
}
2260+
2261+
@Override
2262+
boolean isDisposed() {
2263+
return false;
2264+
}
2265+
2266+
@Override
2267+
Object getProvider() {
2268+
return drawer;
2269+
}
2270+
2271+
@Override
2272+
ImageGcDrawerWrapper createCopy(Image image) {
2273+
return image.new ImageGcDrawerWrapper(drawer, width, height);
2274+
}
2275+
}
2276+
21572277
private class ImageHandle {
21582278
private final long handle;
21592279
private final int zoom;

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

Lines changed: 8 additions & 0 deletions
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)");

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

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,14 @@ public static void main (String [] args) {
6363
};
6464

6565
final Display display = new Display ();
66+
67+
final ImageGcDrawer imageGcDrawer = gc -> {
68+
gc.setBackground(display.getSystemColor(SWT.COLOR_RED));
69+
gc.fillRectangle(0, 0, 16, 16);
70+
gc.setForeground(display.getSystemColor(SWT.COLOR_YELLOW));
71+
gc.drawRectangle(4, 4, 8, 8);
72+
};
73+
6674
final Shell shell = new Shell (display);
6775
shell.setText("Snippet382");
6876
shell.setLayout (new GridLayout (3, false));
@@ -84,6 +92,10 @@ public void handleEvent(Event e) {
8492
final Image disabledImageWithData = new Image (display,imageWithData, SWT.IMAGE_DISABLE);
8593
final Image greyImageWithData = new Image (display,imageWithData, SWT.IMAGE_GRAY);
8694

95+
final Image imageWithGcDrawer = new Image (display, imageGcDrawer, 16, 16);
96+
final Image disabledImageWithGcDrawer = new Image (display, imageWithGcDrawer, SWT.IMAGE_DISABLE);
97+
final Image greyImageWithGcDrawer = new Image (display, imageWithGcDrawer, SWT.IMAGE_GRAY);
98+
8799
try {
88100
drawImages(mainGC, gcData, "Normal",40, imageWithFileNameProvider);
89101
drawImages(mainGC, gcData, "Disabled",80, disabledImageWithFileNameProvider);
@@ -96,6 +108,10 @@ public void handleEvent(Event e) {
96108
drawImages(mainGC, gcData, "Normal",280, imageWithDataProvider);
97109
drawImages(mainGC, gcData, "Disabled",320, disabledImageWithData);
98110
drawImages(mainGC, gcData, "Greyed",360, greyImageWithData);
111+
112+
drawImages(mainGC, gcData, "Normal", 400, imageWithGcDrawer);
113+
drawImages(mainGC, gcData, "Disabled", 440, disabledImageWithGcDrawer);
114+
drawImages(mainGC, gcData, "Greyed", 480, greyImageWithGcDrawer);
99115
} finally {
100116
mainGC.dispose ();
101117
}

0 commit comments

Comments
 (0)