GhostWire Studios - Flash/Flex UI Components Development And Consulting Services
Quality User Interface Controls For Flash Application DevelopmentAspireUI Components

Dec 13 2009

[AS3] Serializing A Bundle Of Bitmaps As Data Objects

Published by at 2:31 pm under Flash,Flash AS3,Tips

This post is a supplement to “Serializing Bitmaps (Storing BitmapData As Raw Binary/ByteArray)”. In that article, we looked at how to convert BitmapData to a ByteArray, save that ByteArray, and re-construct the BitmapData from the saved ByteArray.

It is important to note that the technique saves the ByteArray “as is” in a flat binary file without any header or any block of metadata – this means that the file will in itself not be able to communicate its data structure and therefore, proper usage of the data requires prior knowledge of how the data has been packed (we used the first four bytes for storing the value of the width of the image). As a result, that method may be deemed as an “unorthodox” hack and unsuitable in team development.

In this post, we look at how you can employ the same basic idea while making the saved data more “consumable” by other developers.


Action Message Format (AMF)
ByteArray objects store ActionScript objects using the AMF format. So instead of saving the binary bitmap data in raw form in the way we described previously, we will put the serialized data in an Object and write that object to a ByteArray:

// assume you have Bitmap object "bitmapImage" you want to save
 
// convert the BitmapData to ByteArray
// this time, do not pack any other data in the same ByteArray object
var data:ByteArray = new ByteArray();
data.writeBytes(bitmapImage.bitmapData.getPixels(bitmapImage.bitmapData.rect));
 
// create a new data object
var bmObj:Object = new Object();
bmObj.width = bitmapImage.bitmapData.width;
bmObj.height = bitmapImage.bitmapData.height;
bmObj.data = data;
 
// create a new ByteArray object for saving
var bytes:ByteArray = new ByteArray();
bytes.writeObject(bmObj);
bytes.compress();

So that does it – you can now save the binary data normally (by posting it to a server script, via AIR local filesystem API, via SharedObject, via FP10 FileReference, etc.). Although we are still saving our data in a flat binary file, the difference here is that developers consuming the data only need to know that they must read in an Object – they do not need to have the low-level knowledge of how exactly the data is packed.


ByteArray To BitmapData
Let’s look at how the binary data can now be consumed in an application.

First, load the file as normal:

var ldr:URLLoader	= new URLLoader();
ldr.dataFormat	= URLLoaderDataFormat.BINARY; // ** make sure you do this **
ldr.addEventListener(Event.COMPLETE, on_fileLoad);
ldr.addEventListener(IOErrorEvent.IO_ERROR, on_fileLoadError);
ldr.load(new URLRequest(pathToBitmapDataFile));

Process the loaded data:

private function on_fileLoad(evt:Event):void
{
	if (evt.type == Event.COMPLETE)
	{
		var bytes:ByteArray = URLLoader(evt.target).data as ByteArray;
		if (bytes)
		{
			try
			{
				bytes.uncompress();
			}
			catch(e:Error)
			{
			}
			// bytes is now the uncompressed byte array
			// ... process bytes ...
			var obj:Object = bytes.readObject();
 
			// obj now has the properties "width", "height" and "data" as saved previously
 
			var bmd:BitmapData = new BitmapData(obj.width, obj.height, true, 0); // 32 bit transparent bitmap
			bmd.setPixels(bmd.rect, obj.data);
 
			var bm:Bitmap = new Bitmap(bmd);
			addChild(bm);
		}
	}
}


Bundle of Bitmaps
What if you have multiple images you want to save and use later? You can consider serializing the bitmaps and store them together in a single binary file. To do that, we will give each image a unique id (such as the path of the image file if you are attempting to load and serialize external image files), store the serialized data in a hash, write that hash to a ByteArray, and then write that ByteArray to file.

// for storing the serialized images
private var imgLibrary:Object = { };
 
private function saveImage(id:String, image:Bitmap):void
{
	if (id == null || id == "")
	{
		return;
	}
 
	if (image == null)
	{
		imgLibrary[id] = null; // ** remove previously stored data **
		return;
	}
 
	var data:ByteArray = new ByteArray();
	data.writeBytes(image.bitmapData.getPixels(image.bitmapData.rect));
 
	// create a new data object
	var bmObj:Object = new Object();
	bmObj.width = image.bitmapData.width;
	bmObj.height = image.bitmapData.height;
	bmObj.data = data;
 
	imgLibrary[id] = bmObj; // ** will overwrite previously stored data **
}
 
saveImage("iconA", imageIconA);
saveImage("iconB", imageIconB);
saveImage("iconC", imageIconC);
// imgLibrary now contains three data objects each representing an image

Now, write imgLibrary to file:

// ** must target Flash Player version 10+ **
private function on_buttonClick(evt:MouseEvent):void
{
	var bytes:ByteArray = new ByteArray();
	bytes.writeObject(imgLibrary); // store width of image
	bytes.compress();
	new FileReference().save(bytes, "image.bmd"); // default name "image.bmd"
}

In another application, we load the saved binary file as usual:

