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 16, 2025
1 parent e0b4fe9 commit 2a42de9
Show file tree
Hide file tree
Showing 5 changed files with 241 additions and 6 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*******************************************************************************
* 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);
}
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 ImageDrawingProvider is null</li>
* </ul>
* @since 3.129
*/
public Image(Device device, ImageGcDrawer imageGcDrawer, int width, int height) {
super(device);
this.imageProvider = new ImageDrawingProviderWrapper(imageGcDrawer, width, height);
initialNativeZoom = DPIUtil.getNativeDeviceZoom();
init();
}

private ImageData adaptImageDataIfDisabledOrGray(ImageData data) {
ImageData returnImageData = null;
switch (this.styleFlag) {
Expand Down Expand Up @@ -1282,14 +1313,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 +1966,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 +2080,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 Down Expand Up @@ -2076,6 +2115,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 +2145,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 +2178,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 +2201,11 @@ ImageHandle getImageMetadata(int zoom) {
return zoomLevelToImageHandle.get(zoom);
}

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

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

private class ImageDrawingProviderWrapper extends AbstractImageProviderWrapper {
private ImageGcDrawer provider;
private int width;
private int height;

public ImageDrawingProviderWrapper(ImageGcDrawer imageDrawingProvider, int width, int height) {
checkProvider(imageDrawingProvider, ImageGcDrawer.class);
this.provider = imageDrawingProvider;
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;
provider.drawOn(gc);
ImageData id = image.getImageMetadata(zoom).getImageData();
ImageData newData = adaptImageDataIfDisabledOrGray(id);
init(newData, zoom);
} finally {
gc.dispose();
image.dispose();
}
return zoomLevelToImageHandle.get(zoom);
}

@Override
boolean isDisposed() {
return false;
}

@Override
Object getProvider() {
return provider;
}

@Override
ImageDrawingProviderWrapper createCopy(Image image) {
return image.new ImageDrawingProviderWrapper(provider, 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
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,14 @@ public static void main (String [] args) {
};

final Display display = new Display ();

final ImageGcDrawer imageGcDrawer = gc -> {
gc.setBackground(display.getSystemColor(SWT.COLOR_RED));
gc.fillRectangle(0, 0, 16, 16);
gc.setForeground(display.getSystemColor(SWT.COLOR_YELLOW));
gc.drawRectangle(4, 4, 8, 8);
};

final Shell shell = new Shell (display);
shell.setText("Snippet382");
shell.setLayout (new GridLayout (3, false));
Expand All @@ -84,6 +92,10 @@ public void handleEvent(Event e) {
final Image disabledImageWithData = new Image (display,imageWithData, SWT.IMAGE_DISABLE);
final Image greyImageWithData = new Image (display,imageWithData, SWT.IMAGE_GRAY);

final Image imageWithGcDrawer = new Image (display, imageGcDrawer, 16, 16);
final Image disabledImageWithGcDrawer = new Image (display, imageWithGcDrawer, SWT.IMAGE_DISABLE);
final Image greyImageWithGcDrawer = new Image (display, imageWithGcDrawer, SWT.IMAGE_GRAY);

try {
drawImages(mainGC, gcData, "Normal",40, imageWithFileNameProvider);
drawImages(mainGC, gcData, "Disabled",80, disabledImageWithFileNameProvider);
Expand All @@ -96,6 +108,10 @@ public void handleEvent(Event e) {
drawImages(mainGC, gcData, "Normal",280, imageWithDataProvider);
drawImages(mainGC, gcData, "Disabled",320, disabledImageWithData);
drawImages(mainGC, gcData, "Greyed",360, greyImageWithData);

drawImages(mainGC, gcData, "Normal", 400, imageWithGcDrawer);
drawImages(mainGC, gcData, "Disabled", 440, disabledImageWithGcDrawer);
drawImages(mainGC, gcData, "Greyed", 480, greyImageWithGcDrawer);
} finally {
mainGC.dispose ();
}
Expand Down
Loading

0 comments on commit 2a42de9

Please sign in to comment.