java 如何从 SVG 获取 BufferedImage?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/11435671/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How to get a BufferedImage from a SVG?
提问by mr_georg
I am using Batik to handle SVG images. Is there any way to get a java.awt.image.BufferedImage from a SVG-file?
我正在使用蜡染处理 SVG 图像。有没有办法从 SVG 文件中获取 java.awt.image.BufferedImage ?
I know there are transcoders, with which I could transcode the SVG into, for example, a PNG and then load that PNG with ImageIO.read()· But I don't want to have the temporary file.
我知道有转码器,我可以使用转码器将 SVG 转码为例如 PNG,然后使用 ImageIO.read()· 加载该 PNG 但我不想拥有临时文件。
回答by elias
Using Batik, something like this:
使用蜡染,是这样的:
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];
}
回答by Patrick Favre
A very easy way is to use the TwelveMonkeys libwhich adds additional image type support to java's ImageIO
一个非常简单的方法是使用TwelveMonkeys 库,它为 java 的添加了额外的图像类型支持ImageIO
So for example you just add these to your maven (or copy the needed jars):
例如,您只需将这些添加到您的 maven(或复制所需的 jars):
<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>
And then you just read it with
然后你只需阅读它
BufferedImage image = ImageIO.read(svg-file);
To check if the svg reader is registered correctly you could print out the image readers:
要检查 svg 阅读器是否已正确注册,您可以打印出图像阅读器:
Iterator<ImageReader> readers = ImageIO.getImageReadersByFormatName("SVG");
while (readers.hasNext()) {
System.out.println("reader: " + readers.next());
}
The lib also supports additional params, see readme on github.
该库还支持其他参数,请参阅github 上的自述文件。
回答by Fordi
This is what I use. It's an extension of BufferedImage with its own static factory that can be used wherever a BufferedImage is used. I wrote it so that any call to getScaledInstance(w, h, hint) will render from the SVG, not the rasterized image. A side-effect of this is that the scaling hint parameter has no meaning; you can just pass 0 or DEFAULT to that.It renders lazily - only when graphics data is requested - so the load / scale cycle shouldn't give you too much overhead.
这就是我使用的。它是 BufferedImage 的扩展,具有自己的静态工厂,可以在使用 BufferedImage 的任何地方使用。我编写它以便对 getScaledInstance(w, h,hint) 的任何调用都将从 SVG 呈现,而不是从光栅化图像呈现。 这样做的副作用是缩放提示参数没有意义;您可以将 0 或 DEFAULT 传递给它。它延迟渲染 - 仅在请求图形数据时 - 所以加载/缩放周期不应该给你太多的开销。
Edit: I added support using the above CSS config for scaling quality hints. Edit 2: Lazy rendering wasn't working consistently; I put the render() call into the constructor.
编辑:我使用上述 CSS 配置添加了对缩放质量提示的支持。编辑 2:延迟渲染无法始终如一地工作;我将 render() 调用放入构造函数中。
It has the following dependencies:
它具有以下依赖项:
- org.apache.xmlgraphics:batik-anim
- org.apache.xmlgraphics:batik-bridge
- org.apache.xmlgraphics:batik-gvt
- org.apache.xmlgraphics:batik-transcoder
- org.apache.xmlgraphics:batik-util
- xml-apis:xml-apis-ext
- commons-logging:commons-logging
- org.apache.xmlgraphics:batik-anim
- org.apache.xmlgraphics:batik-bridge
- org.apache.xmlgraphics:batik-gvt
- org.apache.xmlgraphics:batik-transcoder
- org.apache.xmlgraphics:batik-util
- xml-apis:xml-apis-ext
- 公共日志:公共日志
When I made this, I used batik 1.8; YMMV.
当我做这个时,我使用了蜡染 1.8;天啊。
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;
}
}