getColorBoundsRect()のバグへの対処

getColorBoundsRect()のバグ?でバグかなと思ってた部分がとっくの昔にAdobeに報告されてて未だに放置プレイの現状。getColorBoundsRect() 自体は便利なのでどうしても(バグの不安なく)使いたいからこんなことしてみた。けど、こんな方法でいいのかイマイチ自信ない。

バグの内容

BitmapData の {x = 0, y = 0, width = 1, height = 1} に孤立した(周りが別の色の)塗りデータがある場合、 getColorBoundsRect() が {x = 0, y = 0, width = 1, height = 1} を判定してくれない。
(左上の1pxだけ周りと別の色っていうシチュエーション自体が稀と思いきや、このバグに悩まされるのが人生の中で二回目だったという事実。)

バグの検証用SWFの使い方

TestClass_BitmapDataUtil.swf

  • 12px * 8px の 33ffff00 で塗った Bitmap が拡大して貼ってある。方眼1マスが 1px * 1px と思えばおk。
  • Bitmap をクリックすると、クリックしたピクセルを ffff0000 で塗る。
  • Test ボタンで BitmapData クラスの getColorBoundsRect() と、自作 BitmapDataUtil クラスの getColorBoundsRect() を実行する。
  • getColorBoundsRect() の引数は、 Test ボタンの上の3つの TextInput から変えられる。
  • 実行結果は Test ボタンの下に表示され、その結果を視覚化した枠が表示される。 BitmapData クラスの getColorBoundsRect() の実行結果は緑の枠で、自作 BitmapDataUtil クラスの getColorBoundsRect() の実行結果は青の枠。

バグの検証手順

下の1~5の手順で引数を {mask = ffffffff, color: ffff0000, findColor: true} と {mask = ffffffff, color: 33ffff00, findColor: false} の二通りで検証する。

  1. 左上以外の1pxをクリックして赤くしてみる。
  2. Testボタンを押して実行。 width/height 共に 1 でちゃんと取得できてることを確認する。
  3. Resetボタンを押して、塗りをリセットする。
  4. 左上の1pxをクリックして赤くしてみる。
  5. Testボタンを押して実行。BitmapData クラスは width/height 共に 0 でちゃんと取得できない。

BitmapDataUtil クラスの getColorBoundsRect() の実装

まずはASDocながめる。

例えば、あるソースイメージで、0 以外のアルファチャンネルを含むイメージの矩形を判別するには、パラメータとして {mask: 0xFF000000, color: 0x00000000} を渡します。findColor パラメータが true に設定されている場合、(value & mask) == color であるピクセルの境界を見つけるためにイメージ全体が検索されます(value はピクセルのカラー値)。 findColor パラメータが false に設定されている場合、(value & mask) != color であるピクセルの境界を見つけるためにイメージ全体が検索されます(value はピクセルのカラー値)。 イメージの周囲の空白を判別するには、空白以外のピクセルの境界を見つけるために、{mask: 0xFFFFFFFF, color: 0xFFFFFFFF} を渡してください。

getColorBoundsRect() メソッド

左上の1pxだけ BitmapData.getPixel32()する。取得した色と mask の論理積だして color と比較して、 BitmapData.getColorBoundsRect() で出てきた Rectangle と 左上の1pxの Rectangle の和集合出したら終わり。
と思ったら論理積のところに罠があった。

結果は 32 ビットの 2 の補数として解釈されるため、-2147483648 ~ 2147483647 の範囲の整数になります。

& bitwise AND 演算子

-2147483648 ~ 2147483647 は16進数で、 -7fffffff ~ 7fffffff てことで、アルファが 7f 超えるとなんかマイナス戻ってくる・・・
しょうがなく ARGB を A と RGB に分けてそれぞれ論理積だして、足して ARGB に戻して color と比較。
findColor が true で論理積の ARGB と color が同値なら、左上の1pxを足す。findColor が false で論理積の ARGB と color が同値でなければ、左上の1pxを足す。
これで、「左上の1pxだけ塗った場合」と「左上以外の1pxを塗った場合」の挙動が同じになる。気がする。

  • ActionScript
  • BitmapDataUtil.as
  • Source
