Rendering a dynamic SVG document to an offscreen buffer

It can be helpful in some circumstance to render a Dynamic SVG document not to the screen but to an offscreen buffer. This is most useful when you have a large SVG Document that you want to output as JPEG or PNG with a few minor variations from rendering to rendering.

By treating the "template" document as a dynamic document where you just modify small pieces you can avoid the overhead of rereading and parsing the SVG, as well as the building of the Rendering tree from that SVG document (which involves parsing many attributes, performing CSS cascade etc.

The following example takes the filename on the command line of a 'base' document to read, it then creates a 'dynamic' rect on top of the document and renders the document with the rect moving across the top of the document. It renders the images to a BufferedImage which it then writes to a sequence of PNG files.

Example

   1 package org.example;
   2 
   3 import java.awt.geom.AffineTransform;
   4 import java.awt.Rectangle;
   5 import java.io.File;
   6 import java.io.OutputStream;
   7 import java.io.IOException;
   8 import java.io.FileOutputStream;
   9 import java.net.MalformedURLException;
  10 import java.util.List;
  11 
  12 import org.apache.batik.ext.awt.image.codec.png.PNGEncodeParam;
  13 import org.apache.batik.ext.awt.image.codec.png.PNGImageEncoder;
  14 
  15 import org.apache.batik.dom.svg.SAXSVGDocumentFactory;
  16 import org.apache.batik.bridge.BridgeContext;
  17 import org.apache.batik.bridge.BridgeException;
  18 import org.apache.batik.bridge.GVTBuilder;
  19 import org.apache.batik.bridge.UpdateManager;
  20 import org.apache.batik.bridge.UpdateManagerAdapter;
  21 import org.apache.batik.bridge.UpdateManagerEvent;
  22 import org.apache.batik.bridge.UserAgentAdapter;
  23 import org.apache.batik.util.RunnableQueue;
  24 import org.apache.batik.util.XMLResourceDescriptor;
  25 import org.apache.batik.gvt.CanvasGraphicsNode;
  26 import org.apache.batik.gvt.CompositeGraphicsNode;
  27 import org.apache.batik.gvt.GraphicsNode;
  28 import org.apache.batik.gvt.renderer.ConcreteImageRendererFactory;
  29 import org.apache.batik.gvt.renderer.ImageRenderer;
  30 import org.apache.batik.gvt.renderer.ImageRendererFactory;
  31 
  32 import org.w3c.dom.Document;
  33 import org.w3c.dom.Element;
  34 
  35 
  36 public class TestOffScreenRender {
  37     static final String SVGNS = "http://www.w3.org/2000/svg";
  38 
  39     Document document;
  40     UserAgentAdapter userAgent;
  41     GVTBuilder builder;
  42     BridgeContext ctx;
  43     ImageRenderer renderer;
  44     AffineTransform curTxf;
  45     UpdateManager manager;
  46     GraphicsNode gvtRoot;
  47     int DISPLAY_WIDTH = 1280;
  48     int DISPLAY_HEIGHT = 1024;
  49 
  50 
  51     public TestOffScreenRender (Document doc) {
  52         userAgent = new UserAgentAdapter();
  53         ctx       = new BridgeContext(userAgent);
  54         builder   = new GVTBuilder();
  55         document  = doc;
  56     }
  57 
  58     public void init() {
  59         GraphicsNode gvtRoot = null ;
  60 
  61         try {
  62             ctx.setDynamicState(BridgeContext.DYNAMIC);
  63             gvtRoot = builder.build(ctx, document);
  64         }
  65         catch (BridgeException e) { 
  66             e.printStackTrace(); 
  67             System.exit(1);
  68         }
  69 
  70         ImageRendererFactory rendererFactory;
  71         rendererFactory = new ConcreteImageRendererFactory();
  72         renderer        = rendererFactory.createDynamicImageRenderer();
  73         renderer.setDoubleBuffered(false);
  74 
  75         float docWidth  = (float) ctx.getDocumentSize().getWidth();
  76         float docHeight = (float) ctx.getDocumentSize().getHeight();
  77 
  78         float xscale = DISPLAY_WIDTH/docWidth;
  79         float yscale = DISPLAY_HEIGHT/docHeight;
  80         float scale = Math.min(xscale, yscale);
  81 
  82         AffineTransform px  = AffineTransform.getScaleInstance(scale, scale);
  83 
  84         double tx = -0 + (DISPLAY_WIDTH/scale - docWidth)/2;
  85         double ty = -0 + (DISPLAY_WIDTH/scale - docHeight)/2;
  86         px.translate(tx, ty);
  87         CanvasGraphicsNode cgn = getGraphicsNode(gvtRoot);
  88         if (cgn != null) {
  89             cgn.setViewingTransform(px);
  90             curTxf = new AffineTransform();
  91         } else {
  92             curTxf = px;
  93         }
  94         manager = new UpdateManager(ctx, gvtRoot, document);
  95         // 'setMinRepaintTime' was added to SVN.  This isn't
  96         // essential but prevents 'frame skipping' (useful
  97         // for "recording" content, not needed for live display).
  98         manager.setMinRepaintTime(-1);
  99 
 100         try {
 101             manager.dispatchSVGLoadEvent();
 102         } catch (InterruptedException ie) {
 103             ie.printStackTrace();
 104         }
 105 
 106         renderer.updateOffScreen(DISPLAY_WIDTH, DISPLAY_HEIGHT);
 107         renderer.setTree(gvtRoot);
 108         renderer.setTransform(curTxf);
 109         renderer.clearOffScreen();
 110         renderer.repaint(new Rectangle(0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT));
 111         manager.addUpdateManagerListener(new UpdateManagerAdapter() {
 112                 public void updateCompleted(UpdateManagerEvent e) {
 113                     render(e.getImage());
 114                 }
 115 
 116                 public void managerSuspended(UpdateManagerEvent e) {
 117                     // Make sure pending updates are completed.
 118                     System.exit(0);
 119                 }
 120             });
 121         manager.manageUpdates(renderer);
 122         this.gvtRoot = gvtRoot;
 123     }
 124 
 125     private CanvasGraphicsNode getGraphicsNode(GraphicsNode gn) {
 126         if (!(gn instanceof CompositeGraphicsNode))
 127             return null;
 128         CompositeGraphicsNode cgn = (CompositeGraphicsNode) gn;
 129         List children = cgn.getChildren();
 130         if(children.size() == 0)
 131             return null;
 132         gn = (GraphicsNode) children.get(0);
 133         if (!(gn instanceof CanvasGraphicsNode))
 134             return null;
 135         return (CanvasGraphicsNode) gn;
 136 
 137     }
 138 
 139     int imgCount = 1;
 140     public void render(java.awt.image.BufferedImage img) {
 141         // paint the image or stream the image to the client display
 142         try {
 143             String file = "frame."+(imgCount++)+".png";
 144             System.err.println("Output: " + file);
 145             OutputStream os = new FileOutputStream(file);
 146             
 147             PNGEncodeParam params = PNGEncodeParam.getDefaultEncodeParam(img);
 148             PNGImageEncoder pngEncoder = new PNGImageEncoder(os, params);
 149             pngEncoder.encode(img);
 150             os.flush();
 151         } catch (IOException ioe) {
 152             ioe.printStackTrace();
 153         }
 154     }
 155 
 156     /**
 157      * @param args
 158      */
 159     public static void main(String[] args) {
 160         if (args.length < 1) {
 161             System.out.println("You must provide background SVG file");
 162             System.exit(1);
 163         }
 164         String docStr = args[0];
 165         
 166         String xmlParser = XMLResourceDescriptor.getXMLParserClassName();
 167         SAXSVGDocumentFactory df;
 168         df = new SAXSVGDocumentFactory(xmlParser);
 169         Document doc = null;
 170         TestOffScreenRender render = null;
 171         Element r=null;
 172         try {
 173             File f = new File(docStr);
 174             doc = df.createSVGDocument(f.toURL().toString());
 175             r = doc.createElementNS(SVGNS, "rect");
 176             r.setAttributeNS(null, "x", "100");
 177             r.setAttributeNS(null, "y", "200");
 178             r.setAttributeNS(null, "width", "200");
 179             r.setAttributeNS(null, "height", "150");
 180             r.setAttributeNS(null, "fill", "crimson");
 181             r.setAttributeNS(null, "stroke", "gold");
 182             r.setAttributeNS(null, "stroke-width", "3");
 183             doc.getDocumentElement().appendChild(r);
 184             render = new TestOffScreenRender(doc);
 185             render.init();
 186         } catch (IOException ioe) {
 187             ioe.printStackTrace();
 188             System.exit(0);
 189         }
 190 
 191         final Element rect = r;
 192 
 193         RunnableQueue rq = render.manager.getUpdateRunnableQueue();
 194         for (int i=1; i<10; i++) {
 195             final int x = 100+ (i*10);
 196             try {
 197                 rq.invokeAndWait(new Runnable() {
 198                         public void run() {
 199                             rect.setAttributeNS(null, "x", ""+x);
 200                         }
 201                     });
 202             } catch (InterruptedException ie) {
 203                 ie.printStackTrace();
 204             }
 205         }
 206         render.manager.suspend();
 207     }
 208 }

DynamicSvgOffscreen (last edited 2012-06-22 14:08:40 by drool)