In case Flash no longer exists; a copy of this site is included in the Flashpoint archive's "ultimate" collection.

Dead Code Preservation :: Archived AS3 works from wonderfl.net

BitmapDataキャッシュ サポートクラスを作ってみました

例のミツバチを群れにして もぞもぞ感を強調しようとしているが、frocessingで描画しているとすぐに重くなる。
でもfrocessingで実行時にデータを用意する作業にはまっているので、いまさらpaintソフトで画像を用意するのもいやだ。
そこで、BitmapDataでキャッシュしようと思ったが、パーツごとにキャッシュを作るのは面倒になったので
(ミツバチの体だけならともかく、脚間接ごとに全角度分を用意すると思うとぞっとする!!!)、
自分用に汎用的に使えるようなキャッシュクラスを作ってみた。
クラスが提供するのはキャッシュ部分のみ。描画はfrocessingやdraw()メソッドなど好きに使えるように
してみました。いかんせん突貫なので、まだ無駄があると思うけどとりあえずここで公開。
*画像描画の度にキャッシュする方法と、あらかじめ全てのパターンをキャッシュする方法の2通り選べるようにしてみました*
Get Adobe Flash player
by zendenmushi 24 Apr 2010
    Embed
/**
 * Copyright zendenmushi ( http://wonderfl.net/user/zendenmushi )
 * MIT License ( http://www.opensource.org/licenses/mit-license.php )
 * Downloaded from: http://wonderfl.net/c/1qbC
 */

