Skip to content

Commit 73d7ed4

Browse files
committed
Support rendering images at custom sizes
This commit introduces support for drawing images at arbitrary sizes. This enables flexible rendering in scenarios where image size is determined by UI requirements rather than screen zoom, such as: - Scalable images in diagrams (e.g., GEF-based tools) - Resizable UI components that embed images - Annotation rulers that scale with editor font size (#3044) The new API allows consumers to obtain image data at a specific target size, enabling better integration in custom UI scaling contexts.
1 parent 1a1f0c2 commit 73d7ed4

File tree

15 files changed

+732
-78
lines changed

15 files changed

+732
-78
lines changed

bundles/org.eclipse.swt.svg/src/org/eclipse/swt/svg/JSVGRasterizer.java

Lines changed: 28 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -72,34 +72,44 @@ public class JSVGRasterizer implements SVGRasterizer {
7272

7373
@Override
7474
public ImageData rasterizeSVG(InputStream inputStream, int zoom) throws IOException {
75-
SVGDocument svgDocument = loadSVG(inputStream);
76-
if (svgDocument == null) {
77-
SWT.error(SWT.ERROR_INVALID_IMAGE);
78-
}
75+
SVGDocument svgDocument = loadAndValidateSVG(inputStream);
7976
BufferedImage rasterizedImage = renderSVG(svgDocument, zoom);
8077
return convertToSWTImageData(rasterizedImage);
8178
}
82-
83-
private SVGDocument loadSVG(InputStream inputStream) {
84-
return SVG_LOADER.load(inputStream, null, LoaderContext.createDefault());
79+
80+
@Override
81+
public ImageData rasterizeSVG(InputStream inputStream, int width, int height) throws IOException {
82+
SVGDocument svgDocument = loadAndValidateSVG(inputStream);
83+
BufferedImage rasterizedImage = renderSVG(svgDocument, width, height);
84+
return convertToSWTImageData(rasterizedImage);
85+
}
86+
87+
private SVGDocument loadAndValidateSVG(InputStream inputStream) throws IOException {
88+
SVGDocument svgDocument = SVG_LOADER.load(inputStream, null, LoaderContext.createDefault());
89+
if (svgDocument == null) {
90+
SWT.error(SWT.ERROR_INVALID_IMAGE);
91+
}
92+
return svgDocument;
8593
}
8694

8795
private BufferedImage renderSVG(SVGDocument svgDocument, int zoom) {
96+
FloatSize sourceImageSize = svgDocument.size();
8897
float scalingFactor = zoom / 100.0f;
89-
BufferedImage image = createImageBase(svgDocument, scalingFactor);
90-
Graphics2D g = configureRenderingOptions(scalingFactor, image);
98+
int targetImageWidth = calculateTargetWidth(scalingFactor, sourceImageSize);
99+
int targetImageHeight = calculateTargetHeight(scalingFactor, sourceImageSize);
100+
return renderSVG(svgDocument, targetImageWidth, targetImageHeight);
101+
}
102+
103+
private BufferedImage renderSVG(SVGDocument svgDocument, int width, int height) {
104+
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
105+
float widthScalingFactor = width / svgDocument.size().width;
106+
float heightScalingFactor = height / svgDocument.size().height;
107+
Graphics2D g = configureRenderingOptions(widthScalingFactor, heightScalingFactor, image);
91108
svgDocument.render(null, g);
92109
g.dispose();
93110
return image;
94111
}
95112

96-
private BufferedImage createImageBase(SVGDocument svgDocument, float scalingFactor) {
97-
FloatSize sourceImageSize = svgDocument.size();
98-
int targetImageWidth = calculateTargetWidth(scalingFactor, sourceImageSize);
99-
int targetImageHeight = calculateTargetHeight(scalingFactor, sourceImageSize);
100-
return new BufferedImage(targetImageWidth, targetImageHeight, BufferedImage.TYPE_INT_ARGB);
101-
}
102-
103113
private int calculateTargetWidth(float scalingFactor, FloatSize sourceImageSize) {
104114
double sourceImageWidth = sourceImageSize.getWidth();
105115
return (int) Math.round(sourceImageWidth * scalingFactor);
@@ -110,10 +120,10 @@ private int calculateTargetHeight(float scalingFactor, FloatSize sourceImageSize
110120
return (int) Math.round(sourceImageHeight * scalingFactor);
111121
}
112122

113-
private Graphics2D configureRenderingOptions(float scalingFactor, BufferedImage image) {
123+
private Graphics2D configureRenderingOptions(float widthScalingFactor, float heightScalingFactor, BufferedImage image) {
114124
Graphics2D g = image.createGraphics();
115125
g.setRenderingHints(RENDERING_HINTS);
116-
g.scale(scalingFactor, scalingFactor);
126+
g.scale(widthScalingFactor, heightScalingFactor);
117127
return g;
118128
}
119129

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2025 Vector Informatik GmbH and others.
3+
*
4+
* This program and the accompanying materials are made available under the terms of the Eclipse
5+
* Public License 2.0 which accompanies this distribution, and is available at
6+
* https://www.eclipse.org/legal/epl-2.0/
7+
*
8+
* SPDX-License-Identifier: EPL-2.0
9+
*
10+
* Contributors:
11+
* Michael Bangas (Vector Informatik GmbH) - initial API and implementation
12+
*******************************************************************************/
13+
package org.eclipse.swt.graphics;
14+
15+
import java.util.*;
16+
17+
/**
18+
* @since 3.131
19+
*/
20+
public interface ImageDataAtSizeProvider extends ImageDataProvider {
21+
22+
Optional<ImageData> getImageData(int targetWidth, int targetHeight);
23+
24+
}

bundles/org.eclipse.swt/Eclipse SWT/common/org/eclipse/swt/graphics/ImageDataLoader.java

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -41,20 +41,32 @@ public static boolean canLoadAtZoom(InputStream stream, int fileZoom, int target
4141
return ImageLoader.canLoadAtZoom(stream, fileZoom, targetZoom);
4242
}
4343

44-
public static ElementAtZoom<ImageData> load(InputStream stream, int fileZoom, int targetZoom) {
45-
List<ElementAtZoom<ImageData>> data = new ImageLoader().load(stream, fileZoom, targetZoom);
46-
if (data.isEmpty()) SWT.error(SWT.ERROR_INVALID_IMAGE);
47-
return data.get(0);
48-
}
49-
5044
public static boolean canLoadAtZoom(String filename, int fileZoom, int targetZoom) {
5145
return ImageLoader.canLoadAtZoom(filename, fileZoom, targetZoom);
5246
}
5347

54-
public static ElementAtZoom<ImageData> load(String filename, int fileZoom, int targetZoom) {
55-
List<ElementAtZoom<ImageData>> data = new ImageLoader().load(filename, fileZoom, targetZoom);
48+
public static ElementAtZoom<ImageData> loadByZoom(InputStream stream, int fileZoom, int targetZoom) {
49+
List<ElementAtZoom<ImageData>> data = new ImageLoader().loadByZoom(stream, fileZoom, targetZoom);
50+
if (data.isEmpty()) SWT.error(SWT.ERROR_INVALID_IMAGE);
51+
return data.get(0);
52+
}
53+
54+
public static ElementAtZoom<ImageData> loadByZoom(String filename, int fileZoom, int targetZoom) {
55+
List<ElementAtZoom<ImageData>> data = new ImageLoader().loadByZoom(filename, fileZoom, targetZoom);
5656
if (data.isEmpty()) SWT.error(SWT.ERROR_INVALID_IMAGE);
5757
return data.get(0);
5858
}
5959

60+
public static ImageData loadByTargetSize(InputStream stream, int targetWidth, int targetHeight) {
61+
ImageData data = new ImageLoader().loadByTargetSize(stream, targetWidth, targetHeight);
62+
if (data == null) SWT.error(SWT.ERROR_INVALID_IMAGE);
63+
return data;
64+
}
65+
66+
public static ImageData loadByTargetSize(String filename, int targetWidth, int targetHeight) {
67+
ImageData data = new ImageLoader().loadByTargetSize(filename, targetWidth, targetHeight);
68+
if (data == null) SWT.error(SWT.ERROR_INVALID_IMAGE);
69+
return data;
70+
}
71+
6072
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2025 Vector Informatik GmbH and others.
3+
*
4+
* This program and the accompanying materials are made available under the terms of the Eclipse
5+
* Public License 2.0 which accompanies this distribution, and is available at
6+
* https://www.eclipse.org/legal/epl-2.0/
7+
*
8+
* SPDX-License-Identifier: EPL-2.0
9+
*
10+
* Contributors:
11+
* Michael Bangas (Vector Informatik GmbH) - initial API and implementation
12+
*******************************************************************************/
13+
package org.eclipse.swt.graphics;
14+
15+
import java.util.*;
16+
17+
/**
18+
* @since 3.131
19+
*/
20+
public interface ImageFileNameAtSizeProvider extends ImageFileNameProvider {
21+
22+
/**
23+
* Returns the image file path most suitable for rendering at the given target size.
24+
*
25+
*/
26+
Optional<String> getImagePath(int targetWidth, int targetHeight);
27+
28+
}

bundles/org.eclipse.swt/Eclipse SWT/common/org/eclipse/swt/graphics/ImageLoader.java

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -151,18 +151,26 @@ void reset() {
151151
* </ul>
152152
*/
153153
public ImageData[] load(InputStream stream) {
154-
load(stream, FileFormat.DEFAULT_ZOOM, FileFormat.DEFAULT_ZOOM);
154+
loadByZoom(stream, FileFormat.DEFAULT_ZOOM, FileFormat.DEFAULT_ZOOM);
155155
return data;
156156
}
157157

158-
List<ElementAtZoom<ImageData>> load(InputStream stream, int fileZoom, int targetZoom) {
158+
List<ElementAtZoom<ImageData>> loadByZoom(InputStream stream, int fileZoom, int targetZoom) {
159159
if (stream == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
160160
reset();
161161
List<ElementAtZoom<ImageData>> images = NativeImageLoader.load(new ElementAtZoom<>(stream, fileZoom), this, targetZoom);
162162
data = images.stream().map(ElementAtZoom::element).toArray(ImageData[]::new);
163163
return images;
164164
}
165165

166+
ImageData loadByTargetSize(InputStream stream, int targetWidth, int targetHeight) {
167+
if (stream == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
168+
reset();
169+
ImageData image = NativeImageLoader.load(stream, this, targetWidth, targetHeight);
170+
data = new ImageData[] {image};
171+
return image;
172+
}
173+
166174
static boolean canLoadAtZoom(InputStream stream, int fileZoom, int targetZoom) {
167175
if (stream == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
168176
return FileFormat.canLoadAtZoom(new ElementAtZoom<>(stream, fileZoom), targetZoom);
@@ -187,14 +195,24 @@ static boolean canLoadAtZoom(InputStream stream, int fileZoom, int targetZoom) {
187195
* </ul>
188196
*/
189197
public ImageData[] load(String filename) {
190-
load(filename, FileFormat.DEFAULT_ZOOM, FileFormat.DEFAULT_ZOOM);
198+
loadByZoom(filename, FileFormat.DEFAULT_ZOOM, FileFormat.DEFAULT_ZOOM);
191199
return data;
192200
}
193201

194-
List<ElementAtZoom<ImageData>> load(String filename, int fileZoom, int targetZoom) {
202+
List<ElementAtZoom<ImageData>> loadByZoom(String filename, int fileZoom, int targetZoom) {
203+
if (filename == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
204+
try (InputStream stream = new FileInputStream(filename)) {
205+
return loadByZoom(stream, fileZoom, targetZoom);
206+
} catch (IOException e) {
207+
SWT.error(SWT.ERROR_IO, e);
208+
}
209+
return null;
210+
}
211+
212+
ImageData loadByTargetSize(String filename, int targetWidth, int targetHeight) {
195213
if (filename == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
196214
try (InputStream stream = new FileInputStream(filename)) {
197-
return load(stream, fileZoom, targetZoom);
215+
return loadByTargetSize(stream, targetWidth, targetHeight);
198216
} catch (IOException e) {
199217
SWT.error(SWT.ERROR_IO, e);
200218
}

bundles/org.eclipse.swt/Eclipse SWT/common/org/eclipse/swt/internal/DPIUtil.java

Lines changed: 31 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -144,34 +144,49 @@ public static ImageData scaleImageData (Device device, final ElementAtZoom<Image
144144
return scaleImageData(device, elementAtZoom.element(), targetZoom, elementAtZoom.zoom());
145145
}
146146

147-
public static ImageData autoScaleImageData (Device device, final ImageData imageData, float scaleFactor) {
148-
// Guards are already implemented in callers: if (deviceZoom == 100 || imageData == null || scaleFactor == 1.0f) return imageData;
149-
int width = imageData.width;
150-
int height = imageData.height;
151-
int scaledWidth = Math.round (width * scaleFactor);
152-
int scaledHeight = Math.round (height * scaleFactor);
147+
public static ImageData autoScaleImageData(Device device, final ImageData imageData, float scaleFactor) {
148+
int targetWidth = Math.round(imageData.width * scaleFactor);
149+
int targetHeight = Math.round(imageData.height * scaleFactor);
150+
return scaleImage(device, imageData, imageData.width, imageData.height, targetWidth, targetHeight, Image::drawAtTargetSize);
151+
}
152+
153+
public static ImageData autoScaleImageData(Device device, final ImageData imageData, int targetWidth, int targetHeight) {
154+
return scaleImage(device, imageData, imageData.width, imageData.height, targetWidth, targetHeight, Image::drawAtTargetSize);
155+
}
156+
157+
@FunctionalInterface
158+
private interface ImageDrawFunction {
159+
void draw(GC gc, Image original, int originalWidth, int originalHeight, int targetWidth, int targetHeight);
160+
}
161+
162+
private static ImageData scaleImage(Device device, ImageData imageData, int originalWidth, int originalHeight,
163+
int targetWidth, int targetHeight, ImageDrawFunction drawFunction) {
164+
153165
boolean useSmoothScaling = isSmoothScalingEnabled() && imageData.getTransparencyType() != SWT.TRANSPARENCY_MASK;
166+
154167
if (useSmoothScaling) {
155-
Image original = new Image (device, (ImageDataProvider) zoom -> imageData);
156-
ImageGcDrawer drawer = new ImageGcDrawer() {
168+
Image original = new Image(device, (ImageDataProvider) zoom -> imageData);
169+
170+
ImageGcDrawer drawer = new ImageGcDrawer() {
157171
@Override
158172
public void drawOn(GC gc, int imageWidth, int imageHeight) {
159-
gc.setAntialias (SWT.ON);
160-
Image.drawScaled(gc, original, width, height, scaleFactor);
161-
};
173+
gc.setAntialias(SWT.ON);
174+
drawFunction.draw(gc, original, originalWidth, originalHeight, targetWidth, targetHeight);
175+
}
162176

163177
@Override
164178
public int getGcStyle() {
165179
return SWT.TRANSPARENT;
166180
}
167181
};
168-
Image resultImage = new Image (device, drawer, scaledWidth, scaledHeight);
169-
ImageData result = resultImage.getImageData (100);
170-
original.dispose ();
171-
resultImage.dispose ();
182+
183+
Image resultImage = new Image(device, drawer, targetWidth, targetHeight);
184+
ImageData result = resultImage.getImageData(100);
185+
original.dispose();
186+
resultImage.dispose();
172187
return result;
173188
} else {
174-
return imageData.scaledTo (scaledWidth, scaledHeight);
189+
return imageData.scaledTo(targetWidth, targetHeight);
175190
}
176191
}
177192

bundles/org.eclipse.swt/Eclipse SWT/common/org/eclipse/swt/internal/image/FileFormat.java

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,11 @@ static abstract class StaticImageFileFormat extends FileFormat {
8585
List<ElementAtZoom<ImageData>> loadFromByteStream(int fileZoom, int targetZoom) {
8686
return Arrays.stream(loadFromByteStream()).map(d -> new ElementAtZoom<>(d, fileZoom)).toList();
8787
}
88+
89+
@Override
90+
ImageData loadFromByteStreamByTargetSize(int targetWidth, int targetHeight) {
91+
return loadFromByteStream()[0];
92+
}
8893
}
8994

9095
LEDataInputStream inputStream;
@@ -104,6 +109,8 @@ List<ElementAtZoom<ImageData>> loadFromByteStream(int fileZoom, int targetZoom)
104109
*/
105110
abstract List<ElementAtZoom<ImageData>> loadFromByteStream(int fileZoom, int targetZoom);
106111

112+
abstract ImageData loadFromByteStreamByTargetSize(int targetWidth, int targetHeight);
113+
107114
/**
108115
* Read the specified input stream, and return the
109116
* device independent image array represented by the stream.
@@ -122,6 +129,20 @@ public List<ElementAtZoom<ImageData>> loadFromStream(LEDataInputStream stream, i
122129
}
123130
}
124131

132+
public ImageData loadFromStreamByTargetSize(LEDataInputStream stream, int targetWidth, int targetHeight) {
133+
try {
134+
inputStream = stream;
135+
return loadFromByteStreamByTargetSize(targetWidth, targetHeight);
136+
} catch (Exception e) {
137+
if (e instanceof IOException) {
138+
SWT.error(SWT.ERROR_IO, e);
139+
} else {
140+
SWT.error(SWT.ERROR_INVALID_IMAGE, e);
141+
}
142+
return null;
143+
}
144+
}
145+
125146
/**
126147
* Read the specified input stream using the specified loader, and
127148
* return the device independent image array represented by the stream.
@@ -136,6 +157,16 @@ public static List<ElementAtZoom<ImageData>> load(ElementAtZoom<InputStream> is,
136157
return fileFormat.loadFromStream(stream, is.zoom(), targetZoom);
137158
}
138159

160+
public static ImageData load(InputStream is, ImageLoader loader, int targetWidth, int targetHeight) {
161+
LEDataInputStream stream = new LEDataInputStream(is);
162+
FileFormat fileFormat = determineFileFormat(stream).orElseGet(() -> {
163+
SWT.error(SWT.ERROR_UNSUPPORTED_FORMAT);
164+
return null;
165+
});
166+
fileFormat.loader = loader;
167+
return fileFormat.loadFromStreamByTargetSize(stream, targetWidth, targetHeight);
168+
}
169+
139170
public static boolean canLoadAtZoom(ElementAtZoom<InputStream> is, int targetZoom) {
140171
return is.zoom() == targetZoom || isDynamicallySizableFormat(is.element());
141172
}

bundles/org.eclipse.swt/Eclipse SWT/common/org/eclipse/swt/internal/image/SVGFileFormat.java

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,26 @@ List<ElementAtZoom<ImageData>> loadFromByteStream(int fileZoom, int targetZoom)
6060
}
6161
}
6262

63+
@Override
64+
ImageData loadFromByteStreamByTargetSize(int targetWidth, int targetHeight) {
65+
if (RASTERIZER == null) {
66+
SWT.error(SWT.ERROR_UNSUPPORTED_FORMAT, null, " [No SVG rasterizer found]");
67+
}
68+
if (targetWidth <= 0 || targetHeight <= 0) {
69+
SWT.error(SWT.ERROR_INVALID_ARGUMENT, null, " [Cannot rasterize SVG for targetWidth or targetHeight <= 0]");
70+
}
71+
if (targetHeight <= 0 || targetHeight <= 0) {
72+
SWT.error(SWT.ERROR_INVALID_ARGUMENT, null, " [Cannot rasterize SVG for targetHeight or targetHeight <= 0]");
73+
}
74+
try {
75+
ImageData rasterizedImageData = RASTERIZER.rasterizeSVG(inputStream, targetWidth, targetHeight);
76+
return rasterizedImageData;
77+
} catch (IOException e) {
78+
SWT.error(SWT.ERROR_INVALID_IMAGE, e);
79+
return null;
80+
}
81+
}
82+
6383
@Override
6484
void unloadIntoByteStream(ImageLoader loader) {
6585
throw new UnsupportedOperationException();

bundles/org.eclipse.swt/Eclipse SWT/common/org/eclipse/swt/internal/image/SVGRasterizer.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,4 +32,6 @@ public interface SVGRasterizer {
3232
* the input is not a valid SVG file or cannot be processed.
3333
*/
3434
public ImageData rasterizeSVG(InputStream stream, int zoom) throws IOException;
35+
36+
public ImageData rasterizeSVG(InputStream stream, int width, int height) throws IOException;
3537
}

0 commit comments

Comments
 (0)