*Update*
Sometimes you’re an idiot and you spend a lot of time reinventing the wheel because you misread one little line of documentation. This is one of those times where I get to be that idiot. I invented a solution for bilinear resampling in Flash Player when I didn’t need to. You can actually get the same results pretty simply using just BitmapData.draw() method with smoothing on (I had read this didn’t work when down sizing), but it requires creating a temp BitmapData object if the source you’re feeding the BitmapData.draw() method is not another BitmapData object (i.e. a DisplayObject). Here’s what that scenario would look like.
// source in this example is a DisplayObject var temp:BitmapData = new BitmapData( sourceWidth, sourceHeight ); temp.draw( source ); var output:BitmapData = new BitmapData( outputWidth, outputHeight ); var matrix:Matrix = new Matrix(); matrix.scale( outputWidth / sourceWidth, outputHeight / sourceHeight ); output.draw( temp, matrix, null, null, null, true ); temp.dispose();
Many thanks to Felix for leaving a comment that made me question myself and go back to the problem with fresh eyes.
Now I can take some consolation in the fact that I actually learned a lot by going down this path. I dug in to Pixel Bender again and learned how to use ShaderJob. To that end, portions of this “tutorial” illustrate how to use a PixelBender shader and a ShaderJob together. ShaderJob, when run with the async flag (false), is as close as you can come to creating new threads in Flash–that’s some pretty neat Flash hackery.
So, if you find yourself reading this, you’re advised to skip down to the code included below and take a look at the embedded filter, Shader / ShaderJob portion.
*End of Update*
If you’re looking to generate high quality scaled bitmaps in Flash Player / AIR, you’ll need to do some form of bilinear or bicubic resampling. Why you ask? Because, Flash Player resampling is, by default, nearest neighbor and that looks, well, like ass (see evidence directly below).

If you dig around long enough on the net you’ll find some AS3 libraries (Java ports) like clevrlib which will do bilinear or bicubic resampling using BitmapData methods like getPixel / setPixel. As seen below, clevrlib definitely decreases jaggies and improves the legibility of text, but its extremely slow–scaling a 960×540 BitmapData object to 640×360 takes around 1.5 – 2 seconds for bilinear resampling and around 3 – 5 seconds for bicubic resampling (mileage will vary based on the type of imagery and size of the picture). Also, the results, in some cases, exhibit noticeable resampling artifacts (check out the fullscreen icon in the right corner of the examples immediately below).

I was looking to implement bilinear resampling in ThumbGenie, but, as the old saying goes–speed kills. I just couldn’t tolerate 2 or 3 seconds to resample an image–there had to to be a better way. After a bit of thought I fell on the answer–PixelBender.
Flash Player 10 PixelBender bytecode is designed to manipulate individual pixels in a bitmap super fast. In fact, it executes fast enough to apply real time filter effects to video. This seemed promising and sure enough after reading through the sdk docs and playing around for awhile (read I spent a Saturday evening trying to wrap my little melon around the issue), I was able to find a built-in PixelBender method that does bilinear sampling. Slap a parameter on that biatch (aka kernel) controlling the desired scale of the output and little ol’ me was rendering silky-smooth, scaled bitmaps out of Flash Player in mere milliseconds (8-13 milliseconds scaling a 960×540 bitmap to 640×360–take that suckaz).

