Sunday, November 8, 2009

ColdFusion: Introduction to the Java Barbecue Library (For Beginners)

Just to get this out of the way: if you are looking for a CF wrapper for the Barbecue library, this is not the entry for you. If you are interested in the basics of using Barbecue, feel free to read on.

Why write yet another entry on Barbecue? Well, I was recently reading about barcodes, for my own edification, and did not find many introductory resources (you know ... ones that ask and answer those silly novice questions?). So decided to write up my findings and first impressions, in the hopes of providing someone else with a starting point in their research. Even if they are not using CF.

Background on Barcodes
Obviously using the library requires you know at least a little bit about the different bar code types. For example, some encode numeric digits only, others have different limits as to how many characters they can encode, etcetera. Two of the more helpful references I came across were: Different Types of Barcodes and a high level Barcode Comparison Chart.

Installing the Barbecue Library
Using the Barbecue library is very simple. Just download the library from sourceforge.net and extract the main jar from the .zip file. The current jar version is: barbecue-1.5-beta1.jar. Then place the main jar somewhere in your CF classpath, such as: C:\ColdFusion8\wwwroot\WEB-INF\lib. Finally restart the ColdFusion server so it will detect the jar.

Another option is to use Mark Mandel's JavaLoader.cfc, which is the option I chose. Just load the Barbecue jar in your array of paths, and create a new instance of the JavaLoader. (Obviously, this is just for demonstration. Usually you store the javaLoader instance in the server scope to avoid memory leaks).

Note: The jdom.jar must also be added to the JavaLoader paths if you intend to use Barbecue's SVG functionality.

<cfset paths = [] />
<cfset arrayAppend(paths, expandPath('barbecue-1.5-beta1/barbecue-1.5-beta1.jar')) />
<cfset arrayAppend(paths, expandPath('barbecue-1.5-beta1/lib/jdom.jar')) />
<cfset loader = createObject("component", "javaloader.JavaLoader").init( paths ) />

Factory Workers: (So much for progress..)
I found the easiest way to work with bar codes was using the factory classes. The factories provide easy to use static methods that make it a breeze to create, format and save barcodes.


Creating a Barcode
To a create a barcode, you use the BarcodeFactory. This class contains about 25+ methods for creating several different types of barcodes, such as Interleaved 2 or 5, EAN128, etcetera. Currently, only one 2d bacode is supported: PDF417. The methods are intuitively named, and most require only the string to be encoded. For example, to create a Bookland barcode, just grab a reference to the Factory and call the createBookland() method with the value you wish to encode:

<cfset factory = loader.create("net.sourceforge.barbecue.BarcodeFactory") />
<cfset barcode = factory.createBookland( "0123456789" ) />

Creating a Barcode Image (Simple)

Now the object returned from the BarCodeFactory is not image. It is an instance of one of the Barbecue classes: net.sourceforge.barbecue.Barcode. But there are several easy ways for converting the barcode object into an image. I found at least five (5) methods. (There is a catch to a few of them, but more about that in a minute) .

The first three (3) options use the BarcodeImageHandler factory. It has a few handy methods for converting a barcode object to either an in memory image or to a physical file.

(1) Generating a BufferedImage
To convert a barcode object to a BufferedImage, simply grab a reference to the factory, then call the getImage() method. To display or manipulate the BufferedImage in CF, just wrap it in a CF image object.
<cfset ImageHandler = loader.create("net.sourceforge.barbecue.BarcodeImageHandler") />
<cfset buffImage = ImageHandler.getImage( barcode ) />
<cfset barcodeImage = ImageNew(buffImage) />
<cfimage action="writeToBrowser" source="#barcodeImage#" />
(2) Writing Images to an OutputStream
You can also write the image directly to an OutputStream. There are various uses of OuputStream's, such as streaming images to the browser. There is probably less need for that particular technique in later versions of CF. But as there are several different types of streams, it is good to be aware of this option.

The image handler has three (3) methods for writing images to a stream: writePNG(), writeGIF() and writeJPG(). As their names imply, they generate an image in the desired format and write the image bytes to the supplied stream. You can then use the stream however you wish:

<cfset ImageHandler = loader.create("net.sourceforge.barbecue.BarcodeImageHandler") />
<cfset outStream = loader.create("java.io.ByteArrayOutputStream").init() />
<cfset ImageHandler.writePNG( barcode, outStream ) />
<!--- 1) Display the image using cfimage ... --->
<cfset barcodeImage = ImageNew(outStream.toByteArray()) />
<cfimage action="writeToBrowser" source="#barcodeImage#" />

<!--- 2) OR .. using cfcontent ... --->
<cfcontent type="image/png" variable="#outStream.toByteArray()#" />

