RENDERING_HINTS = Map.of( //
+ KEY_ANTIALIASING, VALUE_ANTIALIAS_ON, //
+ KEY_ALPHA_INTERPOLATION, VALUE_ALPHA_INTERPOLATION_QUALITY, //
+ KEY_COLOR_RENDERING, VALUE_COLOR_RENDER_QUALITY, //
+ KEY_DITHERING, VALUE_DITHER_DISABLE, //
+ KEY_FRACTIONALMETRICS, VALUE_FRACTIONALMETRICS_ON, //
+ KEY_INTERPOLATION, VALUE_INTERPOLATION_BICUBIC, //
+ KEY_RENDERING, VALUE_RENDER_QUALITY, //
+ KEY_STROKE_CONTROL, VALUE_STROKE_PURE, //
+ KEY_TEXT_ANTIALIASING, VALUE_TEXT_ANTIALIAS_ON //
+ );
+
+ @Override
+ public ImageData rasterizeSVG(InputStream inputStream, int zoom) throws IOException {
+ SVGDocument svgDocument = loadSVG(inputStream);
+ if (svgDocument == null) {
+ SWT.error(SWT.ERROR_INVALID_IMAGE);
+ }
+ BufferedImage rasterizedImage = renderSVG(svgDocument, zoom);
+ return convertToSWTImageData(rasterizedImage);
+ }
+
+ private SVGDocument loadSVG(InputStream inputStream) {
+ return SVG_LOADER.load(inputStream, null, LoaderContext.createDefault());
+ }
+
+ private BufferedImage renderSVG(SVGDocument svgDocument, int zoom) {
+ float scalingFactor = zoom / 100.0f;
+ BufferedImage image = createImageBase(svgDocument, scalingFactor);
+ Graphics2D g = configureRenderingOptions(scalingFactor, image);
+ svgDocument.render(null, g);
+ g.dispose();
+ return image;
+ }
+
+ private BufferedImage createImageBase(SVGDocument svgDocument, float scalingFactor) {
+ FloatSize sourceImageSize = svgDocument.size();
+ int targetImageWidth = calculateTargetWidth(scalingFactor, sourceImageSize);
+ int targetImageHeight = calculateTargetHeight(scalingFactor, sourceImageSize);
+ return new BufferedImage(targetImageWidth, targetImageHeight, BufferedImage.TYPE_INT_ARGB);
+ }
+
+ private int calculateTargetWidth(float scalingFactor, FloatSize sourceImageSize) {
+ double sourceImageWidth = sourceImageSize.getWidth();
+ return (int) Math.round(sourceImageWidth * scalingFactor);
+ }
+
+ private int calculateTargetHeight(float scalingFactor, FloatSize sourceImageSize) {
+ double sourceImageHeight = sourceImageSize.getHeight();
+ return (int) Math.round(sourceImageHeight * scalingFactor);
+ }
+
+ private Graphics2D configureRenderingOptions(float scalingFactor, BufferedImage image) {
+ Graphics2D g = image.createGraphics();
+ g.setRenderingHints(RENDERING_HINTS);
+ g.scale(scalingFactor, scalingFactor);
+ return g;
+ }
+
+ private ImageData convertToSWTImageData(BufferedImage rasterizedImage) {
+ int width = rasterizedImage.getWidth();
+ int height = rasterizedImage.getHeight();
+ int[] pixels = ((DataBufferInt) rasterizedImage.getRaster().getDataBuffer()).getData();
+ PaletteData paletteData = new PaletteData(0x00FF0000, 0x0000FF00, 0x000000FF);
+ ImageData imageData = new ImageData(width, height, 32, paletteData);
+ int index = 0;
+ for (int y = 0; y < imageData.height; y++) {
+ for (int x = 0; x < imageData.width; x++) {
+ int alpha = (pixels[index] >> 24) & 0xFF;
+ imageData.setAlpha(x, y, alpha);
+ imageData.setPixel(x, y, pixels[index++]);
+ }
+ }
+ return imageData;
+ }
+}
diff --git a/bundles/org.eclipse.swt/Eclipse SWT/common/org/eclipse/swt/SWT.java b/bundles/org.eclipse.swt/Eclipse SWT/common/org/eclipse/swt/SWT.java
index 010e828d1d8..db336185913 100644
--- a/bundles/org.eclipse.swt/Eclipse SWT/common/org/eclipse/swt/SWT.java
+++ b/bundles/org.eclipse.swt/Eclipse SWT/common/org/eclipse/swt/SWT.java
@@ -4465,7 +4465,6 @@ public class SWT {
/**
* Image format constant indicating a SVG format image (value is 8).
- *
Note that this is a HINT and is currently only supported on GTK.
*
* @since 3.113
*/
diff --git a/bundles/org.eclipse.swt/Eclipse SWT/common/org/eclipse/swt/internal/image/FileFormat.java b/bundles/org.eclipse.swt/Eclipse SWT/common/org/eclipse/swt/internal/image/FileFormat.java
index ea524de8fd0..fd9c89a8347 100644
--- a/bundles/org.eclipse.swt/Eclipse SWT/common/org/eclipse/swt/internal/image/FileFormat.java
+++ b/bundles/org.eclipse.swt/Eclipse SWT/common/org/eclipse/swt/internal/image/FileFormat.java
@@ -53,6 +53,9 @@ public abstract class FileFormat {
try {
FORMAT_FACTORIES.add(OS2BMPFileFormat::new);
} catch (NoClassDefFoundError e) { } // ignore format
+ try {
+ FORMAT_FACTORIES.add(SVGFileFormat::new);
+ } catch (NoClassDefFoundError e) { } // ignore format
}
public static final int DEFAULT_ZOOM = 100;
diff --git a/bundles/org.eclipse.swt/Eclipse SWT/common/org/eclipse/swt/internal/image/SVGFileFormat.java b/bundles/org.eclipse.swt/Eclipse SWT/common/org/eclipse/swt/internal/image/SVGFileFormat.java
new file mode 100644
index 00000000000..001b9b6828d
--- /dev/null
+++ b/bundles/org.eclipse.swt/Eclipse SWT/common/org/eclipse/swt/internal/image/SVGFileFormat.java
@@ -0,0 +1,66 @@
+/*******************************************************************************
+ * Copyright (c) 2025 Vector Informatik GmbH and others.
+ *
+ * This program and the accompanying materials are made available under the terms of the Eclipse
+ * Public License 2.0 which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Michael Bangas (Vector Informatik GmbH) - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.swt.internal.image;
+
+import java.io.*;
+import java.nio.charset.*;
+import java.util.*;
+
+import org.eclipse.swt.*;
+import org.eclipse.swt.graphics.*;
+import org.eclipse.swt.internal.DPIUtil.*;
+
+/**
+ * A {@link FileFormat} implementation for handling SVG (Scalable Vector
+ * Graphics) files.
+ *
+ * This class detects SVG files based on their header and uses a registered
+ * {@link SVGRasterizer} service to rasterize SVG content.
+ *
+ */
+public class SVGFileFormat extends FileFormat {
+
+ /** The instance of the registered {@link SVGRasterizer}. */
+ private static final SVGRasterizer RASTERIZER = ServiceLoader.load(SVGRasterizer.class).findFirst().orElse(null);
+
+ @Override
+ boolean isFileFormat(LEDataInputStream stream) throws IOException {
+ byte[] firstBytes = new byte[5];
+ int bytesRead = stream.read(firstBytes);
+ stream.unread(firstBytes);
+ String header = new String(firstBytes, 0, bytesRead, StandardCharsets.UTF_8).trim();
+ return header.startsWith("> loadFromByteStream(int fileZoom, int targetZoom) {
+ if (RASTERIZER == null) {
+ SWT.error(SWT.ERROR_UNSUPPORTED_FORMAT, null, " [No SVG rasterizer found]");
+ }
+ if (targetZoom <= 0) {
+ SWT.error(SWT.ERROR_INVALID_ARGUMENT, null, " [Cannot rasterize SVG for zoom <= 0]");
+ }
+ try {
+ ImageData rasterizedImageData = RASTERIZER.rasterizeSVG(inputStream, 100 * targetZoom / fileZoom);
+ return List.of(new ElementAtZoom<>(rasterizedImageData, targetZoom));
+ } catch (IOException e) {
+ SWT.error(SWT.ERROR_INVALID_IMAGE, e);
+ return List.of();
+ }
+ }
+
+ @Override
+ void unloadIntoByteStream(ImageLoader loader) {
+ throw new UnsupportedOperationException();
+ }
+}
diff --git a/bundles/org.eclipse.swt/Eclipse SWT/common/org/eclipse/swt/internal/image/SVGRasterizer.java b/bundles/org.eclipse.swt/Eclipse SWT/common/org/eclipse/swt/internal/image/SVGRasterizer.java
new file mode 100644
index 00000000000..9586abfb5c6
--- /dev/null
+++ b/bundles/org.eclipse.swt/Eclipse SWT/common/org/eclipse/swt/internal/image/SVGRasterizer.java
@@ -0,0 +1,35 @@
+/*******************************************************************************
+ * Copyright (c) 2025 Vector Informatik GmbH and others.
+ *
+ * This program and the accompanying materials are made available under the terms of the Eclipse
+ * Public License 2.0 which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Michael Bangas (Vector Informatik GmbH) - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.swt.internal.image;
+
+import java.io.*;
+
+import org.eclipse.swt.graphics.*;
+
+/**
+ * Defines the interface for an SVG rasterizer, responsible for converting SVG
+ * data into rasterized images.
+ */
+public interface SVGRasterizer {
+ /**
+ * Rasterizes an SVG image from the provided {@code InputStream} using the
+ * specified zoom.
+ *
+ * @param stream the SVG image as an {@link InputStream}.
+ * @param zoom the scaling factor (in percent) e.g. {@code 200} for doubled
+ * size. This value must be greater zero.
+ * @return the {@link ImageData} for the rasterized image, or {@code null} if
+ * the input is not a valid SVG file or cannot be processed.
+ */
+ public ImageData rasterizeSVG(InputStream stream, int zoom) throws IOException;
+}
diff --git a/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/AllNonBrowserTests.java b/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/AllNonBrowserTests.java
index af9a1bd017d..2f5ae54445e 100644
--- a/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/AllNonBrowserTests.java
+++ b/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/AllNonBrowserTests.java
@@ -45,6 +45,7 @@
Test_org_eclipse_swt_accessibility_AccessibleControlEvent.class,
Test_org_eclipse_swt_accessibility_AccessibleEvent.class,
Test_org_eclipse_swt_accessibility_AccessibleTextEvent.class,
+ Test_org_eclipse_swt_internal_SVGRasterizer.class,
DPIUtilTests.class})
public class AllNonBrowserTests {
private static List leakedResources;
diff --git a/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_internal_SVGRasterizer.java b/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_internal_SVGRasterizer.java
new file mode 100644
index 00000000000..32a02163727
--- /dev/null
+++ b/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_internal_SVGRasterizer.java
@@ -0,0 +1,95 @@
+/*******************************************************************************
+ * Copyright (c) 2025 Vector Informatik GmbH and others.
+ *
+ * This program and the accompanying materials are made available under the terms of the Eclipse
+ * Public License 2.0 which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Michael Bangas (Vector Informatik GmbH) - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.swt.tests.junit;
+
+import static org.eclipse.swt.tests.junit.SwtTestUtil.assertSWTProblem;
+import static org.junit.Assert.fail;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.io.File;
+import java.net.URL;
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.SWTException;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.graphics.ImageData;
+import org.eclipse.swt.graphics.ImageDataProvider;
+import org.eclipse.swt.graphics.ImageFileNameProvider;
+import org.eclipse.swt.widgets.Display;
+import org.junit.Test;
+
+/**
+ * When executed locally (outside Tycho build), this tests needs to be run as
+ * JUnit plug-in test in order to have the SVGRasterizer fragment on the
+ * classpath.
+ */
+public class Test_org_eclipse_swt_internal_SVGRasterizer {
+
+ @Test
+ public void test_ConstructorLorg_eclipse_swt_graphics_Device_ImageFileNameProvider() {
+ ImageFileNameProvider validImageFileNameProvider = zoom -> getPath("collapseall.svg");
+ Image image = new Image(Display.getDefault(), validImageFileNameProvider);
+ image.dispose();
+
+ ImageFileNameProvider corruptImageFileNameProvider = zoom -> getPath("corrupt.svg");
+ SWTException e = assertThrows(SWTException.class,
+ () -> new Image(Display.getDefault(), corruptImageFileNameProvider));
+ assertSWTProblem("Incorrect exception thrown for provider with corrupt images", SWT.ERROR_INVALID_IMAGE, e);
+ }
+
+ @Test
+ public void test_ConstructorLorg_eclipse_swt_graphics_Device_ImageDataProvider() {
+ ImageDataProvider validImageDataProvider = zoom -> {
+ String fileName = "collapseall.svg";
+ return new ImageData(getPath(fileName));
+ };
+ Image image = new Image(Display.getDefault(), validImageDataProvider);
+ image.dispose();
+
+ ImageDataProvider corruptImageDataProvider = zoom -> {
+ String fileName = "corrupt.svg";
+ return new ImageData(getPath(fileName));
+ };
+ SWTException e = assertThrows(SWTException.class,
+ () -> new Image(Display.getDefault(), corruptImageDataProvider));
+ assertSWTProblem("Incorrect exception thrown for provider with corrupt images", SWT.ERROR_INVALID_IMAGE, e);
+ }
+
+ String getPath(String fileName) {
+ String urlPath = "";
+ String pluginPath = System.getProperty("PLUGIN_PATH");
+ if (pluginPath == null) {
+ URL url = getClass().getClassLoader().getResource(fileName);
+ if (url == null) {
+ fail("URL == null for file " + fileName);
+ }
+ urlPath = url.getFile();
+ } else {
+ urlPath = pluginPath + "/data/" + fileName;
+ }
+ if (File.separatorChar != '/')
+ urlPath = urlPath.replace('/', File.separatorChar);
+ if (SwtTestUtil.isWindows && urlPath.indexOf(File.separatorChar) == 0)
+ urlPath = urlPath.substring(1);
+ urlPath = urlPath.replaceAll("%20", " ");
+ // Fallback when test is locally executed as plug-in test
+ if (!Files.exists(Path.of(urlPath))) {
+ urlPath = Path.of("data/" + fileName).toAbsolutePath().toString();
+ }
+ assertTrue(Files.exists(Path.of(urlPath)), "file not found: " + urlPath);
+ return urlPath;
+ }
+}
\ No newline at end of file
diff --git a/tests/org.eclipse.swt.tests/META-INF/p2.inf b/tests/org.eclipse.swt.tests/META-INF/p2.inf
new file mode 100644
index 00000000000..d63e5216db8
--- /dev/null
+++ b/tests/org.eclipse.swt.tests/META-INF/p2.inf
@@ -0,0 +1,4 @@
+# pull in the applicable implementation fragment at build time (bug 461427)
+requires.0.namespace = org.eclipse.equinox.p2.iu
+requires.0.name = org.eclipse.swt.svg
+requires.0.range = 0.0.0
diff --git a/tests/org.eclipse.swt.tests/data/collapseall.svg b/tests/org.eclipse.swt.tests/data/collapseall.svg
new file mode 100644
index 00000000000..587c3c3497e
--- /dev/null
+++ b/tests/org.eclipse.swt.tests/data/collapseall.svg
@@ -0,0 +1,223 @@
+
+
+
+
diff --git a/tests/org.eclipse.swt.tests/data/corrupt.svg b/tests/org.eclipse.swt.tests/data/corrupt.svg
new file mode 100644
index 00000000000..edac6e7e9f2
--- /dev/null
+++ b/tests/org.eclipse.swt.tests/data/corrupt.svg
@@ -0,0 +1,210 @@
+
+
+
+< inkscape:version="0.91 r13725"
+ sodipodi:docname="collapseall.svg">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ image/svg+xml
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+