Skip to content

Commit

Permalink
Introduce SVG rasterizer logic
Browse files Browse the repository at this point in the history
  • Loading branch information
Michael5601 committed Dec 3, 2024
1 parent 2ce8542 commit 1efaf36
Show file tree
Hide file tree
Showing 14 changed files with 377 additions and 12 deletions.
8 changes: 8 additions & 0 deletions bundles/org.eclipse.swt.svg/.classpath
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?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="lib" path="libs/jsvg-1.6.1.jar"/>
<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
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
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
10 changes: 10 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,10 @@
Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: SvgPlugin
Bundle-SymbolicName: org.eclipse.swt.svg
Bundle-Version: 1.0.0.qualifier
Automatic-Module-Name: org.eclipse.swt.svgPlugin
Bundle-RequiredExecutionEnvironment: JavaSE-17
Export-Package: org.eclipse.swt.svg
Import-Package: org.eclipse.swt.graphics
Bundle-ClassPath: ., libs/jsvg-1.6.1.jar
5 changes: 5 additions & 0 deletions bundles/org.eclipse.swt.svg/build.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
source.. = src/
output.. = bin/
bin.includes = META-INF/,\
.,\
libs/jsvg-1.6.1.jar
Binary file added bundles/org.eclipse.swt.svg/libs/jsvg-1.6.1.jar
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package org.eclipse.swt.svg;

import static java.awt.RenderingHints.*;

import java.awt.*;
import java.awt.image.*;
import java.io.*;
import java.nio.charset.*;
import java.util.*;
import org.eclipse.swt.graphics.ISVGRasterizer;
import org.eclipse.swt.graphics.SVGRasterizerRegistry;

import com.github.weisj.jsvg.*;
import com.github.weisj.jsvg.geometry.size.*;
import com.github.weisj.jsvg.parser.*;

