Embed Images as binaryData in AS3 to reduce SWF size
This evolved from a lengthy discussion on the file size of an SWF.
It seems that Flash/Flex embeds PNGs as 32bit only. And this in some cases causes the size of the image to be greatly increased. In some cases the size of the image more than doubles if the image had been compressed using optimization utilities.
The solution to keep the reduced size that I came up with is to embed the image as binaryData instead of a bitmap using the mimeType='application/octet-stream' like this
[Embed(source = "assets/8bit.png", mimeType = "application/octet-stream")] public static const IMG:Class; |
But this gives us a ByteArray instead of the needed bitmap.
This ByteArray can be decoded into a bitmap using the Loader.loadBytes() function, but this function is asynchronous and thus this step needs to be performed in advance, during initial game loading.
For this I have made the following OctetStreamBitmapDecoder class
/** * An asynchronous bitmap data decoder, for assets embedded as octet streams * * @author Danish Goel * @copyright 2012 Danish Goel * @license MIT License */ package com.danishgoel { import flash.display.Bitmap; import flash.display.Loader; import flash.events.Event; import flash.utils.ByteArray; import flash.utils.describeType; /** * ... * @author Danish Goel */ public class OctetStreamBitmapDecoder { // image loaded vs total public var _totalImages:uint; public var _loadedImages:uint = 0; // matching regex for assets private var _regex:RegExp; // call on loading complete private var _onComplete:Function; // call on each image decode private var _onProgress:Function; /** * Initialize the asset loader * * @param assetsClass The class which contains the embedded octet-stream bitmaps * @param onLoadComplete [optional] function to call once all the images are decoded * @param matchRegex [optional] only decode constants matching the given regex * @param onProgress [optional] call on each image decode with percent complete */ public function OctetStreamBitmapDecoder(assetsClass:Class, onComplete:Function = null, matchRegex:RegExp = null, onProgress:Function = null) { // assign parameters _regex = matchRegex; _onComplete = onComplete; _onProgress = onProgress; // get type info (reflection) var assetsXML:XML = describeType(assetsClass); // total number of images _totalImages = assetsXML.constant.length(); // loop over all constants for each(var item:XML in assetsXML.constant) { var notDecodable:Boolean = true; // if item is of type Class and matches the given regex (if any) if (item.@type == "Class" && (_regex == null || _regex.test(item.@name))) { // if its also a ByteArray if (new assetsClass[item.@name] is ByteArray) { // load the image using the byteArray loadImage(assetsClass[item.@name]); // and it is decodable notDecodable = false; } } // if the constant is not decodable, remove it from total count if (notDecodable) _totalImages--; } } /** * Asynchronously decode the byteStream image to a bitmapData * @param byteSrc Image byte stream */ public function loadImage(byteSrc:Class):void { // loader to load the image var loader:Loader = new Loader(); // on load complete loader.contentLoaderInfo.addEventListener(Event.COMPLETE, function(e:Event):void { // set decoded bitmap data byteSrc.bitmapData = Bitmap(e.target.content).bitmapData; // increment images loaded count _loadedImages++; // call progress callback if defined if (_onProgress != null) _onProgress(Number(_loadedImages) / _totalImages); // if all images loaded if (_totalImages == _loadedImages) { // and onComplete defined if (_onComplete != null) // call it _onComplete(); } } ); // begin load loader.loadBytes(ByteArray(new byteSrc)); } } } |
The usage of above is pretty simple, like following
new EmbeddedBitmapDecoder(Assets, imagesDecoded); |
where Assets is the class which contains all the embedded images to be decoded, and imagesDecoded is the function which will be called after loading all images.
the other 2 parameters of this class's constructor are
matchRegex - This is the regex which can be used to selectively decode constants from the Asset class. If supplied only constants matching the given regex are decoded. onProgress - a function taking a single argument, which is the percent of images loaded. This function if supplied is called after each image load, and can be used to make a progress bar. |
Once the images are decoded, the resulting BitmapData is stored in the bitmapData property of the Asset.
So for the the following Assets class
package { /** * All image assets, embedded as Octet Streams */ public class Assets { [Embed(source = "../assets/1.png", mimeType = "application/octet-stream")] public static const ONE:Class; [Embed(source = "../assets/2.png", mimeType = "application/octet-stream")] public static const TWO:Class; [Embed(source = "../assets/3.png", mimeType = "application/octet-stream")] public static const THREE:Class; [Embed(source = "../assets/4.png", mimeType = "application/octet-stream")] public static const FOUR:Class; } } |
We would use the resulting BitmapData like this
Assets.ONE.bitmapData |
Thus a simple approach to keep the image sizes in the swf to minimum is to compress ALL PNGs using a program like PNGGauntlet. Then embedding them as octet-streams and decoding at runtime using the OctetStreamBitmapDecoder
The GitHub link for this, with a sample project is this https://github.com/danishgoel/OctetStreamBitmapDecoder
Happy Coding 🙂
Enjoy this article?
Categories
Recent Posts
- Embed Images as binaryData in AS3 to reduce SWF size
- Syllabus of 8th Semester CSE
- Using CppUnit with Visual Studio
Archives
- November 2012 (1)
- January 2010 (1)
- November 2009 (1)
Leave a comment