2012-07-11 13 views
14

Estoy usando Batik para manejar imágenes SVG. ¿Hay alguna forma de obtener una java.awt.image.BufferedImage desde un archivo SVG?¿Cómo obtener una imagen Bufferer desde un SVG?

Sé que hay transcodificadores, con los que podría transcodificar el SVG en, por ejemplo, un PNG y luego cargar ese PNG con ImageIO.read() · Pero no quiero tener el archivo temporal.

Respuesta

17

Usando Batik, algo como esto:

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]; 
} 
+0

existe la ventaja de crear una matriz de imagePointer frente a la asignación de imagePointer a una variable local como esta http://bbgen.net/blog/2011/06/java-svg-to-bufferedimage/? –

+0

Realmente no. Es solo una solución para evitar errores de compilación en el método 'writeImage'. – elias

+0

Ah, genial. ¡Gracias por verificar! –

2

Esto es lo que yo uso. Es una extensión de BufferedImage con su propia fábrica estática que se puede utilizar en cualquier lugar donde se utilice una imagen Buffered. Lo escribí para que cualquier llamada a getScaledInstance (w, h, hint) se realice desde SVG, no desde la imagen rasterizada. Un efecto secundario de esto es que el parámetro de sugerencia de escala no tiene significado; simplemente puede pasar 0 o DEFAULT a eso. Se renderiza de forma perezosa, solo cuando se solicitan datos de gráficos, por lo que el ciclo de carga/escala no debe generar demasiada sobrecarga.

Editar: He añadido soporte utilizando la configuración de CSS anterior para escalar consejos de calidad. Editar 2: La prestación de latencia no funcionaba de forma coherente; Pongo la llamada a render() en el constructor.

Tiene las siguientes dependencias:

  • org.apache.xmlgraphics:
  • org.apache.xmlgraphics batik-anim batik-puente
  • org.apache.xmlgraphics batik-GVT
  • org.apache.xmlgraphics batik-transcodificador
  • org.apache.xmlgraphics: batik-util
  • xml-apis: XML-API-ext
  • commons-logging: commons-logging

Cuando hice esto, yo solía 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; 
    } 
} 
+0

Por cierto, creo que voy a estar robando la sugerencia de transcodificador CSS anterior para emular las sugerencias de escalado de BufferedImage. – Fordi

10

una manera muy fácil es utilizar el TwelveMonkeys lib que añade soporte tipo de imagen adicional para ImageIO

Así, por ejemplo, usted sólo tiene que añadir de inmediato a su experto (o copiar los frascos necesarios) de java:

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

Y a continuación, que acaba de leer con

BufferedImage image = ImageIO.read(svg-file); 

Para comprobar si el lector SVG se ha registrado correctamente se podía imprimir los lectores de imagen:

Iterator<ImageReader> readers = ImageIO.getImageReadersByFormatName("SVG"); 
while (readers.hasNext()) { 
    System.out.println("reader: " + readers.next()); 
} 

El lib también es compatible con params adicionales, ver readme on github.

+0

Esto es raro lib. No tiene documentación en línea. No tiene ejemplos. Tiene un problema con la dependencia (vea su publicación con un tercero). Y tiene un problema con el icono redimensionar-cargar. Me llevaron a una pequeña cantidad de código y me arrepentí. – JayDi

Cuestiones relacionadas