Wednesday, April 21, 2010

ColdFusion: ZXing - Read / Write QRCode Barcode Example

I have been slowly working my way through more of the zxing library. Since I did not find any CF examples in my searches, I am posting some of my code snippets in case it helps someone else.


Generate Barcode
To generate a barcode, I first created an instance of the QRCodeWriter. Then called its encode() method with four settings: the text to encode, barcode type and the desired width and height. The encode() method does it magic and returns a matrix of bytes. Next I used the MatrixToImageWriter class, and its toBufferedImage(), method to convert the matrix into something useful. Not surprisingly, it returns a BufferedImage which is easily converted to a CF image object.

Note: This example uses the JavaLoader.cfc

<!---
    Generate barcode
--->
<cftry>
    <cfset origText = "http://code.google.com/p/zxing/wiki/GettingStarted" />
    <!--- initialize writer and create a new barcode matrix --->
    <cfset BarcodeFormat = loader.create("com.google.zxing.BarcodeFormat") />
    <cfset writer = loader.create("com.google.zxing.qrcode.QRCodeWriter").init() />
    <cfset bitMatrix = writer.encode( origText, BarcodeFormat.QR_CODE, 80, 80 )>
    <!--- render the matrix as a bufferedimage --->
    <cfset converter = loader.create("com.google.zxing.client.j2se.MatrixToImageWriter")>
    <cfset buff = converter.toBufferedImage( bitMatrix ) />
    <!--- convert it to a CF compatible image --->
    <cfset img = ImageNew( buff ) />

    <!--- display results --->
    <b>Original Text = </b> <cfoutput>#origText#</cfoutput>
    <div>
        <cfimage action="writeToBrowser" source="#img#" format="png">
    </div>
    <!--- add real exception handling here ...--->
    <cfcatch>
        ERROR: Unable to generate barcode <cfoutput>#cfcatch.message#</cfoutput>
    </cfcatch>
</cftry>

Read Barcode
Now to read / decode, I passed the BufferedImage into a series of classes that attempt to locate a barcode within an image and essentially extract it into matrix of bits. Finally, I passed the matrix into a QRCodeReader for decoding and voila - it returned the decoded text.

Result:


<!---
    Decode barcode
--->
<cftry>
    <!--- extract the BufferedImage of the current barcode --->
    <cfset buff = ImageGetBufferedImage( img ) />
    <!--- prepare the image for decoding --->
    <cfset source = loader.create("com.google.zxing.client.j2se.BufferedImageLuminanceSource").init( buff ) />
    <cfset binarizer = loader.create("com.google.zxing.common.GlobalHistogramBinarizer").init( source ) />
    <cfset bitmap = loader.create("com.google.zxing.BinaryBitmap").init( binarizer ) />
    <cfset reader = loader.create("com.google.zxing.qrcode.QRCodeReader").init() />
    <!--- decode the barcode. skipping "hints" just for simplicity --->
    <cfset decodedResult = reader.decode( bitmap, javacast("null", "")) />
    
    <!--- display results --->
    <b>Decoded Text = </b> <cfoutput>#decodedResult.getText()#</cfoutput>

    <!--- add real exception handling here ...--->
    <cfcatch>
        ERROR: Unable to generate barcode <cfoutput>#cfcatch.message#</cfoutput>
    </cfcatch>
</cftry>

Related Entries

20 comments:

webRat April 21, 2010 at 6:07 AM  

Great post! Been meaning to look into this.

cfSearching April 21, 2010 at 6:34 AM  

Thanks! (If only you knew how long I have been 'looking into it' ;-)

BTW: If you do use zxing in the near future, be sure to get take latest sources. There is a fix for QRCodes, not in the general 1.5 release.

-Leigh

webRat April 21, 2010 at 10:00 AM  

I built from source, I copied the two jar files over elsewhere and I converted this from using javaloader to using Railo's createObject() which has 2 additional arguments.

The error I'm getting is:
ERROR: Unable to generate barcode No matching Method/Function for com.google.zxing.qrcode.QRCodeWriter.ENCODE(string, com.google.zxing.BarcodeFormat, numeric, numeric) found

I guess I'll have to download javaloader and see if it works the normal way and then see if it's a javaloader bug in Railo.

webRat April 21, 2010 at 10:49 AM  

btw, I was able to get this working with javaloader / Railo. I'll have to poke Micha and ask him what's up with createObject().

:)

cfSearching April 21, 2010 at 12:59 PM  

Cool. Maybe it just needs a javacast? The method signature is:

encode(String, BarcodeFormat, int, int)

So you might try using javacast() on the three primitive types: String, int, int

-Leigh

Libel Vox June 7, 2010 at 2:16 PM  

Good post. I've gotten things compile and am able to produce a QR image. However, the colors seemed to be inverse (black is white, white is black) than what is produced from other services on the web - or even your example. It's an issue that's causing my QR reader to not recognize things.

Is there an output setting for flipping the colors?

- Matthew

cfSearching June 10, 2010 at 1:06 AM  

@Matthew,

