Thursday, November 19, 2009

Seeing Stripes: Experiment with ZXing

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.


Setup
Not seeing any 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:
  • 2of7
  • Codabar
  • Int2of5
  • Monarch
  • NW7
  • PDF417
  • PostNet
  • Std2of5
  • USD4
While I knew some of them are not supported, but I was surprised that the PD417 and Interleaved 2 of 5 samples failed. I am not yet sure why. For now I will assume it was an error on my part. I will have to investigate the individual readers further.

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>

Other Classes
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.

Example:

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.

Example:
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 ...)

...Read More

Wednesday, November 18, 2009

SOT: Who is the fairest browser of them all?

I have long been a fan of Firefox. But somewhere in between versions 2 and 3.5 it started behaving like a black-hole of resources, sucking up more and more memory, growing increasing sluggish. Not to mention taking forever to start up. So fickle user that I am, I have been trying out other browsers while I give Firefox one last chance before giving it the boot.

Of course one of the browsers I am (re-)trying is Chrome. I did use it briefly when it first came out. But quickly gravitated back to my beloved Firefox, with all of its wonderful extensions and flexible configuration. But I will admit Chrome is extremely fast. No news there. What was a nice surprise was its mini-Task-Manager (Shft-Esc) and the "Stats for Geeks" breakdown.

Meanwhile, I did some file shuffling and installed a completely fresh copy of FireFox 3.5.5. Just to see if I could get a sense of whether the true culprit was FireFox itself, a specific setting or one of my plugins. I tried to start with as little functionality and minimal settings as possible. Not all of them are relevant. But in the interest of full disclosure, here is a run down of the changes I made to the default settings:

Plugins:
- *Disabled* all of the plugins detected by default (EXCEPT Shockwave Flash 10.0.32.18)

I always wondered why my plugin list displayed certain entries I never added explicitly. It turns out it is due to Firefox's automatic scan for common plugins: http://kb.mozillazine.org/Plugin_scanning#Related_preferences

Extensions:
- *Added* Echofon (aka TwitterFox)

Privacy:
- *Changed* Remember browsing history to (at least 30 days)
- *Disabled* Remember search and form history
- *Enabled* Clear history when Firefox closes: everything

Cache:
- *Changed* Cache settings to use up to 30MB
Reason: Sometime after upgrading to FF3+, I noticed the autosuggest feature in the location bar had changed. It got progressively slower over time. So I am experimenting with a lower history threshold to see how it performs (before taking the extreme step of disabling it entirely).

Content:
- *Disabled* Java (No point enabling it since I disabled the plugin)

Startup:
- *Changed* Show a blank page

Update:
- *Changed* When updates to Firefox are found: (Ask me what I want to do)

So far Firefox is doing a lot better. Definitely faster, though maybe not quite as fast as Chrome. At one point I had 15 tabs open in Firefox (only one with Flash Content). Firefox peaked at around 160-180M memory usage in the windows Task Manager and still remained very responsive.

Now to start adding back a plugin at a time, and wait for ugly things to happen. Though I have to say I am enjoying reading some of the more creative ways people express their displeasure with the latest versions of Firefox.


Update November 19, 2009: I am now experimenting with the Firefox 3.6b3 beta and also a plugin called AFOM 2.0 for memory management. (Note: I do not know if this plugin officially supports 3.6b3 yet). But the results from my limited testing seem very promising. (Windows only)


...Read More

Monday, November 16, 2009

CFBarbecue.cfc (...Just because)

While experimenting with the Barbecue library last week, I put together a cfc for my testing. (You knew that was coming. It is the old developer story: read, test, build something, re-test). Anyway, I decided to post it for anyone else out there experimenting with Barbecue. The cfc can generate any of the 25+ barcodes Barbecue handles, with a full range of settings. (Rather than repeat the same code 25+ times, the cfc uses reflection)




Keep in mind the Barbecue library also includes servlet, which is extremely easy to use. Since I was using the built-in webserver at the time, the greatest challenge for me was figuring out how to add a servlet. Not surprisingly I did not find much in the way of documentation. But once I finally got it working, the servlet was a snap to use. Just a simple call to a url:

http://127.0.0.1:8500/barbecue/?data=12345&type=PDF417




November 19, 2009: Updated source to include missing lib\jdom.jar. Note: This jar is only needed if you are using the JavaLoader and want SVG functionality.

...Read More

Sunday, November 15, 2009

The Wrong Way to Retrieve the Last Record ID Inserted

Hardly a week goes by without seeing some forum post that either uses or recommends my pet peeve: the erroneous SELECT MAX(id) query. Mind you this is across the board, not just in CF. For some bizarre reason, this faulty technique never seems to die. And it REALLY should. There are so many correct alternatives, I cannot understand why anyone would still use it for new code. If you are even considering it, might I inject a note of sanity and recommend some excellent reading material on Adrian J. Moreno's blog:


(His comments on the topic are much more educational and polite than mine ;-)

...Read More

  © Blogger templates The Professional Template by Ourblogtemplates.com 2008

Header image adapted from atomicjeep