Monday, October 17, 2005

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:
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:

Blogger Sarge said...

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

Monday, October 17, 2005 6:27:00 PM  
Anonymous Mike Chambers said...

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

Monday, October 17, 2005 6:53:00 PM  
Anonymous Zeh said...

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.

Monday, October 17, 2005 7:05:00 PM  
Anonymous Anonymous said...

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

Wednesday, October 19, 2005 4:40:00 AM  
Anonymous Anonymous said...

OK

Wednesday, October 19, 2005 6:52:00 AM  
Anonymous Ralph Hauwert said...

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.

Wednesday, October 19, 2005 4:49:00 PM  
Anonymous Martijn de Visser said...

Cool stuff, I attended at Gary Grossman's session at MAX where he also mentioned this. Pretty exciting!

Wednesday, October 19, 2005 4:50:00 PM  
Blogger mdk said...

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. :)

Thursday, October 20, 2005 7:54:00 AM  
Anonymous davr said...

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()

Monday, October 24, 2005 4:56:00 PM  
Anonymous Baz said...

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.

Tuesday, October 25, 2005 12:29:00 AM  
Anonymous Patrick Gutlich said...

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

Friday, October 28, 2005 3:17:00 PM  
Anonymous Anonymous said...

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

Saturday, October 29, 2005 4:26:00 PM  
Anonymous Jason Nussbaum said...

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...

Monday, October 31, 2005 7:04:00 PM  
Anonymous Anonymous said...

@ 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.

Tuesday, November 01, 2005 6:55:00 PM  
Anonymous Patrick Gutlich said...

Got the base64 decoding working:
example

Wednesday, November 23, 2005 11:47:00 AM  
Anonymous Anonymous said...

Can't the PNG Encoder be done using Flash 8?

Tuesday, December 13, 2005 5:27:00 PM  
Anonymous XDude said...

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. :)

Monday, January 02, 2006 11:08:00 PM  
Blogger jhave said...

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

Friday, March 17, 2006 5:15:00 PM  
Anonymous Casper said...

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?

Friday, April 07, 2006 9:15:00 PM  
Anonymous Anonymous said...

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?

Monday, June 12, 2006 11:14:00 AM  
Blogger Tinic Uro said...

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.

Monday, June 12, 2006 12:06:00 PM  
Anonymous gary@dehash said...

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

Saturday, August 12, 2006 7:14:00 AM  
Anonymous Anonymous said...

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.

Thursday, February 08, 2007 9:08:00 AM  
Anonymous Anonymous said...

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.

Friday, March 23, 2007 9:59:00 AM  
Anonymous PippoFlash said...

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

Friday, May 18, 2007 4:27:00 AM  
Anonymous Anonymous said...

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?

Thursday, May 31, 2007 3:46:00 PM  
Blogger HappinessSam said...

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.

Tuesday, July 17, 2007 5:28:00 AM  
Anonymous Anonymous said...

I am thinking about using your PNG Encoder for a commercial project. What are your thoughts on this ?

Friday, July 20, 2007 6:00:00 PM  
Anonymous Anonymous said...

set the .position to 0 before trying to access it. that might work.

Tuesday, July 24, 2007 1:56:00 AM  
Anonymous <a href="http://drugscenterhere.com">ShopMan</a> said...

I like articles like this. Thanks!

Sunday, August 26, 2007 12:37:00 AM  
Anonymous Dave said...

Hi,
i'm getting this error:
1180: Call to a possibly undefined method shr.

Wednesday, September 19, 2007 12:37:00 AM  
Blogger wsstefan said...

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.

Wednesday, September 26, 2007 8:07:00 AM  
Anonymous Anonymous said...

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?

Monday, November 19, 2007 1:46:00 PM  
Anonymous casey said...

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/////////////////////

Wednesday, November 21, 2007 6:22:00 AM  
Anonymous Anonymous said...

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.

Saturday, November 24, 2007 7:36:00 PM  
Anonymous Anonymous said...

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///////////////

Monday, November 26, 2007 1:01:00 PM  
Anonymous casey said...

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.

Tuesday, November 27, 2007 6:19:00 AM  
Anonymous Anonymous said...

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

Tuesday, November 27, 2007 11:04:00 AM  
Anonymous robertbain said...

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.

Thursday, December 06, 2007 10:34:00 AM  
Anonymous Tom said...

Hy !

I am looking for open a PNG file and get the bitmap data.

Is there a function for decode PNG ???

Saturday, December 08, 2007 12:45:00 PM  
Blogger Colin said...

is there anyway to make it so the png encoder can take a screenshot or encode a specific area or masked area?

Tuesday, December 18, 2007 5:07:00 PM  
Blogger Febret said...

Thanks man! Thats exactly what I was looking for :)

Friday, December 21, 2007 10:55:00 AM  
Anonymous creek23 said...

how do I set a 24bit RGBA?

IHDR.writeUnsignedInt(0x08060000); // 32bit RGBA <--- what value do I set?

Tuesday, February 23, 2010 5:53:00 AM  
Anonymous ricky said...

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);

Thursday, March 11, 2010 7:30:00 PM  

Post a Comment

<< Home