Yes, I ran into that problem too. It was bug. The last time I checked, the fix was not in the latest distribution jars. So I downloaded the latest source from svn and compiled new jars in Eclipse.

-Leigh

cfSearching June 10, 2010 at 1:11 AM  

@Matthew,

BTW: I have older versions of the "core" and "se" jars with that fix. If for some reason you cannot compile with Eclipse, feel free to email me (cfsearching / yahoo) and I can send them to you.

-Leigh

Rob G.,  August 6, 2010 at 3:23 PM  

Quick newb question...I program in CFML using Dreamweaver and really am interested in your example how to generate QR Codes which is going to be necessary for my next CF Project...How can i compile the jars and get started? Is CFBuilder a necessity? Is there any other way to compile the jars?

Thanks.

cfSearching August 6, 2010 at 7:14 PM  

@Rob G.,

The last time I checked, I do not think there were any pre-compiled jars available for download. So yes, you would need to compile the source yourself.

To compile, you do not need CFBuilder specifically (I used vanilla Eclipse). But you do need some IDE because it is not the type of project you want to try and compile via the command line ;-)

Compiling with Eclipse was very simple. You just download the source from svn and use the included build.xml files to create the jars with ANT. If you are not familiar with Eclipse, it probably sounds like a big deal. But once you have downloaded the sources, it is equivalent to selecting a task and saying "build jar".

Personally, I would recommend compiling the latest sources yourself. But as I mentioned in an earlier comment, I have some slightly older jars lying around if anyone wants to play around with them. Just shoot me an email.


-Leigh

cfSearching August 6, 2010 at 7:24 PM  

Silly me. There is a recent entry on Railo Blog about barcode generation, complete with examples .. and the zxing jars!

Thanks Todd!

http://www.railo.ch/blog/index.cfm/2010/6/23/Barcode-Generation-with-Railo

Shaun Mccran January 27, 2011 at 7:03 AM  

Hi,
Great example, I put something similar together, but I can't use cfimage. Any pointers on how you would convert it out to an image then? I've tried imageCFC but I'm not having much luck.

cfSearching January 27, 2011 at 7:24 AM  

@Shaun,

ImageCFC's write() function should work. Just pass in the BufferedImage returned from the converter. You could also use ImageIO to save it. Something like

<cfscript>
// ...
buff = converter.toBufferedImage( bitMatrix );
saveToFile = "c:\myBarcode.png";
ImageIO = createObject("java", "javax.imageio.ImageIO");
outStream = createObject("java", "java.io.FileOutputStream").init( saveToFile );
// formats ie "png", "gif", ....
wasWritten = ImageIO.write( buff, "png", outStream);
outStream.close();
</cfscript>


-Leigh

Shaun McCran January 29, 2011 at 2:10 PM  

Hi,
Thanks that worked a treat, imageCFC kept throwing an error using the BufferedImage, but your code was excellent.

Now I'm getting a Java error: Unsupported major.minor version 49.0

so am working on that.

Thanks
Shaun

cfSearching January 30, 2011 at 11:34 AM  

Hi @Shaun,

Regarding ImageCFC - I am not as familiar with it. So it is entirely possible I was thinking of the wrong method. I am pretty sure it can handle BufferedImages.

As far as the error, that usually means it was compiled for a later jvm than you are using (ie compiled for 1.5+ but you are using 1.4).

If the base code does _not_ use any of the newer features introduced in 1.5, you could recompile it under 1.4. But my guess would be it does. In which case you would need to upgrade the jvm to use the jar. Probably not a bad idea since 1.4 reached EOL anyway.

-Leigh

Shaun McCran January 30, 2011 at 11:47 AM  

Hi,

You are spot on the money there, the server is running 1.4: http://www.mccran.co.uk/examples/java_version/

I've raised a request for them to upgrade it but I'm not holding my breath.

cfSearching January 30, 2011 at 12:09 PM  

Bummer.

For grins, you might try recompiling for 1.4. It probably will not work. But no harm in confirming it.

You might also try google charts instead. Obviously not the same as generating your own locally. But it does use zxing internally ;-)

-Leigh

cfSearching January 30, 2011 at 12:59 PM  

@Shaun,

Well .. scratch the recompile idea. The developer notes say 1.5+ is required. So it is either upgrade or use google charts.

-Leigh

Unknown August 23, 2013 at 1:52 PM  

In the example you state skipping hints for sake of simplicity. How can I set the TRYHARDER ENUM to make the dectection more reliable?

I have been unable to set the TRYHARDER to True as a CF process

cfSearching August 27, 2013 at 8:43 AM  

@Unknown

IIRC, just create a CF structure.

key: is a DecodeHintType object
value: true (enabled) or false (disabled)

Not tested, but in cfscript something like this:

// get hints object
DecodeHint = createObject("java", "com.google.zxing.DecodeHintType");
// use CF struct to store hints
hints = {};
// add the desired hint, and enable it by setting it to "true"
hints[DecodeHintType.TRY_HARDER] = true;
decodedResult = reader.decode( bitmap, hints);

  © Blogger templates The Professional Template by Ourblogtemplates.com 2008

Header image adapted from atomicjeep