Generating scalable, stretchy, and smart graphics with ColdFusion – Part 3

In today’s post we’re going to build a renderer to convert SVG files into PNG or JPEG images. The Batik documentation is decent (better than most open source projects), and the API is very straightforward. As long as you can remember to wrap all of you calls in JavaCast statements, you’ll be good to go.

In a nutshell:

  1. Start with an SVG input file.

  2. Create a PNG or JPEG Batik transcoder.

  3. Add any number of hints to help the transcoder get closer to what you want to see.

  4. Have the transcoder render the SVG file to a raster image.

We’ll start at the end and work backwards:

<cffunction name="RenderFile">
  <cfargument name="InFile" type="string" required="true">
  <cfargument name="OutFile" type="string" required="true">
  <cfargument name="Width" type="string" required="false" default="">
  <cfargument name="Format" type="string" required="false" default="png8">
  <cfset var transcoder="">
  <cfset var in="">
  <cfset var out="">
  <cfset var uri=CreateObject("java", "java.io.File").init(Arguments.InFile).toURL().toString()>
  <cfset var outstream=CreateObject("java", "java.io.FileOutputStream").init(Arguments.OutFile)>
  <cfset transcoder=CreateObject("java","org.apache.batik.transcoder.image.PNGTranscoder").init()>
  <cfif IsNumeric(Arguments.Width) AND (Arguments.Width GTE 10) AND (Arguments.Width LTE 640)>
    <cfset transcoder.addTranscodingHint(transcoder.KEY_WIDTH,JavaCast("float",Arguments.Width))>
  </cfif>
  <cfset in=CreateObject("java", "org.apache.batik.transcoder.TranscoderInput").init(uri)>
  <cfset out=CreateObject("java", "org.apache.batik.transcoder.TranscoderOutput").init(outstream)>
  <cfset transcoder.transcode(in,out)>
  <cfset outstream.flush()>
  <cfset outstream.close()>
</cffunction>

The first non-syntactic-sugar line is this one, and it looks a little weird:

<cfset var uri=CreateObject("java", "java.io.File").init(Arguments.InFile).toURL().toString()>

For input, Batik doesn’t work with regular file paths, it works with URI representations of file paths. In theory, you could construct the URI yourself, making it look like “file:///blah”, but why bother?

For output, Batik wants open file streams:

<cfset var outstream=CreateObject("java", "java.io.FileOutputStream").init(Arguments.OutFile)>

The third line creates the object that does all of the magic, the transcoder:

<cfset transcoder=CreateObject("java","org.apache.batik.transcoder.image.PNGTranscoder").init()>

If we wanted to generate JPEG files instead of PNG files, we would create a JPEGTranscoder instead.

We’ve seen that SVG files have an inherent notion of their native dimensions, via the width and height attributes to the root svg element. However, as one of the benefits of using SVG is that the transcoder can scale the graphic to any size, we’ve added the ability to override that size. The transcoder handles this with what they call a Transcoding Hint:

<cfset transcoder.addTranscodingHint(transcoder.KEY_WIDTH,JavaCast("float",Arguments.Width))>

There are a few hints that both the PNG and JPEG transcoders share, and a few hints that are unique to each, such as JPEG compression level.

The next two lines handle converting from File URIs and Streams to inputs that the transcoder can handle:

<cfset in=CreateObject("java", "org.apache.batik.transcoder.TranscoderInput").init(uri)>
<cfset out=CreateObject("java", "org.apache.batik.transcoder.TranscoderOutput").init(outstream)>

Finally, the line that does all of the magic!

<cfset transcoder.transcode(in,out)>

Kind of anticlimactic, isn’t it?

The last two lines are just clean-up:

<cfset outstream.flush()>
<cfset outstream.close()>

Twenty lines isn’t a bad size for a function. Better yet, we can now render SVG files to raster images in one quick statement:

<cfset RenderFile("input.svg", "output.png", "", "png")>

The truly courageous can try creating functions that work with other types of inputs. For example, the transcoders also take XML DOM documents as inputs, as well as InputStreams (meaning you could, in theory, build images from in-memory strings).

The next part of the series will deal with munging manipulating XML documents on-the-fly with ColdFusion to create dynamic graphics.

Published by

Rick Osborne

I am a web geek who has been doing this sort of thing entirely too long. I rant, I muse, I whine. That is, I am not at all atypical for my breed.