var ldr:URLLoader	= new URLLoader();
ldr.dataFormat	= URLLoaderDataFormat.BINARY; // ** make sure you do this **
ldr.addEventListener(Event.COMPLETE, on_fileLoad);
ldr.addEventListener(IOErrorEvent.IO_ERROR, on_fileLoadError);
ldr.load(new URLRequest(pathToFile));

And process and use the loaded binary data as follows:

private var imgLibrary:Object;
 
private function on_fileLoad(evt:Event):void
{
	if (evt.type == Event.COMPLETE)
	{
		var bytes:ByteArray = URLLoader(evt.target).data as ByteArray;
		if (bytes)
		{
			try
			{
				bytes.uncompress();
			}
			catch(e:Error)
			{
			}
			// bytes is now the uncompressed byte array
			// ... process bytes ...
			imgLibrary = bytes.readObject();
 
			// imgLibrary is now initialized and contains data objects each representing a bitmap image
		}
	}
}

After populating the imgLibrary hash, you can use a method like the one below to query it:

private function getImage(id:String):Bitmap
{
	if (id == "" || id == null || imgLibrary == null || imgLibrary[id] == null)
	{
		return null;
	}
	var obj:Object = imgLibrary[id];
	try
	{
		// obj now has the properties "width", "height" and "data" as saved previously
 
		var bmd:BitmapData = new BitmapData(obj.width, obj.height, true, 0); // 32 bit transparent bitmap
		bmd.setPixels(bmd.rect, obj.data);
 
 		return new Bitmap(bmd);
	}
	catch(e:Error)
	{
	}
	return null;
}
 
getImage("iconA"); // returns a Bitmap with BitmapData constructed from saved byte array

The getImage() method can also be improved such that the ByteArray-to-BitmapData conversion is only done once per image:

private function getImage(id:String):Bitmap
{
	if (id == "" || id == null || imgLibrary == null || imgLibrary[id] == null)
	{
		return null;
	}
	var obj:* = imgLibrary[id];
 
	if (obj is BitmapData)
	{
		return new Bitmap(BitmapData(obj).clone());
	}
 
	try
	{
		// obj now has the properties "width", "height" and "data" as saved previously
 
		var bmd:BitmapData = new BitmapData(obj.width, obj.height, true, 0); // 32 bit transparent bitmap
		bmd.setPixels(bmd.rect, obj.data);
 
		imgLibrary[id] = bmd;
 
 		return new Bitmap(bmd.clone());
	}
	catch(e:Error)
	{
	}
	return null;
}

Note: If you are confident that the BitmapData is not going to be modified by instances, remove the clone() calls above – it will save memory since that means all Bitmap instances with the same id will be using the exact same BitmapData.


Embedding Image Asset(s)
After you have saved the binary file, remember that you are not limited to using it only as an external resource – you can also embed the file into your SWF:

// ActionScript 3.0
[Embed(source="assets/images/icons.bin", mimeType="application/octet-stream")]
private static const iconsBundle:Class;
 
private var imgLibrary:Object;
 
private function initIcons():void
{
	var bytes:ByteArray = new iconsBundle() as ByteArray;
	if (bytes)
	{
		try
		{
			bytes.uncompress();
		}
		catch(e:Error)
		{
			// data not compressed
		}
		imgLibrary = bytes.readObject();
		// imgLibrary is now initialized and contains data objects each representing a bitmap image
	}
}
pixelstats trackingpixel
Be Sociable, Share!
             

    5 responses so far

    5 Responses to “[AS3] Serializing A Bundle Of Bitmaps As Data Objects”

    1. Markon 24 Jun 2010 at 2:35 pm

      Hi There,

      I compressed a 5 MB xml-file. decompressing it works fine, but takes a while. How can check if file is completely decompressed?

      Mark

    2. sunnyon 25 Jun 2010 at 2:12 am

      Note the try-catch block. It is single-threaded, so if bytes.uncompress() is successful the data has been uncompressed.

    3. Eric Dauleyon 15 Sep 2012 at 1:24 am

      I noticed a huge file size increase from my bundle of jpegs. I have about 5 Jpegs that totaled about 3 megabytes, however after using this technique my binary data files was about 16 megs. Am I doing something wrong? Is this normal?

    4. Govindon 06 Nov 2012 at 10:33 am

      var obj:Object = bytes.readObject(); // This returns only the byte array. so i could not get the width and height of the object as below. Plz give me the solution.

      var bmd:BitmapData = new BitmapData(obj.width, obj.height, true, 0); //This shows an error that bytearray doesnot contain a property of width.

    5. Scott Yannitellon 10 Nov 2012 at 8:40 pm

      Thank you so much for this I’ve been trying to figure this out for some time now. I have a project that has over 676 images that have to be loaded and it takes many seconds to load one by one. By putting it all into byteArray libs it makes the loading apparently instantaneous.

    Trackback URI | Comments RSS

    Leave a Reply

    *
    To prove you're a person (not a spam script), type the security word shown in the picture. Click on the picture to hear an audio file of the word.
    Click to hear an audio file of the anti-spam word