I recently took a look at the ZXing Project which is described as "an open-source, multi-format 1D/2D barcode image processing library implemented in Java.". While its primary focus is mobile phones, I decided to look at the javase component of the project. ZXing also comes with a servlet. But I first wanted to see how the lower level components worked.
Not seeing any pre-compiled jars, I downloaded the latest sources (ZXing-1.4.zip). After loading it into Eclipse, and poked around a bit to figure out how to decode barcode images of different types. Finally, I used the pre-made ant files to create the two jars I needed: core.jar and javase.jar.
Decoding barcode images
While the project has some decent documentation, the developer notes seem to be outdated in a few places. But examining some of the javase classes turned up a convenience class called MultiFormatReader. From what I could tell it provides a simple way to decode barcodes when you may not know the type ahead of time. Obviously if you know the barcode type ahead of time, it is more efficient to use the specific reader for that type. But since I wanted to test all supported types the MultiFormatReader was perfect for my needs.
Now before I could test ZXing, I needed some barcode images. So I used CFBarbecue.cfc to generate a sample of all of the barcodes supported by the Barbecue library. Now ZXing does not support all of them. But there is a good intersection between the two. Once I had my images, I created an instance of MultiFormatReader, using the ever-handy JavaLoader.cfc. I also passed the reader some hints to increase accuracy.
Note: This code is for demonstration purposes only. Normally, the javaLoader should be stored in the server scope to avoid memory leaks.
<cfset paths =  /> <cfset arrayAppend(paths, expandPath('zxing-1.4/core/core.jar')) /> <cfset arrayAppend(paths, expandPath('zxing-1.4/javase/javase.jar')) /> <cfset loader = createObject("component", "javaloader.JavaLoader").init( paths ) /> <!--- Create hints for the reader. TRY_HARDER increases accuracy ---> <cfset DecodeHintType = loader.create("com.google.zxing.DecodeHintType") /> <cfset hints = loader.create("java.util.Hashtable").init() /> <cfset hints.put(DecodeHintType.TRY_HARDER, javacast("boolean", true)) /> <!--- Since we will be reading multiple images, create one reader and reuse it ---> <cfset Reader = loader.create("com.google.zxing.MultiFormatReader").init() /> <cfset Reader.setHints(hints) />
Next, I used cfdirectory to grab a list of all of my barcode images. Then looped through the images and attempted to decode each one. Now in order to use the reader's decode method, I needed to convert my image to a compatible format: BinaryBitmap. So I read in each image, extracted the underlying BufferedImage and passed it through a few of the ZXing classes designed to prepare images for decoding.
Finally, I called the reader's decode method to decode the image. If the image was successfully decoded it returns a Result object containing some useful properties like the raw text and barcode type.
One thing to be aware of is that error handling is unlikely to yield any useful information, beyond the fact that an error occurred. From the notes in the current source code, the way in which decoding errors are handled is undergoing a transformation, to increase performance. So for now do not expect much exception information beyond name, rank and serial number.
<!--- read all barcode images in my subdirectory ---> <cfdirectory action="list" directory="#ExpandPath('images')#" filter="*.gif" name="barcodeImages" /> <!--- Try and decode each barcode with z-xing. Note: Not all codes are supported by z-xing!!! ---> <cfoutput query="barcodeImages"> <cfset imagePath = Directory &"/"& name /> <cftry> <!--- display the original image ---> <strong>Barcode Image</strong> #name# <br /> <cfimage action="writeToBrowser" source="#imagePath#"><br /> <!--- extract the BufferedImage of the current barcode ---> <cfset img = ImageGetBufferedImage(ImageRead(imagePath)) /> <!--- prepare the image for decoding ---> <cfset source = loader.create("com.google.zxing.client.j2se.BufferedImageLuminanceSource").init(img) /> <cfset binarizer = loader.create("com.google.zxing.common.GlobalHistogramBinarizer").init(source) /> <cfset bitmap = loader.create("com.google.zxing.BinaryBitmap").init(binarizer) /> <!--- This method reuses the existing settings for decoding multiple images ---> <cfset decodedResult = Reader.decodeWithState(bitmap) /> <!--- display the results ---> <strong>Decoded Text</strong> #decodedResult.getText()# <br /> <strong>Decoded Format</strong> #decodedResult.getBarcodeFormat()# <br /> <cfcatch> Not supported or unable to decode: #cfcatch.type# #cfcatch.message# </cfcatch> </cftry> <br /><br /><hr /> </cfoutput>
All of my sample images were decoded except:
However, I was able to decode a DataMatrix barcode using the DataMatrixReader. (Created using the free servlet at IDAutomation.com). Just be aware the current project documentation lists DataMatrix decoding as "alpha quality". So do not expect miracles.
<cfset imagePath = "c:\barCode_loremIpsum_idAutomation.gif" /> <cfset DecodeHintType = loader.create("com.google.zxing.DecodeHintType") /> <cfset hints = loader.create("java.util.Hashtable").init() /> <cfset hints.put(DecodeHintType.PURE_BARCODE, javacast("boolean", true)) /> <!--- prepare the image for decoding ---> <cfset img = ImageGetBufferedImage(ImageNew(imagePath)) /> <cfset source = loader.create("com.google.zxing.client.j2se.BufferedImageLuminanceSource").init(img) /> <cfset binarizer = loader.create("com.google.zxing.common.GlobalHistogramBinarizer").init(source) /> <cfset bitmap = loader.create("com.google.zxing.BinaryBitmap").init(binarizer) /> <!--- attempt to decode the image ---> <cfset Reader = loader.create("com.google.zxing.datamatrix.DataMatrixReader") /> <cfset decodedResult = Reader.decode( bitmap, hints ) /> <cfoutput> <!--- display the original image ---> <strong>Barcode Image</strong> #imagePath# <br /> <cfimage action="writeToBrowser" source="#imagePath#"><br /> <!--- display the results ---> <strong>Decoded Result</strong><br /> #decodedResult.getText()# <hr /> </cfoutput>
The j2se component also has a few classes you might find useful for quick tests. The first is GUIRunner. It is a very rudimentary GUI you can use to test a single bar code image. To use it, simply open a command prompt and enter the following. Just modify the "-cp" value (classpath) to point to the location of your jars.
C:\test\zxing> java -cp c:\test\zxing\core.jar;c:\test\zxing\javase.jar com.google.zxing.client.j2se.GUIRunner
The other class is CommandLineRunner. It can be used to decode a single image, a directory of images or a url. Just supply the locations of your jars and the file/directory/url you wish to decode. The "--try_harder" hint can be used to increase accuracy, and the "--dump_results" option to save the results to a file. Note: The results of each barcode are saved to individual files. So keep that in mind if you are decoding an entire directory.
C:\test\zxing> java -cp c:\test\zxing\core.jar;c:\test\zxing\javase.jar com.google.zxing.client.j2se.CommandLineRunner c:/test/zxing/barcodeimages --try_harder
Well, that is it for now. I will have to experiment with some lower quality images, to see how the results stack up in a real test. (Once I find a supported phone ...)