actionscript-3optimizationtexturesbitmapdata

AS3: Most efficient way to create a tinted copy of a BitmapData


I have a BitmapData which is generated by drawing a DisplayObject. I would like to create a pure white silhouette copy of that BitmapData. It seems there would be two approaches to do so:

  1. Apply a ColorTransform to the DisplayObject and then have a new BitmapData with the same dimensions draw it.
  2. Create a new BitmapData, then copyPixels the original, then apply a ColorTransform via BitmapData.colorTransform.

Neither BitmapData will be displayed inside a Bitmap, so applying a ColorTransform to that is not an option. They will instead be turned into a Starling Texture.

Which method would be more efficient? I would run some benchmarks myself, but I'm concerned the answer would be dependent on the size and complexity of the DisplayObject being drawn. Perhaps the copyPixels method would be better when the size is small and worse if it's large, and the draw method would be better if the DisplayObject to be drawn is simple and worse if it's complex. Also, are the copyPixels and BitmapData.colorTransform methods done on the GPU? If so, I suppose that would tilt things in their favor.


Solution

  • So I did some benchmarks, and in the best possible scenario for the draw method, where the DisplayObject to be drawn was simply a colored rectangle, draw was always faster than copyPixels by around 33%, regardless of size. Then I tried using some complex DisplayObjects with lots of shapes inside, and in those cases, draw was 300% to 400% slower. So the answer is "it depends". But I have to say, using the draw method has an upside potential of a 33% speed increase at most, but an enormous downside potential if the DisplayObject is complex.

    I did some additional testing of the copyPixels/colorTransform method against the getPixels/setPixels method, and remarkably the getPixels/setPixels method was 20x slower! Here is my copyPixels method:

    var start_time:int = getTimer();
    var s1:S1 = new S1(); //S1 is simply a 100x100 red circle
    var bmd1:BitmapData = new BitmapData(s1.width,s1.height,true,0x00000000);
    bmd1.drawWithQuality(s1,null,null,null,null,false,StageQuality.HIGH);
    var bmd2:BitmapData;
    var sourceRect:Rectangle = new Rectangle(0,0,bmd1.width,bmd1.height);
    var point00:Point = new Point();
    var colorTransform:ColorTransform = new ColorTransform(0,0,0,1,255,255,255,0);
    var w:uint = bmd1.width,
        h:uint = bmd1.height,
        pixelHex:uint,
        pixelAlpha:uint;
    var ba:ByteArray;
    for (var i:uint=0; i<1000; i++) {
        bmd2 = new BitmapData(w,h,true,0x00000000);
        bmd2.copyPixels(bmd1,sourceRect,point00);
        bmd2.colorTransform(sourceRect,colorTransform);
    }
    trace("execution time: ", getTimer()-start_time); //execution time:  74
    

    And here is my getPixels method for loop:

    for (var i:uint=0; i<1000; i++) {
        ba = bmd1.getPixels(sourceRect);
        ba.position = 0;
        while (ba.bytesAvailable > 0) {
            pixelHex = ba.readUnsignedInt();
            pixelAlpha = pixelHex >>> 24;
            if (pixelAlpha > 0) {
                ba.position -= 4;
                ba.writeUnsignedInt((pixelAlpha<<24)|0xffffff);
            }
        }
        ba.position = 0;
        bmd2 = new BitmapData(w,h,true,0x00000000);
        bmd2.setPixels(sourceRect,ba);
    }
    trace("execution time: ", getTimer()-start_time); //execution time:  1349