views:

41

answers:

3

Am currently working on a tool created by a colleague of mine and would like to improve performance. Basically, it's a tool for drawing on screen and uses a combination of Sprites and the Graphics class to draw a line segment every time the mouse is moved when the mouse button is down. Here is the drawing bit:

// on MouseMove
protected function drawLine(e:MouseEvent):void {
        if (currentTool.thickness > 0){
            //pen
            var line:Sprite = new Sprite();

            line.graphics.lineStyle(currentTool.thickness, currentColour);
            line.graphics.moveTo(lastKnownPoint.x, lastKnownPoint.y);
            line.graphics.lineTo(e.localX, e.localY);
            inkLayer.addChild(line);
            lastKnownPoint.x = e.localX;
            lastKnownPoint.y = e.localY;
            e.updateAfterEvent();
        } else {
            //eraser
            var inkChildren:int = inkLayer.numChildren;
            for (var i:uint = inkChildren; i > 0; i--){
                if (toolCursor.hitTestObject(inkLayer.getChildAt(i - 1))){
                    inkLayer.removeChildAt(i - 1);
                }
            }
        }
    }

As you can see, it checks if the line 'thickness' property and draws if it is and erases if it isn't.

I did think of using a technique similar to blitting here where it'd draw to a bitmap but I'm not sure this'd give the performance boost I want or if, indeed, there would be any way to have an eraser function.

Any ideas on a better way to do this? The drawing itself works nicely - this isn't the problem, it's the performance of the subsequent 'drawn' sprites.

A: 

While drawing you will notice that curves are not sharp anymore.. and they will become composed from bigger and bigger lines. The workaround is not to create millions of small lines and add them as children, but to write in the same container using copyBitmapData.

Adrian Pirvulescu
A: 

You could optimize this by checking at the start of the method whether lastKnownPoint.x and lastKnownPoint.y are the same value or within a certain distance (2 or 3 pixels, perhaps). If they are, you simply return without doing anything. Experiment and find the distance at which you strike a good balance between performance and smoothness of the line.

Robusto
+1  A: 

I have solved similar problem recently and I could not think of efficient way to implement eraser with vectors. So I made a bitmap to erase drawings from. It works this way: when user selects colored pen, while he holds the left button, draw lines to sprite. When left button is released, sprite is flushed to BitmapData and board is redrawn using this bitmap. When user selects eraser, he draws thick black lines on sprite (they are invisible with alpha = 0). After each movement, affected part of sprite is flushed to BitmapData in erasing mode and this is reflected on board. To support scaling, I made bitmap big and hooked BlurFilter on it. It's not perfect, but works fast enough.

Edit: some code, not tested after cut, but should give the idea:

public class DrawingBoard extends Sprite {
    //...vars declaration
    public function DrawingBoard(width:int, height:int, bitmapWidth:int = 1000, bitmapHeight:int = 1000)
    {
        super();
        scrollRect = new Rectangle(0, 0, width, height);

        downMatrix = new Matrix();
        downMatrix.scale(bitmapWidth / width, bitmapHeight / height);

        upMatrix = new Matrix();
        upMatrix.scale(width / bitmapWidth, height / bitmapHeight);

        bitmap = new BitmapData(bitmapWidth, bitmapHeight, true, 0x00000000);

        //on canvas we draw vector pencils and eraser traces before flush
        canvas = new Sprite();
        addChild(canvas);

        filters = [new BlurFilter(3, 3)];
    }

        //external control api
    public function moveTo(point:Point):void {
        canvas.graphics.moveTo(point.x, point.y);
        var lineWidth:Number = 4 / parent.scaleX;
        canvas.graphics.lineStyle(lineWidth, currentColor, 0.8, false, LineScaleMode.NORMAL);
    }

    public function lineTo(point:Point):void {
        canvas.graphics.lineTo(point.x, point.y);
    }

    public function eraserLineTo(point:Point):void {
        canvas.graphics.lineStyle(eraserSize, 0, 1.0);
        canvas.graphics.lineTo(point.x, point.y);
        flush(BlendMode.ERASE);
        canvas.graphics.moveTo(point.x, point.y);
    }

    public function flush(blendMode:String = null):void {
                    //draw temporary vectors to bitmap
        bitmap.draw(canvas, downMatrix, null, blendMode, null, true);
                    //update board
        var bounds:Rectangle = canvas.getBounds(this);
        with (graphics) {
            beginBitmapFill(bitmap, upMatrix);
            drawRect(bounds.x, bounds.y, bounds.width, bounds.height);
            endFill();
        }
                    //erase temporary vectors
        canvas.graphics.clear();
    }
}
alxx
This seems similar to what I had in mind, any chance you could show me some code?
dr_tchock
I'll post some key parts in a few hours (need to clean them up first, there is a lot of app-specific code.)
alxx
Much appreciated. I'm fine with the drawing bit, it's the erasing I'd like to see if possible.
dr_tchock
Actually, I figured it out - was so simple. Will post code myself in a bit.
dr_tchock