Interested in chasing the same dream? Here’s, the rough outline you’ll need to follow:
Ouch, that’s pretty painful. And believe me, I did some serious bleeding before figuring this one out–I really had to push the two or three sleep deprived brain cells I have left in order to get the job done. And well, this is just a long-winded way of saying that I’ll be providing y’all the Pixel Bender kernel (.pbk) and bytcode (.pbj) plus the AS3 implementation that I developed for ThumbGenie (just my way of saying thanks for all of the code you Flash / Flex cats so liberally share all of time).
Whew, you made it this far, now its time for the good stuff. Download the PixelBender source / bytecode here and feel free to peruse / use the implementation seen below.
/** * Copyright 2009 (c) , Brooks Andrus * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. * */ package com.brooksandrus.utils { import flash.display.BitmapData; import flash.display.DisplayObject; import flash.display.Shader; import flash.display.ShaderJob; public class BilinearResample { [Embed ( source="../../../../assets/bilinearresample.pbj", mimeType="application/octet-stream" ) ] private var BilinearScaling:Class; public function BilinearResample() { super(); } public function resampleBitmap( input:BitmapData, desiredWidth:int, desiredHeight:int, cleanup:Boolean = true ):BitmapData { var aspectRatio:Number = input.width / input.height; var factor:Number = Math.max( input.width / desiredWidth, input.height / desiredHeight ); // create and configure a Shader object var shader:Shader = new Shader(); shader.byteCode = new BilinearScaling(); // instantiate embedded Pixel Bender bytecode shader.data.src.input = input; // supply the shader with BitmapData it will manipulate shader.data.scale.value = [factor]; // scale factor. shader params are all stored in arrays. var outputWidth:int; var outputHeight:int; // determine output bitmap dimensions if ( input.width > input.height ) { outputWidth = desiredWidth; outputHeight = desiredWidth / aspectRatio; } else { outputWidth = desiredHeight * aspectRatio; outputHeight = desiredHeight; } // create a bitmap - our shader will return its data (an image) to this bitmap var output:BitmapData = new BitmapData( outputWidth, outputHeight ); // shader jobs are wicked cool var job:ShaderJob = new ShaderJob(); job.target = output; // ShaderJob returns to this object job.shader = shader; // The Shader assigned to this job job.start( true ); // true flag runs the job synchronously. if ( cleanup ) { input.dispose(); } return output; } public function resampleDisplayObject( source:DisplayObject, desiredWidth:int, desiredHeight:int ):BitmapData { var aspectRatio:Number = source.width / source.height; var factor:Number = Math.max( source.width / desiredWidth, source.height / desiredHeight ); var input:BitmapData = new BitmapData( source.width, source.height, true ); input.draw( source ); // configure the shader var shader:Shader = new Shader(); shader.byteCode = new BilinearScaling(); shader.data.src.input = input; shader.data.scale.value = [factor]; // scale factor. shader params are all stored in arrays. var outputWidth:int; var outputHeight:int; if ( input.width > input.height ) { outputWidth = desiredWidth; outputHeight = desiredWidth / aspectRatio; } else { outputWidth = desiredHeight * aspectRatio; outputHeight = desiredHeight; } var output:BitmapData = new BitmapData( outputWidth, outputHeight ); var job:ShaderJob = new ShaderJob(); job.target = output; job.shader = shader; job.start( true ); // true flag runs the job synchronously. input.dispose(); return output; } } }
Nice! Good post. I really need to get around to reimplementing the CleVR stitcher using Pixel Bender.
I did not tested it yet but it looks promising.
Thanks for sharing.
Thanks!
Perfect! Thanks a lot for sharing!
Can’t you use Bitmapdata.draw() with smoothing turned on to achieve the same thing?
@felix – hmm…yah, you can. You actually have to create a temp bitmap at input dimensions, do a bitmap.draw() on your bitmap, then create another bitmap at output dimensions and do a bitmap.draw() with smoothing turned on and a Matrix that contains your scale.
I thought I had read out on the web that smoothing didn’t work when the image was downsampled, but after your comment I double checked and it seems to do a decent job (initially when I tested I was turning smoothing and using a DisplayObject as the source which won’t work. You need to create the temp bitmap first).
The only consolation I can give anyone is this post gives a nice example of how to use a Pixel Bender shader with a ShaderJob, rather than just applying it as a filter.
[...] > Bilinear Resampling With Flash Player and Pixel Bender [...]
Trying to run your code to test drive, but get
Error: Type was not found or was not a compile-time constant: BilinearScaling.
Snagging on this line:
shader.byteCode = new BilinearScaling();
Any suggestions? (I am able to link to the pbj file, btw, so I don’t think that is the error).
Ah, I see. I changed the package to “test” and the pbj/pbk’s have their path’s coded in.
Heh.. the path information had nothing to do with my problems (environment issues were the culprit). That annoyance aside, I am wondering if you’ve come across an implementation of bicubic sampling for pixel bender? Thanks for introducing me to pixel bender!
@Erik – I’ve looked but haven’t seen anything that will do bicubic sampling. The limitations on PBK code for Flash Player might make it hard to to pull that off. I did read somewhere that the PixelBender team was considering adding different sampling techniques in the future–hopefully they do.
Do you know what scaling function is used for bitmapdata.draw with and without smooth?
Hi Brooks,
It seems Flash wont apply smoothing when scaling large images to smaller bitmapDatas.
So far you’re solution is the only one i’ve found.
thanks
[...] some more searching I found that Brooks Andrus played with some algorithms using Pixel Bender. The conclusion to that was he found he could do the same thing with the “smoothing” option in the [...]
I found that taking a larger picture and dividing the width and height by 4 gave me jaggy edges yet if I first divided width and height by 2, then used that image and divided width and height by 2 again this was much cleaner. Would bicubic resampling solve this problem? Any eta when adobe might make that option available?
the best fix is stageQualiy = best
I’ve just made bicubic resampling in Pixel Bender:) See it at http://blog.onthewings.net/2009/08/25/bicubic-resampling-by-pixel-bender/
[...] If you are looking for transformation between BitmapData, Bitmap, ByteArray, read this article. Continue reading about Bilinear Resampling, ShaderJob, Pixel Bender And Flash Player. [...]
Hi, I have just figured out smoother downscaling
http://blog.yoz.sk/2010/01/how-to-resize-an-image-with-actionscript/
legend. thanks
Great post. tnx:)