Skip to content

Commit baf0ab3

Browse files
Michael5601HannesWell
authored andcommitted
Add support for SVG images
SWT currently loads icons exclusively as raster graphics (e.g., PNGs) without support for vector formats like SVG (except for Linux). A major drawback of raster graphics is their inability to scale without degrading image quality. Additionally, generating icons of different sizes requires manually rasterization of SVGs as a preparatory step, leading to unnecessary effort and many icon files. This change introduces support for vector graphics in images, enabling SVGs to be used for images. An SVG rasterizer can be provided via an SWT fragment. This change adds an according fragment based on the JSVG library. An according FileFormat implementation is added, which utilized a present SVG rasterization fragment to rasterize images for the desired zoom factor. Fixes #1438
1 parent b29048a commit baf0ab3

File tree

16 files changed

+868
-0
lines changed

16 files changed

+868
-0
lines changed
+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<classpath>
3+
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-17"/>
4+
<classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
5+
<classpathentry kind="src" path="src"/>
6+
<classpathentry kind="output" path="bin"/>
7+
</classpath>

bundles/org.eclipse.swt.svg/.project

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<projectDescription>
3+
<name>org.eclipse.swt.svg</name>
4+
<comment></comment>
5+
<projects>
6+
</projects>
7+
<buildSpec>
8+
<buildCommand>
9+
<name>org.eclipse.jdt.core.javabuilder</name>
10+
<arguments>
11+
</arguments>
12+
</buildCommand>
13+
<buildCommand>
14+
<name>org.eclipse.pde.ManifestBuilder</name>
15+
<arguments>
16+
</arguments>
17+
</buildCommand>
18+
<buildCommand>
19+
<name>org.eclipse.pde.SchemaBuilder</name>
20+
<arguments>
21+
</arguments>
22+
</buildCommand>
23+
</buildSpec>
24+
<natures>
25+
<nature>org.eclipse.pde.PluginNature</nature>
26+
<nature>org.eclipse.jdt.core.javanature</nature>
27+
</natures>
28+
</projectDescription>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
eclipse.preferences.version=1
2+
encoding/<project>=UTF-8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
eclipse.preferences.version=1
2+
org.eclipse.jdt.core.compiler.codegen.targetPlatform=17
3+
org.eclipse.jdt.core.compiler.compliance=17
4+
org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
5+
org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled
6+
org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
7+
org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=warning
8+
org.eclipse.jdt.core.compiler.release=enabled
9+
org.eclipse.jdt.core.compiler.source=17
10+
org.eclipse.jdt.core.incompleteClasspath=warning
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
Manifest-Version: 1.0
2+
Bundle-ManifestVersion: 2
3+
Bundle-Name: SWT SVG Rendering Support
4+
Bundle-SymbolicName: org.eclipse.swt.svg
5+
Bundle-Version: 3.130.0.qualifier
6+
Automatic-Module-Name: org.eclipse.swt.svg
7+
Bundle-RequiredExecutionEnvironment: JavaSE-17
8+
Fragment-Host: org.eclipse.swt
9+
Import-Package: com.github.weisj.jsvg;version="[1.7.0,2.0.0)",
10+
com.github.weisj.jsvg.geometry.size;version="[1.7.0,2.0.0)",
11+
com.github.weisj.jsvg.parser;version="[1.7.0,2.0.0)"
12+
Export-Package: org.eclipse.swt.svg
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
org.eclipse.swt.svg.JSVGRasterizer
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
source.. = src/
2+
output.. = bin/
3+
bin.includes = META-INF/,\
4+
.
5+
tycho.pomless.parent = ../../local-build/local-build-parent
6+
jars.extra.classpath = platform:/plugin/org.eclipse.swt.cocoa.macosx.aarch64,\
7+
platform:/plugin/org.eclipse.swt.cocoa.macosx.x86_64,\
8+
platform:/plugin/org.eclipse.swt.gtk.linux.aarch64,\
9+
platform:/plugin/org.eclipse.swt.gtk.linux.ppc64le,\
10+
platform:/plugin/org.eclipse.swt.gtk.linux.riscv64,\
11+
platform:/plugin/org.eclipse.swt.gtk.linux.x86_64,\
12+
platform:/plugin/org.eclipse.swt.win32.win32.aarch64,\
13+
platform:/plugin/org.eclipse.swt.win32.win32.x86_64
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
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.svg;
14+
15+
import static java.awt.RenderingHints.KEY_ALPHA_INTERPOLATION;
16+
import static java.awt.RenderingHints.KEY_ANTIALIASING;
17+
import static java.awt.RenderingHints.KEY_COLOR_RENDERING;
18+
import static java.awt.RenderingHints.KEY_DITHERING;
19+
import static java.awt.RenderingHints.KEY_FRACTIONALMETRICS;
20+
import static java.awt.RenderingHints.KEY_INTERPOLATION;
21+
import static java.awt.RenderingHints.KEY_RENDERING;
22+
import static java.awt.RenderingHints.KEY_STROKE_CONTROL;
23+
import static java.awt.RenderingHints.KEY_TEXT_ANTIALIASING;
24+
import static java.awt.RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY;
25+
import static java.awt.RenderingHints.VALUE_ANTIALIAS_ON;
26+
import static java.awt.RenderingHints.VALUE_COLOR_RENDER_QUALITY;
27+
import static java.awt.RenderingHints.VALUE_DITHER_DISABLE;
28+
import static java.awt.RenderingHints.VALUE_FRACTIONALMETRICS_ON;
29+
import static java.awt.RenderingHints.VALUE_INTERPOLATION_BICUBIC;
30+
import static java.awt.RenderingHints.VALUE_RENDER_QUALITY;
31+
import static java.awt.RenderingHints.VALUE_STROKE_PURE;
32+
import static java.awt.RenderingHints.VALUE_TEXT_ANTIALIAS_ON;
33+
34+
import java.awt.Graphics2D;
35+
import java.awt.RenderingHints.Key;
36+
import java.awt.image.BufferedImage;
37+
import java.awt.image.DataBufferInt;
38+
import java.io.IOException;
39+
import java.io.InputStream;
40+
import java.util.Map;
41+
42+
import org.eclipse.swt.SWT;
43+
import org.eclipse.swt.graphics.ImageData;
44+
import org.eclipse.swt.graphics.PaletteData;
45+
import org.eclipse.swt.internal.image.SVGRasterizer;
46+
47+
import com.github.weisj.jsvg.SVGDocument;
48+
import com.github.weisj.jsvg.geometry.size.FloatSize;
49+
import com.github.weisj.jsvg.parser.LoaderContext;
50+
import com.github.weisj.jsvg.parser.SVGLoader;
51+
52+
/**
53+
* A rasterizer implementation for converting SVG data into rasterized images.
54+
* This class uses the third party library JSVG for the raterization of SVG
55+
* images.
56+
*/
57+
public class JSVGRasterizer implements SVGRasterizer {
58+
59+
private static final SVGLoader SVG_LOADER = new SVGLoader();
60+
61+
private final static Map<Key, Object> RENDERING_HINTS = Map.of( //
62+
KEY_ANTIALIASING, VALUE_ANTIALIAS_ON, //
63+
KEY_ALPHA_INTERPOLATION, VALUE_ALPHA_INTERPOLATION_QUALITY, //
64+
KEY_COLOR_RENDERING, VALUE_COLOR_RENDER_QUALITY, //
65+
KEY_DITHERING, VALUE_DITHER_DISABLE, //
66+
KEY_FRACTIONALMETRICS, VALUE_FRACTIONALMETRICS_ON, //
67+
KEY_INTERPOLATION, VALUE_INTERPOLATION_BICUBIC, //
68+
KEY_RENDERING, VALUE_RENDER_QUALITY, //
69+
KEY_STROKE_CONTROL, VALUE_STROKE_PURE, //
70+
KEY_TEXT_ANTIALIASING, VALUE_TEXT_ANTIALIAS_ON //
71+
);
72+
73+
@Override
74+
public ImageData rasterizeSVG(InputStream inputStream, int zoom) throws IOException {
75+
SVGDocument svgDocument = loadSVG(inputStream);
76+
if (svgDocument != null) {
77+
return generateRasterizedImageData(svgDocument, zoom);
78+
} else {
79+
SWT.error(SWT.ERROR_INVALID_IMAGE);
80+
}
81+
return null;
82+
}
83+
84+
private SVGDocument loadSVG(InputStream inputStream) {
85+
return SVG_LOADER.load(inputStream, null, LoaderContext.createDefault());
86+
}
87+
88+
private ImageData generateRasterizedImageData(SVGDocument svgDocument, int zoom) {
89+
BufferedImage rasterizedImage = renderSVG(svgDocument, zoom);
90+
return convertToSWTImageData(rasterizedImage);
91+
}
92+
93+
private BufferedImage renderSVG(SVGDocument svgDocument, int zoom) {
94+
float scalingFactor = zoom / 100.0f;
95+
BufferedImage image = createImageBase(svgDocument, scalingFactor);
96+
Graphics2D g = configureRenderingOptions(scalingFactor, image);
97+
svgDocument.render(null, g);
98+
g.dispose();
99+
return image;
100+
}
101+
102+
private BufferedImage createImageBase(SVGDocument svgDocument, float scalingFactor) {
103+
FloatSize sourceImageSize = svgDocument.size();
104+
int targetImageWidth = calculateTargetWidth(scalingFactor, sourceImageSize);
105+
int targetImageHeight = calculateTargetHeight(scalingFactor, sourceImageSize);
106+
return new BufferedImage(targetImageWidth, targetImageHeight, BufferedImage.TYPE_INT_ARGB);
107+
}
108+
109+
private int calculateTargetWidth(float scalingFactor, FloatSize sourceImageSize) {
110+
double sourceImageWidth = sourceImageSize.getWidth();
111+
return (int) Math.round(sourceImageWidth * scalingFactor);
112+
}
113+
114+
private int calculateTargetHeight(float scalingFactor, FloatSize sourceImageSize) {
115+
double sourceImageHeight = sourceImageSize.getHeight();
116+
return (int) Math.round(sourceImageHeight * scalingFactor);
117+
}
118+
119+
private Graphics2D configureRenderingOptions(float scalingFactor, BufferedImage image) {
120+
Graphics2D g = image.createGraphics();
121+
g.setRenderingHints(RENDERING_HINTS);
122+
g.scale(scalingFactor, scalingFactor);
123+
return g;
124+
}
125+
126+
private ImageData convertToSWTImageData(BufferedImage rasterizedImage) {
127+
int width = rasterizedImage.getWidth();
128+
int height = rasterizedImage.getHeight();
129+
int[] pixels = ((DataBufferInt) rasterizedImage.getRaster().getDataBuffer()).getData();
130+
PaletteData paletteData = new PaletteData(0x00FF0000, 0x0000FF00, 0x000000FF);
131+
ImageData imageData = new ImageData(width, height, 32, paletteData);
132+
int index = 0;
133+
for (int y = 0; y < imageData.height; y++) {
134+
for (int x = 0; x < imageData.width; x++) {
135+
int alpha = (pixels[index] >> 24) & 0xFF;
136+
imageData.setAlpha(x, y, alpha);
137+
imageData.setPixel(x, y, pixels[index++]);
138+
}
139+
}
140+
return imageData;
141+
}
142+
}

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

