BitmapData#paletteMap 高速化

2013 / 10 / 10 by
Filed under: AS 
Bookmark this on Delicious
[`livedoor` not found]
[`yahoo` not found]

先日、ある縁から、以前 ActionScript 3.0 で作った画像処理フィルターのいくつかを見直すことになりました。
処理速度のチューンアップが命題になりまして、いろいろ試してみたところ、高速化を図れる要素がいくつか見つかりました。

しかし時は流れて、世は Stage3D 時代。そのフレームワークである Starling がもて囃されているらしい今日この頃。
今さら flash.display.BitmapData の高速化に如何ほどの需要があるのかはなはだ疑問ではありますが、というか ActionScript 自体の寿命すら危ぶまれてる気がしないでもないですが、せっかく知見を得たのでエントリーしようかと。

検証したのは BitmapData#peletteMap と BitmapData#threshold のふたつのメソッド。
今回は paletteMap の高速化についてです。

なお、コード開発環境は FlashDevelop 4.4.3 RTM。
過去のバージョン全削除。クリーン・インストールで、AS コンパイラや Flash プレイヤーも同時にインストールし直しました。

paletteMap 高速化のポイントはたったひとつ。

paletteMap の対象となる BitmapData の transparent を false に指定すること。
それだけ。

1,000回ループで、各々10回実行した結果は以下のとおり。

sourceBitmapData.transparent = true のとき

第1回:5,559 msec.
第2回:5,315 msec.
第3回:5,447 msec.
第4回:5,587 msec.
第5回:5,242 msec.
第6回:5,526 msec.
第7回:5,326 msec.
第8回:5,321 msec.
第9回:5,503 msec.
第10回:5,338 msec.
平均:5,416.4 msec.

sourceBitmapData.transparent = false のとき

第1回:4,382 msec.
第2回:4,299 msec.
第3回:4,597 msec.
第4回:4,302 msec.
第5回:4,424 msec.
第6回:4,478 msec.
第7回:4,275 msec.
第8回:4,233 msec.
第9回:4,359 msec.
第10回:4,332 msec.
平均:4,368.1 msec.

sourceBitmapData.transparent = false の方が124.0%(計算合ってる?)高速ですね。

検証に使ったのは以下のファイルです。

Main.as(ドキュメント・ファイル)

package {
 import flash.display.Bitmap;
 import flash.display.BitmapData;
 import flash.display.GradientType;
 import flash.display.Graphics;
 import flash.display.Shape;
 import flash.display.Sprite;
 import flash.geom.Matrix;
 import flash.geom.Point;
 import flash.text.TextField;
 import flash.text.TextFieldAutoSize;
 import flash.text.TextFormat;
 import flash.utils.getTimer;
 [SWF(width = "500", height = "500", frameRate = "30", backgroundColor = "#ffffff")]


 public class Main extends Sprite {

  public function Main() {
   // ソースイメージ
   var trueBmd:BitmapData  = createSrcBmd(true);
   var falseBmd:BitmapData = createSrcBmd(false);

   //addChild(new Bitmap(trueBmd));
   //addChild(new Bitmap(falseBmd));
   //return;

   // 結果表示
   var display:TextField = new TextField();
   display.autoSize = TextFieldAutoSize.LEFT;
   display.defaultTextFormat = new TextFormat("_typewriter", 12, 0x000000);
   addChild(display);

   display.text = "BitmapData#paletteMap\n";

   // 処理回数
   const LOOP:int = 1000;

   // 減色フィルター
   var degree:int = 16;
   var filter:Posterize = new Posterize();
   filter.degree = degree;

   // 減色段階値
   display.appendText("posterize degree : " + String(degree) + "\n");

   var i:int = 0;
   var buf:BitmapData;

   // transparent = false
   display.appendText("src trasnparent = " + String(falseBmd.transparent) + ".\n");
   started = getTimer();
   for (i = 0; i < LOOP; i++) {
    falseBmd.lock();
    buf = falseBmd.clone();
    buf.lock();
    filter.applyEffect(buf);
    falseBmd.unlock();
    buf.unlock();
    buf.dispose();
   }
   display.appendText("    loop : " + String(LOOP) + " times, required : " + String(getTimer() - started) + " msec.\n");

   // transparent = true
   display.appendText("src trasnparent = " + String(trueBmd.transparent) + ".\n");
   var started:int = getTimer();
   for (i = 0; i < LOOP; i++) {
    trueBmd.lock();
    buf = trueBmd.clone();
    buf.lock();
    filter.applyEffect(buf);
    trueBmd.unlock();
    buf.unlock();
    buf.dispose();
   }
   display.appendText("    loop : " + String(LOOP) + " times, required : " + String(getTimer() - started) + " msec.\n");
  }


  // ソースイメージ生成
  private function createSrcBmd(sw:Boolean):BitmapData {
   const IMAGE_WIDTH:int  = 500;
   const IMAGE_HEIGHT:int = 500;

   var shape:Shape = new Shape();
   var matrix:Matrix = new Matrix();
   matrix.createGradientBox(IMAGE_WIDTH, IMAGE_HEIGHT);
   var g:Graphics = shape.graphics;
   g.beginGradientFill(GradientType.LINEAR, [0xff0000, 0x00ff00, 0x0000ff], [1, 1, 1], [0, 128, 255], matrix);
   g.drawRect(0, 0, IMAGE_WIDTH, IMAGE_HEIGHT);
   g.endFill();

   var bmd:BitmapData = new BitmapData(IMAGE_WIDTH, IMAGE_HEIGHT, sw);
   bmd.draw(shape);
   return bmd;
  }
 }
}

