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 }