PNG Encoder in AS3
I hope you are already playing around with AS3, because in my opinion it rocks! There are really endless possiblities, especially when taking into account some of the new classes like ByteArray and the new number types uint and int. I added a little sample here. This code is not entirely my doing, I merely fixed a couple of bugs, but hopefully it shows the power. It implements a simple PNG encoder, taking a BitmapData as input and returns the encoded PNG as a ByteArray which you can then process further, f.ex. sending it to your server. The main reason that this is trivial to do in AS3 is a 'little' feature in ByteArray which allows you to compress data using zlib.
To use this class, have a BitmapData object somewhere and then call it this way:
To use this class, have a BitmapData object somewhere and then call it this way:
var myPNG:ByteArray = PNGEnc.encode(myBitmapData);Very simple. Obviously this could be extended to do a better job overall, support for scanline filters would be the first thing to look at probably. Here is the class which does the job:
import flash.geom.*;
import flash.display.*;
import flash.util.*;
public class PNGEnc {
public static function encode(img:BitmapData):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);
IHDR.writeUnsignedInt(0x08060000); // 32bit RGBA
IHDR.writeByte(0);
writeChunk(png,0x49484452,IHDR);
// Build IDAT chunk
var IDAT:ByteArray= new ByteArray();
for(var i:int=0;i < img.height;i++) {
// no filter
IDAT.writeByte(0);
var p:uint;
if ( !img.transparent ) {
for(var j:int=0;j < img.width;j++) {
p = img.getPixel(j,i);
IDAT.writeUnsignedInt(
uint(((p&0xFFFFFF) << 8)|0xFF));
}
} else {
for(var j:int=0;j < img.width;j++) {
p = img.getPixel32(j,i);
IDAT.writeUnsignedInt(
uint(((p&0xFFFFFF) << 8)|
(shr(p,24))));
}
}
}
IDAT.compress();
writeChunk(png,0x49444154,IDAT);
// Build IEND chunk
writeChunk(png,0x49454E44,null);
// return PNG
return png;
}
private static var crcTable:Array;
private static var crcTableComputed:Boolean = false;
private static function writeChunk(png:ByteArray,
type:uint, data:ByteArray) {
if (!crcTableComputed) {
crcTableComputed = true;
crcTable = [];
for (var n:uint = 0; n < 256; n++) {
var c:uint = 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;
var c:uint = 0xffffffff;
for (var i:int = 0; i < (e-p); i++) {
c = uint(crcTable[
(c ^ png.readUnsignedByte()) &
uint(0xff)] ^ uint(c >>> 8));
}
c = uint(c^uint(0xffffffff));
png.position = e;
png.writeUnsignedInt(c);
}
}


44 Comments:
I have a really dumb question - I am a long time Flasher but a Flex n00b big time. How can I try this AS3 script and others I have seen. I assume I need to compile through Flex Builder 2? Thanks!
-Sarge
Yes. Right now you need to download and compile using Flex Builder alpha.
You can get the from:
http://labs.macromedia.com
hope that helps...
mike chambers
mesh@macromedia.com
You know Tinic, a long time ago, when you posted about the zlib thing for the Flash Player, I thought of adding a comment here saying "BUT! Can't you make that zlib compression available to the code? So people could pass their data through zlib without having to rewrite it via AS? It'd easier and much faster this way for people to compress data!". I dismissed the idea and thought "Oh well, I probably should submit a wish on the wishlist instead" then forgot about it.
I'm happy to know I wasn't the only one thinking about it. You guys probably had done that already even before I had the idea. :) Thanks.
Hi, this is awesome, but I have a question, why do you use the getPixel() method instead of the getPixels() method? I see you loop through each pixel and convert it, but couldn't you get an entire byteArray and then loop through that and convert it that way? I'm not that smart so I'm just trying to learn the whys and what-fors
OK
MAX 2005 just ended, so back in the hotel reading up. Gary Grossman showed this example during the advanced AS session, and you are as nice to post it. This is great. I'll play tomorow on the way back again.
Cool stuff, I attended at Gary Grossman's session at MAX where he also mentioned this. Pretty exciting!
what would be really cool is a way to display the encoded image without requiring a round-trip to the server.
Now that you can do proper byte level manipulation in flash, it would make sense (well it does to me) to be able to create bitmaps and sounds directly from byte array data.
Perhaps through the use of some static class methods. You could also supply an extra parameter to the method to specify what type of data you are giving it, e.g. jpg, png, wav, mp3...or have appropriately named methods.
otherwise you are left with requiring a server or some other native process that can send back the processed data to flash so you can show the user what you have actually done to it.
im sure you get the idea. :)
mdk: Have you looked at the AS3 docs? You can load jpeg/png/gif from a bytearray without sending it to the server...
Look here:
http://livedocs.macromedia.com/labs/1/flex/langref/flash/display/Loader.html#loadBytes()
I fully agree with MDK - allowing a user to choose an image from their computer and have it resized/compressed BEFORE uploading to the server would be awsome. Does anyone know if there are working examples of this? Is it possible?
I know Flash can't directly access a user's filesystem for security reasons, but can it manipulate a file once the client chooses it - before it ever gets to the server?
A good application for this would be a photo album site. It's annoying to have to upload images that are 1mb each when all u wanna do is display an 800x600 version of them. If Flash could resize and compress before upload, users wouldn't have to mess with desktop apps or download additional tools.
Very nice,
Now all we need is a base64Decoder and some png/jpg/gif decoders, and we can start showing inline images in emails and avatars in IM-profiles...
-Patrick
A thought, if we can access the byteArray of an object before it is uploaded to the server then then any number of things can be done from virus checking to pornographic image detection and prevent malicious users from uploading anything dodgy so that it is never transefered to the server at all
I'd be damn impressed if you could write an algorithm that could do pornographic image detection.
I mean...think of the possibilities - a google images search that could ensure that only pornographic images are returned, based not on meta-data/file information, but on the actual pixels.
As for a Base64 decoder: I would guess that will be part of mx.utils.Base64Encoder ...documentation is sketchy at the moment though. For an AS 2.0 implementation, check out blog.jasonnussbaum.com.
Great post, Tinic. Keep 'em coming...
@ Jason Nussbaum
Obviously pornographic image detection is complicated, but there are already many scripts that can do this, just not for flash (yet). I think it works on a principle of flesh tone detection and position of pixels in relation to each other - I have done some experimenting with these systems and they are very accurate usually, often it's quite scary. I think the easiest way to test for colours and shapes would be to use bayesian filtering, so the more marked images that get put through it the better the system gets, this however would require a database but would still mean that image checking could be done before transfer. On a more interesting note has anyone ever tried to use bayesian style filtering to create shape, colour, or even facial recognition software? Now that has some many cool uses it would be awesome.
Got the base64 decoding working:
example
Can't the PNG Encoder be done using Flash 8?
I get "TypeError: Error #2023: Class PNGEnc$ must inherit from Sprite to link to the root."
Of course, I'm a total complete utter Flex n00b... but a pointer would be appreciated. :)
Wonderful code.
Is there anyonce who could suggest resources on how to implement writing the PNG to server with AS3?
(I am new to AS3 but have grasped the basics, just can't find relevant reference in the programming in as3 pdf)
Thanks
David
I'm just wondering if using these new capabilities of AS3 we could see bitmap editing programs built with Flash?
I'm thinking that this would differ from many server based systems at hand as they use the server to do all the number crunching whereas using Flash would use the User's local system.
Could we see an everyday level photoshop killer released as a RIA?
A little late to this discussion, but I think that the 32 bit ARGB to RGBA conversion is supposed to be:
uint(((p&0xFFFFFF) << 8)|(p>>24))
not:
uint(((p&0xFFFFFF) << 8)|(shr(p,24)))
Ported from Pascal?
Anonymous,
shr is correct since '>>' will always do a signed shift, meaning bit 31 will stick around. Another alternative is to use the '>>>' operator, very much like in Java.
This might seem counterintuitive as the p variable is an uint, but in ActionScript 3.0 the arithmentic right shift operator will nevertheless handle any value as signed.
Excellent code, thanks for posting it. I made an example using this code with Flex 2 SDK with a few small edits to make it compile:
import flash.util.*; to
import flash.utils.*;
uint(((p&0xFFFFFF)<<8)|(shr(p,24))));
to
uint(((p&0xFFFFFF)<< 8)|((p>>>24))));
and I wrapped the whole thing with
package { ... }
- gary
Hello,
That's a really great piece of work. I got it to work very easily.
I was just wondering, I want to upload the image to my server as a PNG file, and I'd like to use the PHP file upload script that I have. It's just the standard script that expects an HTML form file upload, just like FileReference.upload() sends.
I looked everywhere in the documentation, tried URLLoader, FileReference etc. but I can't get it to work.
Has anyone been able to write a function for uploading a ByteArray with PNG data to a file upload script?
regards
Sander.
I found one way, sending a HTTP Request following the RFC 1867 (sending the binary data as multi-part request) here:
http://www.actionscript.org/forums/showthread.php3?t=130537
I have another solution, sending the byte array through a post request, just streaming the data after the headers, but the code is not mine, so I can't share it.
Hi!
I am trying to compile this class into a Flash CS3 project. Besides renaming flash.util.* into flash.utils.*, I get a compile error.
FLash cannot find the function shr(). I guess thats a native flex function? any hints?
thx!
Pippo
I've started using the built in Base64Encoder and Base64Decoder and they work fine. Why is everyone writing their own? Am I missing something here?
I've been trying to get this to work (using the changes suggested by gary@dehash), and I keep on getting an error back when I try to access the byteArray returned by the pngEncoder. The error is Error: Error #2030: End of file was encountered.
I am thinking about using your PNG Encoder for a commercial project. What are your thoughts on this ?
set the .position to 0 before trying to access it. that might work.
I like articles like this. Thanks!
Hi,
i'm getting this error:
1180: Call to a possibly undefined method shr.
yeah i get that function error too...that whole block is inefficient anyway so if you replace the loop with:
var IDAT:ByteArray = img.getPixels(new Rectangle(0,0,img.width, img.height));
then it compiles and it works much faster anyway.
Hi...Im no stranger to actionscript..but As3 feels strange. It seems I havent been paying attn, and the whole platform has moved in new directions. But come on, I have work to do! lol!.
Nowadays it seems that most code out here for actionscript is realy realated to either the Flex framework, or AIR...or both, and normally it is my impression that you must guess what' what. Honestly...that sucks.
I am happy getting confortable with AS3 in the Flash IDE only for now - but am having a hard time telling what code goes for what.
This code demo for example...
It took a solid half-hour of playing, reading, and re-reading this posting before I even noticed the comment stating this is dependent on the Flex framework. I just dont get that. Its actionscript for crytin out loud. Why would a class and its dependencies NOT be portable to the native editor????
Flash has made me alot..(I mean ALOT) of money. but these days I almost feel like Adobe has gone another way with the technology. tee-hee....Flex hijacked Actionscript maybe?
Howdy Folks...
Dumb Question here:
Is it true you still cany use this Class without Flex Builder?
I am trying to implement wityh Flash CS3 and though it compiles, I do not get a ByteArray back...I dont think. Instead I get this square binary(?) char...( ).
Might that be a blank PNG???
For ref - here is my implementation:
//CODE/////////////////////
import com.gmri.PNGEnc;
import flash.display.MovieClip;
import flash.display.BitmapData;
import flash.utils.ByteArray;
var myMovie:MovieClip = new bitmapTest;
addChild (myMovie);
var matrix = new Matrix();
var bitmapData = new BitmapData(myMovie.width, myMovie.height);
bitmapData.draw( myMovie, matrix);
var myPNG = PNGEnc.encode(bitmapData);
trace(myPNG);
//END CODE/////////////////////
I agree that its becoming quite overwhelming to me to. Am an adobe certified flash professional and everytime i click on the website there is a new technology and a new ap development tool. I can't keep up.
Hi everybudy...
This is great stuff no?
So has anyone successfully saved a PNG with transparent pixels to a sever/or local file system....?
I am attempting with VB.net - but transparent pixels are rendered as white. (!?)
Here is my (incredibly simple) VB code:
///////////CODE //////////////
Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
Dim saveToFolder As String = "C:\Inetpub\wwwroot\dumdum\test.png"
Dim b() As Byte = Request.BinaryRead(Request.TotalBytes)
If File.Exists(saveToFolder) Then
File.Delete(saveToFolder)
End If
File.WriteAllBytes(saveToFolder, b)
End Sub
///////////End Code///////////////
Also, below is my AS3 code for sending the PNG to server:
///////////Code///////////////
function sendByteArray (myByteArray:ByteArray):void
{
var U:URLRequest = new URLRequest;
U.method = URLRequestMethod.POST;
U.data = myByteArray;
U.contentType="application/octet-stream";
U.url = Registry.PathToSaveImageScript;
var UR:URLLoader = new URLLoader;
UR.dataFormat = URLLoaderDataFormat.TEXT;
UR.load (U);
}
///////////End Code///////////////
Hi again everybudy...
I found my problem.
*SOLUTION:
Rendering a PNG with transparency from a BITMAP data object.
For those trying to do the same, be aware that although the PNGEcoder by default is set to render WITH transparency, the BitmapData Object WILL OVERRIDE any transparent pixels in your BitmapData by default.
To overcome this little caveat, carefully review the BitmapData Class (as this is the problem, not the PNGEncoder), and notice the optional parameters that you have access to.
//////CODE////////////
(from documentation)
BitmapData(width:int, height:int, transparent:Boolean = true, fillColor:uint = 0xFFFFFFFF)
/////END CODE////////
Notice the default values passed to the constructor for the "transparent", and "fillColor" arguments.
If you don’t specify otherwise, both "transparent", and most importantly, "fillColor" parameters use the default values you see in the code sample above.
If you expect to see transparent pixels in your rendered PNG file once you get it on a filesystem (or DB) you must make sure to explicitly set the fillColor parameter to ("0x00000000") thus overriding the default (“0xFFFFFFFF”) value that is passed to the constructor !!!!
By doing so, you will ensure transparency
ex:
//////CODE////////////
var bitMD:BitmapData = new BitmapData(myMovie.width,myMovie.height, true , 0x00000000 );
/////END CODE////////
Good luck, and happy coding.
if you want transparent bitmaps.
when printing a sprite onto a BitmapData Object
first you need to set that BitmapData to transparent
bitmapData.fillRect(new Rectangle(0,0,bitmapData.width,bitmapData.height),0x00000000);
its default init is non transparent white
I think what you did, whit modifying the corelib and adding your own namespace just to conceal certain functions and brake the functionality of the code of anyone who tries to actually use it, is awful.
You suck.
Hy !
I am looking for open a PNG file and get the bitmap data.
Is there a function for decode PNG ???
is there anyway to make it so the png encoder can take a screenshot or encode a specific area or masked area?
Thanks man! Thats exactly what I was looking for :)
how do I set a 24bit RGBA?
IHDR.writeUnsignedInt(0x08060000); // 32bit RGBA <--- what value do I set?
Thanks for the tip about getting bitmap transparent pixels to work. I was struggling with this one for awhile but that helped out alot.
var pngSourceBitmapData:BitmapData = new BitmapData (drawingCanvas.width, drawingCanvas.height, true, 0x00000000);
Post a Comment
<< Home