More Batik Goodness

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.

9 responses to “More Batik Goodness”

  1. Jim Collins

    Nice work Barney. I'm assuming that that output method could be used to send anything to the browser not just a PNG image?

  2. Barney

    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.

  3. Jim Collins

    I'm doing something similar with SAX but your solution is much more elegant that the kludge I hacked together :)

  4. Geoff

    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

  5. Geoff

    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…

  6. Geoff

    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?