// 例のミツバチを群れにして もぞもぞ感を強調しようとしているが、frocessingで描画しているとすぐに重くなる。
// でもfrocessingで実行時にデータを用意する作業にはまっているので、いまさらpaintソフトで画像を用意するのもいやだ。
//
// そこで、BitmapDataでキャッシュしようと思ったが、パーツごとにキャッシュを作るのは面倒になったので
// (ミツバチの体だけならともかく、脚間接ごとに全角度分を用意すると思うとぞっとする!!!)、
// 自分用に汎用的に使えるようなキャッシュクラスを作ってみた。
//
// クラスが提供するのはキャッシュ部分のみ。描画はfrocessingやdraw()メソッドなど好きに使えるように
// してみました。いかんせん突貫なので、まだ無駄があると思うけどとりあえずここで公開。
//
// *画像描画の度にキャッシュする方法と、あらかじめ全てのパターンをキャッシュする方法の2通り選べるようにしてみました*
package  
{
	import adobe.utils.CustomActions;
	import com.actionsnippet.qbox.objects.PolyObject;
	import com.bit101.components.PushButton;
	import flash.display.Bitmap;
	import flash.display.BitmapData;
	import flash.display.Sprite;
	import flash.events.Event;
	import flash.geom.Point;
	import flash.text.TextField;
	import flash.text.TextFieldAutoSize;
	import flash.utils.getTimer;
	import frocessing.core.F5BitmapData2D;
	import net.hires.debug.Stats;
	/**
	 * ...
	 * @author TMaeda
	 */
    [SWF(width=465,height=465,backgroundColor=0xcccccc,frameRate=60)]
	public class BitmapCacheTest extends Sprite
	{
		private var nextButton : PushButton;
		private var txt  : TextField = new TextField();

		private var feather_cache : BitmapCacheProxy;
		private var feather_cache_mini : BitmapCacheProxy;
		private var canvas : F5BitmapData2D;
		private var backbuff : F5BitmapData2D;
		
		private var initfuncs : Array = [ initNormal, initImmediateCacheDraw, initPreCacheDraw ];
		private var framefuncs : Array = [ normal, immediateCacheDraw, preCacheDraw ];
		private var step : int = 1;
		private var state : int = 0;
		
		public function BitmapCacheTest() 
		{
                        Wonderfl.capture_delay( 50 );
			if (stage) init();
			else addEventListener(Event.ADDED_TO_STAGE, init);
		}
		
		public function init(e : Event = null) : void
		{
			removeEventListener(Event.ADDED_TO_STAGE, init);
			
			addEventListener(Event.ENTER_FRAME, enterFrame);
			
			backbuff = new F5BitmapData2D(stage.stageWidth, stage.stageHeight,true,0);
			canvas = new F5BitmapData2D(stage.stageWidth, stage.stageHeight, true, 0);
			addChild(new Bitmap(canvas.bitmapData));
			
			// 大きな羽毛
			feather_cache = new BitmapCacheProxy( { F5:backbuff/*描画作業に使いたいオブジェクトを渡す*/, scale:0.5 }, this, true, 360, 4, drawWithCache /*,最終blt関数はデフォルトを使用*/);
			
			nextButton =  new PushButton(this, stage.stageWidth - 128, 0, "NEXT>>", nextButtonClick);
			
			txt.autoSize = TextFieldAutoSize.LEFT;
			addChild(txt);
			goStep(0);

			var stat : Stats = new Stats();
			stat.y = 32;
			stat.x = stage.stageWidth-100;
			addChild(stat);
		}
		
		private function nextButtonClick(e:Event) : void
		{
			goStep(step + 1);
		}
		
		private function goStep(no : int) : void
		{
			if (no < initfuncs.length) step = no;
			
			initfuncs[ step ].call();
		}
		

		private var rad : Number = 0;
		private function enterFrame(e:Event):void 
		{
			framefuncs[step].call();
		}

		// --------------------------------------------------------------------------
		private function initNormal() : void
		{
			txt.text = "paintソフトで画像を用意するよりも、frocessingでその場で\n生成するのが好きだけど、毎フレーム描画するとどうしても遅い。";
		}
		
		private function normal() : void
		{
			rad += 2*Math.PI / 180;

			canvas.bitmapData.fillRect(canvas.bitmapData.rect, 0xffcccccc);
			canvas.beginDraw();
			
			state = (state + 1) & 0x3;
			drawFeather(canvas, rad, state, 0.5, stage.stageWidth / 2, stage.stageHeight / 2);
			drawFeather(canvas, rad + 1, state, 0.5, 100,100);
			drawFeather(canvas, rad + 2, state, 0.5, 400,300);
			drawFeather(canvas, rad + 3, state, 0.5, 80,400);
			
			canvas.endDraw();
			
		}
		// --------------------------------------------------------------------------

		private var zeroPoint : Point = new Point(0, 0);
		// BitmapCacheProxy()で指定したキャッシュ画像描画用の関数
		private function drawWithCache( param : Object, cache_item : BitmapCacheImage, angle : Number, state : int, exparam : Object = null) : void
		{
			// 一度描画作業用のオブジェクトに描画してから、キャッシュに保存する。定型的な処理になるはずだがカスタマイズ性を考え、ユーザー関数内で記述する
			
			var scale : Number = 0.5;
			if (param.scale != undefined) scale = param.scale;
			// 前処理
			// キャッシュ用bitmapDataを生成。回転に伴う領域サイズ計算は自動で行う
			cache_item.build( 100*scale, 100*scale, angle, 200*scale, 200*scale, 0);
			
			// 実際の描画処理
			// paramのフォーマットは任意。ここではF5に描画用のF5BitmapData2Dが渡されてくる。↑のBitmapCacheProxy()で指定
			var f5 : F5BitmapData2D = param.F5;
			f5.bitmapData.fillRect(f5.bitmapData.rect, 0);
			f5.beginDraw();
			drawFeather( f5 , angle, state, scale, cache_item.offset.x, cache_item.offset.y);
			f5.endDraw();
			
			
			// 後処理
			// cache_item.canvas がキャッシュ画像を記録するためのオブジェクトなので、bltしておく
			cache_item.canvas.copyPixels(param.F5.bitmapData, cache_item.canvas.rect, zeroPoint, null, null, true);
		}
		
		private var waittime : uint = 0;
		private function initImmediateCacheDraw() : void
		{
			waittime = getTimer() + 10000;
			nextButton.enabled = false;
			txt.text = "そこでBitmapDataによるキャッシュの出番。\nでも毎回キャッシュ機構を作るのは面倒なので\nキャッシュクラスを用意してみた\n\nキャッシュ前の角度/ステートの際は描画が発生するので\n重くなるが、一度描画されるとキャッシュされるので、\nその内に速度があがる\n\n(少し待ってください)";
		}
		
		private function immediateCacheDraw() : void
		{
			if (getTimer() > waittime) {
				nextButton.enabled = true;
			}
			rad += 2*Math.PI / 180;

			canvas.bitmapData.fillRect(canvas.bitmapData.rect, 0xffcccccc);
			canvas.beginDraw();
			
			state = (state + 1) & 0x3;
			feather_cache.draw(canvas.bitmapData, stage.stageWidth / 2, stage.stageHeight / 2, rad, state);
			feather_cache.draw(canvas.bitmapData, 100,100, rad+1, state);
			feather_cache.draw(canvas.bitmapData, 400,300, rad+2, state);
			feather_cache.draw(canvas.bitmapData, 80,400, rad+3, state);
			
			canvas.endDraw();
		}
		
		// --------------------------------------------------------------------------
		// 
		private var feathers : Vector.<feather_item> = new Vector.<feather_item>;
		private var freep : int = -1;
		private const itemlimit : int = 5000;
		
		private function initPreCacheDraw() : void
		{
			nextButton.visible = false;
			txt.text = "数が少ないとわかりにくいので大量生成。\nさらに、あらかじめ全角度/ステート分のキャッシュ生成";
			// 小さな羽毛
			feather_cache_mini = new BitmapCacheProxy( { F5:backbuff/*描画作業に使いたいオブジェクトを渡す*/, scale:0.3 }, this, true, 360, 4, drawWithCache /*,最終blt関数はデフォルトを使用*/);
		}
		
		private var init_done : Boolean = false;
		private function preCacheDraw() : void
		{
			canvas.bitmapData.fillRect(canvas.bitmapData.rect, 0xffcccccc);
			
			// 初期化進捗バー表示
			if (!init_done) {
				var progress : Number = feather_cache_mini.progressiveInitialize();
				init_done = progress == 1.0;
				canvas.beginDraw();
				canvas.strokeWeight(4);
				canvas.stroke(0xffffff);
				canvas.moveTo(80, stage.stageHeight / 2);
				canvas.lineTo(80 + progress * (stage.stageWidth - 160), stage.stageHeight / 2);
				canvas.strokeWeight(1);
				canvas.endDraw();
				
				if (init_done) {
					txt.text = "";
				}
			} else {
				if (freep < 100) newItem(Math.random() * stage.stageWidth, -32, Math.random() * 2 - 1,  1+Math.random() * 5, Math.random());
				
				for (var i : int = freep; i >= 0; i--) {
					var f : feather_item = feathers[i];
					feather_cache_mini.draw(canvas.bitmapData, f.x, f.y, f.angle, f.x & 0x3);
					f.angle += f.rspeed*2*Math.PI / 180;
					f.x += f.dx + Math.cos(f.angle);
					f.y += f.dy + Math.sin(f.angle);
					if (f.y > stage.stageHeight+50) remove( f.index);
				}
			}
			
		}
		private function newItem(x : Number, y : Number , dx : Number, dy : Number, rspeed : Number) : feather_item
		{
			var cnt : int = feathers.length;
			if (((itemlimit > 0) && cnt >= itemlimit) && (freep >= cnt-1)) return null;
			
			freep++;

			if (freep == cnt) {
				feathers[cnt] = new feather_item();
			}
			feathers[freep].index = freep;
			feathers[freep].visible = true;
			feathers[freep].x = x;
			feathers[freep].y = y;
			feathers[freep].angle = Math.random()*Math.PI*2;
			feathers[freep].rspeed = rspeed;
			feathers[freep].dx = dx;
			feathers[freep].dy = dy;
		
			return feathers[freep];
		}
		private function remove(index : int) : void
		{
			var cnt : int = feathers.length;
			var temp : feather_item = feathers[index];
			var lastp : int = freep;

			temp.visible = false;
			if (lastp != index) {
				feathers[index] = feathers[lastp];
				feathers[index].index = index;
				feathers[lastp] = temp;
			}
			freep = lastp - 1;
			
		}

		// --------------------------------------------------------------------------
		// 共通描画関数  軽い描画だと差がわかりにくいので無理やり重くしてます
		private var points : Array = [100,-100, 50,-80, 0,0, -100,100];
		private function drawFeather(target : F5BitmapData2D, rad : Number, state : int, scale : Number, x : Number, y : Number)  : void
		{
			target.stroke(0xffffff);
			target.pushMatrix();
			target.translate(x,y);
			target.rotate(rad);
			target.scale(scale, scale);
			
			var l : int = points.length;

			for (var i : int = 0; i < 100; i++) {
				var t : Number = i / 100;
				var mt : Number = (1 - t);
				var x : Number = mt * mt * mt * points[0] + 3 * mt * mt * t * points[2] + 3 * mt * t * t * points[4] + t * t * t * points[6];
				var y : Number = mt * mt * mt * points[1] + 3 * mt * mt * t * points[3] + 3 * mt * t * t * points[5] + t * t * t * points[7];
				
				target.strokeAlpha = 1;
				target.point(x, y);
				
				var yy1 : Number = y + Math.sin( (i-10) * Math.PI / 90) * 32+ Math.sin( 2*(i-10) * Math.PI / 90) * (16+state);
				var yy2 : Number = y - Math.sin( (i-10) * Math.PI / 90) * 64+ Math.sin( 2*(i-10) * Math.PI / 90) * (16+state);
				
				var xx1 : Number = x + Math.sin( (i-10) * Math.PI / 90) * 64;
				var xx2 : Number = x - Math.sin( (i-10) * Math.PI / 90) * 48;
				
				if (i > 10) {
					target.stroke(0xff * mt, 0xff*t, 0x80 * t);
					target.strokeAlpha = 0.5;
					target.moveTo(x, y);
					target.arcCurveTo(x,y, xx1, yy1, xx1-x, yy1-y);
					target.moveTo(x, y);
					target.arcCurveTo(x,y, xx2, yy2, x-xx2, y-yy2, false, false);

					target.stroke(0x80 * mt,0, 0xff * t);
					target.strokeAlpha = 0.5;
					target.moveTo(x, y);
					target.lineTo(xx1, y);
					target.moveTo(x, y);
					target.lineTo(xx2, y);
				}
			}
			target.popMatrix();
			target.strokeAlpha = 1;
		}
		
	}

}

