Last October I posted about using ColdFusion and Batik together to convert dynamically generated SVG content into PNGs for easier consumption. Well thanks to a little more digging and a post by Christian Cantrell, I've improved upon it further.
<cfscript> context = getPageContext(); context.setFlushOutput(false); response = context.getResponse().getResponse(); response.setContentType("image/png"); transcoder = createObject("java", "org.apache.batik.transcoder.image.PNGTranscoder").init(); inputStream = createObject("java", "java.io.StringBufferInputStream").init(svg); input = createObject("java", "org.apache.batik.transcoder.TranscoderInput").init(inputStream); outputStream = response.getOutputStream(); output = createObject("java", "org.apache.batik.transcoder.TranscoderOutput").init(outputStream); transcoder.transcode(input, output); output.close(); outputStream.close(); </cfscript>
The primary difference here is that your SVG content comes from a string (rather than a file), and the PNG content will be sent directly to the browser, rather than stored in a file. If you're operating on files, not much benefit, but if you're generating your SVG on-the-fly every request and sending it directly out, cutting out all the filesystem access (four separate trips) can be a nice benefit.
As before, you need a full installation of Batik (WEB-INF/lib
is a good place for it), and if you're on CF7, you'll need to remove the partial install in WEB-INF/cfform/jars
as well.
Nice work Barney. I'm assuming that that output method could be used to send anything to the browser not just a PNG image?
Jim,
Absolutely. With a file you just use CFCONTENT, but if your data is anywhere else (memory, database) you can use the response's OutputStream to write it directly. You could even use it to send back a string of HTML if you wanted, but CF provides much nicer ways for doing that (like CFOUTPUT).
Note that in my example, I'm not actually writing to the OutputStream (as Christian's example showed), I'm just wrapping it up and letting the PNGTranscoder write to it for me. I could have used a ByteArrayOutputStream for the PNGTranscoder's destination, and then extracted the byte array and passed it to the response's OutputStream to achieve the same effect, but doing it as I did saves an extra buffering.
I'm doing something similar with SAX but your solution is much more elegant that the kludge I hacked together :)
Just gave this a spin – works great! Many thanks.
One small problem though – output.close() seems to chuck an error:
The close method was not found.Either there are no methods with the specified method name and argument types, or the close method is overloaded with argument types that ColdFusion cannot decipher reliably. ColdFusion found 0 methods that matched the provided arguments. If this is a Java object and you verified that the method exists, you may need to use the javacast function to reduce ambiguity
Geoff,
What version of ColdFusion are you using? I haven't tested this on CF9, or later CF8 builds, so if you're using one of those it's quite possible Adobe changed something that renders this technique unusable, at least exactly as I've used it. 'output' is a Batik instance, and CF uses Batik for some of its form processing stuff, so if they've updated the bundled version that might be it.
I've tried it on both CF7 & 8 – same issue occurs on both.
I guess it might be the way I installed it… I removed the batik jars from web-inf/cfform and dropped the new .jars in web-inf/lib and restarted…
I wasn't sure whether to keep batik's file structure though – I have:
web-inf/lib/batik.jar + others
web-inf/lib/lib/batik-anim.jar + more
or just move all the .jars into the one web-inf/lib folder…
Geoff,
You'll want put all the JARs directly in /WEB-INF/lib, without the extra intermediate 'lib' folder. Batik (along with a lot of other projects) packages it that way so it's obvious which JARs are the core project and which are the project's dependencies. But the JVM only loads from /WEB-INF/lib, not child folders, so if you want to use the bundled dependencies, you have to flatten it out.
Many thanks Barney. I've flattened my JARs as you suggest. Works fine, but I've still got my 'close' problem.
Might it be 'outputStream.close();' instead of 'output.close();' in your example above?
Geoff,
You are exactly right. I went and dug the code out of Subversion where I was using this technique and the code closes the outputStream not output. This is perhaps a CF7 -> CF8 difference, judging by the post date. I've corrected the code in the post as well.