+3
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,9 @@ public abstract class FileFormat {
5353
try {
5454
FORMAT_FACTORIES.add(OS2BMPFileFormat::new);
5555
} catch (NoClassDefFoundError e) { } // ignore format
56+
try {
57+
FORMAT_FACTORIES.add(SVGFileFormat::new);
58+
} catch (NoClassDefFoundError e) { } // ignore format
5659
}
5760

5861
public static final int DEFAULT_ZOOM = 100;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
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.internal.image;
14+
15+
import java.io.*;
16+
import java.nio.charset.*;
17+
import java.util.*;
18+
19+
import org.eclipse.swt.*;
20+
import org.eclipse.swt.graphics.*;
21+
import org.eclipse.swt.internal.DPIUtil.*;
22+
23+
/**
24+
* A {@link FileFormat} implementation for handling SVG (Scalable Vector
25+
* Graphics) files.
26+
* <p>
27+
* This class detects SVG files based on their header and uses a registered
28+
* {@link SVGRasterizer} service to rasterize SVG content.
29+
* </p>
30+
*/
31+
public class SVGFileFormat extends FileFormat {
32+
33+
/** The instance of the registered {@link SVGRasterizer}. */
34+
private static final SVGRasterizer RASTERIZER = ServiceLoader.load(SVGRasterizer.class).findFirst().orElse(null);
35+
36+
@Override
37+
boolean isFileFormat(LEDataInputStream stream) throws IOException {
38+
byte[] firstBytes = new byte[5];
39+
int bytesRead = stream.read(firstBytes);
40+
stream.unread(firstBytes);
41+
String header = new String(firstBytes, 0, bytesRead, StandardCharsets.UTF_8).trim();
42+
return header.startsWith("<?xml") || header.startsWith("<svg");
43+
}
44+
45+
@Override
46+
List<ElementAtZoom<ImageData>> loadFromByteStream(int fileZoom, int targetZoom) {
47+
if (RASTERIZER == null) {
48+
SWT.error(SWT.ERROR_UNSUPPORTED_FORMAT, null, " [No SVG rasterizer found]");
49+
}
50+
if (targetZoom <= 0) {
51+
SWT.error(SWT.ERROR_INVALID_ARGUMENT, null, " [Cannot rasterize SVG for zoom <= 0]");
52+
}
53+
try {
54+
ImageData rasterizedImageData = RASTERIZER.rasterizeSVG(inputStream, 100 * targetZoom / fileZoom);
55+
return List.of(new ElementAtZoom<>(rasterizedImageData, targetZoom));
56+
} catch (IOException e) {
57+
SWT.error(SWT.ERROR_INVALID_IMAGE, e);
58+
}
59+
return Collections.emptyList();
60+
}
61+
62+
@Override
63+
void unloadIntoByteStream(ImageLoader loader) {
64+
throw new UnsupportedOperationException();
65+
}
66+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
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.internal.image;
14+
15+
import java.io.*;
16+
17+
import org.eclipse.swt.graphics.*;
18+
19+
/**
20+
* Defines the interface for an SVG rasterizer, responsible for converting SVG
21+
* data into rasterized images.
22+
*
23+
* @since 3.130
24+
*/
25+
public interface SVGRasterizer {
26+
/**
27+
* Rasterizes an SVG image from the provided {@code InputStream} using the
28+
* specified zoom factor.
29+
*
30+
* @param stream the SVG image as an {@link InputStream}.
31+
* @param zoom the scaling factor (in percent) e.g. 200 for doubled size. This
32+
* value must not be 0.
33+
* @return the {@link ImageData} for the rasterized image, or {@code null} if
34+
* the input is not a valid SVG file or cannot be processed.
35+
* @throws IOException
36+
*/
37+
public ImageData rasterizeSVG(InputStream stream, int zoom) throws IOException;
38+
}

tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/AllNonBrowserTests.java

+1
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
Test_org_eclipse_swt_accessibility_AccessibleControlEvent.class,
4646
Test_org_eclipse_swt_accessibility_AccessibleEvent.class,
4747
Test_org_eclipse_swt_accessibility_AccessibleTextEvent.class,
48+
Test_org_eclipse_swt_internal_SVGRasterizer.class,
4849
DPIUtilTests.class})
4950
public class AllNonBrowserTests {
5051
private static List<Error> leakedResources;

0 commit comments

Comments
 (0)