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/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..a3ae805394a --- /dev/null +++ b/bundles/org.eclipse.swt/Eclipse SWT/common/org/eclipse/swt/graphics/ImageDataAtSizeProvider.java @@ -0,0 +1,24 @@ +/******************************************************************************* + * 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; + +import java.util.*; + +/** + * @since 3.131 + */ +public interface ImageDataAtSizeProvider extends ImageDataProvider { + + Optional 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/ImageFileNameAtSizeProvider.java b/bundles/org.eclipse.swt/Eclipse SWT/common/org/eclipse/swt/graphics/ImageFileNameAtSizeProvider.java new file mode 100644 index 00000000000..63ec3330879 --- /dev/null +++ b/bundles/org.eclipse.swt/Eclipse SWT/common/org/eclipse/swt/graphics/ImageFileNameAtSizeProvider.java @@ -0,0 +1,28 @@ +/******************************************************************************* + * 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; + +import java.util.*; + +/** + * @since 3.131 + */ +public interface ImageFileNameAtSizeProvider extends ImageFileNameProvider { + + /** + * Returns the image file path most suitable for rendering at the given target size. + * + */ + Optional getImagePath(int targetWidth, int targetHeight); + +} \ No newline at end of file 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..8cb312fcc33 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 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 load(stream, fileZoom, targetZoom); + return loadByTargetSize(stream, targetWidth, targetHeight); } catch (IOException e) { SWT.error(SWT.ERROR_IO, e); } 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 510bcb33490..79b3caa34ec 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 @@ -144,34 +144,49 @@ public static ImageData scaleImageData (Device device, final ElementAtZoom imageData); - ImageGcDrawer drawer = new ImageGcDrawer() { + Image original = new Image(device, (ImageDataProvider) zoom -> imageData); + + ImageGcDrawer drawer = new ImageGcDrawer() { @Override public void drawOn(GC gc, int imageWidth, int imageHeight) { - gc.setAntialias (SWT.ON); - Image.drawScaled(gc, original, width, height, scaleFactor); - }; + gc.setAntialias(SWT.ON); + drawFunction.draw(gc, original, originalWidth, originalHeight, targetWidth, targetHeight); + } @Override public int getGcStyle() { return SWT.TRANSPARENT; } }; - Image resultImage = new Image (device, drawer, scaledWidth, scaledHeight); - ImageData result = resultImage.getImageData (100); - original.dispose (); - resultImage.dispose (); + + Image resultImage = new Image(device, drawer, targetWidth, targetHeight); + ImageData result = resultImage.getImageData(100); + original.dispose(); + resultImage.dispose(); return result; } else { - return imageData.scaledTo (scaledWidth, scaledHeight); + return imageData.scaledTo(targetWidth, targetHeight); } } 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/win32/org/eclipse/swt/graphics/GC.java b/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/graphics/GC.java index ebbbfbd1b0d..4e33ffd4b7b 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 @@ -1132,22 +1132,29 @@ 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); + Rectangle src; + Rectangle dest; + if (image.createdWithTargetSize()) { + dest = new Rectangle(destX, destY, destWidth, destHeight); + src = dest; + } else { + src = Win32DPIUtils.pointToPixel(drawable, new Rectangle(srcX, srcY, srcWidth, srcHeight), scaledImageZoom); + 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); + } } } } @@ -1231,17 +1238,37 @@ private void drawImage(Image srcImage, int srcX, int srcY, int srcWidth, int src } return; } - long imageHandle = Image.win32_getHandle(srcImage, imageZoom); - switch (srcImage.type) { - case SWT.BITMAP: - drawBitmap(srcImage, imageHandle, srcX, srcY, srcWidth, srcHeight, destX, destY, destWidth, destHeight, simple); - break; - case SWT.ICON: - drawIcon(imageHandle, srcX, srcY, srcWidth, srcHeight, destX, destY, destWidth, destHeight, simple); - break; + if (srcImage.createdWithTargetSize()) { + drawWithTempHandle(srcImage, srcX, srcY, srcWidth, srcHeight, destX, destY, destWidth, destHeight, simple); + } else { + long imageHandle = Image.win32_getHandle(srcImage, imageZoom); + switch (srcImage.type) { + case SWT.BITMAP: + drawBitmap(srcImage, imageHandle, srcX, srcY, srcWidth, srcHeight, destX, destY, destWidth, destHeight, + simple); + break; + case SWT.ICON: + drawIcon(imageHandle, srcX, srcY, srcWidth, srcHeight, destX, destY, destWidth, destHeight, simple); + break; + } } } +private void drawWithTempHandle(Image srcImage, int srcX, int srcY, int srcWidth, int srcHeight, int destX, int destY, + int destWidth, int destHeight, boolean simple) { + srcImage.executeOnImageHandleAtSize(tempImageHandle -> { + switch (srcImage.type) { + case SWT.BITMAP: + drawBitmap(srcImage, tempImageHandle, srcX, srcY, srcWidth, srcHeight, destX, destY, destWidth, destHeight, + simple); + break; + case SWT.ICON: + drawIcon(tempImageHandle, srcX, srcY, srcWidth, srcHeight, destX, destY, destWidth, destHeight, simple); + break; + } + }, destWidth, destHeight); +} + private void drawIcon(long imageHandle, int srcX, int srcY, int srcWidth, int srcHeight, int destX, int destY, int destWidth, int destHeight, boolean simple) { int technology = OS.GetDeviceCaps(handle, OS.TECHNOLOGY); 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 9530d94fb4c..37297ef0bc0 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 @@ -20,6 +20,7 @@ import java.util.*; import java.util.Map.*; import java.util.function.*; +import java.util.stream.*; import org.eclipse.swt.*; import org.eclipse.swt.internal.*; @@ -27,6 +28,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 @@ -809,6 +811,25 @@ public static long win32_getHandle (Image image, int zoom) { return image.getImageMetadata(zoom).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); + } +} + +/** + * @since 3.131 + */ +public boolean createdWithTargetSize() { + return this.imageProvider.isSizeAware(); +} + /** * IMPORTANT: This method is not part of the public * API for Image. It is marked public only so that it @@ -825,8 +846,17 @@ public static long win32_getHandle (Image image, int zoom) { * @noreference This method is not intended to be referenced by clients. */ public static void drawScaled(GC gc, Image original, int width, int height, float scaleFactor) { + int targetWidth = Math.round(width * scaleFactor); + int targetHeight = Math.round(height * scaleFactor); + drawAtTargetSize(gc, original, width, height, targetWidth, targetHeight); +} + +/** + * @since 3.131 + */ +public static void drawAtTargetSize(GC gc, Image original, int width, int height, int targetWidth, int targetHeight) { gc.drawImage (original, 0, 0, width, height, - 0, 0, Math.round (width * scaleFactor), Math.round (height * scaleFactor), false); + 0, 0, targetWidth, targetHeight, false); } long [] createGdipImage(Integer zoom) { @@ -1223,7 +1253,6 @@ public ImageData getImageData (int zoom) { return this.imageProvider.newImageData(zoom); } - /** * Returns an ImageData based on the receiver. * Modifications made to this ImageData will not @@ -1868,8 +1897,35 @@ public static Image win32_new(Device device, int type, long handle, int nativeZo return new Image(device, type, handle, nativeZoom); } +@FunctionalInterface +interface ElementAtSizeFunction { + Optional getForSize(int width, int height); +} + +private Optional getElementAtTargetSize(ElementAtSizeFunction elementForSizeProvider, int targetWidth, + int targetHeight) { + Optional dataAtOriginalSize = elementForSizeProvider.getForSize(targetWidth, targetHeight); + if (dataAtOriginalSize.isPresent()) { + T data = dataAtOriginalSize.get(); + if (data instanceof String fileName) { + ImageData imageData = ImageDataLoader.loadByTargetSize(fileName, targetWidth, targetHeight); + if (imageData.width == targetWidth && imageData.height == targetHeight) { + return dataAtOriginalSize; + } + } else if (data instanceof ImageData) + return dataAtOriginalSize; + } + return Optional.empty(); +} + private abstract class AbstractImageProviderWrapper { + protected boolean sizeAware = false; + + public boolean isSizeAware (){ + return this.sizeAware; + } + protected abstract Rectangle getBounds(int zoom); protected long configureGCData(GCData data) { @@ -1882,6 +1938,38 @@ public Collection getPreservedZoomLevels() { abstract ImageData newImageData(int zoom); + 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(1f * targetWidth / bounds.width); + int imageZoomForHeight = Math.round(1f * targetHeight / bounds.height); + int imageZoom = Math.max(imageZoomForWidth, imageZoomForHeight); + if (getAllCurrentMonitorZooms().contains(imageZoom)) { + return newImageData(imageZoom); + } + if (imageZoom > 150) { + imageData = newImageData(200); + } else { + imageData = newImageData(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) { @@ -1984,7 +2072,6 @@ private ImageHandle initializeHandleFromSource(int zoom) { imageData = adaptImageDataIfDisabledOrGray(imageData); return newImageHandle(imageData, zoom); } - } private class PlainImageDataProviderWrapper extends ImageFromImageDataProviderWrapper { @@ -2068,7 +2155,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 @@ -2237,7 +2324,6 @@ ImageData newImageData(int zoom) { return cachedImageData.computeIfAbsent(zoom, imageDataRetrival); } - @Override protected ImageHandle newImageHandle(int zoom) { ImageData cachedData = cachedImageData.remove(zoom); @@ -2266,6 +2352,9 @@ protected Rectangle getBounds(int zoom) { private class ImageFileNameProviderWrapper extends BaseImageProviderWrapper { ImageFileNameProviderWrapper(ImageFileNameProvider provider) { super(provider, ImageFileNameProvider.class); + if (provider instanceof ImageFileNameAtSizeProvider) { + this.sizeAware = true; + } // Checks for the contract of the passed provider require // checking for valid image data creation newImageData(DPIUtil.getDeviceZoom()); @@ -2277,7 +2366,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); } @@ -2290,7 +2379,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(); @@ -2298,6 +2387,35 @@ protected ElementAtZoom loadImageData(int zoom) { return imageDataAtZoom; } + @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); + } + if (provider instanceof ImageFileNameAtSizeProvider sizeAwareProvider) { + Optional imagePathAtTargetSize = getElementAtTargetSize(sizeAwareProvider::getImagePath, + targetWidth, targetHeight); + if (!imagePathAtTargetSize.isEmpty()) { + return imagePathAtTargetSize; + } + } + Rectangle originalSize = getBounds(100); + int imageZoomForWidth = Math.round(1f * targetWidth / originalSize.width); + int imageZoomForHeight = Math.round(1f * targetHeight / originalSize.height); + int imageZoom = Math.max(imageZoomForWidth, imageZoomForHeight); + String imagePath = DPIUtil.validateAndGetImagePathAtZoom(provider, imageZoom).element(); + return Optional.of(imagePath); + } + @Override public int hashCode() { return Objects.hash(provider, styleFlag, transparentPixel); @@ -2505,6 +2623,11 @@ private long extractHandleForPixelFormat(int width, int height, int pixelFormat) private class ImageDataProviderWrapper extends BaseImageProviderWrapper { ImageDataProviderWrapper(ImageDataProvider provider) { super(provider, ImageDataProvider.class); + // Checks for the contract of the passed provider require + // checking for valid image data creation + if (provider instanceof ImageDataAtSizeProvider) { + this.sizeAware = true; + } } @Override @@ -2512,6 +2635,23 @@ protected ElementAtZoom loadImageData(int zoom) { return DPIUtil.validateAndGetImageDataAtZoom (provider, zoom); } + @Override + protected ImageData loadImageData(int targetWidth, int targetHeight) { + return validateAndGetImageDataAtTargetSize(targetWidth, targetHeight).get(); + } + + private Optional validateAndGetImageDataAtTargetSize(int targetWidth, int targetHeight) { + if (provider == null) { + SWT.error(SWT.ERROR_NULL_ARGUMENT); + } + Optional imageDataAtTargetSize = getElementAtTargetSize((x, z) -> ((ImageDataAtSizeProvider) provider).getImageData(x, z), targetWidth, targetHeight); + if (imageDataAtTargetSize == null) { + SWT.error(SWT.ERROR_INVALID_ARGUMENT, null, + ": ImageDataProvider [" + provider + "] returns null ImageData at 100% zoom."); + } + return imageDataAtTargetSize; + } + @Override ImageDataProviderWrapper createCopy(Image image) { return image.new ImageDataProviderWrapper(provider); 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/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..a44ac5e8569 --- /dev/null +++ b/examples/org.eclipse.swt.snippets/src/org/eclipse/swt/snippets/Snippet386.java @@ -0,0 +1,100 @@ +/******************************************************************************* + * 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.snippets; + +import java.io.*; +import java.util.*; + +import org.eclipse.swt.*; +import org.eclipse.swt.graphics.*; +import org.eclipse.swt.internal.*; +import org.eclipse.swt.internal.DPIUtil.*; +import org.eclipse.swt.widgets.*; + +public class Snippet386 { + + public static void main(String[] args) { + drawWithImageFileNameProvider(); + drawWithImageDataProvider(); + } + + private static void drawWithImageFileNameProvider() { + ImageFileNameProvider provider = createImageFileNameProvider(); + Image image = new Image(Display.getDefault(), provider); + createShellWithImage(image, "Snippet 386 - ImageFileNameProvider"); + } + + private static void drawWithImageDataProvider() { + ImageDataProvider provider = createImageDataProvider(); + Image image = new Image(Display.getDefault(), provider); + createShellWithImage(image, "Snippet 386 - ImageDataProvider"); + } + + private static ImageFileNameProvider createImageFileNameProvider() { + return new ImageFileNameAtSizeProvider() { + @Override + public Optional getImagePath(int targetWidth, int targetHeight) { + return Optional.of("resources/Snippet386/collapseall.svg"); + } + + @Override + public String getImagePath(int zoom) { + return "resources/Snippet386/collapseall.svg"; + } + }; + } + + private static ImageDataProvider createImageDataProvider() { + return new ImageDataAtSizeProvider() { + @SuppressWarnings("restriction") + @Override + public Optional getImageData(int targetWidth, int targetHeight) { + try (InputStream stream = new FileInputStream("resources/Snippet386/collapseall.svg")) { + return Optional.of(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(), 100).get(0).element(); + } catch (IOException e) { + SWT.error(SWT.ERROR_IO, e); + } + return null; + } + }; + } + + private static void createShellWithImage(Image image, String title) { + Shell shell = new Shell(Display.getDefault(), SWT.SHELL_TRIM | SWT.DOUBLE_BUFFERED); + shell.setText(title); + + shell.addListener(SWT.Paint, e -> { + Rectangle rect = image.getBounds(); + e.gc.drawImage(image, 0, 0, rect.width, rect.height, 0, 0, 100, 200); + }); + + shell.setSize(600, 400); + shell.open(); + while (!shell.isDisposed()) { + if (!Display.getDefault().readAndDispatch()) + Display.getDefault().sleep(); + } + Display.getDefault().dispose(); + } +}