Skip to content

Commit c493ae8

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 c493ae8

File tree

9 files changed

+454
-27
lines changed

9 files changed

+454
-27
lines changed

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

+19-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,25 @@ 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 Point imageSize = new Point(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, int imageWidth, int imageHeight) {
733+
Rectangle imageBounds = new Rectangle(0, 0, imageWidth, imageHeight);
734+
gc.setBackground(transColor);
735+
gc.fillRectangle(imageBounds);
736+
renderer.draw(button, SWT.NONE, imageBounds, gc);
737+
}
738+
};
739+
return new Image(display, imageGcDrawer, imageSize.x, imageSize.y);
740740
}
741741

742742
private void notifyItemCountChange() {

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

+61-1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515

1616

1717
import java.io.*;
18+
import java.util.*;
1819

1920
import org.eclipse.swt.*;
2021
import org.eclipse.swt.internal.*;
@@ -135,6 +136,11 @@ public final class Image extends Resource implements Drawable {
135136
*/
136137
private ImageDataProvider imageDataProvider;
137138

139+
/**
140+
* ImageGcDrawer to provide a callback to draw on a GC for various zoom levels
141+
*/
142+
private ImageGcDrawer imageGcDrawer;
143+
138144
/**
139145
* Style flag used to differentiate normal, gray-scale and disabled images based
140146
* 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) {
384390

385391
imageFileNameProvider = srcImage.imageFileNameProvider;
386392
imageDataProvider = srcImage.imageDataProvider;
393+
imageGcDrawer = srcImage.imageGcDrawer;
387394
this.styleFlag = srcImage.styleFlag | flag;
388-
if (imageFileNameProvider != null || imageDataProvider != null) {
395+
if (imageFileNameProvider != null || imageDataProvider != null ||srcImage.imageGcDrawer != null) {
389396
/* If source image has 200% representation then create the 200% representation for the new image & apply flag */
390397
NSBitmapImageRep rep200 = srcImage.getRepresentation (200);
391398
if (rep200 != null) createRepFromSourceAndApplyFlag(rep200, srcWidth * 2, srcHeight * 2, flag);
@@ -843,6 +850,54 @@ public Image(Device device, ImageDataProvider imageDataProvider) {
843850
}
844851
}
845852

853+
/**
854+
* The provided ImageGcDrawer will be called on demand whenever a new variant of the
855+
* Image for an additional zoom is required. Depending on the OS specific implementation
856+
* these calls will be done during the instantiation or later when a new variant is
857+
* requested.
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, width, height);
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,9 @@ 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) && width == image.width
1181+
&& height == image.height;
11241182
} else {
11251183
return handle == image.handle;
11261184
}
@@ -1357,6 +1415,8 @@ public int hashCode () {
13571415
return imageDataProvider.hashCode();
13581416
} else if (imageFileNameProvider != null) {
13591417
return imageFileNameProvider.hashCode();
1418+
} else if (imageGcDrawer != null) {
1419+
return Objects.hash(imageGcDrawer, height, width);
13601420
} else {
13611421
return handle != null ? (int)handle.id : 0;
13621422
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
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
19+
* the application is moved from a low DPI monitor to a high DPI monitor. This
20+
* 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
23+
* draws 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 The GC will draw on the underlying Image and is configured
33+
* for the targeted zoom
34+
* @param imageWidth The width of the image in points to draw on
35+
* @param imageHeight The height of the image in points to draw on
36+
*/
37+
void drawOn(GC gc, int imageWidth, int imageHeight);
38+
39+
/**
40+
* Executes post processing on ImageData. This method will always be called
41+
* after <code>drawOn</code> and contain the resulting ImageData.
42+
*
43+
* @param imageData The resulting ImageData after <code>drawOn</code> was called
44+
*/
45+
default void postProcess(ImageData imageData) {
46+
}
47+
48+
}
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

+69
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515

1616

1717
import java.io.*;
18+
import java.util.*;
1819

1920
import org.eclipse.swt.*;
2021
import org.eclipse.swt.internal.*;
@@ -151,6 +152,11 @@ public final class Image extends Resource implements Drawable {
151152
*/
152153
private ImageDataProvider imageDataProvider;
153154

155+
/**
156+
* ImageGcDrawer to provide a callback to draw on a GC for various zoom levels
157+
*/
158+
private ImageGcDrawer imageGcDrawer;
159+
154160
/**
155161
* Style flag used to differentiate normal, gray-scale and disabled images based
156162
* 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) {
263269
this.type = srcImage.type;
264270
this.imageDataProvider = srcImage.imageDataProvider;
265271
this.imageFileNameProvider = srcImage.imageFileNameProvider;
272+
this.imageGcDrawer = srcImage.imageGcDrawer;
266273
this.styleFlag = srcImage.styleFlag | flag;
267274
this.currentDeviceZoom = srcImage.currentDeviceZoom;
268275

@@ -661,6 +668,36 @@ public Image(Device device, ImageDataProvider imageDataProvider) {
661668
init ();
662669
}
663670

671+
/**
672+
* The provided ImageGcDrawer will be called on demand whenever a new variant of the
673+
* Image for an additional zoom is required. Depending on the OS specific implementation
674+
* these calls will be done during the instantiation or later when a new variant is
675+
* requested.
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,9 @@ 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) && width == image.width
957+
&& height == image.height;
907958
} else {
908959
return surface == image.surface;
909960
}
@@ -1110,11 +1161,27 @@ public ImageData getImageData (int zoom) {
11101161
} else if (imageFileNameProvider != null) {
11111162
ElementAtZoom<String> fileName = DPIUtil.validateAndGetImagePathAtZoom (imageFileNameProvider, zoom);
11121163
return DPIUtil.scaleImageData (device, new ImageData (fileName.element()), zoom, fileName.zoom());
1164+
} else if (imageGcDrawer != null) {
1165+
return drawWithImageGcDrawer(width, height, zoom);
11131166
} else {
11141167
return DPIUtil.scaleImageData (device, getImageDataAtCurrentZoom (), zoom, currentDeviceZoom);
11151168
}
11161169
}
11171170

1171+
private ImageData drawWithImageGcDrawer(int width, int height, int zoom) {
1172+
Image image = new Image(device, width, height);
1173+
GC gc = new GC(image);
1174+
try {
1175+
imageGcDrawer.drawOn(gc, width, height);
1176+
ImageData imageData = image.getImageData(zoom);
1177+
imageGcDrawer.postProcess(imageData);
1178+
return imageData;
1179+
} finally {
1180+
gc.dispose();
1181+
image.dispose();
1182+
}
1183+
}
1184+
11181185
/**
11191186
* Invokes platform specific functionality to allocate a new image.
11201187
* <p>
@@ -1179,6 +1246,8 @@ public int hashCode () {
11791246
return imageDataProvider.hashCode();
11801247
} else if (imageFileNameProvider != null) {
11811248
return imageFileNameProvider.hashCode();
1249+
} else if (imageGcDrawer != null) {
1250+
return Objects.hash(imageGcDrawer, width, height);
11821251
} else {
11831252
return (int)surface;
11841253
}

0 commit comments

Comments
 (0)