Posterize.as(減色フィルター)

package {
 import flash.display.BitmapData;
 import flash.geom.Point;
 /**
  * paletteMap による BitmapData の減色
  * @author YOSHIDA, Akio
  */
 final public class Posterize {
  /*
   * 減色の段階
   * @param value 段階(2 ~ 256)
   */
  public function set degree(value:uint):void {
   // value の有効範囲は 2 ~ 256
   // 最小値は 0 と 255 の 2 段階なので 2
   // 最大値は 0 ~ 255 の 256 段階なので 256(255 ではない点に注意)
   if (value <   2) value =   2;
   if (value > 256) value = 256;

   // _gradation の生成または初期化
   _gradation.fixed  = false;
   _gradation.length = 0;

   // paletteMap 用 Array
   var prevGray:uint = 0xff;
   for (var i:int = 0; i < 256; i++) {
    var gray:uint = ((i / (256 / value)) >> 0) * 255 / (value - 1) >> 0;
    rMap_[i] = gray << 16;
    gMap_[i] = gray <<  8;
    bMap_[i] = gray;
    if (prevGray != gray) {
     _gradation.push(gray);
     prevGray = gray;
    }
   }
   _gradation.fixed = true;
  }

  /**
   * 減色化によって計算された gradation の値を格納する Vector
   * degree を set することで有効になる
   * length は degree
   */
  public function get gradation():Vector.<uint> { return _gradation; }
  private var _gradation:Vector.<uint> = new Vector.<uint>();

  // paletteMap 用 Array
  private var rMap_:Array = [];
  private var gMap_:Array = [];
  private var bMap_:Array = [];

  private static const ZERO_POINT:Point = new Point();


  /*
   * コンストラクタ
   */
  public function Posterize() {
   degree = 8;  // degree のデフォルト
  }

  /*
   * 効果適用
   * @param value 効果対象 BitmapData
   */
  public function applyEffect(value:BitmapData):void {
   value.lock();
   value.paletteMap(value, value.rect, ZERO_POINT, rMap_, gMap_, bMap_);
   value.unlock();
  }
 }
}

paletteMap を使った画像処理フィルターとして減色フィルター擬似カラーフィルターを作りましたが、今回の検証では、減色フィルターを使いました。



Comments

Tell me what you're thinking...
and oh, if you want a pic to show with your comment, go get a gravatar!





WP-SpamFree by Pole Position Marketing