Search code examples
actionscript-3filteranimated

AS3-animate position of filtered area (Bitmapdata)


I know it's simple to animate the amount of any given filter, but lets say you have a filter on a specific region(Rectangle) of the stage. How would you animate(tween) the position of this filtered area so that the effect moves on the stage?


Solution

  • One way to achieve the effect that comes to mind is to draw the contents of the stage to a BitmapData and then use BitmapData.applyFilter(). applyFilter() lets you specify a sourceRect and a destPoint. So in your case you can animate the properties of sourceRect. destPoint's x and y should be the same as sourceRect's x and y in this case.

    Here's a working example:

    package {
        import flash.display.*;
        import flash.events.*;
        import flash.filters.*;
        import flash.geom.*;
    
        public class Main extends Sprite {
    
            public function Main():void {
                if (stage) init();
                else addEventListener(Event.ADDED_TO_STAGE, init);
            }
    
            private function init(e:Event = null):void {
                removeEventListener(Event.ADDED_TO_STAGE, init);
    
                var bitmapData : BitmapData = new BitmapData(this.stage.stageWidth, this.stage.stageHeight);
                var sourceRectVelocity : Point = new Point(3, 2);
                var sourceRect : Rectangle = new Rectangle(50, 100, 200, 150);
                var bitmap : Bitmap = new Bitmap(bitmapData);
    
                // draw some random circles on the stage
                for (var i:int = 0; i < 100; i++) {
                    this.graphics.beginFill((Math.floor(Math.random()*255) << 16) + (Math.floor(Math.random()*255) << 8) + Math.floor(Math.random()*255), 1);
                    this.graphics.drawCircle(Math.random()*this.stage.stageWidth, Math.random()*this.stage.stageHeight, 50 + Math.random()*50);
                }
    
                this.addChild(bitmap);
    
                this.addEventListener(Event.ENTER_FRAME, function(event : Event):void {
                    sourceRect.x = Math.min(Math.max(sourceRect.x + sourceRectVelocity.x, 0), bitmapData.width - sourceRect.width);
                    sourceRect.y = Math.min(Math.max(sourceRect.y + sourceRectVelocity.y, 0), bitmapData.height - sourceRect.height);
    
                    if (sourceRect.right >= bitmapData.width) {
                        sourceRectVelocity.x = -Math.abs(sourceRectVelocity.x);
                    } else if (sourceRect.left <= 0) {
                        sourceRectVelocity.x = Math.abs(sourceRectVelocity.x);
                    }
    
                    if (sourceRect.bottom >= bitmapData.height) {
                        sourceRectVelocity.y = -Math.abs(sourceRectVelocity.y);
                    } else if (sourceRect.top <= 0) {
                        sourceRectVelocity.y = Math.abs(sourceRectVelocity.y);
                    }
    
                    // clear the bitmap with white (not needed if the stage doesn't have any transparency)
                    bitmapData.fillRect(bitmapData.rect, 0xffffff);
    
                    // draw the stage to the bitmapData, but make sure the Bitmap display object showing the BitmapData isn't visible
                    bitmap.visible = false;
                    bitmapData.draw(stage);
                    bitmap.visible = true;
    
                    // apply greyscale filter
                    bitmapData.applyFilter(bitmapData, sourceRect, new Point(sourceRect.x, sourceRect.y), new ColorMatrixFilter([0.3, 0.59, 0.11, 0, 0,0.3, 0.59, 0.11, 0, 0,0.3, 0.59, 0.11, 0, 0,0, 0, 0, 1, 0]));
                });
            }
        }
    }
    

    This example draws the whole stage to a BitmapData but then only apply the filter to a region. A more optimized approach (especially if the region never/rarely change size) is to only draw the region you want to filter to a BitmapData with the size of the region and then apply the filter to the whole BitmapData.