diff --git a/bundles/org.eclipse.swt/Eclipse SWT/cocoa/org/eclipse/swt/graphics/Image.java b/bundles/org.eclipse.swt/Eclipse SWT/cocoa/org/eclipse/swt/graphics/Image.java
index 62bee06ab0..29eb789f6d 100644
--- a/bundles/org.eclipse.swt/Eclipse SWT/cocoa/org/eclipse/swt/graphics/Image.java
+++ b/bundles/org.eclipse.swt/Eclipse SWT/cocoa/org/eclipse/swt/graphics/Image.java
@@ -1818,23 +1818,21 @@ public String toString () {
*
* @param gc the GC to draw on the resulting image
* @param imageData the imageData which is used to draw the scaled Image
- * @param width the width of the original image
- * @param height the height of the original image
- * @param scaleFactor the factor with which the image is supposed to be scaled
+ * @param width the width to which the image is supposed to be scaled
+ * @param height the height to which the image is supposed to be scaled
*
* @noreference This method is not intended to be referenced by clients.
*/
-public static void drawScaled(GC gc, ImageData imageData, int width, int height, float scaleFactor) {
+public static void drawAtSize(GC gc, ImageData imageData, int width, int height) {
StrictChecks.runWithStrictChecksDisabled(() -> {
Image imageToDraw = new Image(gc.device, (ImageDataProvider) zoom -> imageData);
- gc.drawImage(imageToDraw, 0, 0, CocoaDPIUtil.pixelToPoint(width), CocoaDPIUtil.pixelToPoint(height),
+ gc.drawImage(imageToDraw, 0, 0, CocoaDPIUtil.pixelToPoint(imageData.width), CocoaDPIUtil.pixelToPoint(imageData.height),
/*
* E.g. destWidth here is effectively DPIUtil.autoScaleDown (scaledWidth), but
* avoiding rounding errors. Nevertheless, we still have some rounding errors
* due to the point-based API GC#drawImage(..).
*/
- 0, 0, Math.round(CocoaDPIUtil.pixelToPoint(width * scaleFactor)),
- Math.round(CocoaDPIUtil.pixelToPoint(height * scaleFactor)));
+ 0, 0, Math.round(CocoaDPIUtil.pixelToPoint(width)), Math.round(CocoaDPIUtil.pixelToPoint(height)));
imageToDraw.dispose();
});
}
diff --git a/bundles/org.eclipse.swt/Eclipse SWT/common/org/eclipse/swt/graphics/ImageDataAtSizeProvider.java b/bundles/org.eclipse.swt/Eclipse SWT/common/org/eclipse/swt/graphics/ImageDataAtSizeProvider.java
new file mode 100644
index 0000000000..703aabd908
--- /dev/null
+++ b/bundles/org.eclipse.swt/Eclipse SWT/common/org/eclipse/swt/graphics/ImageDataAtSizeProvider.java
@@ -0,0 +1,49 @@
+/*******************************************************************************
+ * Copyright (c) 2025 Vector Informatik GmbH 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:
+ * Michael Bangas (Vector Informatik GmbH) - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.swt.graphics;
+
+/**
+ * Provides an API that is invoked by SWT when an image needs to be drawn at a
+ * specified width and height.
+ *
+ * Client code must implement this interface to supply {@link ImageData} on demand.
+ *
+ *
+ * @since 3.132
+ * @noreference This class is still experimental API and might be subject to change.
+ */
+public interface ImageDataAtSizeProvider extends ImageDataProvider {
+
+ /**
+ * Returns the {@link ImageData} for the given width and height.
+ *
+ *
+ * Implementation notes:
+ *
+ *
+ * - Returning
null
is not permitted. If null
is
+ * returned, SWT will throw an exception when loading the image.
+ * - The returned {@link ImageData} must match the requested width and height
+ * exactly. Implementations should ensure proper resizing and scaling of the
+ * image based on the height and width requested
+ *
+ *
+ * @param width the desired width of the {@link ImageData} to be returned
+ * @param height the desired height of the {@link ImageData} to be returned
+ * @return the {@link ImageData} that exactly matches the requested width and
+ * height
+ * @since 3.132
+ */
+ ImageData getImageData(int width, int height);
+
+}
\ No newline at end of file
diff --git a/bundles/org.eclipse.swt/Eclipse SWT/common/org/eclipse/swt/graphics/ImageDataLoader.java b/bundles/org.eclipse.swt/Eclipse SWT/common/org/eclipse/swt/graphics/ImageDataLoader.java
index 7ee6077dba..5f3bbc118f 100644
--- a/bundles/org.eclipse.swt/Eclipse SWT/common/org/eclipse/swt/graphics/ImageDataLoader.java
+++ b/bundles/org.eclipse.swt/Eclipse SWT/common/org/eclipse/swt/graphics/ImageDataLoader.java
@@ -45,6 +45,14 @@ public static boolean canLoadAtZoom(String filename, int fileZoom, int targetZoo
return ImageLoader.canLoadAtZoom(filename, fileZoom, targetZoom);
}
+ static boolean isDynamicallySizable(String filename) {
+ return ImageLoader.isDynamicallySizable(filename);
+ }
+
+ static boolean isDynamicallySizable(InputStream stream) {
+ return ImageLoader.isDynamicallySizable(stream);
+ }
+
public static ElementAtZoom loadByZoom(InputStream stream, int fileZoom, int targetZoom) {
List> data = new ImageLoader().loadByZoom(stream, fileZoom, targetZoom);
if (data.isEmpty()) SWT.error(SWT.ERROR_INVALID_IMAGE);
diff --git a/bundles/org.eclipse.swt/Eclipse SWT/common/org/eclipse/swt/graphics/ImageLoader.java b/bundles/org.eclipse.swt/Eclipse SWT/common/org/eclipse/swt/graphics/ImageLoader.java
index 989627e095..a38042ae2a 100644
--- a/bundles/org.eclipse.swt/Eclipse SWT/common/org/eclipse/swt/graphics/ImageLoader.java
+++ b/bundles/org.eclipse.swt/Eclipse SWT/common/org/eclipse/swt/graphics/ImageLoader.java
@@ -229,6 +229,19 @@ static boolean canLoadAtZoom(String filename, int fileZoom, int targetZoom) {
return false;
}
+static boolean isDynamicallySizable(String filename) {
+ try (InputStream stream = new FileInputStream(filename)) {
+ return FileFormat.isDynamicallySizableFormat(stream);
+ } catch (IOException e) {
+ SWT.error(SWT.ERROR_IO, e);
+ }
+ return false;
+}
+
+static boolean isDynamicallySizable(InputStream stream) {
+ return FileFormat.isDynamicallySizableFormat(stream);
+}
+
/**
* Saves the image data in this ImageLoader to the specified stream.
* The format parameter can have one of the following values:
diff --git a/bundles/org.eclipse.swt/Eclipse SWT/common/org/eclipse/swt/internal/DPIUtil.java b/bundles/org.eclipse.swt/Eclipse SWT/common/org/eclipse/swt/internal/DPIUtil.java
index 44d67e7140..bc55a3aac1 100644
--- a/bundles/org.eclipse.swt/Eclipse SWT/common/org/eclipse/swt/internal/DPIUtil.java
+++ b/bundles/org.eclipse.swt/Eclipse SWT/common/org/eclipse/swt/internal/DPIUtil.java
@@ -146,6 +146,16 @@ public static ImageData autoScaleImageData (Device device, final ImageData image
int height = imageData.height;
int scaledWidth = Math.round (width * scaleFactor);
int scaledHeight = Math.round (height * scaleFactor);
+ return scaleImage(device, imageData, Image::drawAtSize, scaledWidth, scaledHeight);
+}
+
+@FunctionalInterface
+private interface ImageDrawFunction {
+ void draw(GC gc, ImageData imageData, int width, int height);
+}
+
+private static ImageData scaleImage(Device device, final ImageData imageData, ImageDrawFunction drawFunction,
+ int scaledWidth, int scaledHeight) {
int defaultZoomLevel = 100;
boolean useSmoothScaling = isSmoothScalingEnabled() && imageData.getTransparencyType() != SWT.TRANSPARENCY_MASK;
if (useSmoothScaling) {
@@ -153,7 +163,7 @@ public static ImageData autoScaleImageData (Device device, final ImageData image
@Override
public void drawOn(GC gc, int imageWidth, int imageHeight) {
gc.setAntialias (SWT.ON);
- Image.drawScaled(gc, imageData, width, height, scaleFactor);
+ drawFunction.draw(gc, imageData, imageWidth, imageHeight);
};
@Override
@@ -170,6 +180,10 @@ public int getGcStyle() {
}
}
+public static ImageData autoScaleImageData(Device device, final ImageData imageData, int targetWidth, int targetHeight) {
+ return scaleImage(device, imageData, Image::drawAtSize, targetWidth, targetHeight);
+}
+
public static boolean isSmoothScalingEnabled() {
return autoScaleMethod == AutoScaleMethod.SMOOTH;
}
diff --git a/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/graphics/Image.java b/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/graphics/Image.java
index 131807e57c..eecde6f8a2 100644
--- a/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/graphics/Image.java
+++ b/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/graphics/Image.java
@@ -1579,22 +1579,21 @@ public String toString () {
*
* @param gc the GC to draw on the resulting image
* @param imageData the imageData which is used to draw the scaled Image
- * @param width the width of the original image
- * @param height the height of the original image
- * @param scaleFactor the factor with which the image is supposed to be scaled
+ * @param width the width to which the image is supposed to be scaled
+ * @param height the height to which the image is supposed to be scaled
*
* @noreference This method is not intended to be referenced by clients.
*/
-public static void drawScaled(GC gc, ImageData imageData, int width, int height, float scaleFactor) {
+public static void drawAtSize(GC gc, ImageData imageData, int width, int height) {
StrictChecks.runWithStrictChecksDisabled(() -> {
Image imageToDraw = new Image(gc.device, (ImageDataProvider) zoom -> imageData);
- gc.drawImage(imageToDraw, 0, 0, width, height,
+ gc.drawImage(imageToDraw, 0, 0, imageData.width, imageData.height,
/*
* E.g. destWidth here is effectively DPIUtil.autoScaleDown (scaledWidth), but
* avoiding rounding errors. Nevertheless, we still have some rounding errors
* due to the point-based API GC#drawImage(..).
*/
- 0, 0, Math.round(width * scaleFactor), Math.round(height * scaleFactor));
+ 0, 0, width, height);
imageToDraw.dispose();
});
}
diff --git a/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/graphics/GC.java b/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/graphics/GC.java
index 33b7ad7d4f..140ea240a6 100644
--- a/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/graphics/GC.java
+++ b/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/graphics/GC.java
@@ -1061,7 +1061,8 @@ void apply() {
private void drawImageInPixels(Image image, Point location) {
if (image.isDisposed()) SWT.error(SWT.ERROR_INVALID_ARGUMENT);
- drawImage(image, 0, 0, -1, -1, location.x, location.y, -1, -1, true, getZoom());
+ long handle = Image.win32_getHandle(image, getZoom());
+ drawImage(image, 0, 0, -1, -1, location.x, location.y, -1, -1, true, handle);
}
}
@@ -1157,8 +1158,7 @@ public void drawImage(Image image, int destX, int destY, int destWidth, int dest
SWT.error(SWT.ERROR_INVALID_ARGUMENT);
}
- storeAndApplyOperationForExistingHandle(new DrawScalingImageToImageOperation(image, new Rectangle(0, 0, 0, 0),
- new Rectangle(destX, destY, destWidth, destHeight)));
+ storeAndApplyOperationForExistingHandle(new DrawScaledImageOperation(image, new Rectangle(destX, destY, destWidth, destHeight)));
}
void drawImage(Image srcImage, int srcX, int srcY, int srcWidth, int srcHeight, int destX, int destY, int destWidth, int destHeight, boolean simple) {
@@ -1216,6 +1216,29 @@ private int calculateZoomForImage(int gcZoom, int srcWidth, int srcHeight, int d
}
}
+private class DrawScaledImageOperation extends ImageOperation {
+ private final Rectangle destination;
+
+ DrawScaledImageOperation(Image image, Rectangle destination) {
+ super(image);
+ this.destination = destination;
+ }
+
+ @Override
+ void apply() {
+ int gcZoom = getZoom();
+ drawImage(getImage(), destination.x, destination.y, destination.width, destination.height, gcZoom);
+ }
+}
+
+private void drawImage(Image image, int destX, int destY, int destWidth, int destHeight, int imageZoom) {
+ Rectangle destPixels = Win32DPIUtils.pointToPixel(drawable, new Rectangle(destX, destY, destWidth, destHeight), imageZoom);
+ image.executeOnImageHandleAtSize((tempHandle, handleSize) -> {
+ drawImage(image, 0, 0, handleSize.x, handleSize.y, destPixels.x, destPixels.y, destPixels.width,
+ destPixels.height, false, tempHandle);
+ }, destPixels.width, destPixels.height);
+}
+
private void drawImage(Image image, int srcX, int srcY, int srcWidth, int srcHeight, int destX, int destY,
int destWidth, int destHeight, int imageZoom, int scaledImageZoom) {
Rectangle src = Win32DPIUtils.pointToPixel(drawable, new Rectangle(srcX, srcY, srcWidth, srcHeight), scaledImageZoom);
@@ -1237,7 +1260,7 @@ private void drawImage(Image image, int srcX, int srcY, int srcWidth, int srcHei
}
}
}
- drawImage(image, src.x, src.y, src.width, src.height, dest.x, dest.y, dest.width, dest.height, false, scaledImageZoom);
+ drawImage(image, src.x, src.y, src.width, src.height, dest.x, dest.y, dest.width, dest.height, false, image.getHandle(scaledImageZoom, data.nativeZoom));
}
private class DrawImageToImageOperation extends ImageOperation {
@@ -1254,17 +1277,19 @@ private class DrawImageToImageOperation extends ImageOperation {
@Override
void apply() {
- drawImage(getImage(), source.x, source.y, source.width, source.height, destination.x, destination.y, destination.width, destination.height, simple, getZoom());
+ long handle = Image.win32_getHandle(getImage(), getZoom());
+ drawImage(getImage(), source.x, source.y, source.width, source.height, destination.x, destination.y, destination.width, destination.height, simple, handle);
}
}
-private void drawImage(Image srcImage, int srcX, int srcY, int srcWidth, int srcHeight, int destX, int destY, int destWidth, int destHeight, boolean simple, int imageZoom) {
+private void drawImage(Image srcImage, int srcX, int srcY, int srcWidth, int srcHeight, int destX, int destY, int destWidth, int destHeight, boolean simple, long tempImageHandle) {
if (data.gdipGraphics != 0) {
//TODO - cache bitmap
- long [] gdipImage = srcImage.createGdipImage(imageZoom);
+ long [] gdipImage = srcImage.createGdipImageFromHandle(tempImageHandle);
long img = gdipImage[0];
int imgWidth = Gdip.Image_GetWidth(img);
int imgHeight = Gdip.Image_GetHeight(img);
+
if (srcWidth == 0 && srcHeight == 0) {
srcWidth = imgWidth;
srcHeight = imgHeight;
@@ -1319,13 +1344,13 @@ private void drawImage(Image srcImage, int srcX, int srcY, int srcWidth, int src
}
return;
}
- long imageHandle = srcImage.getHandle(imageZoom, data.nativeZoom);
switch (srcImage.type) {
case SWT.BITMAP:
- drawBitmap(srcImage, imageHandle, srcX, srcY, srcWidth, srcHeight, destX, destY, destWidth, destHeight, simple);
+ drawBitmap(srcImage, tempImageHandle, srcX, srcY, srcWidth, srcHeight, destX, destY, destWidth, destHeight,
+ simple);
break;
case SWT.ICON:
- drawIcon(imageHandle, srcX, srcY, srcWidth, srcHeight, destX, destY, destWidth, destHeight, simple);
+ drawIcon(tempImageHandle, srcX, srcY, srcWidth, srcHeight, destX, destY, destWidth, destHeight, simple);
break;
}
}
diff --git a/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/graphics/Image.java b/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/graphics/Image.java
index 44004b7a6b..10841f925f 100644
--- a/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/graphics/Image.java
+++ b/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/graphics/Image.java
@@ -820,6 +820,27 @@ long getHandle (int targetZoom, int nativeZoom) {
return getImageMetadata(zoomContext).handle;
}
+@FunctionalInterface
+interface HandleAtSizeConsumer {
+ void accept(long imageHandle, Point handleSize);
+}
+
+void executeOnImageHandleAtSize(HandleAtSizeConsumer handleAtSizeConsumer, int widthHint, int heightHint) {
+ ImageData imageData;
+ imageData = this.imageProvider.loadImageDataAtSize(widthHint, heightHint);
+ executeOnImageHandle(handleAtSizeConsumer, imageData);
+}
+
+private void executeOnImageHandle(HandleAtSizeConsumer handleAtSizeConsumer, ImageData imageData) {
+ ImageHandle handleContainer = init(imageData, -1);
+ long tempHandle = handleContainer.handle;
+ try {
+ handleAtSizeConsumer.accept(tempHandle, new Point(imageData.width, imageData.height));
+ } finally {
+ handleContainer.destroy();
+ }
+}
+
/**
* IMPORTANT: This method is not part of the public
* API for Image. It is marked public only so that it
@@ -829,24 +850,29 @@ long getHandle (int targetZoom, int nativeZoom) {
*
* @param gc the GC to draw on the resulting image
* @param imageData the imageData which is used to draw the scaled Image
- * @param width the width of the original image
- * @param height the height of the original image
- * @param scaleFactor the factor with which the image is supposed to be scaled
+ * @param width the width to which the image is supposed to be scaled
+ * @param height the height to which the image is supposed to be scaled
*
* @noreference This method is not intended to be referenced by clients.
*/
-public static void drawScaled(GC gc, ImageData imageData, int width, int height, float scaleFactor) {
+public static void drawAtSize(GC gc, ImageData imageData, int width, int height) {
StrictChecks.runWithStrictChecksDisabled(() -> {
Image imageToDraw = new Image(gc.device, (ImageDataProvider) zoom -> imageData);
- gc.drawImage(imageToDraw, 0, 0, width, height, 0, 0, Math.round(width * scaleFactor),
- Math.round(height * scaleFactor), false);
+ gc.drawImage(imageToDraw, 0, 0, imageData.width, imageData.height, 0, 0, width,
+ height, false);
imageToDraw.dispose();
});
}
+
+
long [] createGdipImage(Integer zoom) {
long handle = Image.win32_getHandle(this, zoom);
+ return createGdipImageFromHandle(handle);
+}
+
+long[] createGdipImageFromHandle(long handle) {
switch (type) {
case SWT.BITMAP: {
BITMAP bm = new BITMAP();
@@ -1956,8 +1982,28 @@ ElementAtZoom getClosestAvailableImageData(int zoom) {
return new ElementAtZoom<>(getImageMetadata(new ZoomContext(closestZoom)).getImageData(), closestZoom);
}
+ public ImageData loadImageDataAtSize(int widthHint, int heightHint) {
+ Optional exact = loadImageDataAtExactSize(widthHint, heightHint);
+ if (exact.isPresent()) {
+ return adaptImageDataIfDisabledOrGray(exact.get());
+ }
+ return loadImageDataClosestTo(widthHint, heightHint);
+ }
+
+ private ImageData loadImageDataClosestTo(int targetWidth, int targetHeight) {
+ Rectangle bounds = getBounds(100);
+ int imageZoomForWidth = 100 * targetWidth / bounds.width;
+ int imageZoomForHeight = 100 * targetHeight / bounds.height;
+ int imageZoom = DPIUtil.getZoomForAutoscaleProperty(Math.max(imageZoomForWidth, imageZoomForHeight));
+ return loadImageData(imageZoom).element();
+ }
+
+ protected Optional loadImageDataAtExactSize(int width, int height) {
+ return Optional.empty(); // exact size not available
+ }
+
protected ImageHandle newImageHandle(ZoomContext zoomContext) {
- ImageData resizedData = getImageData(zoomContext.targetZoom());
+ ImageData resizedData = getScaledImageData (zoomContext.targetZoom());
return newImageHandle(resizedData, zoomContext);
}
@@ -1999,7 +2045,7 @@ protected Rectangle getBounds(int zoom) {
@Override
ImageData newImageData(ZoomContext zoomContext) {
- return getScaledImageData(zoomContext.targetZoom());
+ return getImageData(zoomContext.targetZoom());
}
@Override
@@ -2054,7 +2100,6 @@ private ImageHandle initializeHandleFromSource(ZoomContext zoomContext) {
imageData = adaptImageDataIfDisabledOrGray(imageData);
return newImageHandle(imageData, zoomContext);
}
-
}
private class PlainImageDataProviderWrapper extends ImageFromImageDataProviderWrapper {
@@ -2151,6 +2196,15 @@ protected Rectangle getBounds(int zoom) {
AbstractImageProviderWrapper createCopy(Image image) {
return image.new ImageDataLoaderStreamProviderWrapper(inputStreamData);
}
+
+ @Override
+ protected Optional loadImageDataAtExactSize(int targetWidth, int targetHeight) {
+ if (ImageDataLoader.isDynamicallySizable(new ByteArrayInputStream(this.inputStreamData))) {
+ ImageData imageDataAtSize = ImageDataLoader.loadBySize(new ByteArrayInputStream(this.inputStreamData), targetWidth, targetHeight);
+ return Optional.of(imageDataAtSize);
+ }
+ return Optional.empty();
+ }
}
private class PlainImageProviderWrapper extends AbstractImageProviderWrapper {
@@ -2324,7 +2378,6 @@ ImageData newImageData(ZoomContext zoomContext) {
return (ImageData) cachedImageData.computeIfAbsent(zoomContext.targetZoom(), imageDataRetrival).clone();
}
-
@Override
protected ImageHandle newImageHandle(ZoomContext zoomContext) {
int targetZoom = zoomContext.targetZoom();
@@ -2587,6 +2640,16 @@ private long extractHandleForPixelFormat(int width, int height, int pixelFormat)
}
return handle;
}
+
+ @Override
+ protected Optional loadImageDataAtExactSize(int targetWidth, int targetHeight) {
+ String fileName = DPIUtil.validateAndGetImagePathAtZoom(this.provider, 100).element();
+ if (ImageDataLoader.isDynamicallySizable(fileName)) {
+ ImageData imageDataAtSize = ImageDataLoader.loadBySize(fileName, targetWidth, targetHeight);
+ return Optional.of(imageDataAtSize);
+ }
+ return Optional.empty();
+ }
}
private class ImageDataProviderWrapper extends BaseImageProviderWrapper {
@@ -2603,6 +2666,19 @@ protected ElementAtZoom loadImageData(int zoom) {
ImageDataProviderWrapper createCopy(Image image) {
return image.new ImageDataProviderWrapper(provider);
}
+
+ @Override
+ protected Optional loadImageDataAtExactSize(int targetWidth, int targetHeight) {
+ if (provider instanceof ImageDataAtSizeProvider imageDataAtSizeProvider) {
+ ImageData imageData = imageDataAtSizeProvider.getImageData(targetWidth, targetHeight);
+ if (imageData == null) {
+ SWT.error(SWT.ERROR_INVALID_ARGUMENT, null,
+ " ImageDataAtSizeProvider returned null for width=" + targetWidth + ", height=" + targetHeight);
+ }
+ return Optional.of(imageData);
+ }
+ return Optional.empty();
+ }
}
private class ImageGcDrawerWrapper extends DynamicImageProviderWrapper {