/PNGEncoder2

A better PNG encoder for Flash

Primary LanguageHaxe

Overview

This is the quick-start guide to my PNGEncoder2 library, which compresses BitmapData objects into PNG files (stored in ByteArray objects). For a more in-depth explanation of how it works check out my blog post about it.

Features

  • Blazing fast performance: Written in haXe and highly tuned for speed, it can outperform the as3corelib PNGEncoder by up to 5x (on the FAST setting)! This is made possible by the custom DEFLATE library I wrote from scratch specifically for this encoder.

  • Compression options: Also made possible by my custom DEFLATE implementation, you can now choose between compression levels: FAST, NORMAL, and GOOD (and UNCOMPRESSED too, but...).

  • True asynchronous encoding: Most other asynchronous PNG encoders do the conversion from bitmap data to PNG data asynchronously, then call ByteArray.compress() at the end -- which is a long, synchronous operation that can take half the total encoding time or more. (An exception is the cool In-Spirit PNG encoder, but its asynchronous mode is amazingly slow, and the SWC weighs in at over 100KB.) Mine does completely smooth encoding, asynchronously (with progress events!) and scales to a target FPS without wasting any cycles idling.

  • Large image support: Can compress ridiculously large images on-the-fly, since it does the work in manageable chunks (when done asynchronously) instead of all at once. There is no single-step bottleneck.

  • Opaque bitmap support: If your BitmapData is not transparent, then it will be automatically encoded using the (generally smaller) 24-bit PNG format, otherwise the 32-bit format is used. (Note that transparency is detected via the transparent property of the BitmapData object, and not by scanning every pixel, which would be too slow.)

  • Open source: Use the haXe classes if you have a haXe project, or build against an SWC if you have an AS3 project. See the licenses at the top of PNGEncoder2.hx and DeflateStream.hx (both have separate, but similarly permissive, licenses).

Usage

PNGEncoder2 supports both synchronous (fast but freezes the UI while it encodes) and asynchronous PNG encoding (a smidgeon slower, but can maintain a target FPS).

Synchronous example:

var bmp : BitmapData = ...;		// The bitmap you want to encode
var png : ByteArray;			// Variable to store the result

PNGEncoder2.level = CompressionLevel.FAST;	// Optional. Defaults to FAST

png = PNGEncoder2.encode(bmp);

Asynchronous example:

var bmp : BitmapData = ...;		// The bitmap you want to encode

PNGEncoder2.level = CompressionLevel.FAST;	// Optional. Defaults to FAST

var encoder : PNGEncoder2 = PNGEncoder2.encodeAsync(bmp);
encoder.targetFPS = 12;		// Optional. Defaults to 20. Lower FPSs yield faster compression

// Because the encoder is guaranteed to fire the COMPLETE event
// after at least one frame, it's safe to attach the listener after
// starting the encoding (as long as it's done before the next frame)
encoder.addEventListener(Event.COMPLETE, function (e) {
	var png : ByteArray = encoder.png;
});

// encoder also dispatches ProgressEvent.PROGRESS events if you
// want to be notified of progress

Memory-constrained environments:

If you're working in a memory-constrained environment, or with truly huge images, the asynchronous method is usually the best choice as it encodes the image one chunk at a time, and therefore requires much less working memory.

After encoding an image, however, the working memory is not freed. This is not a memory leak, but rather an optimization to speed up the next encoding (the memory is reused, which among other things eliminates the need to recalculate the CRC tables each time). With large images, or low available memory, this may be undesirable. To free the working memory, simplpy call PNGEncoder2.freeCachedMemory() after each encoding is complete. This will reduce the resident memory usage to near zero from what could otherwise be potentially several kilobytes or even megabytes (what a savings!).

Bonus: PNG decoding

Update: Support for synchronous decoding of PNGs that were created using PNGEncoder2 has been added:

var bmp : BitmapData = ...;
var png : ByteArray = PNGEncoder2.encode(bmp);
bmp = PNGEncoder2.decode(png);		// Round-trip back to BitmapData

Decoding is about twice as fast as encoding, though it should be stressed that only PNGEncoder2 PNGs can be decoded properly, not arbitrary PNGs. The advantage is that decoding is faster than using the built-in flash.display.Loader object, at the cost of reduced flexibility; if you need to decode arbitrary PNG files, or you want to decode asynchronously, use the Loader as usual:

var png : ByteArray = ...;    // From a file or encoded PNG ByteArray
var loader : Loader = new Loader();
loader.contentLoaderInfo.addEventListener(Event.COMPLETE, function (e) {
    var bmp : BitmapData = new BitmapData((int)loader.width, (int)loader.height, true, 0x00FFFFFF);
    bmp.draw(loader);        // Write the decoded PNG to a bitmap data object
});
loader.loadBytes(png);

Bonus: Metadata support

Update: Support for encoding text metadata into the generated PNG has been added:

var bmp : BitmapData =  ...;
var metadata : Object = { };
metadata[PNGKeywords.TITLE] = "My PNG";
metadata["Custom key"] = "Chunky bacon!";
var png : ByteArray = PNGEncoder2.encodeWithMetadata(bmp, metadata);

Every key-value pair in the given metadata object will be encoded into the PNG as either a tEXt chunk (if the value can be represented in Latin-1) or an iTXt chunk (which supports arbitrary Unicode strings).

Installation

Note that this library requires Flash 10 or higher. Unfortunately, until Adobe decides what they're doing with the next version of Alchemy, this library won't work in any SWF compiled to target Flash player 11 (SWF version 13) or above (as of Flash Player 11.2). So, you can target Flash 10 (SWF versions 10-12) only for now. Update: It seems this library works fine even with recent versions of Flash Player (11.7 at the time of writing). Go figure.

ActionScript 3

If you're using ActionScript 3 you can simply download the SWC file and link it to your project. There are also "slim" versions of the library that contain only a single compression method each; these are recommended for production since they are smaller (assuming you only use one compression level).

To link an SWC into a project made with the Flash CS5.5 IDE, the steps are as follows:

  1. In the File menu, select "ActionScript Settings...".
  2. Under the "Library Path" tab, click the little red icon with the "f" logo on it. The tooltip of the button reads: "Browse to SWC file"
  3. Browse to wherever it was that you saved the SWC, and select it
  4. Press OK!

To link an SWC in other versions of the Flash IDE, follow this guide instead.

HaXe

If you're using haXe, just use the source directly instead of fiddling with SWCs (actually, your project won't compile if you use the SWC). All you need are the PNGEncoder2.hx and DeflateStream.hx files. Compiles under both haXe 2 and 3.

If you want to have only one compression method compiled (to cut down on generated code size), add -D FAST_ONLY, -D NORMAL_ONLY, or -D GOOD_ONLY to your compile options (this is equivalent to using the slim SWC versions). The decode method is by default not compiled in; to enable it, add the -D DECODER compile option (which can be combined with the others above).