Simply put, I'm looking for a way to make an ImageIcon from an SVG file using the batik library. I don't want to have to raster the SVG to disk first, I just want to be able to pull an svg out of the jar file and have it land as a UI element.
I feel like this should be reasonably easy, but the batik javadocs aren't telling me what I need to know.
(Why batik? Well, we're already using it, so we don't have to run another library past legal.)
It's really quite easy, just not very intuitive.
You need to extend ImageTranscoder
. In the createImage
method you allocate a BufferedImage
, cache it as a member variable, and return it. The writeImage
method is empty. And you'll need to add a getter to retrieve the BufferedImage
.
It will look something like this:
class MyTranscoder extends ImageTranscoder {
private BufferedImage image = null;
public BufferedImage createImage(int w, int h) {
image = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
return image;
}
public void writeImage(BufferedImage img, TranscoderOutput out) {
}
public BufferedImage getImage() {
return image;
}
}
Now, to create an image you create an instance of your transcoder and pass it the desired width and height by setting TranscodingHints
. Finally you transcode from a TranscoderInput to a null target. Then call the getter on your transcoder to obtain the image.
The call looks something like this:
MyTranscoder transcoder = new MyTranscoder();
TranscodingHints hints = new TranscodingHints();
hints.put(ImageTranscoder.KEY_WIDTH, width);
hints.put(ImageTranscoder.KEY_HEIGHT, height);
transcoder.setTranscodingHints(hints);
transcoder.transcode(new TranscoderInput(url), null);
BufferedImage image = transcoder.getImage();
Simple, right? (Yeah, right. Only took me 2 weeks to figure that out. Sigh.)
I have just followed Devon's approach with Batik-1.7
However, in order to make it work I had to make the following additions to the hints object:
MyTranscoder transcoder =new MyTranscoder()
DOMImplementation impl = SVGDOMImplementation.getDOMImplementation();
TranscodingHints hints = new TranscodingHints();
hints.put(ImageTranscoder.KEY_WIDTH, width); // e.g. width=new Float(300)
hints.put(ImageTranscoder.KEY_HEIGHT,height);// e.g. height=new Float(75)
hints.put(ImageTranscoder.KEY_DOM_IMPLEMENTATION, impl.getDOMImplementation());
hints.put(ImageTranscoder.KEY_DOCUMENT_ELEMENT_NAMESPACE_URI,SVGConstants.SVG_NAMESPACE_URI);
hints.put(ImageTranscoder.KEY_DOCUMENT_ELEMENT_NAMESPACE_URI,SVGConstants.SVG_NAMESPACE_URI);
hints.put(ImageTranscoder.KEY_DOCUMENT_ELEMENT, SVGConstants.SVG_SVG_TAG);
hints.put(ImageTranscoder.KEY_XML_PARSER_VALIDATING, false);
transcoder.setTranscodingHints(hints);
TranscoderInput ti=new TranscoderInput(uri)
transcoder.transcode(ti, null);
BufferedImage image = transcoder.getImage();
Seems like something has been updated in batik's XMLAbstractTranscoder( http://svn.apache.org/repos/asf/xmlgraphics/batik/tags/batik-1_7/sources/org/apache/batik/transcoder/XMLAbstractTranscoder.java) with version 1.7.
If ever you no longer wish to include the dependency on Batik in your application you can transform a SVG file directly into Java2D with the Flamingo SVG Transcoder:
http://ebourg.github.com/flamingo-svg-transcoder
It generates icon classes roughly equivalent in size to a compressed SVG file. The code generated has no external dependency.
To avoid passing dom parameters :
transcoder.setTranscodingHints((Map<?, ?>) hints);
I tried using Devon's and John's suggestions, which nearly worked for me. I had to make some tweaks as follows, feel free to use:
package com.corp.util;
import static org.apache.batik.transcoder.SVGAbstractTranscoder.KEY_WIDTH;
import static org.apache.batik.transcoder.XMLAbstractTranscoder.KEY_DOCUMENT_ELEMENT;
import static org.apache.batik.transcoder.XMLAbstractTranscoder.KEY_DOCUMENT_ELEMENT_NAMESPACE_URI;
import static org.apache.batik.transcoder.XMLAbstractTranscoder.KEY_DOM_IMPLEMENTATION;
import static org.apache.batik.util.SVGConstants.SVG_NAMESPACE_URI;
import static org.apache.batik.util.SVGConstants.SVG_SVG_TAG;
import com.google.common.flogger.GoogleLogger;
import org.apache.batik.anim.dom.SVGDOMImplementation;
import org.apache.batik.transcoder.TranscoderException;
import org.apache.batik.transcoder.TranscoderInput;
import org.apache.batik.transcoder.TranscoderOutput;
import org.apache.batik.transcoder.TranscodingHints;
import org.apache.batik.transcoder.image.ImageTranscoder;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import javax.annotation.concurrent.ThreadSafe;
import javax.inject.Singleton;
/** Loads SVG images from disk. See https://en.wikipedia.org/wiki/Scalable_Vector_Graphics. */
@Singleton
@ThreadSafe
public class SvgImageLoader {
private static final GoogleLogger logger = GoogleLogger.forEnclosingClass();
/**
* Reads in an SVG image file and return it as a BufferedImage with the given width and a height
* where the original aspect ratio is preserved.
*
* @param url URL referencing the SVG image file, which is typically an XML file
* @param width width in pixels the returned BufferedImage should be
*
* @return a valid image representing the SVG file
* @throws IOException if the file cannot be parsed as valid SVG
*/
public static BufferedImage loadSvg(URL url, float width) throws IOException {
SvgTranscoder transcoder = new SvgTranscoder();
transcoder.setTranscodingHints(getHints(width));
try {
TranscoderInput input = new TranscoderInput(url.openStream());
transcoder.transcode(input, null);
} catch (TranscoderException e) {
throw new IOException("Error parsing SVG file " + url, e);
}
BufferedImage image = transcoder.getImage();
logger.atInfo().log("Read '%s' SVG image from disk requested with width=%.1f, sized as %dx%d pixels.",
new File(url.getFile()).getName(), width, image.getWidth(), image.getHeight());
return image;
}
private static TranscodingHints getHints(float width) {
TranscodingHints hints = new TranscodingHints();
hints.put(KEY_DOM_IMPLEMENTATION, SVGDOMImplementation.getDOMImplementation());
hints.put(KEY_DOCUMENT_ELEMENT_NAMESPACE_URI, SVG_NAMESPACE_URI);
hints.put(KEY_DOCUMENT_ELEMENT, SVG_SVG_TAG);
hints.put(KEY_WIDTH, width);
return hints;
}
private static class SvgTranscoder extends ImageTranscoder {
private BufferedImage image = null;
@Override
public BufferedImage createImage(int width, int height) {
image = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
return image;
}
@Override
public void writeImage(BufferedImage img, TranscoderOutput out) {}
BufferedImage getImage() {
return image;
}
}
}