views:

187

answers:

2

I'm looking for an efficient way to filter a specific color from a bitmapData object in ActionScript 3. Currently I use a loop with readByte32(). This takes about a second to process which is unacceptable. I have been trying to get paletteMap() to work but so far haven't been able to grasp its API (any truly useful links? Google has failed me...).

Here's my current logic, which I want to improve:

var n:int = bitmapData.width;
for (var i:int = 0; i < n; i++) {
 var m:int = bitmapData.height;
 for (var j:int = 0; j < m; j++) {
  var color:int = bitmapData.getPixel(i, j);
  if (color == 0xCACACA) {
   bitmapData.setPixel32(i, j, 0x00000000);
  }
 }
}

I can get slightly better performance from using Vectors but it's only marginally better...

var v:Vector.<uint> = bitmapData.getVector(bitmapData.rect);
var n:int = bitmapData.width * bitmapData.height;
for (var i:int = 0; i < n; i++) {
 var color:uint = v[i];
 v[i] = color == 0xFFCACACA ? 0x00000000 : color;
}
bitmapData.setVector(bitmapData.rect, v);

I really think there must be a better way to do this that only takes a few 100 milliseconds. If anyone can unlock the mysteries of bitmapData for me, you will be the new leader of my people.

PS I am using bitmapData.lock() and unlock(); I just didn't post the boilerplate stuff.

+1  A: 

Flash has an API to a shader like language called pixel bender that may be of use to you in this case. Here is a tutorial from adobe on how to apply a pixel bender filter to an image in flash.

Otherwise you could processes rows at a time. (Note a slight error in your code was to re-get the height on each iteration of width):

private var currentRow:Number = 0;
private var timer:Timer;
public function processImage(event:Event=null):void
{
    var m:int = bitmapData.height;
    for (var j:int = 0; j < m; j++)
    {
        if (bitmapData.getPixel(currentRow, j) == 0xCACACA)
        {
            bitmapData.setPixel32(currentRow, j, 0x00000000);
        }
    }

    currentRow++;
    if(currentRow < bitmapData.width)
    {
        timer = new Timer(1, 500);
        timer.addEventListener(TimerEvent.COMPLETE, processImage);
        timer.start();
    }
}

The processing will take a bit longer but at least your display won't be blocked.

James Fassett
Thanks for pointing out the error on the first double-loop version! I am not sure why you are using a Timer above, however. I am not animating an effect. I am just processing an image and must remove a very specific color from the image (replacing it with 0x00000000).
+1  A: 

An easy way is using the threshold method. It's a bit cumbersome at first, but it's pretty fast (as fast as you'll get, I think)

This will change every red pixel (considering red only a pixel whose value is exactly 0xffff0000) to blue (0xff0000ff).

var colorToReplace:uint = 0xffff0000;
var newColor:uint = 0xff0000ff;
var maskToUse:uint = 0xffffffff;

var rect:Rectangle = new Rectangle(0,0,bitmapData.width,bitmapData.height);
var p:Point = new Point(0,0);
bitmapData.threshold(bitmapData, rect, p, "==", colorToReplace, 
        newColor, maskToUse, true);
Juan Pablo Califano
Thanks, let me try this and compare the performance. I'll get back to you.
Just amazing. Using threshold() is over 100 times faster than my attempts above. I can't believe such a complicated method works so quickly. Thank you so much!
@jpwrunyan. No problem. `threshold` is doing here almost the same as your code does, I think. The big difference, though, is that it runs natively; this will allways beat any Actionscript. As a rule of thumb, whenever you see a bunch of setPixel / getPixel calls, especially in a tight loop, try to find a way to replace them with a native equivalent. If that's not possible, pixel bender might help, but it won't be as fast as native code.
Juan Pablo Califano