Can i use SVG Salamander to rasterize SVGs into PNG files?
Yes, to load an SVG document using SVG Salamander:
- Create a
BufferedImage
. - Create a
Graphics2D
context from thatBufferedImage
. - Call
render()
onSVGDiagram
to draw the image.
The process can be simplified using SVGIcon
, which handles the Salamander internals, allowing the object to act as an ordinary Swing Icon
.
To rasterize multiple SVG files on the command line, there's an Ant task for such conversions. See the documentation for details.
What follows is an example rasterizer that, given a path to an SVG resource file, will load and render a vector graphic onto a BufferedImage
. No warranty, no support.
SvgRasterizer.java
import com.kitfox.svg.SVGDiagram;
import com.kitfox.svg.SVGException;
import com.kitfox.svg.SVGUniverse;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.net.URL;
import java.util.Map;
import static java.awt.RenderingHints.*;
import static java.awt.image.BufferedImage.TYPE_INT_ARGB;
/**
* Responsible for converting SVG images into rasterized PNG images.
*/
public class SvgRasterizer {
public 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
);
private final static SVGUniverse sRenderer = new SVGUniverse();
/**
* Loads the resource specified by the given path into an instance of
* {@link SVGDiagram} that can be rasterized into a bitmap format. The
* {@link SVGUniverse} class will
*
* @param path The full path (starting at the root), relative to the
* application or JAR file's resources directory.
* @return An {@link SVGDiagram} that can be rasterized onto a
* {@link BufferedImage}.
*/
public SVGDiagram loadDiagram( final String path ) {
final var url = getResourceUrl( path );
final var uri = sRenderer.loadSVG( url );
final var diagram = sRenderer.getDiagram( uri );
return applySettings( diagram );
}
/**
* A reusable method to help compute the scaling factor between the
* given {@link SVGDiagram} image and the target {@link Dimension}s.
*
* @param diagram A 2-dimensional vector graphic having a width and height.
* @param dstDim The image's target dimensions.
* @return A key-value pair of the source image dimensions (key) and the
* scaled image dimensions (value).
*/
public DimensionTuple calculateScale(
final SVGDiagram diagram, final Dimension dstDim ) {
final var srcDim = new ScalableDimension(
(int) diagram.getWidth(), (int) diagram.getHeight()
);
final var scaled = srcDim.scale( dstDim );
return new DimensionTuple( srcDim, scaled );
}
/**
* Rasterizes a vector graphic to a given size using a {@link BufferedImage}.
* The rendering hints are set to produce high quality output.
*
* @param diagram The diagram to rasterize.
* @param tuple The source and destination image dimensions.
* @return The rasterized {@link Image}.
* @throws SVGException Could not open, read, parse, or render SVG data.
*/
public BufferedImage rasterize(
final SVGDiagram diagram, final DimensionTuple tuple )
throws SVGException {
final var scaled = tuple.getValue();
final var wScaled = (int) scaled.getWidth();
final var hScaled = (int) scaled.getHeight();
final var image = new BufferedImage( wScaled, hScaled, TYPE_INT_ARGB );
final var graphics = image.createGraphics();
graphics.setRenderingHints( RENDERING_HINTS );
final var transform = graphics.getTransform();
transform.setToScale( tuple.getWidthRatio(), tuple.getHeightRatio() );
graphics.setTransform( transform );
diagram.render( graphics );
graphics.dispose();
return image;
}
/**
* Rasterizes a vector graphic to a given size using a {@link BufferedImage}.
* The rendering hints are set to produce high quality output.
*
* @param diagram The diagram to rasterize.
* @param dstDim The output image dimensions.
* @return The rasterized {@link Image}.
* @throws SVGException Could not open, read, parse, or render SVG data.
*/
public Image rasterize(
final SVGDiagram diagram, final Dimension dstDim ) throws SVGException {
return rasterize( diagram, calculateScale( diagram, dstDim ) );
}
/**
* Gets an instance of {@link URL} that references a file in the
* application's resources.
*
* @param path The full path (starting at the root), relative to the
* application or JAR file's resources directory.
* @return A {@link URL} to the file or {@code null} if the path does not
* point to a resource.
*/
private URL getResourceUrl( final String path ) {
return SvgRasterizer.class.getResource( path );
}
/**
* Instructs the SVG renderer to rasterize the image even if it would be
* clipped.
*
* @param diagram The {@link SVGDiagram} to render.
* @return The same instance with ignore clip heuristics set to {@code true}.
*/
private SVGDiagram applySettings( final SVGDiagram diagram ) {
diagram.setIgnoringClipHeuristic( true );
return diagram;
}
}
You'll also need the ScalableDimension
and DimensionTuple
classes.
ScalableDimension.java
import java.awt.*;
public final class ScalableDimension extends Dimension {
/**
* Delegates construction to the superclass.
*
* @param w The dimension's width.
* @param h The dimension's height.
*/
public ScalableDimension( final int w, final int h ) {
super( w, h );
}
/**
* Delegates construction to this class.
*
* @param w The width, cast to an integer.
* @param h The height, cast to an integer.
*/
@SuppressWarnings("unused")
public ScalableDimension( final double w, final double h ) {
this( (int) w, (int) h );
}
/**
* Scales the given source {@link Dimension} to the destination
* {@link Dimension}, maintaining the aspect ratio with respect to
* the best fit.
*
* @param dst The desired image dimensions to scale.
* @return The given source dimensions scaled to the destination dimensions,
* maintaining the aspect ratio.
*/
public Dimension scale( final Dimension dst ) {
final var srcWidth = getWidth();
final var srcHeight = getHeight();
// Determine the ratio that will have the best fit.
final var ratio = Math.min(
dst.getWidth() / srcWidth, dst.getHeight() / srcHeight
);
// Scale both dimensions with respect to the best fit ratio.
return new ScalableDimension( (int) (srcWidth * ratio),
(int) (srcHeight * ratio) );
}
}
DimensionTuple.java
import java.awt.*;
public class DimensionTuple extends Pair<Dimension, Dimension> {
/**
* Associates a new {@link Dimension} tuple.
*
* @param key The key for this key-value pairing.
* @param value The value for this key-value pairing.
*/
public DimensionTuple( final Dimension key, final Dimension value ) {
super( key, value );
}
/**
* Returns the ratio of the value width to the key width.
*
* @return A unit-less ratio between the value and key widths.
*/
public double getWidthRatio() {
return getValue().getWidth() / getKey().getWidth();
}
/**
* Returns the ratio of the value height to the key height.
*
* @return A unit-less ratio between the value and key heights.
*/
public double getHeightRatio() {
return getValue().getHeight() / getKey().getHeight();
}
}
If you're seeing jagged edges, you can fix that by adding a graphics rendering hint.
Also, it's a good idea to call dispose() when you're finished with your graphics context.
Graphics2D ig2 = bi.createGraphics();
ig2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
diagram.render(ig2);
ig2.dispose();
ImageIO.write(bi, "PNG", new File("./yourImageName.png"));