Skip to content

Commit f10b96d

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)
1 parent 93a484e commit f10b96d

File tree

17 files changed

+910
-80
lines changed

17 files changed

+910
-80
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

bundles/org.eclipse.swt/Eclipse SWT/cocoa/org/eclipse/swt/graphics/Image.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1820,11 +1820,12 @@ public String toString () {
18201820
* @param imageData the imageData which is used to draw the scaled Image
18211821
* @param width the width of the original image
18221822
* @param height the height of the original image
1823-
* @param scaleFactor the factor with which the image is supposed to be scaled
1823+
* @param targetWidth the width to which the image is supposed to be scaled
1824+
* @param targetHeight the height to which the image is supposed to be scaled
18241825
*
18251826
* @noreference This method is not intended to be referenced by clients.
18261827
*/
1827-
public static void drawScaled(GC gc, ImageData imageData, int width, int height, float scaleFactor) {
1828+
public static void drawAtTargetSize(GC gc, ImageData imageData, int width, int height, int targetWidth, int targetHeight) {
18281829
StrictChecks.runWithStrictChecksDisabled(() -> {
18291830
Image imageToDraw = new Image(gc.device, (ImageDataProvider) zoom -> imageData);
18301831
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,
18331834
* avoiding rounding errors. Nevertheless, we still have some rounding errors
18341835
* due to the point-based API GC#drawImage(..).
18351836
*/
1836-
0, 0, Math.round(CocoaDPIUtil.pixelToPoint(width * scaleFactor)),
1837-
Math.round(CocoaDPIUtil.pixelToPoint(height * scaleFactor)));
1837+
0, 0, targetWidth, targetHeight);
18381838
imageToDraw.dispose();
18391839
});
18401840
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
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+
/**
16+
* @since 3.131
17+
*/
18+
public interface ImageDataAtSizeProvider extends ImageDataProvider {
19+
20+
ImageData getImageData(int targetWidth, int targetHeight);
21+
22+
}

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
}

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

Lines changed: 32 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) {
195203
if (filename == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
196204
try (InputStream stream = new FileInputStream(filename)) {
197-
return load(stream, fileZoom, targetZoom);
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) {
213+
if (filename == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
214+
try (InputStream stream = new FileInputStream(filename)) {
215+
return loadByTargetSize(stream, targetWidth, targetHeight);
198216
} catch (IOException e) {
199217
SWT.error(SWT.ERROR_IO, e);
200218
}
@@ -211,6 +229,15 @@ static boolean canLoadAtZoom(String filename, int fileZoom, int targetZoom) {
211229
return false;
212230
}
213231

232+
static boolean isDynamicallySizable(String filename) {
233+
try (InputStream stream = new FileInputStream(filename)) {
234+
return FileFormat.isDynamicallySizableFormat(stream);
235+
} catch (IOException e) {
236+
SWT.error(SWT.ERROR_IO, e);
237+
}
238+
return false;
239+
}
240+
214241
/**
215242
* Saves the image data in this ImageLoader to the specified stream.
216243
* The format parameter can have one of the following values:

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

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,14 +146,24 @@ public static ImageData autoScaleImageData (Device device, final ImageData image
146146
int height = imageData.height;
147147
int scaledWidth = Math.round (width * scaleFactor);
148148
int scaledHeight = Math.round (height * scaleFactor);
149+
return scaleImage(device, imageData, Image::drawAtTargetSize, width, height, scaledWidth, scaledHeight);
150+
}
151+
152+
@FunctionalInterface
153+
private interface ImageDrawFunction {
154+
void draw(GC gc, ImageData imageData, int originalWidth, int originalHeight, int targetWidth, int targetHeight);
155+
}
156+
157+
private static ImageData scaleImage(Device device, final ImageData imageData, ImageDrawFunction drawFunction, int width, int height,
158+
int scaledWidth, int scaledHeight) {
149159
int defaultZoomLevel = 100;
150160
boolean useSmoothScaling = isSmoothScalingEnabled() && imageData.getTransparencyType() != SWT.TRANSPARENCY_MASK;
151161
if (useSmoothScaling) {
152162
ImageGcDrawer drawer = new ImageGcDrawer() {
153163
@Override
154164
public void drawOn(GC gc, int imageWidth, int imageHeight) {
155165
gc.setAntialias (SWT.ON);
156-
Image.drawScaled(gc, imageData, width, height, scaleFactor);
166+
drawFunction.draw(gc, imageData, width, height, imageWidth, imageHeight);
157167
};
158168

159169
@Override
@@ -170,6 +180,10 @@ public int getGcStyle() {
170180
}
171181
}
172182

183+
public static ImageData autoScaleImageData(Device device, final ImageData imageData, int targetWidth, int targetHeight) {
184+
return scaleImage(device, imageData, Image::drawAtTargetSize, imageData.width, imageData.height, targetWidth, targetHeight);
185+
}
186+
173187
public static boolean isSmoothScalingEnabled() {
174188
return autoScaleMethod == AutoScaleMethod.SMOOTH;
175189
}

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
}

bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/graphics/Image.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1581,11 +1581,12 @@ public String toString () {
15811581
* @param imageData the imageData which is used to draw the scaled Image
15821582
* @param width the width of the original image
15831583
* @param height the height of the original image
1584-
* @param scaleFactor the factor with which the image is supposed to be scaled
1584+
* @param targetWidth the width to which the image is supposed to be scaled
1585+
* @param targetHeight the height to which the image is supposed to be scaled
15851586
*
15861587
* @noreference This method is not intended to be referenced by clients.
15871588
*/
1588-
public static void drawScaled(GC gc, ImageData imageData, int width, int height, float scaleFactor) {
1589+
public static void drawAtTargetSize(GC gc, ImageData imageData, int width, int height, int targetWidth, int targetHeight) {
15891590
StrictChecks.runWithStrictChecksDisabled(() -> {
15901591
Image imageToDraw = new Image(gc.device, (ImageDataProvider) zoom -> imageData);
15911592
gc.drawImage(imageToDraw, 0, 0, width, height,
@@ -1594,7 +1595,7 @@ public static void drawScaled(GC gc, ImageData imageData, int width, int height,
15941595
* avoiding rounding errors. Nevertheless, we still have some rounding errors
15951596
* due to the point-based API GC#drawImage(..).
15961597
*/
1597-
0, 0, Math.round(width * scaleFactor), Math.round(height * scaleFactor));
1598+
0, 0, targetWidth, targetHeight));
15981599
imageToDraw.dispose();
15991600
});
16001601
}

0 commit comments

Comments
 (0)