Over a million developers have joined DZone.

Actionscript class to create QR Codes

· Mobile Zone
Dear Reader,

Anyone that knows me knows that I love new toys. Sometimes it’s a new piece of hardware or a new game. Sometimes however, it is just a idea to play with. This post is all about an idea. I’ve been intrigued by QR Codes for a while. Actually, my love of bar codes goes way back to when I used to run computer systems at my parent’s company. Even back then I saw how they could change things. These days, Bar Codes are ubiquitous in retail but haven’t moved out of the business niche. QR Codes stands ready to change that. Given that most smart phones can translate them they make a great way to link the real world to the net.

There are great resources on the web to generate QR Codes and save them to your computer for later use. However, being a developers, I wanted something I could play with and incorporate into my own applications. The problem is that generating a QR Code is not a trivial thing. Lucky for us however, Google has done the hard part already and all I really need to do is wrap their API in a class and I can use this in my own applications.

What you need to get started

If you want to create QR Codes, you have to be able to do two things, create them and read them. Since this post is all about creating them, I will leave the reading them part to you. If you have a smart phone, there is probably a good application for you to use to verify your work. For the Android OS, I use BarCode Scanner.

That’s it. All you need is a way to check your work. Google does the heavy lifting of creating the graphic and the code below gives you a nice easy wrapper to allow you to include it in your Flash Builder 4 applications.

The fun part

Ok, let’s take apart the code. This is one of my first Flex components that I am confident enough to release. That having been said, I do welcome any comments or criticisms about the class.

If you haven’t yet, go review Google’s Chart API page on QR Codes. Google has boiled it down to the essentials, 3 required parameters and two optional. The heart of the class puts those parameters together and makes the call to the API. It takes the image it gets back and puts it in a form that Flex applications can re-purpose.

Planning for all EVENTualities

[Event(name="imageCreated", type="ImageCreatedEvent")]
[Event(name="imageFailed", type="ImageFailedEvent")]

First we have to prepare for the events that can happen. In our case, either it works or it doesn’t. Either way, we need to provide the developer using this class with a way to handle both eventualities in their code so we don’t have to in our code.

The code for the events is located below, after the main class. The ImageFailedEvent is just a subclass of FaultEvent and serves only as a marker that can be trapped separately.

ImageCreatedEvent however, has an additional property of thisImage. When the even is fired, it passes a copy of the QRCode object to all listeners allowing them to easily fetch and incorporate the resulting graphic into the application.

The BINDABLEs

[Bindable] public var qualityArray:ArrayCollection = new ArrayCollection(["L","M","Q","H"]);
[Bindable] public var marginArray:ArrayCollection = new ArrayCollection([1,2,3,4,10,40]);

Of all the properties we have, only two are bindable, the two data arrays that specify the acceptable parameters for Error Correction and Margin.

All other parameters have reasonable defaults but in using the class, no reason to actually make them bindable presented itself.

The heart of the matter

var loader:Loader = new Loader();
loader.contentLoaderInfo.addEventListener(Event.COMPLETE, imageComplete);
loader.load(request);

The heart of the class is the createImage() method. To use the class, you instantiate the class, set the required properties, and then call createImage(). Within that method, the first thing we do is a parameter check. Make sure we have given the image a valid height and width and that we actually have something to encode. Once we can make a reasonable call to the API, we setyp for the call, make it and deal with the results.

[Side Note: QR Codes can store a variety of information. The one above is simple text. Anything that can be represented as text can be encoded. The zxing library can interpret several different types of data and the Android app will allow you to act on it appropriately. If the content starts with mailto: then the app asks me f I want to send an email to this person. If it starts with http: then the app asks me if I want to open it in a browser. Similarly, if it starts with BEGIN VCARD: then it will let me store the resulting VCARD in my contacts. Your application will most likely support similar features. In the reference application I created to test QRCode.as, I allow for encoding text, emails, URLs and VCARDS. However, this is ancillary to the QR Code specification. The specification does not dictate formats, only maximums for the different types of data (numeric, alphanumeric, binary, etc.) being stored.]

Back in February I wrote a post about Dynamically loading images from the web. In it, I talked about how calling an API that does not return XML or JSON is not as straightforward as it might be in Flex. I used the techniques (and probably the code) that I displayed in that post to make the call to Google’s chart API. I won’t rehash the previous post, you can review it as your leisure. At it’s core is a call to import flash.display.Loader to load the contents from the web.

createImage() does have one new feature, debug. If you set debug = true you will get an alert showing you the parameters being passed to Google's API. I am a big believer in giving developers a way to see what is going on without having to hack the code. If you have Charles installed and running, you can see the call actually being made, however, I find it easier just to incorporate debugging into the code and allow developers the option. The dark side of this is that developers have the option of shooting themselves in the food this way as well. More than one developer has left debugging turned on when releasing their code.

Dealing with the aftermath

Since Flex is asynchronous, we have to have a method to deal with the image once the API call is done. imageComplete() is the handler for the COMPLETE event. In it, we set the height and width of the actual image as well as the data from the image itself. Then we dispatch the imageComplete event so that the main program can do something with the image.

Wrapping it up

That is it for the class. To use it, just instantiate, set the parameters and fire createImage() Of couse because we are talking to an API at Google, this only works with AIR applications. If you need a Flash application that runs from your own domain to be able to use QRCode.as, you will have to setup a project on your server to proxy the call to Google. (Trivial but beyond the scope of what we are discussing here.)

Until next time,
I <3 |<
=C=