class feather_item
{
	public var x : Number;
	public var y : Number;
	public var dx : Number;
	public var dy : Number;
	public var angle : Number;
	public var rspeed : Number;
	
	public var index : int;
	public var visible : Boolean;
}

// ---------------------------------------------------------------
// ここからキャッシュクラス
import flash.display.BitmapData;
import flash.geom.Point;
import frocessing.core.F5BitmapData2D;

class BitmapCacheProxy
{
	// ジェネリクスが欲しい。
	// private
	private var param : Object; 
	private var singlestate_images : Vector.<BitmapCacheImage>;
	private var multistate_images : Vector.<Vector.<BitmapCacheImage>>;
	private var rotatable : Boolean;
	private var maxstate : int;
	private var anglediv : int;
	private var _radian : Boolean = true;
	private var owner : Object;
	
	// protected
	protected var customDrawfunc : Function;
	protected var customBltfunc : Function;
	
	// methods
	public function BitmapCacheProxy(param : Object, owner : Object, rotatable : Boolean, anglediv : int = 360, maxstate : int = 1, drawfunc : Function = null, bltfunc : Function = null)
	{
		this.owner = owner;
		this.rotatable = rotatable;
		this.maxstate = Math.max(1,maxstate);
		this.anglediv = anglediv;
		this.param = param;
		customDrawfunc = drawfunc;
		customBltfunc = bltfunc;
		if (maxstate > 1) {
			multistate_images = new Vector.<Vector.<BitmapCacheImage>>;
		} else {
			singlestate_images = new Vector.<BitmapCacheImage>;
		}
		
		for (var i : int = 0; i < anglediv; i++) {
			if (maxstate > 1) {
				multistate_images[i] = null;
			} else {
				singlestate_images[i] = null;
			}
		}
		
	}
	
