24

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 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.

4

4 回答 4

26

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];
}
于 2012-07-11T15:57:44.017 回答
13

A very easy way is to use the TwelveMonkeys lib which adds additional image type support to java's ImageIO

So for example you just add these to your maven (or copy the needed 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:

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.

于 2016-03-05T10:16:17.900 回答
2

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<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;
    }
}
于 2015-06-14T05:42:27.503 回答
2

9 years later here is an other solution with Batik :

implementation 'org.apache.xmlgraphics:batik-all:1.14'

implementation '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;
    }

}
于 2021-04-15T10:30:03.337 回答