From 3057ab7f3efb2275f8d1541cc73d346367c3a200 Mon Sep 17 00:00:00 2001 From: Heiko Klare Date: Thu, 3 Apr 2025 12:10:30 +0200 Subject: [PATCH] Allow to test for loading image at zoom and fix faulty Win32 image load The ImageLoader and FileFormat implementations currently combine the check whether an image can be provided at a specific scale value and the provision of an image in the scale itself. In case a consumer wants to only test whether an image can be provided at a specific scale, it needs to request that image at that scale, even if it will not be used later one, e.g., because it cannot be provided in the required scale. With this change, ImageLoader and FileFormat provide separate methods for validating whether an image can be retrieved at a specific zoom (such as from an SVG) and the retrieval of the image itself. This is employed by the Cocoa Image implementation to avoid unnecessary load operations and in the Win32 Image implementation to correct erroneous scaling from an already initialized handle instead of loading at proper zoom (such as for an SVG). In addition, this fixes a faulty reuse of the same stream in the Cocoa Image implementation at the same place. --- .../cocoa/org/eclipse/swt/graphics/Image.java | 45 ++++++++++--------- .../eclipse/swt/graphics/ImageDataLoader.java | 8 ++++ .../org/eclipse/swt/graphics/ImageLoader.java | 15 +++++++ .../swt/internal/image/FileFormat.java | 4 ++ .../win32/org/eclipse/swt/graphics/Image.java | 9 +++- 5 files changed, 58 insertions(+), 23 deletions(-) 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 25ec987f0ee..2c04c23902f 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 @@ -20,7 +20,6 @@ import org.eclipse.swt.*; import org.eclipse.swt.internal.*; -import org.eclipse.swt.internal.DPIUtil.*; import org.eclipse.swt.internal.cocoa.*; import org.eclipse.swt.internal.graphics.*; import org.eclipse.swt.internal.image.*; @@ -692,11 +691,18 @@ public Image(Device device, ImageData source, ImageData mask) { */ public Image(Device device, InputStream stream) { super(device); + if (stream == null) { + SWT.error(SWT.ERROR_NULL_ARGUMENT); + } NSAutoreleasePool pool = null; if (!NSThread.isMainThread()) pool = (NSAutoreleasePool) new NSAutoreleasePool().alloc().init(); try { - initWithSupplier(zoom -> ImageDataLoader.load(stream, FileFormat.DEFAULT_ZOOM, zoom)); + byte[] input = stream.readAllBytes(); + initWithSupplier(zoom -> ImageDataLoader.canLoadAtZoom(new ByteArrayInputStream(input), FileFormat.DEFAULT_ZOOM, zoom), + zoom -> ImageDataLoader.load(new ByteArrayInputStream(input), FileFormat.DEFAULT_ZOOM, zoom).element()); init(); + } catch (IOException e) { + SWT.error(SWT.ERROR_INVALID_ARGUMENT, e); } finally { if (pool != null) pool.release(); } @@ -741,7 +747,10 @@ public Image(Device device, String filename) { try { if (filename == null) SWT.error(SWT.ERROR_NULL_ARGUMENT); initNative(filename); - if (this.handle == null) initWithSupplier(zoom -> ImageDataLoader.load(filename, FileFormat.DEFAULT_ZOOM, zoom)); + if (this.handle == null) { + initWithSupplier(zoom -> ImageDataLoader.canLoadAtZoom(filename, FileFormat.DEFAULT_ZOOM, zoom), + zoom -> ImageDataLoader.load(filename, FileFormat.DEFAULT_ZOOM, zoom).element()); + } init(); } finally { if (pool != null) pool.release(); @@ -795,15 +804,13 @@ public Image(Device device, ImageFileNameProvider imageFileNameProvider) { id id = NSImageRep.imageRepWithContentsOfFile(NSString.stringWith(filename2x)); NSImageRep rep = new NSImageRep(id); handle.addRepresentation(rep); - } else { + } else if (ImageDataLoader.canLoadAtZoom(filename, 100, 200)) { // Try to natively scale up the image (e.g. possible if it's an SVG) - ElementAtZoom imageData2x = ImageDataLoader.load(filename, 100, 200); - if (imageData2x.zoom() == 200) { - alphaInfo_200 = new AlphaInfo(); - NSBitmapImageRep rep = createRepresentation (imageData2x.element(), alphaInfo_200); - handle.addRepresentation(rep); - rep.release(); - } + ImageData imageData2x = ImageDataLoader.load(filename, 100, 200).element(); + alphaInfo_200 = new AlphaInfo(); + NSBitmapImageRep rep = createRepresentation (imageData2x, alphaInfo_200); + handle.addRepresentation(rep); + rep.release(); } } finally { if (pool != null) pool.release(); @@ -1484,17 +1491,11 @@ void init(ImageData image) { handle.setCacheMode(OS.NSImageCacheNever); } -private void initWithSupplier(Function> zoomToImageData) { - ElementAtZoom imageData = zoomToImageData.apply(DPIUtil.getDeviceZoom()); - ImageData imageData2x = null; - if (imageData.zoom() == 200) { - imageData2x = imageData.element(); - } - if (imageData.zoom() != 100) { - imageData = zoomToImageData.apply(100); - } - init(imageData.element()); - if (imageData2x != null) { +private void initWithSupplier(Function canLoadAtZoom, Function zoomToImageData) { + ImageData imageData = zoomToImageData.apply(100); + init(imageData); + if (canLoadAtZoom.apply(200)) { + ImageData imageData2x = zoomToImageData.apply(200); alphaInfo_200 = new AlphaInfo(); NSBitmapImageRep rep = createRepresentation (imageData2x, alphaInfo_200); handle.addRepresentation(rep); 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 c81cc6d27a7..29f517b45c0 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 @@ -37,12 +37,20 @@ public static ImageData load(String filename) { return data[0]; } + public static boolean canLoadAtZoom(InputStream stream, int fileZoom, int targetZoom) { + return ImageLoader.canLoadAtZoom(stream, fileZoom, targetZoom); + } + public static ElementAtZoom load(InputStream stream, int fileZoom, int targetZoom) { List> data = new ImageLoader().load(stream, fileZoom, targetZoom); if (data.isEmpty()) SWT.error(SWT.ERROR_INVALID_IMAGE); return data.get(0); } + public static boolean canLoadAtZoom(String filename, int fileZoom, int targetZoom) { + return ImageLoader.canLoadAtZoom(filename, fileZoom, targetZoom); + } + public static ElementAtZoom load(String filename, int fileZoom, int targetZoom) { List> data = new ImageLoader().load(filename, 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 3c8a12dbccd..9973b8f06e3 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 @@ -163,6 +163,11 @@ List> load(InputStream stream, int fileZoom, int target return images; } +static boolean canLoadAtZoom(InputStream stream, int fileZoom, int targetZoom) { + if (stream == null) SWT.error(SWT.ERROR_NULL_ARGUMENT); + return FileFormat.canLoadAtZoom(new ElementAtZoom<>(stream, fileZoom), targetZoom); +} + /** * Loads an array of ImageData objects from the * file with the specified name. Throws an error if either @@ -196,6 +201,16 @@ List> load(String filename, int fileZoom, int targetZoo return null; } +static boolean canLoadAtZoom(String filename, int fileZoom, int targetZoom) { + if (filename == null) SWT.error(SWT.ERROR_NULL_ARGUMENT); + try (InputStream stream = new FileInputStream(filename)) { + return canLoadAtZoom(stream, fileZoom, targetZoom); + } catch (IOException e) { + SWT.error(SWT.ERROR_IO, e); + } + return false; +} + /** * 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/image/FileFormat.java b/bundles/org.eclipse.swt/Eclipse SWT/common/org/eclipse/swt/internal/image/FileFormat.java index 491c8c19181..8232d71023d 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT/common/org/eclipse/swt/internal/image/FileFormat.java +++ b/bundles/org.eclipse.swt/Eclipse SWT/common/org/eclipse/swt/internal/image/FileFormat.java @@ -136,6 +136,10 @@ public static List> load(ElementAtZoom is, return fileFormat.loadFromStream(stream, is.zoom(), targetZoom); } +public static boolean canLoadAtZoom(ElementAtZoom is, int targetZoom) { + return is.zoom() == targetZoom || isDynamicallySizableFormat(is.element()); +} + /** * Write the device independent image array stored in the specified loader * to the specified output stream using the specified file format. 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 48cd8e27ee7..743af6023c9 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 @@ -2235,13 +2235,20 @@ private class ImageFileNameProviderWrapper extends BaseImageProviderWrapper loadImageData(int zoom) { ElementAtZoom fileForZoom = DPIUtil.validateAndGetImagePathAtZoom(provider, zoom); + + // Load at appropriate zoom via loader + if (fileForZoom.zoom() != zoom && ImageDataLoader.canLoadAtZoom(fileForZoom.element(), fileForZoom.zoom(), zoom)) { + ElementAtZoom imageDataAtZoom = ImageDataLoader.load(fileForZoom.element(), fileForZoom.zoom(), zoom); + return new ElementAtZoom<>(adaptImageDataIfDisabledOrGray(imageDataAtZoom.element()), zoom); + } + + // Load at file zoom (native or via loader) and rescale ImageHandle nativeInitializedImage; if (zoomLevelToImageHandle.containsKey(fileForZoom.zoom())) { nativeInitializedImage = zoomLevelToImageHandle.get(fileForZoom.zoom()); } else { nativeInitializedImage = initNative(fileForZoom.element(), fileForZoom.zoom()); } - ElementAtZoom imageDataAtZoom; if (nativeInitializedImage == null) { imageDataAtZoom = ImageDataLoader.load(fileForZoom.element(), fileForZoom.zoom(), zoom);