	// 最初に全ての角度、ステートを用意したい場合は この関数の戻り値が1になるまで繰り返す
	private var progress : int = 0;
	public function progressiveInitialize() : Number
	{
		var radmode : Boolean = _radian;
		_radian = false;
		
		var total : int = anglediv * maxstate;
		
		draw( null, 0, 0, progress / maxstate, progress % maxstate);
		
		_radian = radmode;
		
		progress++;
		
		return progress / total;
		
	}
	
	public function draw(target : BitmapData, x : Number, y : Number, angle : Number, state : int, exparam : Object = null) : void
	{
		var deg : int = angle;
		if (_radian) {
			deg = angle * 180 / Math.PI ;
		}
		if (deg < 0) deg = -( -deg % anglediv);
		else deg = (deg % anglediv);
		
		var cache_item : BitmapCacheImage;
		if (maxstate > 1) {
			var multistate : Vector.<BitmapCacheImage> = multistate_images[deg];
			if (!multistate) {
				multistate = new Vector.<BitmapCacheImage>;
				for (var s : int = 0; s < maxstate; s++) {
					multistate[s] = null;
				}
				multistate_images[deg] = multistate;
			}
			cache_item = multistate[state];
			if (!cache_item) {
				cache_item = new BitmapCacheImage();
				multistate[state] = cache_item;
			}
			
		} else {
			cache_item = singlestate_images[deg];
			if (!cache_item) {
				cache_item = new BitmapCacheImage();
				singlestate_images[deg] = cache_item;
			}
		}
		
		if (cache_item.empty) {
			var rad : Number = deg * Math.PI / (anglediv / 2.0);
			if (customDrawfunc != null) customDrawfunc.call(owner, param, cache_item, rad, state, exparam);
			else drawfunc(param, cache_item, rad, state, exparam);
			
			cache_item.empty = false;
		}
		
		if (target) {
			if (customBltfunc != null) customBltfunc.call(owner, target, cache_item, x, y, exparam);
			else bltfunc(target, cache_item, x, y);
		}
		
	}
	