package {
	import flash.display.BitmapData;
	import flash.geom.Rectangle;

	/**
	 * BitmapData のユーティリティクラスです.
	 *
	 * @langversion ActionScript 3.0
	 * @playerversion Flash 9.0
	 *
	 * @author dsk
	 * @since 2009/05/20
	 */
	public class BitmapDataUtil {

		/**
		 * (findColor パラメータが true に設定されている場合)ビットマップイメージ内の指定された色のすべてのピクセルを完全に囲む矩形領域を判別します。または、(findColor パラメータが false に設定されている場合)指定された色ではないすべてのピクセルを完全に囲む矩形領域を判別します。
		 *
		 * @param	bitmapData	ビットマップデータです。
		 * @param	mask		対象となる ARGB カラーのビットを指定する 16 進数値です。カラー値は、&(ビット単位の論理積(AND))演算子を使用して、この 16 進数値と組み合わせられます。
		 * @param	color		16 進数値です。(findColor が true に設定されている場合は)一致すべき ARGB カラー、一致すべきでない ARGB カラー(findColor が false に設定されている場合)をそれぞれ指定します。
		 * @param	findColor	値が true に設定された場合、イメージ内のカラー値の境界を返します。 値が false に設定された場合、イメージ内の指定されたカラーが存在しない領域の境界を返します。
		 * @return
		 */
		public static function getColorBoundsRect(bitmapData:BitmapData, mask:uint = 0x00000000, color:uint = 0xff000000, findColor:Boolean = true):Rectangle {
			var rectangle:Rectangle = bitmapData.getColorBoundsRect(mask, color, findColor);

			var maskA:uint = mask >> 24 & 0xff;
			var maskRGB:uint = mask & 0xffffff;
			var leftTopARGB:uint = bitmapData.getPixel32(0, 0);
			var leftTopA:uint = leftTopARGB >> 24 & 0xff;
			var leftTopRGB:uint = leftTopARGB & 0xffffff;

			var resultA:uint = leftTopA & maskA;
			var resultRGB:uint = leftTopRGB & maskRGB
			var resultARGB:uint = (resultA << 24) + resultRGB;

			if (findColor) {
				if (resultARGB == color) {
					rectangle = rectangle.union(new Rectangle(0, 0, 1, 1));
				}
			} else {
				if (resultARGB != color) {
					rectangle = rectangle.union(new Rectangle(0, 0, 1, 1));
				}
			}

			return rectangle;
		}


	}

}
  1. package {
  2.     import flash.display.BitmapData;
  3.     import flash.geom.Rectangle;
  4.  
  5.     /**
  6.      * BitmapData のユーティリティクラスです.
  7.      *
  8.      * @langversion ActionScript 3.0
  9.      * @playerversion Flash 9.0
  10.      *
  11.      * @author dsk
  12.      * @since 2009/05/20
  13.      */
  14.     public class BitmapDataUtil {
  15.  
  16.         /**
  17.          * (findColor パラメータが true に設定されている場合)ビットマップイメージ内の指定された色のすべてのピクセルを完全に囲む矩形領域を判別します。または、(findColor パラメータが false に設定されている場合)指定された色ではないすべてのピクセルを完全に囲む矩形領域を判別します。
  18.          *
  19.          * @param   bitmapData  ビットマップデータです。
  20.          * @param   mask        対象となる ARGB カラーのビットを指定する 16 進数値です。カラー値は、&(ビット単位の論理積(AND))演算子を使用して、この 16 進数値と組み合わせられます。
  21.          * @param   color       16 進数値です。(findColor が true に設定されている場合は)一致すべき ARGB カラー、一致すべきでない ARGB カラー(findColor が false に設定されている場合)をそれぞれ指定します。
  22.          * @param   findColor   値が true に設定された場合、イメージ内のカラー値の境界を返します。 値が false に設定された場合、イメージ内の指定されたカラーが存在しない領域の境界を返します。
  23.          * @return
  24.          */
  25.         public static function getColorBoundsRect(bitmapData:BitmapData, mask:uint = 0x00000000, color:uint = 0xff000000, findColor:Boolean = true):Rectangle {
  26.             var rectangle:Rectangle = bitmapData.getColorBoundsRect(mask, color, findColor);
  27.  
  28.             var maskA:uint = mask >> 24 & 0xff;
  29.             var maskRGB:uint = mask & 0xffffff;
  30.             var leftTopARGB:uint = bitmapData.getPixel32(0, 0);
  31.             var leftTopA:uint = leftTopARGB >> 24 & 0xff;
  32.             var leftTopRGB:uint = leftTopARGB & 0xffffff;
  33.  
  34.             var resultA:uint = leftTopA & maskA;
  35.             var resultRGB:uint = leftTopRGB & maskRGB
  36.             var resultARGB:uint = (resultA << 24) + resultRGB;
  37.  
  38.             if (findColor) {
  39.                 if (resultARGB == color) {
  40.                     rectangle = rectangle.union(new Rectangle(0, 0, 1, 1));
  41.                 }
  42.             } else {
  43.                 if (resultARGB != color) {
  44.                     rectangle = rectangle.union(new Rectangle(0, 0, 1, 1));
  45.                 }
  46.             }
  47.  
  48.             return rectangle;
  49.         }
  50.  
  51.  
  52.     }
  53.  
  54. }

右に1pxずらした BitmapData を新しく作って、getColorBoundsRect() して取得した Rectangle を左に1px戻すってのでもいいかもしんないけど、なんか気持ち悪い。Adobe先生お願いします。


About this entry