/** * Enhanced PNG Encoder * Originally at * http://www.5etdemi.com/blog/archives/2006/12/as3-png-encoder-faster-better/ * and http://www.5etdemi.com/uploads/PNGEnc.as * Original license is ?? MIT ?? * * Enhanced 19-Aug-2007 by nick galbreath * http://blog.modp.com/2007/08/adding-metadata-to-actionscript-3-png.html * - Added typing information for return values of functions * - Remove semi-bogus duplicate variable warnings/errors (Flex3 SDK Beta 1) * - Added optional metadata support * - Added most minimal documentation * - Reindented and got rid of tabs and MS-DOS line endings * - Changes in public domain, credit welcome. */ package net.kaourantin { import flash.utils.ByteArray; import flash.display.BitmapData; import flash.utils.getTimer; import flash.geom.Rectangle; public class PNGEnc { /** * Encode a raw bitmap into a PNG image * * @param img the raw bitmap data * @param type 0 = transparent, 1 = opaque * @param meta hash table of key/value pairs for optional metadata * @return encode PNG ByteArray */ public static function encode(img:BitmapData, type:uint = 0, meta:Object = null):ByteArray { // Create output byte array var png:ByteArray = new ByteArray(); // Write PNG signature png.writeUnsignedInt(0x89504e47); png.writeUnsignedInt(0x0D0A1A0A); // Build IHDR chunk var IHDR:ByteArray = new ByteArray(); IHDR.writeInt(img.width); IHDR.writeInt(img.height); if(img.transparent || type == 0) { IHDR.writeUnsignedInt(0x08060000); // 32bit RGBA } else { IHDR.writeUnsignedInt(0x08020000); //24bit RGB } IHDR.writeByte(0); writeChunk(png,0x49484452,IHDR); // Write text metadata // should be before IDAT so ImageMagic can read it for (var k:String in meta) { writeChunk_tEXt(png, k, meta[k]); } // Build IDAT chunk var IDAT:ByteArray= new ByteArray(); switch(type) { case 0: writeRaw(img, IDAT); break; case 1: writeSub(img, IDAT); break; } IDAT.compress(); writeChunk(png,0x49444154,IDAT); // Build IEND chunk writeChunk(png,0x49454E44,null); // return PNG return png; } private static function writeRaw(img:BitmapData, IDAT:ByteArray):void { var h:int = img.height; var w:int = img.width; var transparent:Boolean = img.transparent; for(var i:int=0;i < h;i++) { // no filter if ( !transparent ) { var subImage:ByteArray = img.getPixels( new Rectangle(0, i, w, 1)); //Here we overwrite the alpha value of the first pixel //to be the filter 0 flag subImage[0] = 0; IDAT.writeBytes(subImage); //And we add a byte at the end to wrap the alpha values IDAT.writeByte(0xff); } else { IDAT.writeByte(0); var p:uint; for(var j:int=0;j < w;j++) { p = img.getPixel32(j,i); IDAT.writeUnsignedInt( uint(((p&0xFFFFFF) << 8)| (p>>>24))); } } } } private static function writeSub(img:BitmapData, IDAT:ByteArray):void { var r1:uint; var g1:uint; var b1:uint; var a1:uint; var r2:uint; var g2:uint; var b2:uint; var a2:uint; var r3:uint; var g3:uint; var b3:uint; var a3:uint; var p:uint; var h:int = img.height; var w:int = img.width; var j:int for(var i:int=0;i < h;i++) { // no filter IDAT.writeByte(1); if ( !img.transparent ) { r1 = 0; g1 = 0; b1 = 0; a1 = 0xff; for(j=0;j < w;j++) { p = img.getPixel(j,i); r2 = p >> 16 & 0xff; g2 = p >> 8 & 0xff; b2 = p & 0xff; r3 = (r2 - r1 + 256) & 0xff; g3 = (g2 - g1 + 256) & 0xff; b3 = (b2 - b1 + 256) & 0xff; IDAT.writeByte(r3); IDAT.writeByte(g3); IDAT.writeByte(b3); r1 = r2; g1 = g2; b1 = b2; a1 = 0; } } else { r1 = 0; g1 = 0; b1 = 0; a1 = 0; for(j=0;j < w;j++) { p = img.getPixel32(j,i); a2 = p >> 24 & 0xff; r2 = p >> 16 & 0xff; g2 = p >> 8 & 0xff; b2 = p & 0xff; r3 = (r2 - r1 + 256) & 0xff; g3 = (g2 - g1 + 256) & 0xff; b3 = (b2 - b1 + 256) & 0xff; a3 = (a2 - a1 + 256) & 0xff; IDAT.writeByte(r3); IDAT.writeByte(g3); IDAT.writeByte(b3); IDAT.writeByte(a3); r1 = r2; g1 = g2; b1 = b2; a1 = a2; } } } } private static var crcTable:Array; private static var crcTableComputed:Boolean = false; private static function writeChunk(png:ByteArray, type:uint, data:ByteArray):void { var c:uint; if (!crcTableComputed) { crcTableComputed = true; crcTable = []; for (var n:uint = 0; n < 256; n++) { c = n; for (var k:uint = 0; k < 8; k++) { if (c & 1) { c = uint(uint(0xedb88320) ^ uint(c >>> 1)); } else { c = uint(c >>> 1); } } crcTable[n] = c; } } var len:uint = 0; if (data != null) { len = data.length; } png.writeUnsignedInt(len); var p:uint = png.position; png.writeUnsignedInt(type); if ( data != null ) { png.writeBytes(data); } var e:uint = png.position; png.position = p; c = 0xffffffff; for (var i:int = 0; i < (e-p); i++) { c = uint(crcTable[ (c ^ png.readUnsignedByte()) & 0xff] ^ (c >>> 8)); } c = uint(c^uint(0xffffffff)); png.position = e; png.writeUnsignedInt(c); } /** * write out metadata using Latin1, uncompressed * * @param png The output bytearray * @param key the metadata key. Must be in latin1, between 1-79 characters * @param value the metadata value. Must be in latin1. * * the key or value is null or violates some contraints, the metadata * is silently not added */ private static function writeChunk_tEXt(png:ByteArray, key:String, value:String):void { if (key == null || key.length == 0 || key.length > 79) { return; } if (value == null) { value = ""; } // the spec says this should be latin1, // but UTF8 is probably ok, but be care of overflows var tEXt:ByteArray = new ByteArray(); tEXt.writeMultiByte(key, "iso-8859-1"); tEXt.writeByte(0x0); tEXt.writeMultiByte(value, "iso-8859-1"); writeChunk(png, 0x74455874, tEXt); } } }