Skip to content

Commit 3dc4fb3

Browse files
Michael5601HeikoKlare
authored andcommitted
Introduce SVG Rasterization for Icons
Feature Proposal: Rasterization of SVGs at Runtime for Eclipse Icons Fixes #1438 Eclipse currently loads icons exclusively as raster graphics (e.g., `.png`), without support for vector formats like `.svg`. A major drawback of raster graphics is their inability to scale without degrading image quality. Additionally, generating icons of different sizes requires manually rasterizing SVGs outside Eclipse, leading to unnecessary effort and many icon files. This PR introduces support for vector graphics in Eclipse, enabling SVGs to be used for icons. Existing PNG icons will continue to be loaded alongside SVGs, allowing the use of the new functionality without the need to replace all PNG files at once. --- - **How It Works**: - To use SVG icons, simply place the SVG file in the bundle and reference it in the `plugin.xml` and other necessary locations, as is done for PNGs. No additional configuration is required. - At runtime, Eclipse uses the library JSVG to rasterize the SVG into a raster image of the desired size, eliminating the need for scaling. My analysis shows that JSVG is the most suitable Java library for this purpose. - You need to write the flag `-Dswt.autoScale=quarter` into your `eclipse.ini` file or into the run arguments of a new configuration.
1 parent 9b2ef91 commit 3dc4fb3

File tree

16 files changed

+855
-0
lines changed

16 files changed

+855
-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);
49+
}
50+
if (targetZoom <= 0) {
51+
SWT.error(SWT.ERROR_INVALID_ARGUMENT);
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 value must
32+
* 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)