(3) Saving Images to a File
Lastly, the handler has three methods for saving barcode images to a physical file: saveGIF(), saveJPG() and savePNG(). It is much like the previous option, only you pass in a java.io.File object representing the output file.

<cfset ImageHandler = loader.create("net.sourceforge.barbecue.BarcodeImageHandler") />
<cfset IOFile = loader.create("java.io.File") />

<!--- save the barcode in three (3) different formats  --->
<cfset imageFile = IOFile.init( ExpandPath("myBarcode.gif") ) />
<cfset ImageHandler.saveGIF( barcode, imageFile ) />
<img src="myBarcode.gif" alt="GIF  Format" />

Now earlier I mentioned a catch. In my tests, the generated images all had a black border along the bottom of the barcode. I am guessing it has something to do with the canvas color of the image object. One way I was able to remove it, was by creating a blank image first. So I could control the canvas color. Then use some lower level methods to draw the barcode onto my image. That brings me to the other two (2) options for creating images.




Creating a Barcode Image (Advanced)

While not as easy as using the factory methods, you can also use two methods within the Barcode class itself to generate an image. But unlike using the image factory, it is strictly B.Y.O. (bring your own) .. image. You must already have a blank image in order to use these methods. They are probably what the BarcodeImageFactory uses behind the scenes. However, I thought I would point them out to explain how they work.

1) Using Barcode.draw(graphics, x, y)
The first, and simpler, method is: draw(). To use it, I just created a blank CF image. Since the default canvas color for "rgb" images is black, I changed it to white. Then extracted the graphics object from my image and passed it into the draw function. The result was a barcode without that bottom border.




<!--- Option 1: RBG image, white canvas --->
<cfset img = ImageNew("", barcode.getWidth(), barcode.getHeight(), "rgb", "ffffff") />
<cfset graphics = ImageGetBufferedImage(img).getGraphics() />
<cfset barcode.draw( graphics, 0, 0) />

<cfimage action="writeToBrowser" source="#img#">
<br /><br />

<!--- Option 2: Transparent image --->
<cfset img = ImageNew("", barcode.getWidth(), barcode.getHeight(), "argb") />
<cfset graphics = ImageGetBufferedImage(img).getGraphics() />
<cfset barcode.draw( graphics, 0, 0) />

<div style="background-color: blue;">
<cfimage action="writeToBrowser" source="#img#"> <br />
</div>

2) Using Barcode.output(output)
The second method is a little more complex. You probably will not need it. But still, it is good to know. The output() method works with an instance of what Barbecue calls an Output object. The are several types of Output classes, and they are all just used to convert Barcode objects from one format to another: like to image, svg or eps.

Since I wanted to create an image, I used the GraphicsOutput class. It is not as complicated as it looks, but it does involve more moving pieces than other options. Constructing a GraphicsOutput object requires several pieces of information: the image graphics, barcode font and fore/background colors. You simply pass these objects into the constructor to create a new Output object. Then call Barcode.output() to paint the barcode onto your image graphics using the supplied settings.

Example

Code

<!--- create a new image and extract the graphics --->
<cfset img = ImageNew("", barcode.getWidth(), barcode.getHeight(), "rgb", "ffffff") />
<cfset graphics = ImageGetBufferedImage(img).getGraphics() />

<!--- create the desired font and colors --->
<cfset Font = createObject("java", "java.awt.Font") />
<cfset Color = createObject("java", "java.awt.Color") />

<cfset barcodeFont = Font.init("Arial", Font.PLAIN, 12 ) />
<cfset backgroundColor = Color.BLUE />
<cfset foregroundColor = Color.decode("##008040") />

<!--- grab a Color object that will be used to set the barcode fore/background --->
<cfset GraphicsOutput = loader.create("net.sourceforge.barbecue.output.GraphicsOutput")>
<cfset gOut = GraphicsOutput.init( graphics, barcodeFont, foregroundColor, backgroundColor ) />
<cfset barcode.output( gOut ) />

<cfset ImageWrite( img, ExpandPath("ColdFusion_Barbecue_GraphicsOutput.png")) />
<!--- display the barcode image --->
<cfimage action="writeToBrowser" source="#img#"> <br />


Working with Barcode Properties

Working with the various ways of creating images eventually brought me back to the Barcode class itself, and using its methods to alter some of the default settings. One of the things that struck me about the Barcode class is that it extends JComponent. At first that seemed a bit strange, as JComponent is one of the base classes for swing gui components. But further reading revealed Barbecue can be used in servlets or swing applications. So extending JComponent made more sense.

Now I mentioned swing for a reason. After testing various Barcode properties, I quickly realized many of the properties only apply if you are using swing. For example, there are several methods for adjusting width and height. But they had no effect when using images. Considering these methods are inherited from JComponent, that makes sense. (Not that you can really force the barcode dimensions anyway). An entry on Adam Presley's blog confirmed my suspicion. You cannot set the dimensions explicitly. But instead should use the barWidth and barHeight properties to adjust the overall barcode size.