/**
* A rasterizer implementation for converting SVG data into rasterized images.
* This class implements the {@code ISVGRasterizer} interface.
*
* @since 3.128
*/
public class SVGRasterizer implements ISVGRasterizer {

/**
* Initializes the SVG rasterizer by registering an instance of this rasterizer
* with the {@link SVGRasterizerRegistry}.
*/
public static void intializeSVGRasterizer() {
SVGRasterizerRegistry.register(new SVGRasterizer());
}

private final static Map<Object, 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 BufferedImage rasterizeSVG(byte[] bytes, int zoom) throws IOException {
SVGLoader loader = new SVGLoader();
SVGDocument svgDocument = null;
if (this.isSVGFile(bytes)) {
try (InputStream stream = new ByteArrayInputStream(bytes)) {
svgDocument = loader.load(stream, null, LoaderContext.createDefault());
}
if (svgDocument != null) {
double scalingFactor = zoom / 100.0;
FloatSize size = svgDocument.size();
double originalWidth = size.getWidth();
double originalHeight = size.getHeight();
int scaledWidth = (int) Math.round(originalWidth * scalingFactor);
int scaledHeight = (int) Math.round(originalHeight * scalingFactor);
BufferedImage image = new BufferedImage(scaledWidth, scaledHeight, BufferedImage.TYPE_INT_ARGB);
Graphics2D g = image.createGraphics();
g.setRenderingHints(RENDERING_HINTS);
g.scale(scalingFactor, scalingFactor);
svgDocument.render(null, g);
g.dispose();
return image;
}
}
return null;
}

private boolean isSVGFile(byte[] data) throws IOException {
String content = new String(data, 0, Math.min(data.length, 512), StandardCharsets.UTF_8);
return content.contains("<svg");
}

@Override
public boolean isSVGFile(InputStream inputStream) throws IOException {
if (inputStream == null) {
throw new IllegalArgumentException("InputStream cannot be null");
}
byte[] data = inputStream.readNBytes(512);
return isSVGFile(data);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package org.eclipse.swt.graphics;

import java.awt.image.*;
import java.io.*;

/**
* Defines the interface for an SVG rasterizer, responsible for converting
* SVG data into rasterized images.
*
* @since 3.129
*/
public interface ISVGRasterizer {

/**
* Rasterizes an SVG image from the provided byte array, using the specified zoom factor.
*
* @param bytes the SVG image as a byte array.
* @param zoom the zoom factor.
* @return a {@link BufferedImage} containing the rasterized image, or {@code null} if the input
* is not a valid SVG file or cannot be processed.
* @throws IOException if an error occurs while reading the SVG data.
*/
public BufferedImage rasterizeSVG(byte[] bytes, int zoom) throws IOException;

/**
* Determines whether the given {@link InputStream} contains a SVG file.
*
* @param inputStream the input stream to check.
* @return {@code true} if the input stream contains SVG content; {@code false} otherwise.
* @throws IOException if an error occurs while reading the stream.
* @throws IllegalArgumentException if the input stream is {@code null}.
*/
public boolean isSVGFile(InputStream inputStream) throws IOException;
}
Original file line number Diff line number Diff line change
Expand Up @@ -331,7 +331,39 @@ scanlinePad, checkData(data), 0, null,
* @see ImageLoader#load(InputStream)
*/
public ImageData(InputStream stream) {
ImageData[] data = ImageDataLoader.load(stream);
this(stream, 0);
}

/**
* Constructs an <code>ImageData</code> loaded from the specified
* input stream. Throws an error if an error occurs while loading
* the image, or if the image has an unsupported type. Application
* code is still responsible for closing the input stream.
* <p>
* This constructor is provided for convenience when loading a single
* image only. If the stream contains multiple images, only the first
* one will be loaded. To load multiple images, use
* <code>ImageLoader.load()</code>.
* </p>
*
* @param stream the input stream to load the image from (must not be null)
* @param zoom the zoom factor to apply when rasterizing a SVG.
* A value of 0 means that the standard method for loading should be used.
*
* @exception IllegalArgumentException <ul>
* <li>ERROR_NULL_ARGUMENT - if the stream is null</li>
* </ul>
* @exception SWTException <ul>
* <li>ERROR_IO - if an IO error occurs while reading from the stream</li>
* <li>ERROR_INVALID_IMAGE - if the image stream contains invalid data</li>
* <li>ERROR_UNSUPPORTED_FORMAT - if the image stream contains an unrecognized format</li>
* </ul>
*
* @see ImageLoader#load(InputStream)
* @since 3.129
*/
public ImageData(InputStream stream, int zoom) {
ImageData[] data = ImageDataLoader.load(stream, zoom);
if (data.length < 1) SWT.error(SWT.ERROR_INVALID_IMAGE);
ImageData i = data[0];
setAllFields(
Expand Down Expand Up @@ -377,7 +409,36 @@ public ImageData(InputStream stream) {
* </ul>
*/
public ImageData(String filename) {
ImageData[] data = ImageDataLoader.load(filename);
this(filename, 0);
}

/**
* Constructs an <code>ImageData</code> loaded from a file with the
* specified name. Throws an error if an error occurs loading the
* image, or if the image has an unsupported type.
* <p>
* This constructor is provided for convenience when loading a single
* image only. If the file contains multiple images, only the first
* one will be loaded. To load multiple images, use
* <code>ImageLoader.load()</code>.
* </p>
*
* @param filename the name of the file to load the image from (must not be null)
* @param zoom the zoom factor to apply when rasterizing a SVG.
* A value of 0 means that the standard method for loading should be used.
* @exception IllegalArgumentException <ul>
* <li>ERROR_NULL_ARGUMENT - if the file name is null</li>
* </ul>
* @exception SWTException <ul>
* <li>ERROR_IO - if an IO error occurs while reading from the file</li>
* <li>ERROR_INVALID_IMAGE - if the image file contains invalid data</li>
* <li>ERROR_UNSUPPORTED_FORMAT - if the image file contains an unrecognized format</li>
* </ul>
*
* @since 3.129
*/
public ImageData(String filename, int zoom) {
ImageData[] data = ImageDataLoader.load(filename, zoom);
if (data.length < 1) SWT.error(SWT.ERROR_INVALID_IMAGE);
ImageData i = data[0];
setAllFields(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,16 @@ public static ImageData[] load(InputStream stream) {
return new ImageLoader().load(stream);
}

public static ImageData[] load(String filename) {
public static ImageData[] load(InputStream stream, int zoom) {
return new ImageLoader().load(stream, zoom);
}

public static ImageData[] load(String filename) {
return new ImageLoader().load(filename);
}

public static ImageData[] load(String filename, int zoom) {
return new ImageLoader().load(filename, zoom);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package org.eclipse.swt.graphics;

/**
* A registry for managing the instance of an {@link ISVGRasterizer} implementation.
* This allows for the registration and retrieval of a single rasterizer instance.
*
* @since 3.129
*/
public class SVGRasterizerRegistry {

/**
* The instance of the registered {@link ISVGRasterizer}.
*/
private static ISVGRasterizer rasterizer;

/**
* Registers the provided implementation of {@link ISVGRasterizer}.
* If a rasterizer has already been registered, subsequent calls to this method
* will have no effect.
*
* @param implementation the {@link ISVGRasterizer} implementation to register.
*/
public static void register(ISVGRasterizer implementation) {
if (rasterizer == null) {
rasterizer = implementation;
}
}

/**
* Retrieves the currently registered {@link ISVGRasterizer} implementation.
*
* @return the registered {@link ISVGRasterizer}, or {@code null} if no implementation
* has been registered.
*/
public static ISVGRasterizer getRasterizer() {
return rasterizer;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -468,7 +468,7 @@ public Image(Device device, ImageData source, ImageData mask) {
public Image (Device device, InputStream stream) {
super(device);
initialNativeZoom = DPIUtil.getNativeDeviceZoom();
ImageData data = DPIUtil.autoScaleUp(device, new ElementAtZoom<>(new ImageData (stream), 100));
ImageData data = DPIUtil.autoScaleUp(device, new ElementAtZoom<>(new ImageData (stream, getZoom()), 100));
init(data, getZoom());
init();
this.device.registerResourceWithZoomSupport(this);
Expand Down Expand Up @@ -510,7 +510,7 @@ public Image (Device device, String filename) {
super(device);
if (filename == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
initialNativeZoom = DPIUtil.getNativeDeviceZoom();
ImageData data = DPIUtil.autoScaleUp(device, new ElementAtZoom<>(new ImageData (filename), 100));
ImageData data = DPIUtil.autoScaleUp(device, new ElementAtZoom<>(new ImageData (filename, getZoom()), 100));
init(data, getZoom());
init();
this.device.registerResourceWithZoomSupport(this);
Expand Down Expand Up @@ -553,10 +553,10 @@ public Image(Device device, ImageFileNameProvider imageFileNameProvider) {
if (fileName.zoom() == getZoom()) {
ImageHandle imageMetadata = initNative (fileName.element(), getZoom());
if (imageMetadata == null) {
init(new ImageData (fileName.element()), getZoom());
init(new ImageData (fileName.element(), getZoom()), getZoom());
}
} else {
ImageData resizedData = DPIUtil.autoScaleImageData (device, new ImageData (fileName.element()), fileName.zoom());
ImageData resizedData = DPIUtil.autoScaleImageData (device, new ImageData (fileName.element(), getZoom()), fileName.zoom());
init(resizedData, getZoom());
}
init();
Expand Down Expand Up @@ -753,7 +753,7 @@ private ImageHandle getImageMetadata(int zoom) {

if (imageFileNameProvider != null) {
ElementAtZoom<String> imageCandidate = DPIUtil.validateAndGetImagePathAtZoom (imageFileNameProvider, zoom);
ImageData imageData = new ImageData (imageCandidate.element());
ImageData imageData = new ImageData (imageCandidate.element(), zoom);
if (imageCandidate.zoom() == zoom) {
/* Release current native resources */
ImageHandle imageMetadata = initNative(imageCandidate.element(), zoom);
Expand Down Expand Up @@ -1389,7 +1389,7 @@ public ImageData getImageData (int zoom) {
return DPIUtil.scaleImageData (device, data.element(), zoom, data.zoom());
} else if (imageFileNameProvider != null) {
ElementAtZoom<String> fileName = DPIUtil.validateAndGetImagePathAtZoom (imageFileNameProvider, zoom);
return DPIUtil.scaleImageData (device, new ImageData (fileName.element()), zoom, fileName.zoom());
return DPIUtil.scaleImageData (device, new ImageData (fileName.element(), zoom), zoom, fileName.zoom());
}

// if a GC is initialized with an Image (memGC != null), the image data must not be resized, because it would
Expand Down
Loading

0 comments on commit 1efaf36

Please sign in to comment.