私はSVG画像を処理するためにBatikを使用しています。 SVGファイルからJava.awt.image.BufferedImageを取得する方法はありますか?
SVGをたとえばPNGにトランスコードし、ImageIO.read()でそのPNGをロードできるトランスコーダーがあることは知っていますが、一時ファイルは必要ありません。
Batik を使用すると、次のようになります。
public static BufferedImage rasterize(File svgFile) throws IOException {
final BufferedImage[] imagePointer = new BufferedImage[1];
// Rendering hints can't be set programatically, so
// we override defaults with a temporary stylesheet.
// These defaults emphasize quality and precision, and
// are more similar to the defaults of other SVG viewers.
// SVG documents can still override these defaults.
String css = "svg {" +
"shape-rendering: geometricPrecision;" +
"text-rendering: geometricPrecision;" +
"color-rendering: optimizeQuality;" +
"image-rendering: optimizeQuality;" +
"}";
File cssFile = File.createTempFile("batik-default-override-", ".css");
FileUtils.writeStringToFile(cssFile, css);
TranscodingHints transcoderHints = new TranscodingHints();
transcoderHints.put(ImageTranscoder.KEY_XML_PARSER_VALIDATING, Boolean.FALSE);
transcoderHints.put(ImageTranscoder.KEY_DOM_IMPLEMENTATION,
SVGDOMImplementation.getDOMImplementation());
transcoderHints.put(ImageTranscoder.KEY_DOCUMENT_ELEMENT_NAMESPACE_URI,
SVGConstants.SVG_NAMESPACE_URI);
transcoderHints.put(ImageTranscoder.KEY_DOCUMENT_ELEMENT, "svg");
transcoderHints.put(ImageTranscoder.KEY_USER_STYLESHEET_URI, cssFile.toURI().toString());
try {
TranscoderInput input = new TranscoderInput(new FileInputStream(svgFile));
ImageTranscoder t = new ImageTranscoder() {
@Override
public BufferedImage createImage(int w, int h) {
return new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
}
@Override
public void writeImage(BufferedImage image, TranscoderOutput out)
throws TranscoderException {
imagePointer[0] = image;
}
};
t.setTranscodingHints(transcoderHints);
t.transcode(input, null);
}
catch (TranscoderException ex) {
// Requires Java 6
ex.printStackTrace();
throw new IOException("Couldn't convert " + svgFile);
}
finally {
cssFile.delete();
}
return imagePointer[0];
}
非常に簡単な方法は、 TwelveMonkeys lib を使用することです。これにより、JavaのImageIO
に画像タイプのサポートが追加されます。
したがって、たとえば、これらをMavenに追加する(または必要なjarファイルをコピーする)だけです。
<dependency>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio-batik</artifactId> <!-- svg -->
<version>3.2.1</version>
</dependency>
<dependency>
<groupId>batik</groupId>
<artifactId>batik-transcoder</artifactId>
<version>1.6-1</version>
</dependency>
そして、あなたはそれを
BufferedImage image = ImageIO.read(svg-file);
Svgリーダーが正しく登録されているかどうかを確認するには、イメージリーダーを印刷します。
Iterator<ImageReader> readers = ImageIO.getImageReadersByFormatName("SVG");
while (readers.hasNext()) {
System.out.println("reader: " + readers.next());
}
Libは追加のパラメーターもサポートしています。 githubのreadme を参照してください。
これが私が使っているものです。これは、BufferedImageが使用されている場所ならどこでも使用できる、独自の静的ファクトリを備えたBufferedImageの拡張です。 getScaledInstance(w、h、hint)の呼び出しが、ラスタライズされた画像ではなくSVGからレンダリングされるように作成しました。 これの副作用は、スケーリングヒントパラメータが意味を持たないことです。 0またはDEFAULTを渡すだけです。グラフィックデータが要求された場合にのみ、遅延レンダリングされるため、ロード/スケールサイクルによってオーバーヘッドが大きくなりすぎないようにする必要があります。
編集:品質ヒントをスケーリングするための上記のCSS構成を使用したサポートを追加しました。編集2:レイジーレンダリングが一貫して機能していませんでした。 render()呼び出しをコンストラクターに入れました。
次の依存関係があります。
これを作ったとき、私はバティック1.8を使いました。 YMMV。
import Java.awt.AlphaComposite;
import Java.awt.Composite;
import Java.awt.Graphics;
import Java.awt.Graphics2D;
import Java.awt.Image;
import Java.awt.image.BufferedImage;
import Java.awt.image.Raster;
import Java.awt.image.WritableRaster;
import Java.io.File;
import Java.io.FileWriter;
import Java.io.IOException;
import Java.io.InputStream;
import Java.net.URL;
import Java.util.HashMap;
import Java.util.Map;
import org.Apache.batik.anim.dom.SAXSVGDocumentFactory;
import org.Apache.batik.bridge.BridgeContext;
import org.Apache.batik.bridge.DocumentLoader;
import org.Apache.batik.bridge.GVTBuilder;
import org.Apache.batik.bridge.UserAgent;
import org.Apache.batik.bridge.UserAgentAdapter;
import org.Apache.batik.gvt.GraphicsNode;
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 org.Apache.batik.util.SVGConstants;
import org.Apache.batik.util.XMLResourceDescriptor;
import org.Apache.commons.logging.Log;
import org.Apache.commons.logging.LogFactory;
import org.w3c.dom.svg.SVGDocument;
public class SVGImage extends BufferedImage {
private static class BufferedImageTranscoder extends ImageTranscoder {
private BufferedImage image = null;
@Override
public BufferedImage createImage(int arg0, int arg1) {
return image;
}
private void setImage(BufferedImage image) {
this.image = image;
}
@Override
public void writeImage(BufferedImage arg0, TranscoderOutput arg1) throws TranscoderException {
}
}
final static GVTBuilder builder = new GVTBuilder();
final static SAXSVGDocumentFactory factory = new SAXSVGDocumentFactory(XMLResourceDescriptor.getXMLParserClassName());
final static UserAgent userAgent = new UserAgentAdapter();
final static DocumentLoader loader = new DocumentLoader(userAgent);
final static BridgeContext bridgeContext = new BridgeContext(userAgent, loader);
static {
bridgeContext.setDynamicState(BridgeContext.STATIC);
}
final static private Log log = LogFactory.getLog(SVGImage.class);
private static final Map<Integer, String> scaleQuality = new HashMap<Integer, String>();
static {
String css = "svg {" +
"shape-rendering: %s;" +
"text-rendering: %s;" +
"color-rendering: %s;" +
"image-rendering: %s;" +
"}";
String precise = "geometricPrecision";
String quality = "optimizeQuality";
String speed = "optimizeSpeed";
String crisp = "crispEdges";
String legible = "optimizeLegibility";
String auto = "auto";
scaleQuality.put(SCALE_DEFAULT, String.format(css, auto, auto, auto, auto));
scaleQuality.put(SCALE_SMOOTH, String.format(css, precise, precise, quality, quality));
scaleQuality.put(SCALE_REPLICATE, String.format(css, speed, speed, speed, speed));
scaleQuality.put(SCALE_AREA_AVERAGING, String.format(css, crisp, legible, auto, auto));
scaleQuality.put(SCALE_FAST, String.format(css, speed, speed, speed, speed));
}
final static BufferedImageTranscoder transcoder = new BufferedImageTranscoder();
public static SVGImage fromSvg(URL resource) throws IOException {
InputStream rs = null;
try {
rs = resource.openStream();
SVGDocument svg = factory.createSVGDocument(resource.toString(), rs);
return fromSvgDocument(resource, svg);
} finally {
if (rs != null) {
try { rs.close(); } catch (IOException ioe) {}
}
}
}
public static SVGImage fromSvgDocument(URL resource, SVGDocument doc) {
GraphicsNode graphicsNode = builder.build(bridgeContext, doc);
Double width = graphicsNode.getBounds().getWidth();
Double height = graphicsNode.getBounds().getHeight();
return new SVGImage(resource, doc, width.intValue(), height.intValue(), SCALE_DEFAULT);
}
boolean hasRendered = false;
private int scalingHint = SCALE_DEFAULT;
final SVGDocument svg;
final URL svgUrl;
private SVGImage(URL resource, SVGDocument doc, int width, int height, int hints) {
super(width, height, TYPE_INT_ARGB);
scalingHint = hints;
svgUrl = resource;
svg = doc;
render();
}
@Override
public void coerceData(boolean isAlphaPremultiplied) {
if (!hasRendered) { render(); }
super.coerceData(isAlphaPremultiplied);
}
@Override
public WritableRaster copyData(WritableRaster outRaster) {
if (!hasRendered) { render(); }
return super.copyData(outRaster);
}
private File createCSS(String css) {
FileWriter cssWriter = null;
File cssFile = null;
try {
cssFile = File.createTempFile("batik-default-override-", ".css");
cssFile.deleteOnExit();
cssWriter = new FileWriter(cssFile);
cssWriter.write(css);
} catch(IOException ioe) {
log.warn("Couldn't write stylesheet; SVG rendered with Batik defaults");
} finally {
if (cssWriter != null) {
try {
cssWriter.flush();
cssWriter.close();
} catch (IOException ioe) {}
}
}
return cssFile;
}
@Override
public WritableRaster getAlphaRaster() {
if (!hasRendered) { render(); }
return super.getAlphaRaster();
}
@Override
public Raster getData() {
if (!hasRendered) { render(); }
return super.getData();
}
@Override
public Graphics getGraphics() {
if (!hasRendered) { render(); }
return super.getGraphics();
}
public Image getScaledInstance(int width, int height, int hints) {
SVGImage newImage = new SVGImage(svgUrl, svg, width, height, hints);
return newImage;
}
private void render() {
TranscodingHints hints = new TranscodingHints();
hints.put(ImageTranscoder.KEY_WIDTH, new Float(getWidth()));
hints.put(ImageTranscoder.KEY_HEIGHT, new Float(getHeight()));
hints.put(ImageTranscoder.KEY_XML_PARSER_VALIDATING, Boolean.FALSE);
hints.put(ImageTranscoder.KEY_DOM_IMPLEMENTATION, svg.getImplementation());
hints.put(ImageTranscoder.KEY_DOCUMENT_ELEMENT_NAMESPACE_URI, SVGConstants.SVG_NAMESPACE_URI);
hints.put(ImageTranscoder.KEY_DOCUMENT_ELEMENT, "svg");
String css = scaleQuality.get(scalingHint);
File cssFile = null;
if (css != null) {
cssFile = createCSS(css);
if (cssFile != null) {
hints.put(ImageTranscoder.KEY_USER_STYLESHEET_URI, cssFile.toURI().toString());
}
}
transcoder.setTranscodingHints(hints);
transcoder.setImage(this);
// This may be a re-render, if the scaling quality hint has changed.
// As such, we force the image into overwrite mode, and kick it back when we're done / fail
Graphics2D gfx = (Graphics2D) super.getGraphics();
Composite savedComposite = gfx.getComposite();
gfx.setComposite(AlphaComposite.Clear);
try {
transcoder.transcode(new TranscoderInput(svg), null);
hasRendered = true;
} catch (TranscoderException te) {
log.warn("Could not transcode " + svgUrl.getPath() + " to raster image; you're going to get a blank BufferedImage of the correct size.");
} finally {
gfx.setComposite(savedComposite);
if (cssFile != null) {
cssFile.delete();
}
}
}
public void setScalingHint(int hint) {
this.scalingHint = hint;
// Forces a re-render
this.hasRendered = false;
}
}