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

[WebCam] BallMan

[WebCam] BallMan
説明:http://aquioux.blog48.fc2.com/blog-entry-711.html
@author Aquioux(Yoshida, Akio)
/**
 * Copyright Aquioux ( http://wonderfl.net/user/Aquioux )
 * MIT License ( http://www.opensource.org/licenses/mit-license.php )
 * Downloaded from: http://wonderfl.net/c/h9yI
 */

package {
	import flash.display.Sprite;
	import net.hires.debug.Stats;
	[SWF(width = "465", height = "465", frameRate = "30", backgroundColor = "#FFFFFF")]
	/**
	 * [WebCam] BallMan
	 * 説明:http://aquioux.blog48.fc2.com/blog-entry-711.html
	 * @author Aquioux(Yoshida, Akio)
	 */
	public class Main extends Sprite {
		public function Main():void {
			Wonderfl.capture_delay(15);

			// model
			try {
				var model:Model = new Model(stage);
			} catch (err:Error) {
				trace(err.message);
				return;
			}
			
			// view
			var view:View = new View(model);
			addChild(view);
			
			// controller
			var controller:Controller = new Controller(model);
			controller.view = view;
			addChild(controller);
			
			//addChild(new Stats());
		}
	}
}


	import flash.display.BitmapData;
	import flash.display.Stage;
	import flash.events.ActivityEvent;
	import flash.events.Event;
	import flash.events.EventDispatcher;
	import flash.geom.Matrix;
	import flash.geom.Point;
	import flash.geom.Rectangle;
	import flash.media.Camera;
	import flash.media.Video;
	/**
	 * Model
	 * @author YOSHIDA, Akio (Aquioux)
	 */
	class Model extends EventDispatcher {
		// ---------- パブリックプロパティ ----------
		//
		// View へ渡すデータ
		public function get data():BitmapData { return _data; }
		private var _data:BitmapData;

		
		// ---------- ローカルプロパティ ----------
		//
		private var stage_:Stage;
		private var cameraWidth_:uint;
		private var cameraHeight_:uint;
		private var camera_:Camera;
		private var video_:Video;
		private var matrix_:Matrix;

		// 各エフェクタ
		private var smooth_:Smooth;			// 平滑化
		private var grayscale_:GrayScale;	// グレイスケール
		private var nega_:Negative;			// 反転
		private var binarize_:Binarize;		// 二値
		private var divider_:SubDivider;	// 平面分割
		
		private var gray_:BitmapData;
		private var rect_:Rectangle;
		private const ZERO_POINT:Point = EffectorUtils.ZERO_POINT;

		private var isNegative_:Boolean = false;
		

		// ---------- パブリックメソッド ----------
		//
		/**
		 * コンストラクタ
		 * @param	stage	ステージ
		 * @param	cw		カメラ幅(省略時はステージ幅)
		 * @param	ch		カメラ高(省略時はステージ高)
		 */
		public function Model(stage:Stage, cw:uint = 0, ch:uint = 0) {
			// 引数の処理
			stage_ = stage;
			var w:uint = stage_.stageWidth;
			var h:uint = stage_.stageHeight;
			cameraWidth_  = (cw == 0) ? w : cw;
			cameraHeight_ = (ch == 0) ? h : ch;

			// View へ渡すデータの生成
			_data = new BitmapData(w, h, true, 0x00000000);

			gray_ = new BitmapData(w, h, false, 0x000000);
			rect_ = _data.rect;
			
			// 鏡像になるよう、Matrix で左右反転
			var tx:uint = (w - cameraWidth_)  / 2 + cameraWidth_;
			var ty:uint = (h - cameraHeight_) / 2;
			matrix_ = new Matrix( -1, 0, 0, 1, tx, ty);
			
			// カメラ準備
			camera_ = Camera.getCamera();
			if (camera_) {
				// camera のセットアップ
				camera_.setMode(cameraWidth_, cameraHeight_, stage_.frameRate);
				// video のセットアップ
				video_ = new Video(cameraWidth_, cameraHeight_);
				video_.attachCamera(camera_);
				// カメラがアクティブになるまで待つ
				camera_.addEventListener(ActivityEvent.ACTIVITY, activeHandler);
			} else {
				throw new Error("カメラがありません。");
			}

			// エフェクタ
			smooth_ = new Smooth();			// 平滑化
			smooth_.strength = 4;
			grayscale_ = new GrayScale();	// グレイスケール
			nega_ = new Negative();			// 反転
			binarize_ = new Binarize();		// 二値
			binarize_.auto = true;
			divider_ = new SubDivider();	// 平面分割
		}
		
		/**
		 * isNegative_ の切り替え
		 * Controller とのインターフェース
		 */
		public function notifyFromController():void {
			isNegative_ = !isNegative_;
		}
		
		
		// ---------- ローカルメソッド ----------
		//
		// ACTIVITY イベントハンドラ
		private function activeHandler(e:ActivityEvent):void {
			camera_.removeEventListener(ActivityEvent.ACTIVITY, arguments.callee);
			stage_.addEventListener(Event.ENTER_FRAME, enterFrameHandler);
		}
		// ENTER_FRAME イベントハンドラ
		private function enterFrameHandler(event:Event):void {
			_data.draw(video_, matrix_);
			
			smooth_.applyEffect(_data);		// 平滑化
			grayscale_.applyEffect(_data);	// グレイスケール
			gray_.copyPixels(_data, rect_, ZERO_POINT);
			if (isNegative_) nega_.applyEffect(gray_);	// 反転
			binarize_.applyEffect(gray_);	// 二値
			_data.fillRect(rect_, 0xFFFFFF);
			divider_.applyEffect(rect_, gray_, _data, 10, 10, binarize_.threshold);

			dispatchEvent(new Event(Event.CHANGE));
		}
	}


	import flash.display.Bitmap;
	import flash.display.BitmapData;
	import flash.events.Event;
	import flash.filters.DropShadowFilter;
	/**
	 * View
	 * @author YOSHIDA, Akio (Aquioux)
	 */
	class View extends Bitmap {
		
		// ---------- パブリックプロパティ ----------
		//
		// Model の参照
		public function set model(value:Model):void { _model = value; }
		private var _model:Model;
		
		
		// ---------- ローカルプロパティ ----------
		//
		private var filter:DropShadowFilter;

		
		// ---------- パブリックメソッド ----------
		//
		/**
		 * コンストラクタ
		 * @param	model	Model
		 */
		public function View(model:Model) {
			_model = model;
			_model.addEventListener(Event.CHANGE, changeHandler);
			
			filter = new DropShadowFilter(5, 45, 0x666666, 1, 8, 8);
			filters = [filter];
		}

		/**
		 * DropShadowFilter の knockout の切り替え
		 * Controller とのインターフェース
		 */
		public function notifyFromController():void {
			filter.knockout = !filter.knockout;
			filters = [filter];
		}

		// ---------- ローカルメソッド ----------
		//
		// Model から Event.CHANGE が発行されたときの処理
		private function changeHandler(e:Event):void {
			bitmapData = _model.data;
		}
	}


	import flash.display.Sprite;
	import flash.events.Event;
	import flash.events.KeyboardEvent;
