AS3: BitmapData Foibles
Today I ran into what looks like an odd oversight in Adobe’s otherwise excellent BitmapData class. Alternatively, it could be an oversight in my otherwise excellent critical thinking skills. Neither is unheard of, but either way, someone is missing something somewhere.
Here’s the scenario: I had loaded an image via a Loader, and I wanted to capture a chunk out of the middle of it. For the sake of this example, let’s say I wanted a 200×200 square, starting at (50, 50) in the source image. So, foolishly and naĆ®vely, I used this code:
// create the BitmapData and draw() the contents of sourceImage into it var bmpData:BitmapData = new BitmapData( 200, 200, true, 0x00000000 ); bmpData.draw( sourceImage, null, null, null, new Rectangle( 50, 50, 200, 200 ), true ); var bmp:Bitmap = new Bitmap( bmpData, PixelSnapping.ALWAYS, true ); // add to display list
So imagine my surprise when I got this:
![]()
This was definitely not what I was expecting, but apparently it is the intended functionality of BitmapData.draw(). I expected this image to line up with the upper left corner of the Bitmap container but as you can see, it doesn’t. It does indeed start copying pixels at (50, 50) on the source image, but when added to a Bitmap, it begins drawing those pixels at (50, 50) as well. That means that the whole thing is shifted 50 pixels along both axes, leaving empty space at the top and left while cutting off pixels at the right and bottom. This, to me, seems like a Bad Thing.
I’ve played with this for a couple hours now and I can’t find a way to do this without resorting to what I think most people would term a “workaround.” This is why I think that either Adobe just left out something, or (more likely), that I am just not grasping how this whole BitmapData/Bitmap thing works.
Anyway, this is my workaround. In my traversals of the AS help files, I noticed that there is a method that allows you to specify both things that I need: coordinates of a set of pixels on the source image, and coordinates for where to put said pixels on the target image. That method is BitmapData.copyPixels. However, it only works between two instances of BitmapData.
So, I created two instances of BitmapData. To make it easier to use, I placed it in a static class called BitmapGrabber.
public static function grab( source:DisplayObject, rect:Rectangle, smoothing:Boolean ): BitmapData { if( !source is IBitmapDrawable ) { throw new Error( "Cannot create BitmapData. Source must implement IBitmapDrawable" ); } var bmpData1:BitmapData = new BitmapData( source.width, source.height, true, 0x00000000 ); var bmpData2:BitmapData = new BitmapData( rect.width, rect.height, true, 0x00000000 ); bmpData1.draw( source, null, null, null, null, smoothing ); bmpData2.copyPixels( bmpData1, rect, new Point( 0, 0 ) ); bmpData1.dispose(); return bmpData2; }
And this is me using the class:
// create the BitmapData and draw() the contents of sourceImage into it var bmpData:BitmapData = BitmapGrabber.grab( sourceImage, new Rectangle( 100, 100, 100, 100 ), true ); var bmp:Bitmap = new Bitmap( bmpData, "auto", true ); // add to display list
And this is what I get:
![]()
It’s a ridiculously simple solution. However, I feel so strongly that BitmapData should have this capability (without a workaround), that I can’t shake the feeling that it must have this capability and I am just missing it. But, until some member of the Flash community tells me a better way to accomplish this, this is the class I’ll be using in my code.
Tags: Actionscript 3.0, Bitmap, BitmapData
May 8th, 2008 at 11:46 am
Totally off topic, but just thought you should know that your archives and contact links on the top right are broken (if you already didn’t).
May 8th, 2008 at 12:43 pm
Ha, yeah, that is a bit off-topic. Thanks for the heads-up, though.
May 11th, 2008 at 2:30 pm
Well the argument *is* called clipRect (as opposed to sourceRect as seen otherwise). What you want to do is use the matrix property.
new Matrix (1, 0, 0, 1, -50, -50)
September 19th, 2008 at 5:40 pm
Well, my first comment was off topic, but this one isn’t. You just saved me a heap of time as I was just trying to get my brain around this very same thing and then I remembered this post. Thanks!
September 20th, 2008 at 10:07 am
This is good but what if your stage is 500×500 and your original image is sitting at x:40 and y:40 ?
I keep getting the image cut off similar to your original problem.
November 9th, 2008 at 2:55 pm
THANK YOU! I have been hitting my head against this for hours. Major props for posting this.
November 17th, 2008 at 11:43 am
[…] BitmapData Foibles (Thanks To: Zack Jordan at Pixelwelders) […]
January 2nd, 2009 at 3:35 pm
Was just doing a little research on BitmapData and came across your post. You may be able to use a matrix to change the offset of the image instead of duplicating it? I ran into something similar while adding in tiled background images into my framework and noticed if I offset the image in the draw function’s x, y, I needed to use a matrix to sample the correct x,y position. Not sure if that works here but its worth a shot.
January 2nd, 2009 at 3:44 pm
@Jesse:
Yeah, you’re right. I posted this quite awhile ago and have been using a matrix ever since. I should probably update the post, eh?
January 2nd, 2009 at 3:58 pm
Good to hear, sorry I was a little late on the help!
January 9th, 2009 at 4:29 am
Just ran into this very same problem! Operation of BitmapData with sourceRect is not intuitive! Fortunately, the matrix transform solves it after much head scratching….
February 11th, 2009 at 6:47 pm
If you are trying to draw from a MASKED object - try taking the mask off! (With a mask, your problem appears like a translate issue but it probably isn’t)
I just spent a very long time banging my head over this. You can set the mask = null then reapply it after the draw(). I hope this helps someone out there
April 8th, 2009 at 3:41 pm
I am having this problem also, did you ever find a solution? The problem is you are drawing a Bitmap based on Bitmap data that is still as wide as the source clip. Therefore no matter what you sample from it the source, you will always end up with a Bitmap Object that is the dimensions of the entire source. Using the clipRect of the draw method doesnt seem to know that it needs to crop out this negative space, I was hoping that a Matrix would be smart enough to do that but unfortunately I dont understand Matrixes enough to do what I am trying to do so I think I will just use the intermediary BitmapData for now!
May 25th, 2009 at 1:13 pm
buy virginia slims ultra reflection cigarettes
tawdry doral cigarette cheaply doral cigarette|cheap doral cigarettes
shoddy doral cigarettes cheap doral cigarettes
inferior gpc cigarette tuppenny gpc cigarettes
economical gpc cigarettes budget-priced gpc cigarettes
virginia slims ultra
June 26th, 2009 at 10:04 am
[…] much Googling, I came across this post: http://pixelwelders.com/blog/actionscript-3/2008/as3-bitmapdata-foibles/ which explained a little hack around this failed method. I’ve taken his code and just […]
June 26th, 2009 at 10:07 am
Thank you for this post, it helped me create this new scaling class: http://ahmednuaman.com/blog/2009/06/26/scale-any-displayobject-with-my-scaleobject-class/
April 16th, 2010 at 12:01 pm
can also do this:
bmpData1.copyPixels( source.bitmapdata, rect, new Point( 0, 0 ) );
then only need one bitmapdata
great post BTW thanks
May 5th, 2010 at 5:41 am
This code solve all of my problems
function Crop( source:DisplayObject, x:Number, y:Number, width:Number, height:Number):BitmapData
{
var bmpd:BitmapData = new BitmapData( width, height, true, 0×00FFFFFF );
var mat:Matrix = new Matrix(1,0,0,1,-x,-y);
bmpd.draw( source, mat, null, null, null, true);
return bmpd;
}
May 26th, 2010 at 10:03 am
you don’t need two bitmapdatas… just use a transform matrix with the proper translate in the draw call.
var bmpData:BitmapData = new BitmapData( 200, 200, true, 0×00000000 );
var matrix:Matrix = new Matrix();
matrix.translate(-50,-50);
bmpData.draw( sourceImage, null, matrix, null, new Rectangle( 50, 50, 200, 200 ), true );
var bmp:Bitmap = new Bitmap( bmpData, PixelSnapping.ALWAYS, true );
August 20th, 2010 at 6:30 am
Problem arises, when your source is 5000px wide