SVG Salamander example?

9.4k views Asked by At

I'm toying with Java and SVG Salamander but can't quite get how to render a simple SVG file into a JPanel.

Could someone give me a brief example? Tried to follow the loose tutorial in the official site, but could not find a simple code to get a better understanding.

2

There are 2 answers

0
Andreas B. On BEST ANSWER

The repository has example code. If you want to use the latest, there are a few steps involved:

  1. Install Apache Maven.

  2. Clone the repository somewhere:

    mkdir -p $HOME/dev/java/
    cd $HOME/dev/java
    git clone https://github.com/blackears/svgSalamander
    
  3. Update pom.xml:

    cd svgSalamander/svg-core
    vi pom.xml
    
  4. Change the source, target, and JDK version numbers:

    <maven.compiler.source>1.7</maven.compiler.source>
    <maven.compiler.target>1.7</maven.compiler.target>
    
    <jdkVersion>1.7</jdkVersion>
    
  5. Save pom.xml.

  6. Generate a JAR file using Maven: mvn package

  7. Copy target/svgSalamander-1.1.2.jar (be sure to change the version number is necessary) to the libraries directory of your program (e.g., libs).

If your program is using Gradle for building, update the dependencies to use the local JAR file:

dependencies {
  // SVG
  implementation fileTree(include: ['**/*.jar'], dir: 'libs')
}

If you're using a different build system, you'll have to change the dependencies accordingly. From there, here's a class that uses SVG Salamander to scale and rasterize a vector graphic resource file:

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

  /**
   * Rasterizes a vector graphic to a given size using a {@link BufferedImage}.
   * The rendering hints are set to produce high quality output.
   *
   * @param path   Fully qualified path to the image resource 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 String path, final Dimension dstDim )
      throws SVGException {
    final var diagram = loadDiagram( path );
    final var wDiagram = diagram.getWidth();
    final var hDiagram = diagram.getHeight();
    final var srcDim = new Dimension( (int) wDiagram, (int) hDiagram );

    final var scaled = fit( srcDim, dstDim );
    final var wScaled = (int) scaled.getWidth();
    final var hScaled = (int) scaled.getHeight();

    final var image = new BufferedImage( wScaled, hScaled, TYPE_INT_ARGB );

    final var g = image.createGraphics();
    g.setRenderingHints( RENDERING_HINTS );

    final var transform = g.getTransform();
    transform.setToScale( wScaled / wDiagram, hScaled / hDiagram );

    g.setTransform( transform );
    diagram.render( g );
    g.dispose();

    return image;
  }

  /**
   * 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 );
  }

  /**
   * 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}.
   */
  private 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 );
  }

  /**
   * 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;
  }

  /**
   * Scales the given source {@link Dimension} to the destination
   * {@link Dimension}, maintaining the aspect ratio with respect to
   * the best fit.
   *
   * @param src The original vector graphic dimensions to change.
   * @param dst The desired image dimensions to scale.
   * @return The given source dimensions scaled to the destination dimensions,
   * maintaining the aspect ratio.
   */
  private Dimension fit( final Dimension src, final Dimension dst ) {
    final var srcWidth = src.getWidth();
    final var srcHeight = src.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 Dimension( (int) (srcWidth * ratio), (int) (srcHeight * ratio) );
  }
}

Use the SvgRasterizer as follows:

final var rasterizer = new SvgRasterizer();
final var image = rasterizer.rasterize( "/images/icon.svg", new Dimension( 200, 200 ) );

That image can be added to a Swing component without much effort. For example, here's a JComponent that can be treated much like any other:

import javax.swing.*;
import java.awt.*;

/**
 * Responsible for drawing an image, which can be changed at any time.
 */
public class ImageComponent extends JComponent {
  /**
   * Mutable image.
   */
  private Image mImage;

  ImageComponent( final Image image ) {
    mImage = image;
  }

  @Override
  public Dimension getPreferredSize() {
    // Race-condition guard.
    final var image = mImage;

    return new Dimension(
        image.getWidth( null ), image.getHeight( null )
    );
  }

  @Override
  protected void paintComponent( final Graphics graphics ) {
    super.paintComponent( graphics );

    final var g = (Graphics2D) graphics.create();
    g.drawImage( mImage, 0, 0, this );
  }

  /**
   * Repaints this component using the given image. This is a mutable
   * operation that changes the internal {@link Image} instance.
   *
   * @param image The new image to use for painting.
   */
  public void redraw( final Image image ) {
    mImage = image;
    repaint();
  }
}
0
Oak On

First, you need to somehow create the diagram (com.kitfox.svg.SVGDiagram).

File f = new File(mysvgfile);
SVGUniverse svgUniverse = new SVGUniverse();
SVGDiagram diagram = svgUniverse.getDiagram(svgUniverse.loadSVG(f.toURL()));

Now, when you want to render your file - typically from the panel's paintComponent() method - you only need to do (with g being the Graphics2D instance):

diagram.render(g);

And (as usual?), if you want to draw it in some modified way:

AffineTransform oldTransform = g.getTransform();
g.scale(...);
g.translate(...);
...
diagram.render(g);
g.setTransform(oldTransform);