diff --git a/binaries/org.eclipse.swt.cocoa.macosx.aarch64/META-INF/MANIFEST.MF b/binaries/org.eclipse.swt.cocoa.macosx.aarch64/META-INF/MANIFEST.MF index ac3a606dae0..126ecc19ba8 100644 --- a/binaries/org.eclipse.swt.cocoa.macosx.aarch64/META-INF/MANIFEST.MF +++ b/binaries/org.eclipse.swt.cocoa.macosx.aarch64/META-INF/MANIFEST.MF @@ -3,7 +3,7 @@ Fragment-Host: org.eclipse.swt;bundle-version="[3.128.0,4.0.0)" Bundle-Name: %fragmentName Bundle-Vendor: %providerName Bundle-SymbolicName: org.eclipse.swt.cocoa.macosx.aarch64; singleton:=true -Bundle-Version: 3.131.100.qualifier +Bundle-Version: 3.132.0.qualifier Bundle-ManifestVersion: 2 Bundle-Localization: fragment Export-Package: diff --git a/binaries/org.eclipse.swt.cocoa.macosx.x86_64/META-INF/MANIFEST.MF b/binaries/org.eclipse.swt.cocoa.macosx.x86_64/META-INF/MANIFEST.MF index cd516b318e0..799baef7c67 100644 --- a/binaries/org.eclipse.swt.cocoa.macosx.x86_64/META-INF/MANIFEST.MF +++ b/binaries/org.eclipse.swt.cocoa.macosx.x86_64/META-INF/MANIFEST.MF @@ -3,7 +3,7 @@ Fragment-Host: org.eclipse.swt;bundle-version="[3.128.0,4.0.0)" Bundle-Name: %fragmentName Bundle-Vendor: %providerName Bundle-SymbolicName: org.eclipse.swt.cocoa.macosx.x86_64; singleton:=true -Bundle-Version: 3.131.100.qualifier +Bundle-Version: 3.132.0.qualifier Bundle-ManifestVersion: 2 Bundle-Localization: fragment Export-Package: diff --git a/binaries/org.eclipse.swt.gtk.linux.aarch64/META-INF/MANIFEST.MF b/binaries/org.eclipse.swt.gtk.linux.aarch64/META-INF/MANIFEST.MF index 77b5072882b..e2a8a8d3661 100644 --- a/binaries/org.eclipse.swt.gtk.linux.aarch64/META-INF/MANIFEST.MF +++ b/binaries/org.eclipse.swt.gtk.linux.aarch64/META-INF/MANIFEST.MF @@ -3,7 +3,7 @@ Fragment-Host: org.eclipse.swt;bundle-version="[3.128.0,4.0.0)" Bundle-Name: %fragmentName Bundle-Vendor: %providerName Bundle-SymbolicName: org.eclipse.swt.gtk.linux.aarch64; singleton:=true -Bundle-Version: 3.131.100.qualifier +Bundle-Version: 3.132.0.qualifier Bundle-ManifestVersion: 2 Bundle-Localization: fragment Export-Package: diff --git a/binaries/org.eclipse.swt.gtk.linux.loongarch64/META-INF/MANIFEST.MF b/binaries/org.eclipse.swt.gtk.linux.loongarch64/META-INF/MANIFEST.MF index 681fcdc9a62..db1efee8c1c 100644 --- a/binaries/org.eclipse.swt.gtk.linux.loongarch64/META-INF/MANIFEST.MF +++ b/binaries/org.eclipse.swt.gtk.linux.loongarch64/META-INF/MANIFEST.MF @@ -3,7 +3,7 @@ Fragment-Host: org.eclipse.swt;bundle-version="[3.128.0,4.0.0)" Bundle-Name: %fragmentName Bundle-Vendor: %providerName Bundle-SymbolicName: org.eclipse.swt.gtk.linux.loongarch64; singleton:=true -Bundle-Version: 3.131.0.qualifier +Bundle-Version: 3.132.0.qualifier Bundle-ManifestVersion: 2 Bundle-Localization: fragment Export-Package: diff --git a/binaries/org.eclipse.swt.gtk.linux.ppc64le/META-INF/MANIFEST.MF b/binaries/org.eclipse.swt.gtk.linux.ppc64le/META-INF/MANIFEST.MF index 6d14bd58674..f6015be594d 100644 --- a/binaries/org.eclipse.swt.gtk.linux.ppc64le/META-INF/MANIFEST.MF +++ b/binaries/org.eclipse.swt.gtk.linux.ppc64le/META-INF/MANIFEST.MF @@ -3,7 +3,7 @@ Fragment-Host: org.eclipse.swt;bundle-version="[3.128.0,4.0.0)" Bundle-Name: %fragmentName Bundle-Vendor: %providerName Bundle-SymbolicName: org.eclipse.swt.gtk.linux.ppc64le;singleton:=true -Bundle-Version: 3.131.100.qualifier +Bundle-Version: 3.132.0.qualifier Bundle-ManifestVersion: 2 Bundle-Localization: fragment Export-Package: diff --git a/binaries/org.eclipse.swt.gtk.linux.riscv64/META-INF/MANIFEST.MF b/binaries/org.eclipse.swt.gtk.linux.riscv64/META-INF/MANIFEST.MF index 7fd71e115d8..76d0b4ea316 100644 --- a/binaries/org.eclipse.swt.gtk.linux.riscv64/META-INF/MANIFEST.MF +++ b/binaries/org.eclipse.swt.gtk.linux.riscv64/META-INF/MANIFEST.MF @@ -3,7 +3,7 @@ Fragment-Host: org.eclipse.swt;bundle-version="[3.128.0,4.0.0)" Bundle-Name: %fragmentName Bundle-Vendor: %providerName Bundle-SymbolicName: org.eclipse.swt.gtk.linux.riscv64; singleton:=true -Bundle-Version: 3.131.100.qualifier +Bundle-Version: 3.132.0.qualifier Bundle-ManifestVersion: 2 Bundle-Localization: fragment Export-Package: diff --git a/binaries/org.eclipse.swt.gtk.linux.x86_64/META-INF/MANIFEST.MF b/binaries/org.eclipse.swt.gtk.linux.x86_64/META-INF/MANIFEST.MF index afe6592fba2..5ce90b73997 100644 --- a/binaries/org.eclipse.swt.gtk.linux.x86_64/META-INF/MANIFEST.MF +++ b/binaries/org.eclipse.swt.gtk.linux.x86_64/META-INF/MANIFEST.MF @@ -3,7 +3,7 @@ Fragment-Host: org.eclipse.swt;bundle-version="[3.128.0,4.0.0)" Bundle-Name: %fragmentName Bundle-Vendor: %providerName Bundle-SymbolicName: org.eclipse.swt.gtk.linux.x86_64; singleton:=true -Bundle-Version: 3.131.100.qualifier +Bundle-Version: 3.132.0.qualifier Bundle-ManifestVersion: 2 Bundle-Localization: fragment Export-Package: diff --git a/binaries/org.eclipse.swt.win32.win32.aarch64/META-INF/MANIFEST.MF b/binaries/org.eclipse.swt.win32.win32.aarch64/META-INF/MANIFEST.MF index b6dc89e7117..3fd6e010835 100644 --- a/binaries/org.eclipse.swt.win32.win32.aarch64/META-INF/MANIFEST.MF +++ b/binaries/org.eclipse.swt.win32.win32.aarch64/META-INF/MANIFEST.MF @@ -3,7 +3,7 @@ Fragment-Host: org.eclipse.swt;bundle-version="[3.128.0,4.0.0)" Bundle-Name: %fragmentName Bundle-Vendor: %providerName Bundle-SymbolicName: org.eclipse.swt.win32.win32.aarch64; singleton:=true -Bundle-Version: 3.131.100.qualifier +Bundle-Version: 3.132.0.qualifier Bundle-ManifestVersion: 2 Bundle-Localization: fragment Export-Package: diff --git a/binaries/org.eclipse.swt.win32.win32.x86_64/META-INF/MANIFEST.MF b/binaries/org.eclipse.swt.win32.win32.x86_64/META-INF/MANIFEST.MF index 873ca47cd08..84f618f2b6a 100644 --- a/binaries/org.eclipse.swt.win32.win32.x86_64/META-INF/MANIFEST.MF +++ b/binaries/org.eclipse.swt.win32.win32.x86_64/META-INF/MANIFEST.MF @@ -3,7 +3,7 @@ Fragment-Host: org.eclipse.swt;bundle-version="[3.128.0,4.0.0)" Bundle-Name: %fragmentName Bundle-Vendor: %providerName Bundle-SymbolicName: org.eclipse.swt.win32.win32.x86_64; singleton:=true -Bundle-Version: 3.131.100.qualifier +Bundle-Version: 3.132.0.qualifier Bundle-ManifestVersion: 2 Bundle-Localization: fragment Export-Package: diff --git a/bundles/org.eclipse.swt.svg/src/org/eclipse/swt/svg/JSVGRasterizer.java b/bundles/org.eclipse.swt.svg/src/org/eclipse/swt/svg/JSVGRasterizer.java index cee41f23d9a..a2a5da280dc 100644 --- a/bundles/org.eclipse.swt.svg/src/org/eclipse/swt/svg/JSVGRasterizer.java +++ b/bundles/org.eclipse.swt.svg/src/org/eclipse/swt/svg/JSVGRasterizer.java @@ -72,34 +72,44 @@ public class JSVGRasterizer implements SVGRasterizer { @Override public ImageData rasterizeSVG(InputStream inputStream, int zoom) throws IOException { - SVGDocument svgDocument = loadSVG(inputStream); - if (svgDocument == null) { - SWT.error(SWT.ERROR_INVALID_IMAGE); - } + SVGDocument svgDocument = loadAndValidateSVG(inputStream); BufferedImage rasterizedImage = renderSVG(svgDocument, zoom); return convertToSWTImageData(rasterizedImage); } - - private SVGDocument loadSVG(InputStream inputStream) { - return SVG_LOADER.load(inputStream, null, LoaderContext.createDefault()); + + @Override + public ImageData rasterizeSVG(InputStream inputStream, int width, int height) throws IOException { + SVGDocument svgDocument = loadAndValidateSVG(inputStream); + BufferedImage rasterizedImage = renderSVG(svgDocument, width, height); + return convertToSWTImageData(rasterizedImage); + } + + private SVGDocument loadAndValidateSVG(InputStream inputStream) throws IOException { + SVGDocument svgDocument = SVG_LOADER.load(inputStream, null, LoaderContext.createDefault()); + if (svgDocument == null) { + SWT.error(SWT.ERROR_INVALID_IMAGE); + } + return svgDocument; } private BufferedImage renderSVG(SVGDocument svgDocument, int zoom) { + FloatSize sourceImageSize = svgDocument.size(); float scalingFactor = zoom / 100.0f; - BufferedImage image = createImageBase(svgDocument, scalingFactor); - Graphics2D g = configureRenderingOptions(scalingFactor, image); + int targetImageWidth = calculateTargetWidth(scalingFactor, sourceImageSize); + int targetImageHeight = calculateTargetHeight(scalingFactor, sourceImageSize); + return renderSVG(svgDocument, targetImageWidth, targetImageHeight); + } + + private BufferedImage renderSVG(SVGDocument svgDocument, int width, int height) { + BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); + float widthScalingFactor = width / svgDocument.size().width; + float heightScalingFactor = height / svgDocument.size().height; + Graphics2D g = configureRenderingOptions(widthScalingFactor, heightScalingFactor, image); svgDocument.render(null, g); g.dispose(); return image; } - private BufferedImage createImageBase(SVGDocument svgDocument, float scalingFactor) { - FloatSize sourceImageSize = svgDocument.size(); - int targetImageWidth = calculateTargetWidth(scalingFactor, sourceImageSize); - int targetImageHeight = calculateTargetHeight(scalingFactor, sourceImageSize); - return new BufferedImage(targetImageWidth, targetImageHeight, BufferedImage.TYPE_INT_ARGB); - } - private int calculateTargetWidth(float scalingFactor, FloatSize sourceImageSize) { double sourceImageWidth = sourceImageSize.getWidth(); return (int) Math.round(sourceImageWidth * scalingFactor); @@ -110,10 +120,10 @@ private int calculateTargetHeight(float scalingFactor, FloatSize sourceImageSize return (int) Math.round(sourceImageHeight * scalingFactor); } - private Graphics2D configureRenderingOptions(float scalingFactor, BufferedImage image) { + private Graphics2D configureRenderingOptions(float widthScalingFactor, float heightScalingFactor, BufferedImage image) { Graphics2D g = image.createGraphics(); g.setRenderingHints(RENDERING_HINTS); - g.scale(scalingFactor, scalingFactor); + g.scale(widthScalingFactor, heightScalingFactor); return g; } 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 0dc7f5e36d5..f310f316c34 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 @@ -692,7 +692,7 @@ public Image(Device device, InputStream stream) { try { 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()); + zoom -> ImageDataLoader.loadByZoom(new ByteArrayInputStream(input), FileFormat.DEFAULT_ZOOM, zoom).element()); init(); } catch (IOException e) { SWT.error(SWT.ERROR_INVALID_ARGUMENT, e); @@ -742,7 +742,7 @@ public Image(Device device, String filename) { initNative(filename); if (this.handle == null) { initWithSupplier(zoom -> ImageDataLoader.canLoadAtZoom(filename, FileFormat.DEFAULT_ZOOM, zoom), - zoom -> ImageDataLoader.load(filename, FileFormat.DEFAULT_ZOOM, zoom).element()); + zoom -> ImageDataLoader.loadByZoom(filename, FileFormat.DEFAULT_ZOOM, zoom).element()); } init(); } finally { @@ -789,7 +789,7 @@ public Image(Device device, ImageFileNameProvider imageFileNameProvider) { if (!NSThread.isMainThread()) pool = (NSAutoreleasePool) new NSAutoreleasePool().alloc().init(); try { initNative(filename); - if (this.handle == null) init(ImageDataLoader.load(filename, 100, 100).element(), 100); + if (this.handle == null) init(ImageDataLoader.loadByZoom(filename, 100, 100).element(), 100); init(); String filename2x = imageFileNameProvider.getImagePath(200); if (filename2x != null) { @@ -799,7 +799,7 @@ public Image(Device device, ImageFileNameProvider imageFileNameProvider) { handle.addRepresentation(rep); } else if (ImageDataLoader.canLoadAtZoom(filename, 100, 200)) { // Try to natively scale up the image (e.g. possible if it's an SVG) - ImageData imageData2x = ImageDataLoader.load(filename, 100, 200).element(); + ImageData imageData2x = ImageDataLoader.loadByZoom(filename, 100, 200).element(); alphaInfo_200 = new AlphaInfo(); NSBitmapImageRep rep = createRepresentation (imageData2x, alphaInfo_200); handle.addRepresentation(rep); @@ -1820,11 +1820,12 @@ public String toString () { * @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 targetWidth the width to which the image is supposed to be scaled + * @param targetHeight 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 drawAtTargetSize(GC gc, ImageData imageData, int width, int height, int targetWidth, int targetHeight) { StrictChecks.runWithStrictChecksDisabled(() -> { Image imageToDraw = new Image(gc.device, (ImageDataProvider) zoom -> imageData); gc.drawImage(imageToDraw, 0, 0, CocoaDPIUtil.pixelToPoint(width), CocoaDPIUtil.pixelToPoint(height), @@ -1833,8 +1834,7 @@ public static void drawScaled(GC gc, ImageData imageData, int width, int height, * 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, targetWidth, targetHeight); imageToDraw.dispose(); }); } diff --git a/bundles/org.eclipse.swt/Eclipse SWT/cocoa/org/eclipse/swt/internal/NativeImageLoader.java b/bundles/org.eclipse.swt/Eclipse SWT/cocoa/org/eclipse/swt/internal/NativeImageLoader.java index 09b39591368..3d122179db7 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT/cocoa/org/eclipse/swt/internal/NativeImageLoader.java +++ b/bundles/org.eclipse.swt/Eclipse SWT/cocoa/org/eclipse/swt/internal/NativeImageLoader.java @@ -26,6 +26,10 @@ public static List> load(ElementAtZoom str return FileFormat.load(streamAtZoom, imageLoader, targetZoom); } + public static ImageData load(InputStream streamAtZoom, ImageLoader imageLoader, int targetWidth, int targetHeight) { + return FileFormat.load(streamAtZoom, imageLoader, targetWidth, targetHeight); + } + public static void save(OutputStream stream, int format, ImageLoader imageLoader) { FileFormat.save(stream, format, imageLoader); } 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..80f6ed948bd --- /dev/null +++ b/bundles/org.eclipse.swt/Eclipse SWT/common/org/eclipse/swt/graphics/ImageDataAtSizeProvider.java @@ -0,0 +1,22 @@ +/******************************************************************************* + * 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; + +/** + * @since 3.132 + */ +public interface ImageDataAtSizeProvider extends ImageDataProvider { + + ImageData getImageData(int targetWidth, int targetHeight); + +} \ 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 29f517b45c0..8b2d1dc36a7 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 @@ -41,20 +41,32 @@ public static boolean canLoadAtZoom(InputStream stream, int fileZoom, int target 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); + 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); + return data.get(0); + } + + public static ElementAtZoom loadByZoom(String filename, int fileZoom, int targetZoom) { + List> data = new ImageLoader().loadByZoom(filename, fileZoom, targetZoom); if (data.isEmpty()) SWT.error(SWT.ERROR_INVALID_IMAGE); return data.get(0); } + public static ImageData loadByTargetSize(InputStream stream, int targetWidth, int targetHeight) { + ImageData data = new ImageLoader().loadByTargetSize(stream, targetWidth, targetHeight); + if (data == null) SWT.error(SWT.ERROR_INVALID_IMAGE); + return data; + } + + public static ImageData loadByTargetSize(String filename, int targetWidth, int targetHeight) { + ImageData data = new ImageLoader().loadByTargetSize(filename, targetWidth, targetHeight); + if (data == null) SWT.error(SWT.ERROR_INVALID_IMAGE); + return data; + } + } 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 9973b8f06e3..dc7917dc349 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 @@ -151,11 +151,11 @@ void reset() { * */ public ImageData[] load(InputStream stream) { - load(stream, FileFormat.DEFAULT_ZOOM, FileFormat.DEFAULT_ZOOM); + loadByZoom(stream, FileFormat.DEFAULT_ZOOM, FileFormat.DEFAULT_ZOOM); return data; } -List> load(InputStream stream, int fileZoom, int targetZoom) { +List> loadByZoom(InputStream stream, int fileZoom, int targetZoom) { if (stream == null) SWT.error(SWT.ERROR_NULL_ARGUMENT); reset(); List> images = NativeImageLoader.load(new ElementAtZoom<>(stream, fileZoom), this, targetZoom); @@ -163,6 +163,14 @@ List> load(InputStream stream, int fileZoom, int target return images; } +ImageData loadByTargetSize(InputStream stream, int targetWidth, int targetHeight) { + if (stream == null) SWT.error(SWT.ERROR_NULL_ARGUMENT); + reset(); + ImageData image = NativeImageLoader.load(stream, this, targetWidth, targetHeight); + data = new ImageData[] {image}; + return image; +} + 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); @@ -187,14 +195,24 @@ static boolean canLoadAtZoom(InputStream stream, int fileZoom, int targetZoom) { * */ public ImageData[] load(String filename) { - load(filename, FileFormat.DEFAULT_ZOOM, FileFormat.DEFAULT_ZOOM); + loadByZoom(filename, FileFormat.DEFAULT_ZOOM, FileFormat.DEFAULT_ZOOM); return data; } -List> load(String filename, int fileZoom, int targetZoom) { +List> loadByZoom(String filename, int fileZoom, int targetZoom) { if (filename == null) SWT.error(SWT.ERROR_NULL_ARGUMENT); try (InputStream stream = new FileInputStream(filename)) { - return load(stream, fileZoom, targetZoom); + return loadByZoom(stream, fileZoom, targetZoom); + } catch (IOException e) { + SWT.error(SWT.ERROR_IO, e); + } + return null; +} + +ImageData loadByTargetSize(String filename, int targetWidth, int targetHeight) { + if (filename == null) SWT.error(SWT.ERROR_NULL_ARGUMENT); + try (InputStream stream = new FileInputStream(filename)) { + return loadByTargetSize(stream, targetWidth, targetHeight); } catch (IOException e) { SWT.error(SWT.ERROR_IO, e); } @@ -211,6 +229,15 @@ 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; +} + /** * 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..bf4b04ca554 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::drawAtTargetSize, width, height, scaledWidth, scaledHeight); +} + +@FunctionalInterface +private interface ImageDrawFunction { + void draw(GC gc, ImageData imageData, int originalWidth, int originalHeight, int targetWidth, int targetHeight); +} + +private static ImageData scaleImage(Device device, final ImageData imageData, ImageDrawFunction drawFunction, int width, int height, + 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, width, height, 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::drawAtTargetSize, imageData.width, imageData.height, targetWidth, targetHeight); +} + public static boolean isSmoothScalingEnabled() { return autoScaleMethod == AutoScaleMethod.SMOOTH; } 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 8232d71023d..6e5da08b774 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 @@ -85,6 +85,11 @@ static abstract class StaticImageFileFormat extends FileFormat { List> loadFromByteStream(int fileZoom, int targetZoom) { return Arrays.stream(loadFromByteStream()).map(d -> new ElementAtZoom<>(d, fileZoom)).toList(); } + + @Override + ImageData loadFromByteStreamByTargetSize(int targetWidth, int targetHeight) { + return loadFromByteStream()[0]; + } } LEDataInputStream inputStream; @@ -104,6 +109,8 @@ List> loadFromByteStream(int fileZoom, int targetZoom) */ abstract List> loadFromByteStream(int fileZoom, int targetZoom); + abstract ImageData loadFromByteStreamByTargetSize(int targetWidth, int targetHeight); + /** * Read the specified input stream, and return the * device independent image array represented by the stream. @@ -122,6 +129,20 @@ public List> loadFromStream(LEDataInputStream stream, i } } +public ImageData loadFromStreamByTargetSize(LEDataInputStream stream, int targetWidth, int targetHeight) { + try { + inputStream = stream; + return loadFromByteStreamByTargetSize(targetWidth, targetHeight); + } catch (Exception e) { + if (e instanceof IOException) { + SWT.error(SWT.ERROR_IO, e); + } else { + SWT.error(SWT.ERROR_INVALID_IMAGE, e); + } + return null; + } +} + /** * Read the specified input stream using the specified loader, and * return the device independent image array represented by the stream. @@ -136,6 +157,16 @@ public static List> load(ElementAtZoom is, return fileFormat.loadFromStream(stream, is.zoom(), targetZoom); } +public static ImageData load(InputStream is, ImageLoader loader, int targetWidth, int targetHeight) { + LEDataInputStream stream = new LEDataInputStream(is); + FileFormat fileFormat = determineFileFormat(stream).orElseGet(() -> { + SWT.error(SWT.ERROR_UNSUPPORTED_FORMAT); + return null; + }); + fileFormat.loader = loader; + return fileFormat.loadFromStreamByTargetSize(stream, targetWidth, targetHeight); +} + public static boolean canLoadAtZoom(ElementAtZoom is, int targetZoom) { return is.zoom() == targetZoom || isDynamicallySizableFormat(is.element()); } diff --git a/bundles/org.eclipse.swt/Eclipse SWT/common/org/eclipse/swt/internal/image/SVGFileFormat.java b/bundles/org.eclipse.swt/Eclipse SWT/common/org/eclipse/swt/internal/image/SVGFileFormat.java index 78d8abebcb6..777e7373ae4 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT/common/org/eclipse/swt/internal/image/SVGFileFormat.java +++ b/bundles/org.eclipse.swt/Eclipse SWT/common/org/eclipse/swt/internal/image/SVGFileFormat.java @@ -60,6 +60,26 @@ List> loadFromByteStream(int fileZoom, int targetZoom) } } + @Override + ImageData loadFromByteStreamByTargetSize(int targetWidth, int targetHeight) { + if (RASTERIZER == null) { + SWT.error(SWT.ERROR_UNSUPPORTED_FORMAT, null, " [No SVG rasterizer found]"); + } + if (targetWidth <= 0 || targetHeight <= 0) { + SWT.error(SWT.ERROR_INVALID_ARGUMENT, null, " [Cannot rasterize SVG for targetWidth or targetHeight <= 0]"); + } + if (targetHeight <= 0 || targetHeight <= 0) { + SWT.error(SWT.ERROR_INVALID_ARGUMENT, null, " [Cannot rasterize SVG for targetHeight or targetHeight <= 0]"); + } + try { + ImageData rasterizedImageData = RASTERIZER.rasterizeSVG(inputStream, targetWidth, targetHeight); + return rasterizedImageData; + } catch (IOException e) { + SWT.error(SWT.ERROR_INVALID_IMAGE, e); + return null; + } + } + @Override void unloadIntoByteStream(ImageLoader loader) { throw new UnsupportedOperationException(); diff --git a/bundles/org.eclipse.swt/Eclipse SWT/common/org/eclipse/swt/internal/image/SVGRasterizer.java b/bundles/org.eclipse.swt/Eclipse SWT/common/org/eclipse/swt/internal/image/SVGRasterizer.java index 9586abfb5c6..79eae39be1c 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT/common/org/eclipse/swt/internal/image/SVGRasterizer.java +++ b/bundles/org.eclipse.swt/Eclipse SWT/common/org/eclipse/swt/internal/image/SVGRasterizer.java @@ -32,4 +32,6 @@ public interface SVGRasterizer { * the input is not a valid SVG file or cannot be processed. */ public ImageData rasterizeSVG(InputStream stream, int zoom) throws IOException; + + public ImageData rasterizeSVG(InputStream stream, int width, int height) throws IOException; } 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 f77c83f79e9..cdc6d5c38d9 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 @@ -552,7 +552,7 @@ public Image(Device device, ImageData source, ImageData mask) { public Image(Device device, InputStream stream) { super(device); currentDeviceZoom = DPIUtil.getDeviceZoom(); - ElementAtZoom image = ImageDataLoader.load(stream, FileFormat.DEFAULT_ZOOM, currentDeviceZoom); + ElementAtZoom image = ImageDataLoader.loadByZoom(stream, FileFormat.DEFAULT_ZOOM, currentDeviceZoom); ImageData data = DPIUtil.scaleImageData(device, image, currentDeviceZoom); init(data); init(); @@ -594,7 +594,7 @@ public Image(Device device, String filename) { super(device); if (filename == null) SWT.error(SWT.ERROR_NULL_ARGUMENT); currentDeviceZoom = DPIUtil.getDeviceZoom(); - ElementAtZoom image = ImageDataLoader.load(filename, FileFormat.DEFAULT_ZOOM, currentDeviceZoom); + ElementAtZoom image = ImageDataLoader.loadByZoom(filename, FileFormat.DEFAULT_ZOOM, currentDeviceZoom); ImageData data = DPIUtil.scaleImageData(device, image, currentDeviceZoom); init(data); init(); @@ -785,7 +785,7 @@ private void initFromFileNameProvider(int zoom) { initNative(fileForZoom.element()); } if (this.surface == 0) { - ElementAtZoom imageDataAtZoom = ImageDataLoader.load(fileForZoom.element(), fileForZoom.zoom(), zoom); + ElementAtZoom imageDataAtZoom = ImageDataLoader.loadByZoom(fileForZoom.element(), fileForZoom.zoom(), zoom); ImageData imageData = imageDataAtZoom.element(); if (imageDataAtZoom.zoom() != zoom) { imageData = DPIUtil.scaleImageData(device, imageDataAtZoom, zoom); @@ -1581,11 +1581,12 @@ public String toString () { * @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 targetWidth the width to which the image is supposed to be scaled + * @param targetHeight 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 drawAtTargetSize(GC gc, ImageData imageData, int width, int height, int targetWidth, int targetHeight) { StrictChecks.runWithStrictChecksDisabled(() -> { Image imageToDraw = new Image(gc.device, (ImageDataProvider) zoom -> imageData); gc.drawImage(imageToDraw, 0, 0, width, height, @@ -1594,7 +1595,7 @@ public static void drawScaled(GC gc, ImageData imageData, int width, int height, * 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, targetWidth, targetHeight); imageToDraw.dispose(); }); } diff --git a/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/internal/NativeImageLoader.java b/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/internal/NativeImageLoader.java index 838fd72cd36..91321b5244d 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/internal/NativeImageLoader.java +++ b/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/internal/NativeImageLoader.java @@ -142,6 +142,10 @@ public static List> load(ElementAtZoom str return Arrays.stream(imgDataArray).map(data -> new ElementAtZoom<>(data, streamAtZoom.zoom())).toList(); } + public static ImageData load(InputStream streamAtZoom, ImageLoader imageLoader, int targetWidth, int targetHeight) { + return FileFormat.load(streamAtZoom, imageLoader, targetWidth, targetHeight); + } + /** * Return true if the image is an interlaced PNG file. This is used to check * whether ImageLoaderEvent should be fired when loading images. 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 e2d8c831cc1..b3dd16e0da1 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 @@ -1053,7 +1053,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); } } @@ -1155,26 +1156,25 @@ private int calculateZoomForImage(int gcZoom, int srcWidth, int srcHeight, int d 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); - Rectangle dest = Win32DPIUtils.pointToPixel(drawable, new Rectangle(destX, destY, destWidth, destHeight), imageZoom); - if (scaledImageZoom != 100) { - /* - * This is a HACK! Due to rounding errors at fractional scale factors, - * the coordinates may be slightly off. The workaround is to restrict - * coordinates to the allowed bounds. - */ - Rectangle b = image.getBounds(scaledImageZoom); - int errX = src.x + src.width - b.width; - int errY = src.y + src.height - b.height; - if (errX != 0 || errY != 0) { - if (errX <= scaledImageZoom / 100 && errY <= scaledImageZoom / 100) { - src.intersect(b); - } else { - SWT.error (SWT.ERROR_INVALID_ARGUMENT); - } - } - } - drawImage(image, src.x, src.y, src.width, src.height, dest.x, dest.y, dest.width, dest.height, false, scaledImageZoom); + float widthScalingFactor = (float) destWidth / srcWidth; + float heightScalingFactor = (float) destHeight / srcHeight; + + Rectangle fullImageBounds = image.getBounds(); + int fullImageWidth = Math.round(fullImageBounds.width * widthScalingFactor); + int fullImageHeight = Math.round(fullImageBounds.height * heightScalingFactor); + int scaledImageWidthInPixels = Win32DPIUtils.pointToPixel(fullImageWidth, scaledImageZoom); + int scaledImageHeightInPixels = Win32DPIUtils.pointToPixel(fullImageHeight, scaledImageZoom); + + Rectangle scaledSrc = new Rectangle(Math.round(srcX * widthScalingFactor), Math.round(srcY * heightScalingFactor), + Math.round(srcWidth * widthScalingFactor), Math.round(srcHeight * heightScalingFactor)); + Rectangle srcPixels = Win32DPIUtils.pointToPixel(drawable, scaledSrc, scaledImageZoom); + Rectangle destPixels = Win32DPIUtils.pointToPixel(drawable, new Rectangle(destX, destY, destWidth, destHeight), + imageZoom); + + image.executeOnImageHandleAtSize( + tempHandle -> drawImage(image, srcPixels.x, srcPixels.y, srcPixels.width, srcPixels.height, destPixels.x, + destPixels.y, destPixels.width, destPixels.height, false, tempHandle), + scaledImageWidthInPixels, scaledImageHeightInPixels); } private class DrawImageToImageOperation extends ImageOperation { @@ -1191,14 +1191,15 @@ 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); @@ -1253,13 +1254,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 84147c25e0c..8d7de2af62b 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 @@ -18,8 +18,10 @@ import java.io.*; import java.util.*; +import java.util.List; import java.util.Map.*; import java.util.function.*; +import java.util.stream.*; import org.eclipse.swt.*; import org.eclipse.swt.internal.*; @@ -27,6 +29,7 @@ import org.eclipse.swt.internal.gdip.*; import org.eclipse.swt.internal.image.*; import org.eclipse.swt.internal.win32.*; +import org.eclipse.swt.widgets.*; /** * Instances of this class are graphics which have been prepared @@ -526,7 +529,7 @@ public Image (Device device, InputStream stream) { public Image (Device device, String filename) { super(device); if (filename == null) SWT.error(SWT.ERROR_NULL_ARGUMENT); - this.imageProvider = new ImageFileNameProviderWrapper(zoom -> { + this.imageProvider = createImageFileNameProviderWrapper(zoom -> { if (zoom == 100) { return filename; } @@ -567,7 +570,7 @@ public Image (Device device, String filename) { */ public Image(Device device, ImageFileNameProvider imageFileNameProvider) { super(device); - this.imageProvider = new ImageFileNameProviderWrapper(imageFileNameProvider); + this.imageProvider = createImageFileNameProviderWrapper(imageFileNameProvider); if (imageFileNameProvider.getImagePath(100) == null) { SWT.error(SWT.ERROR_INVALID_ARGUMENT, null, ": ImageFileNameProvider [" + imageFileNameProvider + "] returns null fileName at 100% zoom."); @@ -607,7 +610,11 @@ public Image(Device device, ImageFileNameProvider imageFileNameProvider) { */ public Image(Device device, ImageDataProvider imageDataProvider) { super(device); - this.imageProvider = new ImageDataProviderWrapper(imageDataProvider); + if (imageDataProvider instanceof ImageDataAtSizeProvider imageDataAtSizeProvider) { + this.imageProvider = new ImageDataAtSizeProviderWrapper(imageDataAtSizeProvider); + } else { + this.imageProvider = new ImageDataProviderWrapper(imageDataProvider); + } if (imageDataProvider.getImageData(100) == null) { SWT.error(SWT.ERROR_INVALID_ARGUMENT, null, ": ImageDataProvider [" + imageDataProvider + "] returns null ImageData at 100% zoom."); @@ -643,6 +650,18 @@ public Image(Device device, ImageGcDrawer imageGcDrawer, int width, int height) init(); } +private ImageFileNameProviderWrapper createImageFileNameProviderWrapper(ImageFileNameProvider imageFileNameProvider) { + String fileName = DPIUtil.validateAndGetImagePathAtZoom(imageFileNameProvider, 100).element(); + if(ImageLoader.isDynamicallySizable(fileName)) + { + return new dynamicallySizableImageFileNameProviderWrapper(imageFileNameProvider); + } + else { + return new ImageFileNameProviderWrapper(imageFileNameProvider); + } + +} + private ImageData adaptImageDataIfDisabledOrGray(ImageData data) { ImageData returnImageData = null; switch (this.styleFlag) { @@ -820,6 +839,19 @@ long getHandle (int targetZoom, int nativeZoom) { return getImageMetadata(zoomContext).handle; } +void executeOnImageHandleAtSize(Consumer drawFunction, int targetWidth, int targetHeight) { + ImageData imageData = this.imageProvider.newImageData(targetWidth, targetHeight); + HandleForImageDataContainer handleContainer = init(device, imageData); + long tempHandle = handleContainer.handles()[0]; + drawFunction.accept(tempHandle); + if (handleContainer.type == SWT.ICON) { + OS.DestroyIcon (tempHandle); + } else { + OS.DeleteObject (tempHandle); + } +} + + /** * IMPORTANT: This method is not part of the public * API for Image. It is marked public only so that it @@ -831,22 +863,29 @@ long getHandle (int targetZoom, int nativeZoom) { * @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 targetWidth the width to which the image is supposed to be scaled + * @param targetHeight 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 drawAtTargetSize(GC gc, ImageData imageData, int width, int height, int targetWidth, int targetHeight) { 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, width, height, 0, 0, targetWidth, + targetHeight, 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(); @@ -1900,6 +1939,20 @@ public static Image win32_new(Device device, int type, long handle, int nativeZo return new Image(device, type, handle, nativeZoom); } +@FunctionalInterface +interface SizeProvider { + ImageData getForSize(int width, int height); +} + +private ImageData getElementAtTargetSize(SizeProvider elementForSizeProvider, int targetWidth, + int targetHeight) { + ImageData dataAtOriginalSize = elementForSizeProvider.getForSize(targetWidth, targetHeight); + if (dataAtOriginalSize!=null) { + return dataAtOriginalSize; + } + return null; +} + /** * ZoomContext holds information about zoom details used to create and cache the image * @@ -1933,6 +1986,39 @@ public Collection getPreservedZoomLevels() { abstract ImageData newImageData(ZoomContext zoomContext); + final ImageData newImageData(int targetWidth, int targetHeight) { + ImageData imageData = loadImageData(targetWidth, targetHeight); + imageData = adaptImageDataIfDisabledOrGray(imageData); + + return DPIUtil.autoScaleImageData(device, imageData, targetWidth, targetHeight); + }; + + protected ImageData loadImageData(int targetWidth, int targetHeight) { + ImageData imageData; + Rectangle bounds = getBounds(100); + int imageZoomForWidth = Math.round(100 * targetWidth / bounds.width); + int imageZoomForHeight = Math.round(100 * targetHeight / bounds.height); + int imageZoom = Math.max(imageZoomForWidth, imageZoomForHeight); + if (getAllCurrentMonitorZooms().contains(imageZoom)) { + return getImageData(imageZoom); + } + if (imageZoom > 150) { + imageData = getImageData(200); + } else { + imageData = getImageData(100); + } + return imageData; + }; + + private Collection getAllCurrentMonitorZooms() { + if (device instanceof Display display) { + return Arrays.stream(display.getMonitors()) + .map(Monitor::getZoom) + .collect(Collectors.toSet()); + } + return Collections.emptySet(); + } + abstract AbstractImageProviderWrapper createCopy(Image image); ImageData getScaledImageData (int zoom) { @@ -2035,7 +2121,6 @@ private ImageHandle initializeHandleFromSource(ZoomContext zoomContext) { imageData = adaptImageDataIfDisabledOrGray(imageData); return newImageHandle(imageData, zoomContext); } - } private class PlainImageDataProviderWrapper extends ImageFromImageDataProviderWrapper { @@ -2119,7 +2204,7 @@ private ImageDataLoaderStreamProviderWrapper(byte[] inputStreamData) { @Override protected ElementAtZoom loadImageData(int zoom) { - return ImageDataLoader.load(new ByteArrayInputStream(inputStreamData), FileFormat.DEFAULT_ZOOM, zoom); + return ImageDataLoader.loadByZoom(new ByteArrayInputStream(inputStreamData), FileFormat.DEFAULT_ZOOM, zoom); } @Override @@ -2300,7 +2385,6 @@ ImageData newImageData(ZoomContext zoomContext) { return (ImageData) cachedImageData.computeIfAbsent(zoomContext.targetZoom(), imageDataRetrival).clone(); } - @Override protected ImageHandle newImageHandle(ZoomContext zoomContext) { int targetZoom = zoomContext.targetZoom(); @@ -2341,7 +2425,7 @@ protected ElementAtZoom loadImageData(int 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); + ElementAtZoom imageDataAtZoom = ImageDataLoader.loadByZoom(fileForZoom.element(), fileForZoom.zoom(), zoom); return new ElementAtZoom<>(imageDataAtZoom.element(), zoom); } @@ -2354,7 +2438,7 @@ protected ElementAtZoom loadImageData(int zoom) { } ElementAtZoom imageDataAtZoom; if (nativeInitializedImage == null) { - imageDataAtZoom = ImageDataLoader.load(fileForZoom.element(), fileForZoom.zoom(), zoom); + imageDataAtZoom = ImageDataLoader.loadByZoom(fileForZoom.element(), fileForZoom.zoom(), zoom); } else { imageDataAtZoom = new ElementAtZoom<>(nativeInitializedImage.getImageData(), fileForZoom.zoom()); nativeInitializedImage.destroy(); @@ -2580,6 +2664,65 @@ protected ElementAtZoom loadImageData(int zoom) { ImageDataProviderWrapper createCopy(Image image) { return image.new ImageDataProviderWrapper(provider); } + +} + +private class dynamicallySizableImageFileNameProviderWrapper extends ImageFileNameProviderWrapper { + dynamicallySizableImageFileNameProviderWrapper(ImageFileNameProvider provider) { + super(provider); + } + + @Override + protected ImageData loadImageData(int targetWidth, int targetHeight) { + Optional fileForTargetSize = validateAndGetImagePathAtTargetSize(provider, targetWidth, targetHeight); + if (fileForTargetSize.isPresent()) { + ImageData imageDataAtZoom = ImageDataLoader.loadByTargetSize(fileForTargetSize.get(), targetWidth, + targetWidth); + return imageDataAtZoom; + } + return null; + } + + private Optional validateAndGetImagePathAtTargetSize(ImageFileNameProvider provider, int targetWidth, + int targetHeight) { + if (provider == null) { + SWT.error(SWT.ERROR_NULL_ARGUMENT); + } + String imagePath = DPIUtil.validateAndGetImagePathAtZoom(provider, 100).element(); + return Optional.of(imagePath); + } + +} + +private class ImageDataAtSizeProviderWrapper extends ImageDataProviderWrapper { + ImageDataAtSizeProviderWrapper(ImageDataAtSizeProvider provider) { + super(provider); + } + + @Override + protected ImageData loadImageData(int targetWidth, int targetHeight) { + ImageData imageData = validateAndGetImageDataAtTargetSize(targetWidth, targetHeight); + if (imageData == null) { + imageData = provider.getImageData(100); + } + return imageData; + } + + private ImageData validateAndGetImageDataAtTargetSize(int targetWidth, int targetHeight) { + if (provider == null) { + SWT.error(SWT.ERROR_NULL_ARGUMENT); + } + + return getElementAtTargetSize((x, z) -> ((ImageDataAtSizeProvider) provider).getImageData(x, z), targetWidth, + targetHeight); + + } + + @Override + ImageDataProviderWrapper createCopy(Image image) { + return image.new ImageDataProviderWrapper(provider); + } + } private class ImageGcDrawerWrapper extends DynamicImageProviderWrapper { diff --git a/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/internal/NativeImageLoader.java b/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/internal/NativeImageLoader.java index 09b39591368..3d122179db7 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/internal/NativeImageLoader.java +++ b/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/internal/NativeImageLoader.java @@ -26,6 +26,10 @@ public static List> load(ElementAtZoom str return FileFormat.load(streamAtZoom, imageLoader, targetZoom); } + public static ImageData load(InputStream streamAtZoom, ImageLoader imageLoader, int targetWidth, int targetHeight) { + return FileFormat.load(streamAtZoom, imageLoader, targetWidth, targetHeight); + } + public static void save(OutputStream stream, int format, ImageLoader imageLoader) { FileFormat.save(stream, format, imageLoader); } diff --git a/bundles/org.eclipse.swt/META-INF/MANIFEST.MF b/bundles/org.eclipse.swt/META-INF/MANIFEST.MF index 476aa8b84ab..e00e43a3b4f 100644 --- a/bundles/org.eclipse.swt/META-INF/MANIFEST.MF +++ b/bundles/org.eclipse.swt/META-INF/MANIFEST.MF @@ -2,7 +2,7 @@ Manifest-Version: 1.0 Bundle-Name: %pluginName Bundle-Vendor: %providerName Bundle-SymbolicName: org.eclipse.swt; singleton:=true -Bundle-Version: 3.131.100.qualifier +Bundle-Version: 3.132.0.qualifier Bundle-ManifestVersion: 2 Bundle-Localization: plugin DynamicImport-Package: org.eclipse.swt.accessibility2 diff --git a/bundles/org.eclipse.swt/pom.xml b/bundles/org.eclipse.swt/pom.xml index c6c1d4688a4..8f9ee9711ed 100644 --- a/bundles/org.eclipse.swt/pom.xml +++ b/bundles/org.eclipse.swt/pom.xml @@ -20,7 +20,7 @@ ../../ org.eclipse.swt - 3.131.100-SNAPSHOT + 3.132.0-SNAPSHOT eclipse-plugin diff --git a/examples/org.eclipse.swt.snippets/resources/Snippet386/collapseall.png b/examples/org.eclipse.swt.snippets/resources/Snippet386/collapseall.png new file mode 100644 index 00000000000..0ac25a9247b Binary files /dev/null and b/examples/org.eclipse.swt.snippets/resources/Snippet386/collapseall.png differ diff --git a/examples/org.eclipse.swt.snippets/resources/Snippet386/collapseall.svg b/examples/org.eclipse.swt.snippets/resources/Snippet386/collapseall.svg new file mode 100644 index 00000000000..587c3c3497e --- /dev/null +++ b/examples/org.eclipse.swt.snippets/resources/Snippet386/collapseall.svg @@ -0,0 +1,223 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + diff --git a/examples/org.eclipse.swt.snippets/src/org/eclipse/swt/snippets/Snippet386.java b/examples/org.eclipse.swt.snippets/src/org/eclipse/swt/snippets/Snippet386.java new file mode 100644 index 00000000000..01b55599fe4 --- /dev/null +++ b/examples/org.eclipse.swt.snippets/src/org/eclipse/swt/snippets/Snippet386.java @@ -0,0 +1,226 @@ +package org.eclipse.swt.snippets; + +import java.io.*; + +import org.eclipse.swt.*; +import org.eclipse.swt.custom.*; +import org.eclipse.swt.graphics.*; +import org.eclipse.swt.internal.*; +import org.eclipse.swt.internal.DPIUtil.*; +import org.eclipse.swt.layout.*; +import org.eclipse.swt.widgets.*; + +public class Snippet386 { + + public static void main(String[] args) { + Display display = new Display(); + + Image[] images = new Image[] { + new Image(display, createImageFileNameProvider("resources/Snippet386/collapseall.png")), + new Image(display, createImageFileNameProvider("resources/Snippet386/collapseall.svg")), + new Image(display, createImageDataProviderWithImageLoader()), + new Image(display, createImageDataProvider()), + new Image(display, createImageDataAtSizeProvider()) + }; + + String[] descriptions = new String[] { + "ImageFileNameProvider with PNGs scaled destructively", + "ImageFileNameProvider with SVGs scaled by SVG rasterization", + "ImageDataAtSizeProvider loading svgs with NativeImageLoader", + "ImageDataProvider with fixed font size for a given zoom", + "ImageDataAtSizeProvider which scales font size based on target height and width" + }; + + Slice[] slices = { + new Slice("Full", 0.0, 0.0, 1.0, 1.0), + new Slice("Top Half", 0.0, 0.0, 1.0, 0.5), + new Slice("Bottom Half", 0.0, 0.5, 1.0, 0.5), + new Slice("Left Half", 0.0, 0.0, 0.5, 1.0), + new Slice("Right Half", 0.5, 0.0, 0.5, 1.0), + new Slice("Top-Left Quarter", 0.0, 0.0, 0.5, 0.5) + }; + + createShellWithImages(display, images, descriptions, slices, "Snippet 386 - Flipped Layout"); + } + + private static ImageFileNameProvider createImageFileNameProvider(String fileName) { + return zoom -> fileName; + } + + private static ImageDataProvider createImageDataProviderWithImageLoader() { + return new ImageDataAtSizeProvider() { + @SuppressWarnings("restriction") + @Override + public ImageData getImageData(int targetWidth, int targetHeight) { + try (InputStream stream = new FileInputStream("resources/Snippet386/collapseall.svg")) { + return NativeImageLoader.load(stream, new ImageLoader(), targetWidth, targetHeight); + } catch (IOException e) { + SWT.error(SWT.ERROR_IO, e); + } + return null; + } + + @SuppressWarnings("restriction") + @Override + public ImageData getImageData(int zoom) { + try (InputStream stream = new FileInputStream("resources/Snippet386/collapseall.svg")) { + return NativeImageLoader.load(new ElementAtZoom<>(stream, 100), new ImageLoader(), zoom).get(0).element(); + } catch (IOException e) { + SWT.error(SWT.ERROR_IO, e); + } + return null; + } + }; + } + + private static ImageDataProvider createImageDataProvider() { + return new ImageDataProvider() { + @Override + public ImageData getImageData(int zoomLevel) { + int scaleFactor = zoomLevel / 100; + return createScaledTextImageData(100 * scaleFactor, 100 * scaleFactor); + } + }; + } + + private static ImageDataProvider createImageDataAtSizeProvider() { + return new ImageDataAtSizeProvider() { + @Override + public ImageData getImageData(int zoomLevel) { + int scaleFactor = zoomLevel / 100; + return createScaledTextImageData(100 * scaleFactor, 100 * scaleFactor); + } + + @Override + public ImageData getImageData(int width, int height) { + return createScaledTextImageData(width, height); + } + }; + } + + private static ImageData createScaledTextImageData(int width, int height) { + Display display = Display.getDefault(); + String text = "abcd"; + + int fontSize = Math.max(1, height / 100); + Font font = new Font(display, "Arial", fontSize, SWT.NORMAL); + + Image tmp = new Image(display, 1, 1); + GC measureGC = new GC(tmp); + measureGC.setFont(font); + Point textExtent = measureGC.textExtent(text); + measureGC.dispose(); + tmp.dispose(); + + double scale = Math.min((double) width / textExtent.x, (double) height / textExtent.y); + font.dispose(); + font = new Font(display, "Arial", Math.max(1, (int) (fontSize * scale)), SWT.NORMAL); + + Image image = new Image(display, width, height); + GC gc = new GC(image); + gc.setFont(font); + gc.setForeground(display.getSystemColor(SWT.COLOR_BLACK)); + gc.setBackground(display.getSystemColor(SWT.COLOR_WHITE)); + gc.fillRectangle(image.getBounds()); + + gc.setLineWidth(Math.max(1, width / 20)); + gc.drawLine(0, 0, width / 2, height); + + Point newTextExtent = gc.textExtent(text); + gc.drawText(text, (width - newTextExtent.x) / 2, (height - newTextExtent.y) / 2, true); + + gc.dispose(); + ImageData data = image.getImageData(); + + image.dispose(); + font.dispose(); + + return data; + } + + static class Slice { + String name; + double xFrac, yFrac, wFrac, hFrac; + Slice(String name, double x, double y, double w, double h) { + this.name = name; + this.xFrac = x; + this.yFrac = y; + this.wFrac = w; + this.hFrac = h; + } + } + + private static void createShellWithImages(Display display, Image[] images, String[] descriptions, Slice[] slices, String title) { + Shell shell = new Shell(display, SWT.SHELL_TRIM | SWT.MAX | SWT.RESIZE); + shell.setText(title); + shell.setLayout(new FillLayout()); + + ScrolledComposite scrolledComposite = new ScrolledComposite(shell, SWT.V_SCROLL | SWT.H_SCROLL | SWT.BORDER); + Canvas canvas = new Canvas(scrolledComposite, SWT.DOUBLE_BUFFERED); + scrolledComposite.setContent(canvas); + scrolledComposite.setExpandHorizontal(true); + scrolledComposite.setExpandVertical(true); + + int boxW = 200, boxH = 200, gap = 20; + int titleHeight = 20, descHeight = 40, sliceHeaderHeight = 30; + + int rows = images.length; + int cols = slices.length; + + int canvasWidth = (boxW + gap) * cols + gap; + int canvasHeight = (boxH + titleHeight + gap) * rows + descHeight + sliceHeaderHeight; + canvas.setSize(canvasWidth, canvasHeight); + scrolledComposite.setMinSize(canvasWidth, canvasHeight); + + canvas.addListener(SWT.Paint, e -> { + // Column headers + for (int col = 0; col < cols; col++) { + int x = col * (boxW + gap) + gap; + Font font = new Font(display, "Arial", 18, SWT.NORMAL); + e.gc.setFont(font); + e.gc.drawText(slices[col].name, x, 0, true); + font.dispose(); + + } + + // Rows + for (int row = 0; row < rows; row++) { + Image image = images[row]; + Rectangle rect = image.getBounds(); + int y = row * (boxH + titleHeight + gap) + descHeight + sliceHeaderHeight; + + Font font = new Font(display, "Arial", 18, SWT.NORMAL); + e.gc.setFont(font); + e.gc.drawText(descriptions[row], 0, y - 10, true); + font.dispose(); + + for (int col = 0; col < cols; col++) { + Slice s = slices[col]; + int x = col * (boxW + gap) + gap; + + int srcX = (int) (rect.width * s.xFrac); + int srcY = (int) (rect.height * s.yFrac); + int srcW = (int) (rect.width * s.wFrac); + int srcH = (int) (rect.height * s.hFrac); + + int boxTop = y + titleHeight; + e.gc.drawRectangle(x, boxTop, boxW, boxH); + + + e.gc.drawImage(image, srcX, srcY, srcW, srcH, x, boxTop, boxW, boxH); + + } + } + }); + + shell.setMaximized(true); + shell.open(); + + while (!shell.isDisposed()) { + if (!display.readAndDispatch()) display.sleep(); + } + + for (Image img : images) img.dispose(); + display.dispose(); + } +}