BitmapData を使って文字列でマスク

残念ながら、ActionScript では TextField を使ってマスクすることはできません。画像をグラデーションに塗りたい、とか、写真を文字列くりぬきたい、といった要望には簡単には応えられません。

(追記 2009/9/9) cacheAsBitmap を利用すれば簡単にマスクできました。詳しくは AS3.0 で TextField をマスクに使う簡単な方法 をご覧ください。

じゃあ、どうするかというと、BitmapData の出番です。技術的には BeInteractive! [BitmapDataを簡単にマスクする方法] と全く同じです。

サンプル Flash どーん。

中段のグラデーションに対して、上段の文字列をマスクとして利用しています。上段の文字列は編集できることからも分かるとおり、TextField です。

ソースコードは長いけど、肝はここ。

    bmpDataText.draw(textField);
    bmpData.draw(grad);
    bmpData.copyChannel(bmpDataText, 
        new Rectangle(0, 0, WIDTH, HEIGHT), 
        new Point(0, 0), 
        BitmapDataChannel.RED, 
        BitmapDataChannel.ALPHA);
  1. bmpDataText に TextField を描画
  2. bmpData にグラデーションを描画
  3. bmpData に bmpDataText の Red チャンネルを Alpha チャンネルとしてコピー(テキストの濃淡データを透明度として設定している)。

別に Red チャンネルでなくて、Green でも Blue でもいいんですが、グレースケールなので全部同じ値なので気にしない。

あと、この Flash が秒速10KBずつぐらいのペースでメモリを食いつぶしていくんだけど、なんでだろう。 → (2007.9.12 追記) コメント欄で教えていただきました。グラデーションを描画する前に、graphics.clear() すれば問題なくなった。AS で上塗りする場合は、下に隠れている塗りの情報も保存され続けるようだ。これは知らなかったら絶対にはまる…。

長いけどソースコードは以下に(73行)。

package
{
    import flash.display.*;
    import flash.events.*;
    import flash.geom.*;
    import flash.text.*;
    import flash.system.*;

    public class TextMask extends Sprite
    {
        private const WIDTH:int = 400;
        private const HEIGHT:int = 50;

        public function TextMask()
        {
            stage.scaleMode = "noScale";
            stage.align = "TL";
            stage.frameRate = 12;

            // text
            var tf:TextFormat = new TextFormat();
            tf.size = HEIGHT * 2 / 3;
            tf.color = 0xffffff;
            var textField:TextField = new TextField();
            textField.defaultTextFormat = tf;
            textField.text = "Hello, AS3.0!";
            textField.type = "input";
            textField.width = WIDTH;
            textField.height = HEIGHT;
            textField.background = true;
            textField.backgroundColor = 0;
            addChild(textField);

            // gradation
            var grad:Shape = new Shape();
            grad.y = HEIGHT;
            addChild(grad);

            // output
            var bmpData:BitmapData = new BitmapData(WIDTH, HEIGHT);
            var bmp:Bitmap = new Bitmap(bmpData);
            bmp.y = HEIGHT * 2;
            addChild(bmp);

            // buffer
            var bmpDataText:BitmapData = new BitmapData(WIDTH, HEIGHT, false);

            // animation
            var angle:Number = 0;
            addEventListener("enterFrame", function(event:Event):void
            {
                angle += Math.PI / 16;
                angle = (angle >= Math.PI * 2 ? 0 : angle);

                // update gradation
                var matrix:Matrix = new Matrix();
                matrix.createGradientBox(WIDTH, HEIGHT, angle);
                grad.graphics.clear();
                grad.graphics.beginGradientFill(GradientType.LINEAR, [0xff9900, 0x0000ff], [100, 100], [0, 0xff], matrix);
                grad.graphics.drawRect(0, 0, WIDTH, HEIGHT);
                grad.graphics.endFill();

                // cache textField as BitmapData
                bmpDataText.draw(textField);

                // mask it!
                bmpData.lock();
                bmpData.draw(grad);
                bmpData.copyChannel(bmpDataText, new Rectangle(0, 0, WIDTH, HEIGHT), new Point(0, 0), BitmapDataChannel.RED, BitmapDataChannel.ALPHA);
                bmpData.unlock();
            });
        }
    }
}