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 e0b4fe9 commit ba58796
Show file tree
Hide file tree
Showing 7 changed files with 301 additions and 25 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
@@ -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 @@ -172,8 +172,13 @@ private Image (Device device, int nativeZoom) {
* @see #dispose()
*/
public Image(Device device, int width, int height) {
this(device, width, height, DPIUtil.getNativeDeviceZoom());
}


private Image(Device device, int width, int height, int nativeZoom) {
super(device);
initialNativeZoom = DPIUtil.getNativeDeviceZoom();
initialNativeZoom = nativeZoom;
final int zoom = getZoom();
width = DPIUtil.scaleUp (width, zoom);
height = DPIUtil.scaleUp (height, zoom);
Expand Down Expand Up @@ -602,6 +607,32 @@ public Image(Device device, ImageDataProvider imageDataProvider) {
this.device.registerResourceWithZoomSupport(this);
}

/**
* 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);
this.imageProvider = new ImageGcDrawerWrapper(imageGcDrawer, width, height);
initialNativeZoom = DPIUtil.getNativeDeviceZoom();
init();
}

private ImageData adaptImageDataIfDisabledOrGray(ImageData data) {
ImageData returnImageData = null;
switch (this.styleFlag) {
Expand Down Expand Up @@ -1140,6 +1171,9 @@ ImageHandle initNative(String filename, int zoom) {
void destroy () {
device.deregisterResourceWithZoomSupport(this);
if (memGC != null) memGC.dispose();
if (this.imageProvider != null) {
this.imageProvider.destroy();
}
destroyHandle();
memGC = null;
}
Expand Down Expand Up @@ -1282,14 +1316,17 @@ public Rectangle getBounds() {

Rectangle getBounds(int zoom) {
if (isDisposed()) SWT.error(SWT.ERROR_GRAPHIC_DISPOSED);
ImageHandle imageMetadata;
if (zoomLevelToImageHandle.containsKey(zoom)) {
imageMetadata = zoomLevelToImageHandle.get(zoom);
ImageHandle imageMetadata = zoomLevelToImageHandle.get(zoom);
Rectangle rectangle = new Rectangle(0, 0, imageMetadata.width, imageMetadata.height);
return DPIUtil.scaleBounds(rectangle, zoom, imageMetadata.zoom);
} else if (this.imageProvider != null) {
return this.imageProvider.getBounds(zoom);
} else {
imageMetadata = zoomLevelToImageHandle.values().iterator().next();
ImageHandle imageMetadata = zoomLevelToImageHandle.values().iterator().next();
Rectangle rectangle = new Rectangle(0, 0, imageMetadata.width, imageMetadata.height);
return DPIUtil.scaleBounds(rectangle, zoom, imageMetadata.zoom);
}
Rectangle rectangle = new Rectangle(0, 0, imageMetadata.width, imageMetadata.height);
return DPIUtil.scaleBounds(rectangle, zoom, imageMetadata.zoom);
}

/**
Expand Down Expand Up @@ -1932,6 +1969,9 @@ public void internal_dispose_GC (long hDC, GCData data) {
*/
@Override
public boolean isDisposed() {
if (this.imageProvider != null) {
return this.imageProvider.isDisposed();
}
return zoomLevelToImageHandle.isEmpty();
}

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

private abstract class AbstractImageProviderWrapper {
abstract Object getProvider();
protected abstract Rectangle getBounds(int zoom);
abstract ImageData getImageData(int zoom);
abstract ImageHandle getImageMetadata(int zoom);
abstract AbstractImageProviderWrapper createCopy(Image image);
abstract boolean isDisposed();

protected void checkProvider(Object provider, Class<?> expectedClass) {
if (provider == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
Expand All @@ -2062,6 +2104,9 @@ public boolean equals(Object otherProvider) {
return otherProvider instanceof AbstractImageProviderWrapper aip //
&& getProvider().equals(aip.getProvider());
}

protected void destroy() {
}
}

private class ImageFileNameProviderWrapper extends AbstractImageProviderWrapper {
Expand All @@ -2076,6 +2121,13 @@ private class ImageFileNameProviderWrapper extends AbstractImageProviderWrapper
this.provider = provider;
}

@Override
protected Rectangle getBounds(int zoom) {
ImageHandle imageHandle = zoomLevelToImageHandle.values().iterator().next();
Rectangle rectangle = new Rectangle(0, 0, imageHandle.width, imageHandle.height);
return DPIUtil.scaleBounds(rectangle, zoom, imageHandle.zoom);
}

@Override
ImageData getImageData(int zoom) {
ElementAtZoom<String> fileName = DPIUtil.validateAndGetImagePathAtZoom (provider, zoom);
Expand All @@ -2099,6 +2151,11 @@ ImageHandle getImageMetadata(int zoom) {
return zoomLevelToImageHandle.get(zoom);
}

@Override
boolean isDisposed() {
return zoomLevelToImageHandle.isEmpty();
}

@Override
Object getProvider() {
return provider;
Expand Down Expand Up @@ -2127,6 +2184,13 @@ private class ImageDataProviderWrapper extends AbstractImageProviderWrapper {
this.provider = provider;
}

@Override
protected Rectangle getBounds(int zoom) {
ElementAtZoom<ImageData> data = DPIUtil.validateAndGetImageDataAtZoom (provider, zoom);
Rectangle rectangle = new Rectangle(0, 0, data.element().width, data.element().height);
return DPIUtil.scaleBounds(rectangle, zoom, data.zoom());
}

@Override
ImageData getImageData(int zoom) {
ElementAtZoom<ImageData> data = DPIUtil.validateAndGetImageDataAtZoom (provider, zoom);
Expand All @@ -2143,6 +2207,11 @@ ImageHandle getImageMetadata(int zoom) {
return zoomLevelToImageHandle.get(zoom);
}

@Override
boolean isDisposed() {
return zoomLevelToImageHandle.isEmpty();
}

@Override
Object getProvider() {
return provider;
Expand All @@ -2154,6 +2223,70 @@ ImageDataProviderWrapper createCopy(Image image) {
}
}

private class ImageGcDrawerWrapper extends AbstractImageProviderWrapper {
private ImageGcDrawer drawer;
private int width;
private int height;
private boolean isDestroyed = false;

public ImageGcDrawerWrapper(ImageGcDrawer imageGcDrawer, int width, int height) {
checkProvider(imageGcDrawer, ImageGcDrawer.class);
this.drawer = imageGcDrawer;
this.width = width;
this.height = height;
}

@Override
protected Rectangle getBounds(int zoom) {
Rectangle rectangle = new Rectangle(0, 0, width, height);
return DPIUtil.scaleBounds(rectangle, zoom, 100);
}

@Override
ImageData getImageData(int zoom) {
return getImageMetadata(zoom).getImageData();
}

@Override
ImageHandle getImageMetadata(int zoom) {
initialNativeZoom = zoom;
Image image = new Image(device, width, height, zoom);
GC gc = new GC(image);
try {
gc.data.nativeZoom = zoom;
drawer.drawOn(gc);
ImageData imageData = image.getImageMetadata(zoom).getImageData();
drawer.postProcess(imageData);
ImageData newData = adaptImageDataIfDisabledOrGray(imageData);
init(newData, zoom);
} finally {
gc.dispose();
image.dispose();
}
return zoomLevelToImageHandle.get(zoom);
}

@Override
protected void destroy() {
isDestroyed = true;
}

@Override
boolean isDisposed() {
return isDestroyed;
}

@Override
Object getProvider() {
return drawer;
}

@Override
ImageGcDrawerWrapper createCopy(Image image) {
return image.new ImageGcDrawerWrapper(drawer, width, height);
}
}

private class ImageHandle {
private final long handle;
private final int zoom;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,10 @@ public static void main (String [] args) {
return null;
}
};
final ImageGcDrawer imageGcDrawer = gc -> {
gc.drawRectangle(1, 1, 18, 18);
gc.drawLine(3, 3, 17, 17);
};

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

new Label (shell, SWT.NONE).setText ("ImageGcDrawer:");
new Label (shell, SWT.NONE).setImage (new Image (display, imageGcDrawer, 20, 20));
new Button(shell, SWT.NONE).setImage (new Image (display, imageGcDrawer, 20, 20));

createSeparator(shell);

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

0 comments on commit ba58796

Please sign in to comment.