Skip to content

Commit 428cd54

Browse files
akoch-yattaHeikoKlare
authored andcommitted
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 6e219e1 commit 428cd54

File tree

9 files changed

+439
-27
lines changed

9 files changed

+439
-27
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 TransparencyColorImageGcDrawer(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() {

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

+60-1
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,11 @@ public final class Image extends Resource implements Drawable {
135135
*/
136136
private ImageDataProvider imageDataProvider;
137137

138+
/**
139+
* ImageGcDrawer to provide a callback to draw on a GC for various zoom levels
140+
*/
141+
private ImageGcDrawer imageGcDrawer;
142+
138143
/**
139144
* Style flag used to differentiate normal, gray-scale and disabled images based
140145
* on image data providers. Without this, a normal and a disabled image of the
@@ -384,8 +389,9 @@ public Image(Device device, Image srcImage, int flag) {
384389

385390
imageFileNameProvider = srcImage.imageFileNameProvider;
386391
imageDataProvider = srcImage.imageDataProvider;
392+
imageGcDrawer = srcImage.imageGcDrawer;
387393
this.styleFlag = srcImage.styleFlag | flag;
388-
if (imageFileNameProvider != null || imageDataProvider != null) {
394+
if (imageFileNameProvider != null || imageDataProvider != null ||srcImage.imageGcDrawer != null) {
389395
/* If source image has 200% representation then create the 200% representation for the new image & apply flag */
390396
NSBitmapImageRep rep200 = srcImage.getRepresentation (200);
391397
if (rep200 != null) createRepFromSourceAndApplyFlag(rep200, srcWidth * 2, srcHeight * 2, flag);
@@ -843,6 +849,55 @@ public Image(Device device, ImageDataProvider imageDataProvider) {
843849
}
844850
}
845851

852+
/**
853+
* The provided ImageGcDrawer will be called on demand whenever a new variant of the
854+
* Image for an additional zoom is required. Depending on the OS specific implementation
855+
* these calls will be done during the instantiation or later when a new variant is
856+
* requested
857+
* <p>
858+
*
859+
* @param device the device on which to create the image
860+
* @param imageGcDrawer the ImageGcDrawer object to be called when a new image variant
861+
* for another zoom is required.
862+
* @param width the width of the new image in points
863+
* @param height the height of the new image in points
864+
*
865+
* @exception IllegalArgumentException <ul>
866+
* <li>ERROR_NULL_ARGUMENT - if device is null and there is no current device</li>
867+
* <li>ERROR_NULL_ARGUMENT - if the ImageGcDrawer is null</li>
868+
* </ul>
869+
* @since 3.129
870+
*/
871+
public Image(Device device, ImageGcDrawer imageGcDrawer, int width, int height) {
872+
super(device);
873+
if (imageGcDrawer == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
874+
this.imageGcDrawer = imageGcDrawer;
875+
ImageData data = drawWithImageGcDrawer(imageGcDrawer, width, height, 100);
876+
if (data == null) SWT.error(SWT.ERROR_INVALID_ARGUMENT);
877+
NSAutoreleasePool pool = null;
878+
if (!NSThread.isMainThread()) pool = (NSAutoreleasePool) new NSAutoreleasePool().alloc().init();
879+
try {
880+
init (data);
881+
init ();
882+
} finally {
883+
if (pool != null) pool.release();
884+
}
885+
}
886+
887+
private ImageData drawWithImageGcDrawer(ImageGcDrawer imageGcDrawer, int width, int height, int zoom) {
888+
Image image = new Image(device, width, height);
889+
GC gc = new GC(image);
890+
try {
891+
imageGcDrawer.drawOn(gc);
892+
ImageData imageData = image.getImageData(zoom);
893+
imageGcDrawer.postProcess(imageData);
894+
return imageData;
895+
} finally {
896+
gc.dispose();
897+
image.dispose();
898+
}
899+
}
900+
846901
private AlphaInfo _getAlphaInfoAtCurrentZoom (NSBitmapImageRep rep) {
847902
int deviceZoom = DPIUtil.getDeviceZoom();
848903
if (deviceZoom != 100 && (imageFileNameProvider != null || imageDataProvider != null)) {
@@ -1121,6 +1176,8 @@ public boolean equals (Object object) {
11211176
return styleFlag == image.styleFlag && imageDataProvider.equals (image.imageDataProvider);
11221177
} else if (imageFileNameProvider != null && image.imageFileNameProvider != null) {
11231178
return styleFlag == image.styleFlag && imageFileNameProvider.equals (image.imageFileNameProvider);
1179+
} else if (imageGcDrawer != null && image.imageGcDrawer != null) {
1180+
return styleFlag == image.styleFlag && imageGcDrawer.equals (image.imageGcDrawer);
11241181
} else {
11251182
return handle == image.handle;
11261183
}
@@ -1357,6 +1414,8 @@ public int hashCode () {
13571414
return imageDataProvider.hashCode();
13581415
} else if (imageFileNameProvider != null) {
13591416
return imageFileNameProvider.hashCode();
1417+
} else if (imageGcDrawer != null) {
1418+
return imageGcDrawer.hashCode();
13601419
} else {
13611420
return handle != null ? (int)handle.id : 0;
13621421
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
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 provide logic that draws
23+
* on the empty GC on demand.
24+
*
25+
* @since 3.129
26+
*/
27+
public interface ImageGcDrawer {
28+
29+
/**
30+
* Draws an image on a GC for a requested zoom level.
31+
*
32+
* @param gc
33+
* The GC will draw on the underlying Image and is configured for the targeted zoom
34+
* @since 3.129
35+
*/
36+
void drawOn(GC gc);
37+
38+
/**
39+
* Executes post processing on ImageData. This method will always be called
40+
* after <code>drawOn</code> and contain the resulting ImageData.
41+
*
42+
* @param imageData
43+
* The resulting ImageData after <code>drawOn</code> was called
44+
* @since 3.129
45+
*/
46+
default void postProcess(ImageData imageData) {}
47+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
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.internal;
15+
16+
import org.eclipse.swt.graphics.*;
17+
18+
public abstract class TransparencyColorImageGcDrawer implements ImageGcDrawer {
19+
20+
private final Color transparencyColor;
21+
22+
public TransparencyColorImageGcDrawer(Color transparencyColor) {
23+
this.transparencyColor = transparencyColor;
24+
}
25+
26+
@Override
27+
public void postProcess(ImageData imageData) {
28+
imageData.transparentPixel = imageData.palette.getPixel(transparencyColor.getRGB());
29+
}
30+
31+
}

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

+68
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,11 @@ public final class Image extends Resource implements Drawable {
151151
*/
152152
private ImageDataProvider imageDataProvider;
153153

154+
/**
155+
* ImageGcDrawer to provide a callback to draw on a GC for various zoom levels
156+
*/
157+
private ImageGcDrawer imageGcDrawer;
158+
154159
/**
155160
* Style flag used to differentiate normal, gray-scale and disabled images based
156161
* on image data providers. Without this, a normal and a disabled image of the
@@ -263,6 +268,7 @@ public Image(Device device, Image srcImage, int flag) {
263268
this.type = srcImage.type;
264269
this.imageDataProvider = srcImage.imageDataProvider;
265270
this.imageFileNameProvider = srcImage.imageFileNameProvider;
271+
this.imageGcDrawer = srcImage.imageGcDrawer;
266272
this.styleFlag = srcImage.styleFlag | flag;
267273
this.currentDeviceZoom = srcImage.currentDeviceZoom;
268274

@@ -661,6 +667,37 @@ public Image(Device device, ImageDataProvider imageDataProvider) {
661667
init ();
662668
}
663669

670+
/**
671+
* The provided ImageGcDrawer will be called on demand whenever a new variant of the
672+
* Image for an additional zoom is required. Depending on the OS specific implementation
673+
* these calls will be done during the instantiation or later when a new variant is
674+
* requested
675+
* <p>
676+
*
677+
* @param device the device on which to create the image
678+
* @param imageGcDrawer the ImageGcDrawer object to be called when a new image variant
679+
* for another zoom is required.
680+
* @param width the width of the new image in points
681+
* @param height the height of the new image in points
682+
*
683+
* @exception IllegalArgumentException <ul>
684+
* <li>ERROR_NULL_ARGUMENT - if device is null and there is no current device</li>
685+
* <li>ERROR_NULL_ARGUMENT - if the ImageGcDrawer is null</li>
686+
* </ul>
687+
* @since 3.129
688+
*/
689+
public Image(Device device, ImageGcDrawer imageGcDrawer, int width, int height) {
690+
super(device);
691+
if (imageGcDrawer == null) {
692+
SWT.error(SWT.ERROR_NULL_ARGUMENT);
693+
}
694+
this.imageGcDrawer = imageGcDrawer;
695+
currentDeviceZoom = DPIUtil.getDeviceZoom();
696+
ImageData imageData = drawWithImageGcDrawer(width, height, currentDeviceZoom);
697+
init (imageData);
698+
init ();
699+
}
700+
664701
/**
665702
* Refreshes the image for the current device scale factor.
666703
* <p>
@@ -722,6 +759,17 @@ boolean refreshImageForZoom () {
722759
refreshed = true;
723760
currentDeviceZoom = deviceZoomLevel;
724761
}
762+
} else if (imageGcDrawer != null) {
763+
int deviceZoomLevel = deviceZoom;
764+
if (deviceZoomLevel != currentDeviceZoom) {
765+
ImageData data = drawWithImageGcDrawer(width, height, deviceZoomLevel);
766+
/* Release current native resources */
767+
destroy ();
768+
init(data);
769+
init();
770+
refreshed = true;
771+
currentDeviceZoom = deviceZoomLevel;
772+
}
725773
} else {
726774
if (!DPIUtil.useCairoAutoScale()) {
727775
int deviceZoomLevel = deviceZoom;
@@ -904,6 +952,8 @@ public boolean equals (Object object) {
904952
return (styleFlag == image.styleFlag) && imageDataProvider.equals (image.imageDataProvider);
905953
} else if (imageFileNameProvider != null && image.imageFileNameProvider != null) {
906954
return (styleFlag == image.styleFlag) && imageFileNameProvider.equals (image.imageFileNameProvider);
955+
} else if (imageGcDrawer != null && image.imageGcDrawer != null) {
956+
return styleFlag == image.styleFlag && imageGcDrawer.equals (image.imageGcDrawer);
907957
} else {
908958
return surface == image.surface;
909959
}
@@ -1110,11 +1160,27 @@ public ImageData getImageData (int zoom) {
11101160
} else if (imageFileNameProvider != null) {
11111161
ElementAtZoom<String> fileName = DPIUtil.validateAndGetImagePathAtZoom (imageFileNameProvider, zoom);
11121162
return DPIUtil.scaleImageData (device, new ImageData (fileName.element()), zoom, fileName.zoom());
1163+
} else if (imageGcDrawer != null) {
1164+
return drawWithImageGcDrawer(width, height, zoom);
11131165
} else {
11141166
return DPIUtil.scaleImageData (device, getImageDataAtCurrentZoom (), zoom, currentDeviceZoom);
11151167
}
11161168
}
11171169

1170+
private ImageData drawWithImageGcDrawer(int width, int height, int zoom) {
1171+
Image image = new Image(device, width, height);
1172+
GC gc = new GC(image);
1173+
try {
1174+
imageGcDrawer.drawOn(gc);
1175+
ImageData imageData = image.getImageData(zoom);
1176+
imageGcDrawer.postProcess(imageData);
1177+
return imageData;
1178+
} finally {
1179+
gc.dispose();
1180+
image.dispose();
1181+
}
1182+
}
1183+
11181184
/**
11191185
* Invokes platform specific functionality to allocate a new image.
11201186
* <p>
@@ -1179,6 +1245,8 @@ public int hashCode () {
11791245
return imageDataProvider.hashCode();
11801246
} else if (imageFileNameProvider != null) {
11811247
return imageFileNameProvider.hashCode();
1248+
} else if (imageGcDrawer != null) {
1249+
return imageGcDrawer.hashCode();
11821250
} else {
11831251
return (int)surface;
11841252
}

0 commit comments

Comments
 (0)