Can i use SVG Salamander to rasterize SVGs into PNG files?

3.5k views Asked by At

I have seen it cause SVG-Salamander is small enough to my projects. But I don't know if i can use it for that neither how to do it.

I've use this code :

public static void main(String[] args) throws IOException, SVGException {
    // TODO Auto-generated method stub

    File f = new File("./src/game_scheme.svg");
    SVGUniverse svgUniverse = new SVGUniverse();
    SVGDiagram diagram = svgUniverse.getDiagram(svgUniverse.loadSVG(f.toURL()));
    BufferedImage bi = new BufferedImage(320, 240, BufferedImage.TYPE_INT_ARGB);
    Graphics2D ig2 = bi.createGraphics();
    diagram.render(ig2);
    ImageIO.write(bi, "PNG", new File("./yourImageName.png"));

}

But images are not smooth :( , any idea?

2

There are 2 answers

0
kitfox On BEST ANSWER

Yes, to load an SVG document using SVG Salamander:

  1. Create a BufferedImage.
  2. Create a Graphics2D context from that BufferedImage.
  3. Call render() on SVGDiagram 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();
  }
}
1
kitfox On

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"));