From b238dec5a0776e1268884a1fe6849b3ec9f540c7 Mon Sep 17 00:00:00 2001 From: arunjose696 Date: Mon, 15 Sep 2025 11:06:24 +0200 Subject: [PATCH] [win32] DrawImage API in Gc now loads images at the requested size when possible. The DrawImage API with just destination coordinates now tries to load images at the requested size. If the requested size is not available, it falls back to the closest available size by calculating the appropriate zoom. Images are loadable at the target size if the image is created from an SVG stream or filename, or The image is created with an ImageDataProvider that is ImageDataAtSizeProvider. This commit also introduces the ImageDataAtSizeProvider interface to support dynamic sizing. Co-authored-by: Michael Bangas --- .../cocoa/org/eclipse/swt/graphics/Image.java | 12 +-- .../swt/graphics/ImageDataAtSizeProvider.java | 49 ++++++++++ .../eclipse/swt/graphics/ImageDataLoader.java | 8 ++ .../org/eclipse/swt/graphics/ImageLoader.java | 13 +++ .../org/eclipse/swt/internal/DPIUtil.java | 16 +++- .../gtk/org/eclipse/swt/graphics/Image.java | 11 +-- .../win32/org/eclipse/swt/graphics/GC.java | 45 +++++++-- .../win32/org/eclipse/swt/graphics/Image.java | 96 +++++++++++++++++-- 8 files changed, 216 insertions(+), 34 deletions(-) create mode 100644 bundles/org.eclipse.swt/Eclipse SWT/common/org/eclipse/swt/graphics/ImageDataAtSizeProvider.java 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 62bee06ab07..29eb789f6d6 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 00000000000..703aabd908e --- /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: + *

+ * + * + * @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 7ee6077dba3..5f3bbc118f8 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 989627e0956..a38042ae2a9 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 44d67e71408..bc55a3aac1a 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 131807e57cf..eecde6f8a2a 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 33b7ad7d4f4..140ea240a6d 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 44004b7a6b6..10841f925f5 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 {