//	import flash.events.MouseEvent;
	/**
	 * Controller
	 * @author YOSHIDA, Akio (Aquioux)
	 */
	class Controller extends Sprite {
		// ---------- パブリックプロパティ ----------
		//
		// Model の参照
		public function set model(value:Model):void { _model = value; }
		private var _model:Model;

		// View の参照
		public function set view(value:View):void { _view = value; }
		private var _view:View;
		

		// ---------- パブリックメソッド ----------
		//
		/**
		 * コンストラクタ
		 * @param	model	Model
		 */
		public function Controller(model:Model) {
			_model = model;
			addEventListener(Event.ADDED_TO_STAGE, addToStageHandler);
		}
		

		// ---------- ローカルメソッド ----------
		//
		// イベントハンドラ
		// 自分がステージに登録されたとき
		private function addToStageHandler(e:Event):void {
			removeEventListener(Event.ADDED_TO_STAGE, arguments.callee);
			stage.addEventListener(KeyboardEvent.KEY_DOWN, keyDownHandler);
		}
		
		// イベントハンドラ
		// キーダウンク時
		private function keyDownHandler(e:KeyboardEvent):void {
			if (e.keyCode == 75) _view.notifyFromController();	// "k" キー
			if (e.keyCode == 82) _model.notifyFromController();	// "r" キー
		}
	}


	import flash.display.BitmapData;
	import flash.display.Graphics;
	import flash.geom.Matrix;
	import flash.geom.Rectangle;
	/**
	 * 画面分割
	 * @author YOSHIDA, Akio (Aquioux)
	 */
	class SubDivider {
		// ---------- ローカルプロパティ ----------
		//
		// 共用 Rectangle
		private var nextRect_:Rectangle = new Rectangle();	// 4分木再帰処理に使用
		private var drawRect_:Rectangle = new Rectangle();	// 描画に使用
		
		// ヒストグラムから濃度の合計値を求める関数
		private const GET_BRIGHTNESS:Function = EffectorUtils.getSumOfBrightness1;
		
		private const DOT:Dot = new Dot(0.5, 0x000000);
		private var matrix_:Matrix = new Matrix();

		// ---------- パブリックメソッド ----------
		//
		/*
		 * コンストラクタ
		 */
		public function SubDivider() {
		}
		
		/*
		 * 効果適用
		 * @param	rect	現在の対象 Rectangle
		 * @param	gray	効果対象 BitmapData
		 * @param	canvas	描画 BitmapData
		 * @param	divideThreshold	分割閾値
		 * @param	minSize	分割サイズ最小値
		 * @param	drawThreshold	描画閾値
		 */
		public function applyEffect(rect:Rectangle, gray:BitmapData, canvas:BitmapData, divideThreshold:uint, minSize:uint, drawThreshold:uint):void {
			// 現在のチェック対象矩形の縦または横の長さが指定より小さい場合、再帰を停止
			var isDivide:Boolean = rect.width > minSize && rect.height > minSize;
			
			// 走査範囲のピクセル数
			var numOfPixel:uint = rect.width * rect.height;
			// 走査範囲のヒストグラム
			var hist:Vector.<Vector.<Number>> = gray.histogram(rect);

			// 再帰判定
			if (isDivide) {
				// 濃度の標準偏差
				var deviation:Number = getStandardDeviation(hist[0], numOfPixel);
				if (deviation > divideThreshold) {
					// 標準偏差が指定より大きい場合、4分木再帰を続行
					// Rectangle の x、y、width、height が uint になるようにする
					// (numOfPixel と齟齬が出ないようにするため)
					var nextWidth1:uint  = rect.width / 2 >> 0;
					var nextHeight1:uint = rect.height / 2 >> 0;
					var nextWidth2:uint  = rect.width  - nextWidth1;
					var nextHeight2:uint = rect.height - nextHeight1;
					var left:Number = rect.x;
					var top:Number  = rect.y;
					var center:Number = left + nextWidth1;
					var middle:Number = top  + nextHeight1;
					var xs:Vector.<uint> = Vector.<uint>([left, center, left, center]);
					var ys:Vector.<uint> = Vector.<uint>([top, top, middle, middle]);
					var ws:Vector.<uint> = Vector.<uint>([nextWidth1, nextWidth2, nextWidth1, nextWidth2]);
					var hs:Vector.<uint> = Vector.<uint>([nextHeight1, nextHeight1, nextHeight2, nextHeight2]);
					// 4分木再帰
					for (var i:int = 0; i < 4; i++) {
						nextRect_.x = xs[i];
						nextRect_.y = ys[i];
						nextRect_.width  = ws[i];
						nextRect_.height = hs[i];
						applyEffect(nextRect_, gray, canvas, divideThreshold, minSize, drawThreshold);
					}
				} else {
					// 標準偏差が指定より小さい場合、再帰を停止し、描画をおこなう
					isDivide = false;
				}
			}
			
			// 描画
			if (!isDivide) draw(rect, hist[0], numOfPixel, canvas, drawThreshold);
		}


		// ---------- ローカルメソッド ----------
		//
		// 対象 BitmapData の指定矩形範囲の標準偏差を求める
		private function getStandardDeviation(hist:Vector.<Number>, numOfPixel:uint):Number {
			// 平均値
			var average:Number = hist[255] * 255 / numOfPixel;
			
			// 平均との差の二乗を累積
			var diff:Number = 0;
			var sum:uint = 0;
			diff = -average;
			sum += diff * diff * hist[0];
			diff = 255 - average;
			sum += diff * diff * hist[255];
			
			return Math.sqrt(sum / numOfPixel);
		}
		
		// キャンバスに描画
		private function draw(rect:Rectangle, hist:Vector.<Number>, numOfPixel:uint, canvas:BitmapData, threshold:uint):void {
			var sum:uint = GET_BRIGHTNESS(hist);
			var brightness:uint = sum / numOfPixel;
			if (brightness < threshold) {
				matrix_.a = rect.width  - 1;
				matrix_.d = rect.height - 1;
				matrix_.tx = rect.x;
				matrix_.ty = rect.y;
				canvas.draw(DOT, matrix_);
			}
		}
	}


	import flash.display.BitmapData;
	/**
	 * BitmapDataEffector 用 interface
	 * @author YOSHIDA, Akio (Aquioux)
	 */
	interface IEffector {
		function applyEffect(value:BitmapData):BitmapData;
	}


	import flash.geom.Point;
	/**
	 * bitmapDataEffector パッケージ内のクラスで共通に使う定数など
	 * @author YOSHIDA, Akio (Aquioux)
	 */
	class EffectorUtils {
		// ---------- パブリックプロパティ ----------
		//
		// BitmapData が備える各種メソッドの destPoint 用
		static public const ZERO_POINT:Point = new Point(0, 0);
		
		// グレイスケール用の各チャンネルの重みづけ
		static public const LUM_R:Number = 0.298912;
		static public const LUM_G:Number = 0.586611;
		static public const LUM_B:Number = 0.114478;

		
		// ---------- パブリックメソッド ----------
		//
		// 一つのチャンネルにおける濃度の平均値を求める(引数のヒストグラムで調整する)
		static public function getAverageOfBrightness1(hist:Vector.<Number>):uint {
			var sum:uint = 0;
			var numOfPixel:uint = 0;
			for (var i:int = 0; i < 256; i++) {
				sum        += i * hist[i];
				numOfPixel += hist[i];
			}
			return sum / numOfPixel >> 0;
		}
		// RGB チャンネルにおける濃度の各平均値を求める
		static public function getAverageOfBrightness3(hist:Vector.<Vector.<Number>>):Vector.<uint> {
			var rSum:uint = 0;
			var gSum:uint = 0;
			var bSum:uint = 0;
			var numOfPixel:uint = 0;
			for (var i:int = 0; i < 256; i++) {
				rSum += i * hist[0][i];
				gSum += i * hist[1][i];
				bSum += i * hist[2][i];
				numOfPixel += hist[0];
			}
			return Vector.<uint>([rSum / numOfPixel >> 0, gSum / numOfPixel >> 0, bSum / numOfPixel >> 0]);
		}
		
		// 一つのチャンネルにおける濃度の平均値を求める(引数のヒストグラムで調整する)
		static public function getSumOfBrightness1(hist:Vector.<Number>):uint {
			var sum:uint = 0;
			for (var i:int = 0; i < 256; i++) {
				sum += i * hist[i];
			}
			return sum;
		}
		// RGB チャンネルにおける濃度の各平均値を求める
		static public function getSumOfBrightness3(hist:Vector.<Vector.<Number>>):Vector.<uint> {
			var rSum:uint = 0;
			var gSum:uint = 0;
			var bSum:uint = 0;
			for (var i:int = 0; i < 256; i++) {
				rSum += i * hist[0][i];
				gSum += i * hist[1][i];
				bSum += i * hist[2][i];
			}
			return Vector.<uint>([rSum, gSum, bSum]);
		}
	}


	import flash.display.BitmapData;
	import flash.filters.BitmapFilterQuality;
	import flash.filters.BlurFilter;
	import flash.geom.Point;
	/**
	 * BlurFilter による平滑化
	 * @author YOSHIDA, Akio (Aquioux)
	 */
	class Smooth implements IEffector {
		// ---------- パブリックプロパティ ----------
		//
		/*
		 * ぼかしの強さ
		 * @param	value	数値
		 */
		public function set strength(value:Number):void {
			filter_.blurX = filter_.blurY = value;
		}
		/*
		 * ぼかしの質
		 * @param	value	数値
		 */
		public function set quality(value:int):void {
			filter_.quality = value;
		}


		// ---------- ローカルプロパティ ----------
		//
		private var filter_:BlurFilter;		// ブラーフィルタ
		private const ZERO_POINT:Point = EffectorUtils.ZERO_POINT;


		// ---------- パブリックメソッド ----------
		//
		/*
		 * コンストラクタ
		 */
		public function Smooth() {
			filter_ = new BlurFilter(2, 2, BitmapFilterQuality.MEDIUM);
		}
		
		/*
		 * 効果適用
		 * @param	value	効果対象 BitmapData
		 */
		public function applyEffect(value:BitmapData):BitmapData {
			value.applyFilter(value, value.rect, ZERO_POINT, filter_);
			return value;
		}
	}


	import flash.display.BitmapData;
	import flash.filters.ColorMatrixFilter;
	import flash.geom.Point;
	/**
	 * ColorMatrixFilter による BitmapData のグレイスケール化(NTSC 系加重平均による)
	 * 参考:Foundation ActionScript 3.0 Image Effects(P106)
	 * 		http://www.amazon.co.jp/gp/product/1430218711?ie=UTF8&tag=laxcomplex-22
	 * @author YOSHIDA, Akio (Aquioux)
	 */
	 
	class GrayScale implements IEffector {
		// ---------- ローカルプロパティ ----------
		//
		private const R:Number = EffectorUtils.LUM_R;
		private const G:Number = EffectorUtils.LUM_G;
		private const B:Number = EffectorUtils.LUM_B;

		private const MATRIX:Array = [
			R, G, B, 0, 0,
			R, G, B, 0, 0,
			R, G, B, 0, 0,
			0, 0, 0, 1, 0
		];
		private const FILTER:ColorMatrixFilter = new ColorMatrixFilter(MATRIX);
		
		private const ZERO_POINT:Point = EffectorUtils.ZERO_POINT;

		
		// ---------- パブリックメソッド ----------
		//
		/*
		 * 効果適用
		 * @param	value	効果対象 BitmapData
		 */
		public function applyEffect(value:BitmapData):BitmapData {
			value.applyFilter(value, value.rect, ZERO_POINT, FILTER);
			return value;
		}
	}


	import flash.display.BitmapData;
	import flash.filters.ConvolutionFilter;
	import flash.geom.Point;
	/**
	 * ConvolutionFilter による BitmapData の色反転
	 * 参考:http://www40.atwiki.jp/spellbound/pages/231.html
	 * @author YOSHIDA, Akio (Aquioux)
	 */
	class Negative implements IEffector {
		// ---------- ローカルプロパティ ----------
		//
		private const FILTER:ConvolutionFilter = new ConvolutionFilter(1, 1, [ -1], 1, 255);
		private const ZERO_POINT:Point = EffectorUtils.ZERO_POINT;
		
		
		// ---------- パブリックメソッド ----------
		//
		/*
		 * 効果適用
		 * @param	value	効果対象 BitmapData
		 */
		public function applyEffect(value:BitmapData):BitmapData {
			value.applyFilter(value, value.rect, ZERO_POINT, FILTER);
			return value;
		}
	}


	import flash.display.BitmapData;
	import flash.geom.Point;
	import flash.geom.Rectangle;
	/**
	 * 二値化
	 * 閾値より大きな値のピクセルは白、それ以外は黒に置き換える
	 * @author YOSHIDA, Akio (Aquioux)
	 */
	class Binarize implements IEffector {
		// ---------- パブリックプロパティ ----------
		//
		// 閾値
		public function get threshold():int { return _threshold; }
		public function set threshold(value:int):void {
			if (value < 0)   value = 0;
			if (value > 255) value = 255;
			_threshold = value;
		}
		private var _threshold:int = 127;	// 閾値:0 ~ 255
		
		// 判別分析をするか否か
		public function get auto():Boolean { return _auto; }
		public function set auto(value:Boolean):void { _auto = value; }
		private var _auto:Boolean = false;


		// ---------- ローカルプロパティ ----------
		//
		private const ZERO_POINT:Point = EffectorUtils.ZERO_POINT;

		
		// ---------- パブリックメソッド ----------
		//
		/*
		 * 効果適用
		 * @param	value	効果対象 BitmapData
		 */
		public function applyEffect(value:BitmapData):BitmapData {
			var bufferBmd:BitmapData = new BitmapData(value.width, value.height);
			var rect:Rectangle = bufferBmd.rect;
			bufferBmd = value.clone();

			if (_auto) {
				var hist:Vector.<Vector.<Number>> = value.histogram();
				var numOfPixel:uint = rect.width * rect.height;
				_threshold = calcThresholdRough(hist[2], numOfPixel);
			}
			
			value.fillRect(rect, 0xFF000000);
			value.threshold(bufferBmd, rect, ZERO_POINT, ">", _threshold, 0xFFFFFFFF, 0x000000FF, false);
			return value;
		}

		
		// ---------- ローカルメソッド ----------
		//
		// 閾値の判別分析
		private function calcThresholdRough(hist:Vector.<Number>, numOfPixel:uint):uint {
			var total:uint;
			for (var i:int = 0; i < 256; i++) {
				total += hist[i] * i;
			}
			return total / numOfPixel >> 0;
		}
		
		// 閾値の判別分析
		private function calcThresholdStrict(hist:Vector.<Number>, numOfPixel:uint):uint {
			var maxSeparability:Number  = 0;	// 最大分離値を待避させる変数
			var maxDegree:uint = 0;				// そのときの階調を待避させる変数
			for (var i:int = 1; i < 255; i++) {
				// 1~254 を閾値としたときの分離度を計算し、最大値を待避する
				var Separability:Number = calcSeparability(i, hist, numOfPixel);
				if (Separability > maxSeparability) {
					maxSeparability = Separability;
					maxDegree       = i;
				}
			}
			return maxDegree;
		}
		// 分離度の計算
		private function calcSeparability(threshold:uint, hist:Vector.<Number>, numOfPixel:uint):Number {
			var i:uint;								// ループカウンター
			var num1:uint = 0, num2:uint = 0;		// 各領域の画素数
			var con1:Number = 0, con2:Number = 0;	// 各領域の濃度(濃度平均値)
			var con:Number = 0;						// 濃度中間値
			var dis1:Number, dis2:Number;			// 分散計算用
			var within:Number = 0;					// クラス内分散値
			var between:Number = 0;					// クラス間分散値
			
			// 二つの領域の画素数と濃度を計算
			for (i = 0; i < threshold; i++) {
				num1 += hist[i];
				con1 += i * hist[i];
			}
			for (i = threshold; i < 256; i++) {
				num2 += hist[i];
				con2 += i * hist[i];
			}
			con = (con1 + con2) / numOfPixel;	// 濃度中間値
			con1 /= num1;	// 領域1の濃度平均値
			con2 /= num2;	// 領域2の濃度平均値

			if (num1 == 0 || num2 == 0) return 0;
			
			// 分散を計算
			// クラス内分散
			for (i = 0; i < threshold; i++) {
				dis1 = i - con1;
				within += dis1 * dis1 * hist[i];
			}
			for (i = threshold; i < 256; i++) {
				dis2 = i - con2;
				within += dis2 * dis2 * hist[i];
			}
			within /= numOfPixel;
			// クラス間分散
			for (i = 0; i < threshold; i++) {
				dis1 = con - con1;
				between += dis1 * dis1 * hist[i];
			}
			for (i = threshold; i < 256; i++) {
				dis2 = con - con2;
				between += dis2 * dis2 * hist[i];
			}
			between /= numOfPixel;
			
			return between / within;
		}
	}


	import flash.display.Shape;
	/**
	 * @author YOSHIDA, Akio (Aquioux)
	 */
	class Dot extends Shape{
		public function Dot(radius:Number = 5, color:uint = 0xFFFFFF) {
			graphics.beginFill(color);
			graphics.drawCircle(radius, radius, radius);
			graphics.endFill();
		}
	}