Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Callback for dynamic drawing with a GC on images #1734

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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,25 @@ 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 Point imageSize = new Point(size.x - trim.width, size.y - trim.height);
Color transColor = renderer.parent.getBackground();
final ImageGcDrawer imageGcDrawer = new TransparencyColorImageGcDrawer(transColor) {
@Override
public void drawOn(GC gc, int imageWidth, int imageHeight) {
Rectangle imageBounds = new Rectangle(0, 0, imageWidth, imageHeight);
gc.setBackground(transColor);
gc.fillRectangle(imageBounds);
renderer.draw(button, SWT.NONE, imageBounds, gc);
}
};
return new Image(display, imageGcDrawer, imageSize.x, imageSize.y);
}

private void notifyItemCountChange() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@


import java.io.*;
import java.util.*;

import org.eclipse.swt.*;
import org.eclipse.swt.internal.*;
Expand Down Expand Up @@ -135,6 +136,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 +390,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 +850,54 @@ 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.
*
* @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 ();
} 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, width, height);
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 +1176,9 @@ 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) && width == image.width
&& height == image.height;
} else {
return handle == image.handle;
}
Expand Down Expand Up @@ -1357,6 +1415,8 @@ public int hashCode () {
return imageDataProvider.hashCode();
} else if (imageFileNameProvider != null) {
return imageFileNameProvider.hashCode();
} else if (imageGcDrawer != null) {
return Objects.hash(imageGcDrawer, height, width);
} else {
return handle != null ? (int)handle.id : 0;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/*******************************************************************************
* 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 provide logic that
* draws on the empty GC on demand.
*
* @since 3.129
*/
public interface ImageGcDrawer {

/**
* Draws an image on a GC for a requested zoom level.
*
* @param gc The GC will draw on the underlying Image and is configured
* for the targeted zoom
* @param imageWidth The width of the image in points to draw on
* @param imageHeight The height of the image in points to draw on
*/
void drawOn(GC gc, int imageWidth, int imageHeight);

/**
* Executes post processing on ImageData. This method will always be called
* after <code>drawOn</code> and contain the resulting ImageData.
*
* @param imageData The resulting ImageData after <code>drawOn</code> was called
*/
default void postProcess(ImageData imageData) {
fedejeanne marked this conversation as resolved.
Show resolved Hide resolved
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*******************************************************************************
* 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.internal;

import org.eclipse.swt.graphics.*;

public abstract class TransparencyColorImageGcDrawer implements ImageGcDrawer {

private final Color transparencyColor;

public TransparencyColorImageGcDrawer(Color transparencyColor) {
this.transparencyColor = transparencyColor;
}

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

}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@


import java.io.*;
import java.util.*;

import org.eclipse.swt.*;
import org.eclipse.swt.internal.*;
Expand Down Expand Up @@ -151,6 +152,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 +269,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 +668,36 @@ 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.
*
* @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,9 @@ 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) && width == image.width
&& height == image.height;
} else {
return surface == image.surface;
}
Expand Down Expand Up @@ -1110,11 +1161,27 @@ 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) {
Image image = new Image(device, width, height);
GC gc = new GC(image);
try {
imageGcDrawer.drawOn(gc, width, height);
ImageData imageData = image.getImageData(zoom);
imageGcDrawer.postProcess(imageData);
return imageData;
} finally {
gc.dispose();
image.dispose();
}
}

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