Skip to content

Rasterization of SVGs at Runtime for Eclipse Icons #1638

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Mar 25, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions bundles/org.eclipse.swt.svg/.classpath
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-17"/>
<classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
<classpathentry kind="src" path="src"/>
<classpathentry kind="output" path="bin"/>
</classpath>
28 changes: 28 additions & 0 deletions bundles/org.eclipse.swt.svg/.project
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>org.eclipse.swt.svg</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.pde.ManifestBuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.pde.SchemaBuilder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.pde.PluginNature</nature>
<nature>org.eclipse.jdt.core.javanature</nature>
</natures>
</projectDescription>
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
eclipse.preferences.version=1
encoding/<project>=UTF-8
10 changes: 10 additions & 0 deletions bundles/org.eclipse.swt.svg/.settings/org.eclipse.jdt.core.prefs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
eclipse.preferences.version=1
org.eclipse.jdt.core.compiler.codegen.targetPlatform=17
org.eclipse.jdt.core.compiler.compliance=17
org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled
org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=warning
org.eclipse.jdt.core.compiler.release=enabled
org.eclipse.jdt.core.compiler.source=17
org.eclipse.jdt.core.incompleteClasspath=warning
13 changes: 13 additions & 0 deletions bundles/org.eclipse.swt.svg/META-INF/MANIFEST.MF
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-SymbolicName: org.eclipse.swt.svg
Bundle-Version: 3.130.0.qualifier
Automatic-Module-Name: org.eclipse.swt.svg
Bundle-Name: %fragmentName
Bundle-Vendor: %providerName
Bundle-RequiredExecutionEnvironment: JavaSE-17
Fragment-Host: org.eclipse.swt
Import-Package: com.github.weisj.jsvg;version="[1.7.0,2.0.0)",
com.github.weisj.jsvg.geometry.size;version="[1.7.0,2.0.0)",
com.github.weisj.jsvg.parser;version="[1.7.0,2.0.0)"
Export-Package: org.eclipse.swt.svg
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
org.eclipse.swt.svg.JSVGRasterizer
15 changes: 15 additions & 0 deletions bundles/org.eclipse.swt.svg/build.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
source.. = src/
output.. = bin/
bin.includes = META-INF/,\
.,\
fragment.properties

tycho.pomless.parent = ../../local-build/local-build-parent
jars.extra.classpath = platform:/plugin/org.eclipse.swt.cocoa.macosx.aarch64,\
platform:/plugin/org.eclipse.swt.cocoa.macosx.x86_64,\
platform:/plugin/org.eclipse.swt.gtk.linux.aarch64,\
platform:/plugin/org.eclipse.swt.gtk.linux.ppc64le,\
platform:/plugin/org.eclipse.swt.gtk.linux.riscv64,\
platform:/plugin/org.eclipse.swt.gtk.linux.x86_64,\
platform:/plugin/org.eclipse.swt.win32.win32.aarch64,\
platform:/plugin/org.eclipse.swt.win32.win32.x86_64
14 changes: 14 additions & 0 deletions bundles/org.eclipse.swt.svg/fragment.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
###############################################################################
# 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
###############################################################################
fragmentName = SWT SVG Rendering Support
providerName = Eclipse.org
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
/*******************************************************************************
* 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.svg;

import static java.awt.RenderingHints.KEY_ALPHA_INTERPOLATION;
import static java.awt.RenderingHints.KEY_ANTIALIASING;
import static java.awt.RenderingHints.KEY_COLOR_RENDERING;
import static java.awt.RenderingHints.KEY_DITHERING;
import static java.awt.RenderingHints.KEY_FRACTIONALMETRICS;
import static java.awt.RenderingHints.KEY_INTERPOLATION;
import static java.awt.RenderingHints.KEY_RENDERING;
import static java.awt.RenderingHints.KEY_STROKE_CONTROL;
import static java.awt.RenderingHints.KEY_TEXT_ANTIALIASING;
import static java.awt.RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY;
import static java.awt.RenderingHints.VALUE_ANTIALIAS_ON;
import static java.awt.RenderingHints.VALUE_COLOR_RENDER_QUALITY;
import static java.awt.RenderingHints.VALUE_DITHER_DISABLE;
import static java.awt.RenderingHints.VALUE_FRACTIONALMETRICS_ON;
import static java.awt.RenderingHints.VALUE_INTERPOLATION_BICUBIC;
import static java.awt.RenderingHints.VALUE_RENDER_QUALITY;
import static java.awt.RenderingHints.VALUE_STROKE_PURE;
import static java.awt.RenderingHints.VALUE_TEXT_ANTIALIAS_ON;

import java.awt.Graphics2D;
import java.awt.RenderingHints.Key;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferInt;
import java.io.IOException;
import java.io.InputStream;
import java.util.Map;

import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.ImageData;
import org.eclipse.swt.graphics.PaletteData;
import org.eclipse.swt.internal.image.SVGRasterizer;

import com.github.weisj.jsvg.SVGDocument;
import com.github.weisj.jsvg.geometry.size.FloatSize;
import com.github.weisj.jsvg.parser.LoaderContext;
import com.github.weisj.jsvg.parser.SVGLoader;

/**
* A rasterizer implementation for converting SVG data into rasterized images.
* This class uses the third party library JSVG for the raterization of SVG
* images.
*/
public class JSVGRasterizer implements SVGRasterizer {

private static final SVGLoader SVG_LOADER = new SVGLoader();

private final static Map<Key, Object> 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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4465,7 +4465,6 @@ public class SWT {

/**
* Image format constant indicating a SVG format image (value is 8).
* <br>Note that this is a <em>HINT</em> and is currently only supported on GTK.
*
* @since 3.113
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
* <p>
* This class detects SVG files based on their header and uses a registered
* {@link SVGRasterizer} service to rasterize SVG content.
* </p>
*/
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("<?xml") || header.startsWith("<svg");
}

@Override
List<ElementAtZoom<ImageData>> 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();
}
}
Original file line number Diff line number Diff line change
@@ -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;
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<Error> leakedResources;
Expand Down
Loading
Loading