Skip to content

Commit f78ae92

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 a92d9db commit f78ae92

File tree

23 files changed

+1141
-1
lines changed

23 files changed

+1141
-1
lines changed
Lines changed: 7 additions & 0 deletions
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>
Lines changed: 28 additions & 0 deletions
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>
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
eclipse.preferences.version=1
2+
encoding/<project>=UTF-8
Lines changed: 10 additions & 0 deletions
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
Lines changed: 12 additions & 0 deletions
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: 1.0.0.qualifier
6+
Automatic-Module-Name: org.eclipse.swt.svgPlugin
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
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
org.eclipse.swt.svg.JSVGRasterizer
Lines changed: 13 additions & 0 deletions
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
Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
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.ComponentColorModel;
38+
import java.awt.image.DirectColorModel;
39+
import java.awt.image.IndexColorModel;
40+
import java.awt.image.WritableRaster;
41+
import java.io.IOException;
42+
import java.io.InputStream;
43+
import java.util.Map;
44+
45+
import org.eclipse.swt.SWT;
46+
import org.eclipse.swt.graphics.ImageData;
47+
import org.eclipse.swt.graphics.PaletteData;
48+
import org.eclipse.swt.graphics.RGB;
49+
import org.eclipse.swt.internal.SVGRasterizer;
50+
51+
import com.github.weisj.jsvg.SVGDocument;
52+
import com.github.weisj.jsvg.geometry.size.FloatSize;
53+
import com.github.weisj.jsvg.parser.LoaderContext;
54+
import com.github.weisj.jsvg.parser.SVGLoader;
55+
56+
/**
57+
* A rasterizer implementation for converting SVG data into rasterized images.
58+
* This class implements the {@code SVGRasterizer} interface.
59+
*
60+
* @since 1.0.0
61+
*/
62+
public class JSVGRasterizer implements SVGRasterizer {
63+
64+
private static final SVGLoader SVG_LOADER = new SVGLoader();
65+
66+
private final static Map<Key, Object> RENDERING_HINTS = Map.of( //
67+
KEY_ANTIALIASING, VALUE_ANTIALIAS_ON, //
68+
KEY_ALPHA_INTERPOLATION, VALUE_ALPHA_INTERPOLATION_QUALITY, //
69+
KEY_COLOR_RENDERING, VALUE_COLOR_RENDER_QUALITY, //
70+
KEY_DITHERING, VALUE_DITHER_DISABLE, //
71+
KEY_FRACTIONALMETRICS, VALUE_FRACTIONALMETRICS_ON, //
72+
KEY_INTERPOLATION, VALUE_INTERPOLATION_BICUBIC, //
73+
KEY_RENDERING, VALUE_RENDER_QUALITY, //
74+
KEY_STROKE_CONTROL, VALUE_STROKE_PURE, //
75+
KEY_TEXT_ANTIALIASING, VALUE_TEXT_ANTIALIAS_ON //
76+
);
77+
78+
@Override
79+
public ImageData[] rasterizeSVG(InputStream stream, int zoom) throws IOException {
80+
SVGDocument svgDocument = null;
81+
svgDocument = SVG_LOADER.load(stream, null, LoaderContext.createDefault());
82+
if (svgDocument != null) {
83+
float scalingFactor = zoom / 100.0f;
84+
FloatSize size = svgDocument.size();
85+
double originalWidth = size.getWidth();
86+
double originalHeight = size.getHeight();
87+
int scaledWidth = (int) Math.round(originalWidth * scalingFactor);
88+
int scaledHeight = (int) Math.round(originalHeight * scalingFactor);
89+
BufferedImage image = new BufferedImage(scaledWidth, scaledHeight, BufferedImage.TYPE_INT_ARGB);
90+
Graphics2D g = image.createGraphics();
91+
g.setRenderingHints(RENDERING_HINTS);
92+
g.scale(scalingFactor, scalingFactor);
93+
svgDocument.render(null, g);
94+
g.dispose();
95+
return new ImageData[] { convertToSWT(image) };
96+
} else {
97+
SWT.error(SWT.ERROR_INVALID_IMAGE);
98+
}
99+
return null;
100+
}
101+
102+
private ImageData convertToSWT(BufferedImage bufferedImage) {
103+
if (bufferedImage.getColorModel() instanceof DirectColorModel) {
104+
DirectColorModel colorModel = (DirectColorModel) bufferedImage.getColorModel();
105+
PaletteData palette = new PaletteData(colorModel.getRedMask(), colorModel.getGreenMask(),
106+
colorModel.getBlueMask());
107+
ImageData data = new ImageData(bufferedImage.getWidth(), bufferedImage.getHeight(),
108+
colorModel.getPixelSize(), palette);
109+
for (int y = 0; y < data.height; y++) {
110+
for (int x = 0; x < data.width; x++) {
111+
int rgb = bufferedImage.getRGB(x, y);
112+
int pixel = palette.getPixel(new RGB((rgb >> 16) & 0xFF, (rgb >> 8) & 0xFF, rgb & 0xFF));
113+
data.setPixel(x, y, pixel);
114+
if (colorModel.hasAlpha()) {
115+
data.setAlpha(x, y, (rgb >> 24) & 0xFF);
116+
}
117+
}
118+
}
119+
return data;
120+
} else if (bufferedImage.getColorModel() instanceof IndexColorModel) {
121+
IndexColorModel colorModel = (IndexColorModel) bufferedImage.getColorModel();
122+
int size = colorModel.getMapSize();
123+
byte[] reds = new byte[size];
124+
byte[] greens = new byte[size];
125+
byte[] blues = new byte[size];
126+
colorModel.getReds(reds);
127+
colorModel.getGreens(greens);
128+
colorModel.getBlues(blues);
129+
RGB[] rgbs = new RGB[size];
130+
for (int i = 0; i < rgbs.length; i++) {
131+
rgbs[i] = new RGB(reds[i] & 0xFF, greens[i] & 0xFF, blues[i] & 0xFF);
132+
}
133+
PaletteData palette = new PaletteData(rgbs);
134+
ImageData data = new ImageData(bufferedImage.getWidth(), bufferedImage.getHeight(),
135+
colorModel.getPixelSize(), palette);
136+
data.transparentPixel = colorModel.getTransparentPixel();
137+
WritableRaster raster = bufferedImage.getRaster();
138+
int[] pixelArray = new int[1];
139+
for (int y = 0; y < data.height; y++) {
140+
for (int x = 0; x < data.width; x++) {
141+
raster.getPixel(x, y, pixelArray);
142+
data.setPixel(x, y, pixelArray[0]);
143+
}
144+
}
145+
return data;
146+
} else if (bufferedImage.getColorModel() instanceof ComponentColorModel) {
147+
ComponentColorModel colorModel = (ComponentColorModel) bufferedImage.getColorModel();
148+
PaletteData palette = new PaletteData(0x0000FF, 0x00FF00, 0xFF0000);
149+
ImageData data = new ImageData(bufferedImage.getWidth(), bufferedImage.getHeight(),
150+
colorModel.getPixelSize(), palette);
151+
data.transparentPixel = -1;
152+
WritableRaster raster = bufferedImage.getRaster();
153+
int[] pixelArray = new int[3];
154+
for (int y = 0; y < data.height; y++) {
155+
for (int x = 0; x < data.width; x++) {
156+
raster.getPixel(x, y, pixelArray);
157+
int pixel = palette.getPixel(new RGB(pixelArray[0], pixelArray[1], pixelArray[2]));
158+
data.setPixel(x, y, pixel);
159+
}
160+
}
161+
return data;
162+
}
163+
return null;
164+
}
165+
}

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

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -352,6 +352,62 @@ public ImageData(InputStream stream) {
352352
i.delayTime);
353353
}
354354

