How to get a BufferedImage from a SVG?

前端 未结 3 2030
梦毁少年i
梦毁少年i 2020-12-13 20:35

I am using Batik to handle SVG images. Is there any way to get a java.awt.image.BufferedImage from a SVG-file?

I know there are transcoders, with which I could trans

3条回答
  •  再見小時候
    2020-12-13 21:14

    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.

    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.

    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

    When I made this, I used 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 scaleQuality = new HashMap();
        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;
        }
    }
    

提交回复
热议问题