如何从SVG获取BufferedImage?

时间:2012-07-11 15:06:52

标签: java svg batik

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

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

4 个答案:

答案 0 :(得分:19)

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

答案 1 :(得分:12)

一种非常简单的方法是使用TwelveMonkeys lib为java ImageIO

添加额外的图像类型支持

例如,您只需将这些添加到您的maven(或复制所需的罐子):

    <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还支持其他参数,请参阅readme on github

答案 2 :(得分:2)

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

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

它具有以下依赖项:

  • org.apache.xmlgraphics:蜡染动画
  • org.apache.xmlgraphics:蜡染桥
  • org.apache.xmlgraphics:蜡染GVT
  • org.apache.xmlgraphics:蜡染代码转换器
  • org.apache.xmlgraphics:蜡染UTIL
  • XML的API:XML的API-EXT
  • 共享记录:共享记录

当我这样做时,我使用了蜡染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;
    }
}

答案 3 :(得分:2)

9 年后,这里是蜡染的另一种解决方案:

<块引用>

实现'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;
    }

}