355+
/**
356+
* Constructs an <code>ImageData</code> loaded from the specified
357+
* input stream. Throws an error if an error occurs while loading
358+
* the image, or if the image has an unsupported type. Application
359+
* code is still responsible for closing the input stream.
360+
*
361+
* @param stream the input stream to load the image from (must not be null)
362+
* @param zoom the zoom factor to apply when rasterizing an SVG.
363+
*
364+
* A value of 0 means that the standard method for loading should be used.
365+
* This case is equivalent to calling {@link ImageLoader#load(InputStream)}.
366+
*
367+
* A value above 0 specifies a scaling factor for the output image. For example:
368+
* <ul>
369+
* <li>A value of 100 maintains the original size of the SVG when rasterized.</li>
370+
* <li>A value of 200 doubles the size of the rasterized image.</li>
371+
* <li>A value of 50 reduces the size of the rasterized image to half.</li>
372+
* </ul>
373+
* The scaling is applied uniformly to both width and height.
374+
*
375+
* @exception IllegalArgumentException <ul>
376+
* <li>ERROR_NULL_ARGUMENT - if the stream is null</li>
377+
* </ul>
378+
* @exception SWTException <ul>
379+
* <li>ERROR_IO - if an IO error occurs while reading from the stream</li>
380+
* <li>ERROR_INVALID_IMAGE - if the image stream contains invalid data</li>
381+
* <li>ERROR_UNSUPPORTED_FORMAT - if the image stream contains an unrecognized format</li>
382+
* </ul>
383+
*
384+
* @see ImageLoader#load(InputStream)
385+
* @since 3.130
386+
*/
387+
public ImageData(InputStream stream, int zoom) {
388+
ImageData[] data = ImageDataLoader.load(stream, zoom);
389+
if (data.length < 1) SWT.error(SWT.ERROR_INVALID_IMAGE);
390+
ImageData i = data[0];
391+
setAllFields(
392+
i.width,
393+
i.height,
394+
i.depth,
395+
i.scanlinePad,
396+
i.bytesPerLine,
397+
i.data,
398+
i.palette,
399+
i.transparentPixel,
400+
i.maskData,
401+
i.maskPad,
402+
i.alphaData,
403+
i.alpha,
404+
i.type,
405+
i.x,
406+
i.y,
407+
i.disposalMethod,
408+
i.delayTime);
409+
}
410+
355411
/**
356412
* Constructs an <code>ImageData</code> loaded from a file with the
357413
* specified name. Throws an error if an error occurs loading the
@@ -396,6 +452,59 @@ public ImageData(String filename) {
396452
i.delayTime);
397453
}
398454

455+
/**
456+
* Constructs an <code>ImageData</code> loaded from a file with the
457+
* specified name. Throws an error if an error occurs loading the
458+
* image, or if the image has an unsupported type.
459+
*
460+
* @param filename the name of the file to load the image from (must not be null)
461+
* @param zoom the zoom factor to apply when rasterizing a SVG.
462+
* A value of 0 means that the standard method for loading should be used.
463+
* This case is equivalent to calling {@link ImageLoader#load(String)}.
464+
*
465+
* A value above 0 specifies a scaling factor for the output image. For example:
466+
* <ul>
467+
* <li>A value of 100 maintains the original size of the SVG when rasterized.</li>
468+
* <li>A value of 200 doubles the size of the rasterized image.</li>
469+
* <li>A value of 50 reduces the size of the rasterized image to half.</li>
470+
* </ul>
471+
* The scaling is applied uniformly to both width and height.
472+
*
473+
* @exception IllegalArgumentException <ul>
474+
* <li>ERROR_NULL_ARGUMENT - if the file name is null</li>
475+
* </ul>
476+
* @exception SWTException <ul>
477+
* <li>ERROR_IO - if an IO error occurs while reading from the file</li>
478+
* <li>ERROR_INVALID_IMAGE - if the image file contains invalid data</li>
479+
* <li>ERROR_UNSUPPORTED_FORMAT - if the image file contains an unrecognized format</li>
480+
* </ul>
481+
*
482+
* @since 3.130
483+
*/
484+
public ImageData(String filename, int zoom) {
485+
ImageData[] data = ImageDataLoader.load(filename, zoom);
486+
if (data.length < 1) SWT.error(SWT.ERROR_INVALID_IMAGE);
487+
ImageData i = data[0];
488+
setAllFields(
489+
i.width,
490+
i.height,
491+
i.depth,
492+
i.scanlinePad,
493+
i.bytesPerLine,
494+
i.data,
495+
i.palette,
496+
i.transparentPixel,
497+
i.maskData,
498+
i.maskPad,
499+
i.alphaData,
500+
i.alpha,
501+
i.type,
502+
i.x,
503+
i.y,
504+
i.disposalMethod,
505+
i.delayTime);
506+
}
507+
399508
/**
400509
* Prevents uninitialized instances from being created outside the package.
401510
*/

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,4 +49,8 @@ public static ElementAtZoom<ImageData> load(String filename, int fileZoom, int t
4949
return data.get(0);
5050
}
5151

52+
public static ImageData[] load(String filename, int zoom) {
53+
return new ImageLoader().load(filename, zoom);
54+
}
55+
5256
}

0 commit comments

Comments
 (0)