The Code

/*
* Instantiate the object
*/
var qrCode:QRCode = new QRCode();

/*
* Add our event listeners
*/
qrCode.addEventListener('imageCreated',this.displayImage);
qrCode.addEventListener('imageFailed',this.cannotDisplayImage);


/*
* Setup for the call
*/
qrCode.width = parseInt(imageWidth.text);
qrCode.height = parseInt(imageHeight.text);
qrCode.margin = margin.selectedIndex;
qrCode.errorCorrectionLevel = errorCorrectionLevel.selectedIndex;
qrCode.content = contentToEncode;
qrCode.imageType = grpImageType.selectedValue.toString();

/*
* make the call
*/
qrCode.createImage();
/**
*
* http://code.google.com/apis/chart/docs/gallery/qr_codes.html
*/
package
{
[Event(name="imageCreated", type="ImageCreatedEvent")]
[Event(name="imageFailed", type="ImageFailedEvent")]

public class QRCode
{
import flash.display.BitmapData;
import flash.display.Loader;
import flash.display.LoaderInfo;
import flash.net.FileReference;
import flash.net.URLRequest;
import flash.net.URLVariables;
import flash.utils.ByteArray;

import mx.collections.ArrayCollection;
import mx.controls.Alert;
import mx.controls.Image;
import mx.graphics.codec.JPEGEncoder;
import mx.graphics.codec.PNGEncoder;
import mx.rpc.events.FaultEvent;
import mx.rpc.events.ResultEvent;


[Bindable] public var qualityArray:ArrayCollection = new ArrayCollection(["L","M","Q","H"]);
[Bindable] public var marginArray:ArrayCollection = new ArrayCollection([1,2,3,4,10,40]);

public var height:int = 150;
public var width:int = 150;

public var content:String = "";
public var debug:Boolean = false;
public var errorCorrectionLevel:int = 0;
public var imageType:String = 'PNG';

public var margin:int = 0;

protected var _image:Image = new Image();
protected var apiUrl:String = 'http://chart.apis.google.com/chart';

public function QRCode()
{
/*
* cht=qr
* chs=<width>x<height>
* chl=<data>
* chd
* choe=<output_encoding>
* chld=<error_correction_level>|<margin>
*
* Notes:
* changing the quality needs to make sure the max number of chs hasn't been exceeded.
* Add Types
*/
}



public function createImage():void
{
// do a property check and throw appropriate error for missing properties like content to encode
if (this.width<1) {
dispatchEvent(new ImageFailedEvent(ImageFailedEvent.IMAGE_FAILED,false,false));
return;
}
if (this.height<1) {
dispatchEvent(new ImageFailedEvent(ImageFailedEvent.IMAGE_FAILED,false,false));
return;
}
if (this.content.length<1) {
dispatchEvent(new ImageFailedEvent(ImageFailedEvent.IMAGE_FAILED,false,false));
return;
}

var params:URLVariables = new URLVariables();
params.cht = 'qr';
params.chs = this.height +'x'+ this.width;
params.chl = this.content;
params.chld = qualityArray[errorCorrectionLevel]+"|"+marginArray[margin];

var request:URLRequest = new URLRequest(apiUrl);
request.method="POST";
request.data = params;

// DEBUGGING
if (this.debug) {
var a:String = apiUrl +
'?cht='+params.cht
+"&chs="+params.chs
+"&chld="+params.chld
+"&chl="+params.chl
Alert.show(a);
} // if (this.debug)

var loader:Loader = new Loader();
loader.contentLoaderInfo.addEventListener(Event.COMPLETE, imageComplete);
loader.load(request);
} // protected function createImage():void


protected function imageComplete(event:Event):void
{
var loader:Loader = (event.target as LoaderInfo).loader;
_image.data = loader.content;
_image.width = loader.width;
_image.height = loader.height;
dispatchEvent(new ImageCreatedEvent(ImageCreatedEvent.IMAGE_CREATED,false,false,_image));
return;
} // protected function displayImage(event:Event):void

protected function cannotDisplayImage(event:FaultEvent):void
{
dispatchEvent(new ImageFailedEvent(ImageFailedEvent.IMAGE_FAILED,false,false));
} // protected function cannotDisplayImage(event:FaultEvent):void

public function getImage():Image{
return this._image;
}

}
}

ImageCreatedEvent.as

package 
{
import flash.events.Event;
import mx.controls.Image;

public class ImageCreatedEvent extends Event
{
public static const IMAGE_CREATED:String = 'imageCreated';
protected var _image:Image;

public function ImageCreatedEvent(type:String, bubbles:Boolean=false, cancelable:Boolean=false, thisImage:Image=null)
{
super(type, bubbles, cancelable);
_image = thisImage;
return;
}

override public function clone():Event
{
return new ImageCreatedEvent(type, bubbles, cancelable, _image);
}

}
}

ImageFailedEvent.as

package
{
import mx.rpc.events.FaultEvent;

public class ImageFailedEvent extends FaultEvent
{
public static const IMAGE_FAILED:String = 'imageFailed';

public function ImageFailedEvent(type:String, bubbles:Boolean=false, cancelable:Boolean=false)
{
super(type, bubbles, cancelable);
}

}
}
Topics:

Opinions expressed by DZone contributors are their own.

The best of DZone straight to your inbox.

SEE AN EXAMPLE
Please provide a valid email address.

Thanks for subscribing!

Awesome! Check your inbox to verify your email so you can start receiving the latest in tech news and resources.
Subscribe

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}