BitmapDataキャッシュ サポートクラスを作ってみました
例のミツバチを群れにして もぞもぞ感を強調しようとしているが、frocessingで描画しているとすぐに重くなる。
でもfrocessingで実行時にデータを用意する作業にはまっているので、いまさらpaintソフトで画像を用意するのもいやだ。
そこで、BitmapDataでキャッシュしようと思ったが、パーツごとにキャッシュを作るのは面倒になったので
(ミツバチの体だけならともかく、脚間接ごとに全角度分を用意すると思うとぞっとする!!!)、
自分用に汎用的に使えるようなキャッシュクラスを作ってみた。
クラスが提供するのはキャッシュ部分のみ。描画はfrocessingやdraw()メソッドなど好きに使えるように
してみました。いかんせん突貫なので、まだ無駄があると思うけどとりあえずここで公開。
*画像描画の度にキャッシュする方法と、あらかじめ全てのパターンをキャッシュする方法の2通り選べるようにしてみました*
/**
* 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);
}
}