【问题标题】:How to get a BufferedImage from a SVG?如何从 SVG 中获取 BufferedImage?
【发布时间】:2012-07-11 05:58:50
【问题描述】:

我正在使用 Batik 来处理 SVG 图像。有没有办法从 SVG 文件中获取 java.awt.image.BufferedImage?

我知道有转码器,我可以使用转码器将 SVG 转码为例如 PNG,然后使用 ImageIO.read() 加载该 PNG,但我不想拥有临时文件。

【问题讨论】:

    标签: java svg batik


    【解决方案1】:

    使用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];
    }
    

    【讨论】:

    • 创建 imagePointer 数组与仅将 imagePointer 分配给像这样的局部变量 bbgen.net/blog/2011/06/java-svg-to-bufferedimage 有优势吗?
    • 其实没有。这只是避免writeImage 方法中的编译错误的一种解决方法。
    • 我想出了如何使用例如transcoderHints.put(ImageTranscoder.KEY_WIDTH... 来设置大小,但是如何将输出图像缩放一个因子?
    • @vincent "by a factor" 你的意思是缩放 X%?
    • @elias 没错。您的脚本以某种方式自动确定大小,这很棒。但是我无法使用百分比值进行缩放。我可以以某种方式单独提取大小,并在提示中使用它。还是有更好的办法?
    【解决方案2】:

    一个非常简单的方法是使用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());
    }
    

    该库还支持其他参数,请参阅readme on github

    【讨论】:

    • 这是一个奇怪的库。它没有在线文档。它没有例子。它有依赖性问题(请参阅您与第三方的帖子)。而且图标调整大小加载有问题。我被引导到少量代码并后悔了。
    • 这对我来说不适用于 Java13。我愿意搜索库的当前版本: com.twelvemonkeys.imageioimageio-batik3.4.2org.apache.xmlgraphicsbatik-transcoder1.11
    • 版本是1.6-1还是1.6.1
    【解决方案3】:

    这是我使用的。它是 BufferedImage 的扩展,具有自己的静态工厂,可以在使用 BufferedImage 的任何地方使用。我编写它以便对 getScaledInstance(w, h, hint) 的任何调用都将从 SVG 渲染,而不是光栅化图像。 这样做的副作用是缩放提示参数没有意义;您可以将 0 或 DEFAULT 传递给它。 它会延迟渲染 - 仅在请求图形数据时 - 因此加载/缩放周期不应给您带来太多开销。

    编辑:我使用上述 CSS 配置添加了对缩放质量提示的支持。 编辑 2:延迟渲染无法始终如一地工作;我将 render() 调用放入构造函数中。

    它具有以下依赖关系:

    • org.apache.xmlgraphics:batik-anim
    • org.apache.xmlgraphics:蜡染桥
    • org.apache.xmlgraphics:batik-gvt
    • org.apache.xmlgraphics:batik-transcoder
    • org.apache.xmlgraphics:batik-util
    • xml-apis:xml-apis-ext
    • commons-logging:commons-logging

    当我制作这个时,我使用的是 batik 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;
        }
    }
    

    【讨论】:

    • 顺便说一句,我想我会窃取上面的 CSS 转码器提示来模拟 BufferedImage 的缩放提示。
    【解决方案4】:

    9 年后,这是 Batik 的另一个解决方案:

    实现'org.apache.xmlgraphics:batik-all:1.14'

    实现'org.apache.xmlgraphics:batik-swing:1.14'

    import lombok.extern.slf4j.Slf4j;
    import org.apache.batik.transcoder.Transcoder;
    import org.apache.batik.transcoder.TranscoderException;
    import org.apache.batik.transcoder.TranscoderInput;
    import org.apache.batik.transcoder.TranscoderOutput;
    import org.apache.batik.transcoder.image.PNGTranscoder;
    import org.springframework.stereotype.Component;
    
    import javax.imageio.ImageIO;
    import java.awt.image.BufferedImage;
    import java.io.ByteArrayInputStream;
    import java.io.ByteArrayOutputStream;
    import java.io.File;
    import java.io.FileInputStream;
    import java.io.IOException;
    
    @Slf4j
    @Component
    public class SvgToRasterizeImageConverter {
    
        public BufferedImage transcodeSVGToBufferedImage(File file, int width, int height) {
            // Create a PNG transcoder.
            Transcoder t = new PNGTranscoder();
    
            // Set the transcoding hints.
            t.addTranscodingHint(PNGTranscoder.KEY_WIDTH, (float) width);
            t.addTranscodingHint(PNGTranscoder.KEY_HEIGHT, (float) height);
            try (FileInputStream inputStream = new FileInputStream(file)) {
                // Create the transcoder input.
                TranscoderInput input = new TranscoderInput(inputStream);
    
                // Create the transcoder output.
                ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
                TranscoderOutput output = new TranscoderOutput(outputStream);
    
                // Save the image.
                t.transcode(input, output);
    
                // Flush and close the stream.
                outputStream.flush();
                outputStream.close();
    
                // Convert the byte stream into an image.
                byte[] imgData = outputStream.toByteArray();
                return ImageIO.read(new ByteArrayInputStream(imgData));
    
            } catch (IOException | TranscoderException e) {
                log.error("Conversion error", e);
            }
            return null;
        }
    
    }
    

    【讨论】:

    • so 接近我所需要的,但不幸的是,如果 InputStream 不是基于 File 对象(比如它是 S3Object.getObjectContent() 流)。
    • @PaulTomblin 发生了什么?你有例外吗?
    • 在 18 年前的蜡染开发者列表中找到了它。他们不会修复它。 bz.apache.org/bugzilla/show_bug.cgi?id=10787
    猜你喜欢
    • 2021-04-05
    • 2010-10-13
    • 1970-01-01
    • 2011-04-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-11-06
    • 1970-01-01
    相关资源
    最近更新 更多