Flash Cookbook - Tricks - Super size bitmap, bitmapdata, infinite size
Today I ran into a problem. As we all know, Flash has a size limit on bitmapdata size.
Basically there are two rules to follow (see the specs):
Both the width and height has to be maximum 8,191 pixels
and
the amount pixels of the bitmapdata cannot exceed 16,777,215.
Well, well, well... That is a big number, but you can also try the following:
do a search (for example in google images) for images bigger than 20 MPixels. Load this image the standard way: you ain't get nothing. No-thing. The image doesn't show up, you can't even request for it's dimensions (you can, but you will get 0 in size). The only thing you can do is to load the file as a URLStream. You can read thy bytes, the image is there, my testimage was about 3 megs, but you can't assign this data to any displayobject.
We know that if we load files (practically images) from a site which has no policy file for our flash client then we can't manipulate the image's data, even though we can add the Loader object to the displaylist and have fun with it. But neither this works in the case above.
So what to do then? Technology fastens up, there are imges with more pixels than flash can handle (do the imagesearch, I can't repeat it enough times, there are huge images you can access).
Yes, you are right. We have to write our own class. This tutorial is a step-by-step adventure through the jungle of the Myriadpixel Planet.
So what is the first step? Actually the hardest and also the most important thing is to name the child! We will call this little one SuperBitmapData.
From now on, if you ask "how to handle the huge images?" the answer will be: "SuperBitmapData". To avoid misunderstanding, we use the latin super here to emphasize its pixels, not that it's "soooo super, it's soooo awesome". Event though indeed: it is awesome.
This part is about the easiest function of manipulating a bitmapdata, we will write the setPixel/getPixel boublet of methods.
Don't expect anything superfancy, the easiest is the best. Therefore we will simply extend the BitmapData class:
public class SuperBitmapData extends BitmapData
{
public function SuperBitmapData(w:int, h:int, transparent:Boolean = true, fillColor:uint = 0xFFFFFFFF)
{
super(1, 1);
...
}
}
What we have to do in the "..." is to store the values given in the contructor. You can see, that we called the super() method (calling the BitmapData constructor) with the values 1 and 1. This simply creates the bitmapdata with width 1 and height 1. You could say, haha, but this is unnecessary, why don't we extend the Sprite or MovieClip classes instead? Well, so to say, this is unimportant now. This is up to you, what you choose, in this tutorial we extended the BitmapData class to highlight our efforts. Plus, we are not done with this yet neither, so we will see it in the end, what, how and when to optimize (super(1,1) creates a bitmapdata, what uses the memory and we will never use it, so it's not really cheap now, but just carry on, we will do this part later).
Next, we have some properties:
protected var _width:int;
protected var _height:int;
protected var _transparent:Boolean;
protected var _fillColor:uint;
protected var _bitmapdataCollection:Array;
protected var _bitmapCollection:Array;
protected var _movie:MovieClip;
These speak for themselves, except for the three last ones. These will be used to store our data. So about the concept. The concept is, that when you call the contructor of the base class (BitmapData) with huge numbers, you would get an error:
ArgumentError: Error #2015: Invalid BitmapData.
Which is because the bitmapdata doesn't fit into its limits. So our concept is to create as many bitmapdata objects as needed, and use these small pieces to store the graphical data of a huge image. This mosaic method is not unusual or unknown, as this is the usual way to workaround this problem.
Go on, we have to generate the necessary amount of bitmapdatas. So we have to find out how many we need, and so on, and so on.
What we know is the maximum size of the bitmapdata: 8191 pixels. But for this, the maximum height is 16,777,215 / 8,191 = 2,048.24 = 2,048 pixels. These values will be our maximum values accordingly. If the bitmapdata is bigger in any of these dimensions, we have to start to build the mosaic.
Let's see the basic function for this operation, for now with pseudo-code:
1. store width and height values in a buffer
2. loop until the buffer values are greater than zero
3. in the loops, check if the buffer is greater than the maximum value
if so, use the maximum value
if not, use the remainder value
4. create bitmapdata objects with this method and store them in an array
and so for the setPixel method:
override public function setPixel(nx:int, ny:int, color:uint):void
{
if(nx > _width) return;
if(ny > _height) return;
var nw:Number = Math.floor(nx / 8191);
var nh:Number = Math.floor(ny / 2048);
var mi:int = nx - 8191 * nw;
var mj:int = ny - 2048 * nh;
_bitmapdataCollection[nw][nh].setPixel(mi, mj, color);
}
The trick here is to get the actual bitmapdata object which is hit by the coordintes. For this, we have the nw, nh values: this is to get the actual tile (mosaic piece). The mi and mj values are the position in that certain tile.
The logic of the getPixel method is the same: get the tile, get the coordinates, get the pixel.
This way you can start to draw on the bitmapdata, no matter, how big it is.
Next we have to find out how to implement the other methods, like draw and the channel manipulations.
Obviously, we have to use the same class for this, or build in a function, if a native bitmapdata object is used.
Also a really interesting question is still there: what if you load a huge jpg. How do you draw that on this SuperBItmapData? That is our next step, finding out the solution for external image handling.