Skip to content

Commit

Permalink
Callback for dynamic drawing with a GC on images
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
akoch-yatta committed Jan 17, 2025
1 parent 6e219e1 commit d939382
Show file tree
Hide file tree
Showing 9 changed files with 442 additions and 26 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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.*;

/**
Expand Down Expand Up @@ -717,26 +718,24 @@ 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 Rectangle imageBounds = new Rectangle(0, 0, size.x - trim.width, size.y - trim.height);
Color transColor = renderer.parent.getBackground();
final ImageGcDrawer imageGcDrawer = new TransparancyColorImageGcDrawer(transColor) {
@Override
public void drawOn(GC gc) {
gc.setBackground(transColor);
gc.fillRectangle(imageBounds);
renderer.draw(button, SWT.NONE, new Rectangle(trim.x, trim.y, size.x, size.y), gc);
}
};
return new Image(display, imageGcDrawer, imageBounds.width, imageBounds.height);
}

private void notifyItemCountChange() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,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
Expand Down Expand Up @@ -384,8 +389,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);
Expand Down Expand Up @@ -843,6 +849,62 @@ 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
* <p>
*
* @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 <ul>
* <li>ERROR_NULL_ARGUMENT - if device is null and there is no current device</li>
* <li>ERROR_NULL_ARGUMENT - if the ImageGcDrawer is null</li>
* </ul>
* @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 ();
ImageData data2x = drawWithImageGcDrawer(imageGcDrawer, width, height, 200);
if (data2x != null) {
alphaInfo_200 = new AlphaInfo();
NSBitmapImageRep rep = createRepresentation (data2x, alphaInfo_200);
handle.addRepresentation(rep);
rep.release();
}
} 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);
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)) {
Expand Down Expand Up @@ -1121,6 +1183,8 @@ 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);
} else {
return handle == image.handle;
}
Expand Down Expand Up @@ -1357,6 +1421,8 @@ public int hashCode () {
return imageDataProvider.hashCode();
} else if (imageFileNameProvider != null) {
return imageFileNameProvider.hashCode();
} else if (imageGcDrawer != null) {
return imageGcDrawer.hashCode();
} else {
return handle != null ? (int)handle.id : 0;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*******************************************************************************
* 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 private logic that draws
* on the empty GC on demand.
*
* @since 3.129
*/
public interface ImageGcDrawer {


/**
* Provides a GC to draw on for a requested zoom level.
* <p>
*
* @param gc
* The GC will draw on the underlying Image and is configured for the targeted zoom
* @since 3.129
*/
void drawOn(GC gc);

/**
* Implement this method if any post processing of the ImageData created by the operations on the
* GC in <code>drawOn</code> is necessary.
* <p>
*
* @param imageData
* The resulting ImageData after <code>drawOn</code> was called
* @since 3.129
*/
default void postProcess(ImageData imageData) {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package org.eclipse.swt.internal;

import org.eclipse.swt.graphics.*;

public abstract class TransparancyColorImageGcDrawer implements ImageGcDrawer {

private final Color transparancyColor;

public TransparancyColorImageGcDrawer(Color transparancyColor) {
this.transparancyColor = transparancyColor;
}

@Override
public void postProcess(ImageData imageData) {
imageData.transparentPixel = imageData.palette.getPixel(transparancyColor.getRGB());
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,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
Expand Down Expand Up @@ -263,6 +268,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;

Expand Down Expand Up @@ -661,6 +667,37 @@ 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
* <p>
*
* @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 <ul>
* <li>ERROR_NULL_ARGUMENT - if device is null and there is no current device</li>
* <li>ERROR_NULL_ARGUMENT - if the ImageGcDrawer is null</li>
* </ul>
* @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.
* <p>
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -904,6 +952,8 @@ 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);
} else {
return surface == image.surface;
}
Expand Down Expand Up @@ -1110,11 +1160,33 @@ public ImageData getImageData (int zoom) {
} else if (imageFileNameProvider != null) {
ElementAtZoom<String> 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) {
if (this.imageGcDrawer != null) {
Image image = new Image(device, width, height);
GC gc = new GC(image);
try {
imageGcDrawer.drawOn(gc);
ImageData imageData = image.getImageData(zoom);
imageGcDrawer.postProcess(imageData);
return imageData;
} finally {
gc.dispose();
image.dispose();
}
}
SWT.error(SWT.ERROR_INVALID_ARGUMENT, null, ": ImageGcDrawer [" + imageGcDrawer + "] is null.");
return null;
}

/**
* Invokes platform specific functionality to allocate a new image.
* <p>
Expand Down Expand Up @@ -1179,6 +1251,8 @@ public int hashCode () {
return imageDataProvider.hashCode();
} else if (imageFileNameProvider != null) {
return imageFileNameProvider.hashCode();
} else if (imageGcDrawer != null) {
return imageGcDrawer.hashCode();
} else {
return (int)surface;
}
Expand Down
Loading

0 comments on commit d939382

Please sign in to comment.