	// コンストラクタにカスタム関数を登録しない場合、下記メソッドが呼ばれる。 クラスを継承してオーバーライドする
	protected function drawfunc( param : Object, cache_item : BitmapCacheImage, angle : Number, state : int, exparam : Object = null) : void
	{
		/* override */
	}
	
	private var dest : Point = new Point();
	// デフォルトのblt関数。描画対象にbltする
	protected function bltfunc( target : BitmapData, cache_item : BitmapCacheImage, x : Number, y : Number, exparam : Object = null) : void
	{
		dest.x = x - cache_item.offset.x;
		dest.y = y - cache_item.offset.y;
		target.copyPixels( cache_item.canvas, cache_item.canvas.rect, dest, null, null, true);
	}
	
	
	// property
	public function get radian():Boolean { return _radian; }
	
	public function set radian(value:Boolean):void 
	{
		_radian = value;
	}
	
}

class BitmapCacheImage
{
	public var empty : Boolean = true;
	public var offset : Point = new Point();
	public var canvas : BitmapData;
	
	public function BitmapCacheImage()
	{
		
	}
	
	// 角度0の際の、回転中心位置をhotSpotx/y (左上基準)
	// 角度0の際の、横幅width、縦height
	// 実際の角度angle
	// を与えると、angle角度回転した画像を納めるに十分なサイズのbitmapを確保し、hotspotからそのbitmapの左上までのオフセット座標を算出する
	public function build(hotSpotx : Number, hotSpoty : Number, angle : Number, width : int, height : int, fillColor : uint, inflateLeft : int = 0, inflateRight : int = 0, inflateTop : int = 0, inflateBottom : int = 0) : void
	{
		var cx : int = hotSpotx - width/2;
		var cy : int = hotSpoty - height/2;
		
		var w : int = Math.abs( Math.cos(angle) )*width + Math.abs( Math.sin(angle) )*height;
		if (w < 1) w = 1;
		var h : int = Math.abs( Math.sin(angle) )*width + Math.abs( Math.cos(angle) )*height;
		if (h < 1) h = 1;
		
		offset.x = w / 2 + (Math.cos(angle)) * cx - ( Math.sin(angle)) * cy + inflateLeft;
		offset.y = h / 2 + (Math.sin(angle)) * cx + ( Math.cos(angle)) * cy + inflateRight;
		
		w += inflateLeft + inflateRight;
		h += inflateTop + inflateBottom;
		
		canvas = new BitmapData( w, h, true, fillColor);
		
	}
}