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: + *

+ * + * + * @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 {