おえかきツールにundoを実装する

flex3 SDK+ActionScriptのお勉強にと、お絵かきツールを気まぐれで作ってたらundoの実装に手間取った。

手間取った理由は、Spriteオブジェクト上で書いた線をどうにかしようとしてたから。
解決方法として、Spriteオブジェクトを2つ用意をし、線を描き終えるたびにBitmapDataに反映させる。

誤解覚悟で絵を描くならこんな感じ。

f:id:taizo_onexone:20090115234817j:image

上のSpriteオブジェクトのアルファ値を0,下のSpriteオブジェクトのアルファ値を1にして
下のオブジェクトの子にbitmapを指定。上のオブジェクトで線が描かれるたびにBitmapDataに落としていく。
undoされた場合は、前のBitmapDataを入れ替える。そのために一度、子を消す。

mxmlファイル,asファイル1つずつで書くとしたらこんな感じ。

oekaki.mxml

<?xml version="1.0" encoding="utf-8"?>
<mx:Application 
  xmlns:mx="http://www.adobe.com/2006/mxml"
  layout="absolute"
  xmlns:o="oekaki.*">
  <mx:Canvas id="OekakiCanvas" width="300" height="200" backgroundColor="white">
    <o:OekakiCanvas id="oekaki_canvas"/>
  </mx:Canvas>
  <mx:HBox y="200">
    <mx:Button height="20" label="undo" click="oekaki_canvas.undo();"/>
    <mx:Button height="20" label="clear" click="oekaki_canvas.clear();"/>
  </mx:HBox>
</mx:Application>

oekaki/OekakiCanvas.as

package oekaki
{
  import mx.core.UIComponent;
  import flash.display.BitmapData;
  import flash.display.Bitmap;
  import flash.display.Sprite;
  import flash.display.DisplayObject;
  import flash.events.Event;
  import flash.events.MouseEvent;

  public class OekakiCanvas extends UIComponent
  {
    public var s:Sprite = new Sprite();
    public var bg:Sprite = new Sprite();

    public var bmd:BitmapData;
    public var before_bmd:BitmapData;

    public function OekakiCanvas()
    {
      this.initLine();

      bg.graphics.beginFill(0xffffff, 1);
      bg.graphics.drawRect(0,0,300,200);
      bg.graphics.endFill();

      s.addEventListener( MouseEvent.MOUSE_DOWN, this.onMouseDownHandler );
      s.addEventListener( MouseEvent.MOUSE_UP,   this.onMouseUpHandler );

      bmd = new BitmapData(300, 200, true, 0);
      var bp:Bitmap = new Bitmap(bmd);
      bp.name="drawer";
      bg.addChild(bp);

      this.addChild(bg);
      this.addChild(s);
    }

    protected function onMouseDownHandler( e:MouseEvent ):void 
    {
      before_bmd = bmd.clone();

      s.graphics.moveTo( s.mouseX, s.mouseY );
      s.addEventListener( Event.ENTER_FRAME, drawHandler );
    }

    protected function onMouseUpHandler( e:MouseEvent ):void 
    {
      s.removeEventListener( Event.ENTER_FRAME, drawHandler );
      bmd.draw(s);
      this.initLine();
    }

    protected function initLine():void
    {
      s.graphics.clear();
      s.graphics.beginFill(0xffffff, 0);
      s.graphics.drawRect(0,0,300,200);
      s.graphics.lineStyle(2, 0x000000);
      s.graphics.endFill();
    }

    protected function drawHandler( e:Event ):void
    {
      s.graphics.lineTo( s.mouseX, s.mouseY );
    }

    public function undo():void
    {
      var b_tmp:BitmapData  = bmd.clone();
      bmd                   = before_bmd.clone();
      before_bmd            = b_tmp.clone();
      bg.removeChild(bg.getChildByName("drawer") as DisplayObject);
      var bp:Bitmap = new Bitmap(bmd);
      bp.name="drawer";
      bg.addChild(bp);
    }

    public function clear():void
    {
      this.initLine();
      bg.removeChild(bg.getChildByName("drawer") as DisplayObject);

      bmd = new BitmapData(300, 200, true, 0);
      var bp:Bitmap = new Bitmap(bmd);
      bp.name="drawer";
      bg.addChild(bp);
    }
  }
}

これから、サイズとか位置情報、各Spriteオブジェクト、線を描くところとかは、きちんと分割して、Undo,Redoの実装はUndo,Redoの実装って何十回もやってる気がするあたりのことを詰めていけばいいような気がする。