Here are some of the more useful Barcode properties.

Barcode.setBarWidth()/setBarHeight()
These methods can be used to set the width of the thinnest bar or the height of a single bar, within the overall barcode. You may need to experiment to get the correct settings. So you do not end up with an invalid barcode or one that does not render properly ;)

setBarWidth( 6 )


Code

<cfset BarcodeFactory = loader.create("net.sourceforge.barbecue.BarcodeFactory") />
<cfset barcode = BarcodeFactory.createStd2of5( "0123456789" ) />
<cfset barcode.setBarWidth( 6 ) />
...

Barcode.setDrawingQuietSection(boolean)
This setting controls whether extra whitespace (ie quiet zone) should be rendered.

Code

	<cfset barcode.setDrawingQuietSection( false ) />

setDrawingQuietSection(true)

setDrawingQuietSection(false)


Barcode.setDrawingText(boolean)
This setting controls whether the barcode data (ie un-encoded) is displayed beneath the barcode.

Code

	<cfset barcode.setDrawingText( false ) />
setDrawingText(false)


Barcode.setLabel(string)
This method should allow you to override the text displayed beneath the barcode. But I was not able to get this setting to work.

Barcode.setFont(java.awt.Font)
This method allows you to use a different font for any text displayed beneath the barcode.

Code:

<cfset Font = createObject("java", "java.awt.Font") />
<cfset barcodeFont = Font.init("Arial", Font.ITALIC, 12) />
<cfset barcode.setFont( barcodeFont ) />
setFont( [Arial, Italic, size 12] )



Barcode.setBackground(java.awt.Color)/setForeground(java.awt.Color)
These methods can be used to change the foreground or background colors of the barcode. They are inherited from JComponent, but are used by the Barcode class.

Code:

<!--- change foreground --->
<cfset Color = createObject("java", "java.awt.Color") />
<cfset barcode.setForeground( Color.RED ) />

<!--- change background (hex color) --->
<cfset Color = createObject("java", "java.awt.Color") />
<cfset barcode.setBackground( Color.decode("##0080c0") ) />

setForeground()


setBackground()


Barcode.setResolution(int)
Used to adjust the resolution. Default is 72dpi.

Code:
	<cfset barcode.setResolution( 300 ) />
SVG
You can also convert barcode objects to svg. Simply grab a reference to the SVGFormatter class, then call the formatAsSVG() method. The result is an svg string.

Code:
<cfset BarcodeFactory = loader.create("net.sourceforge.barbecue.BarcodeFactory") />
<cfset barcode = BarcodeFactory.createBookland( "0123456789" ) />
<cfset SVGFormatter = loader.create("net.sourceforge.barbecue.formatter.SVGFormatter") />
<cfset svgString = SVGFormatter.formatAsSVG( barcode )>
Related Entries:
CFBarbecue.cfc (...Just because)
Seeing Stripes: Experiment with ZXing


8 comments:

inj November 9, 2009 at 5:42 PM  

Thanks!
You are my hero! :) It is very necessary library!

Adam Presley November 13, 2009 at 8:33 PM  

Very nice breakdown of barcode generation and usage. And thanks for the mention to my blog. Happy coding!

cfSearching November 13, 2009 at 9:21 PM  

@Adam,

Thanks ...and you are welcome :)

-Leigh

Anthony Webb,  December 2, 2009 at 12:40 PM  

What OS are you testing with? I used barbeque with my windows cf8 install and worked great. On mac OSX everything works great, except I no longer have the drawingtext at the bottom of the image, cant make it show for anything...

Swina Allen October 6, 2010 at 3:52 AM  

I need to save barcode images to 300dpi in order to print them with high definition. I used CFBarbecue and the following code to generate jpg images, but setting resolution=300 I still have images at 72dpi.
creating barcode example

cfSearching October 8, 2010 at 2:13 PM  

Looking at the source code, I am honestly not sure that setting even has an effect on images. Not to mention, it depends on what you mean by DPI.

http://www.houseoffusion.com/groups/cf-talk/thread.cfm/threadid:54696#295737

-Leigh

Uly October 14, 2010 at 8:26 AM  

Hi Leigh,

Thanks so much for this post. I have a quick question: is there a setting to rotate the barcode 90 degrees (vertical orientation)? Or do I need to do it through cfimage?

Thanks again.

cfSearching October 14, 2010 at 3:28 PM  

@Uly,

Not as far as I know. But since it is easy enough to do outside of barbecue, that might be why ;)

-Leigh

  © Blogger templates The Professional Template by Ourblogtemplates.com 2008

Header image adapted from atomicjeep