[WebCam] NeonMan
[WebCam] NeonMan
@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/1ywN
*/
package {
import flash.display.Sprite;
import net.hires.debug.Stats;
[SWF(width = "465", height = "465", frameRate = "30", backgroundColor = "#FFFFFF")]
/**
* [WebCam] NeonMan
* @author Aquioux(Yoshida, Akio)
*/
public class Main extends Sprite {
public function Main():void {
Wonderfl.capture_delay(20);
// 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);
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 posterize_:Posterize;
private var effector_:Effector;
private const STRENGTH_MAX:uint = 16;
private var ratio:uint;
// ---------- パブリックメソッド ----------
//
/**
* コンストラクタ
* @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);
// 鏡像になるよう、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("カメラがありません。");
}
ratio = w / (STRENGTH_MAX - 1);
// エフェクタ
smooth_ = new Smooth();
smooth_.strength = 16;
posterize_ = new Posterize();
posterize_.degree = 2;
effector_ = new Effector();
}
/**
* isNegative_ の切り替え
* Controller とのインターフェース
*/
public function notifyFromController(value:Number):void {
var strength:uint = value / ratio + 2;
smooth_.strength = strength;
}
// ---------- ローカルメソッド ----------
//
// 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); // 平滑化
posterize_.applyEffect(_data);
effector_.applyEffect(_data);
dispatchEvent(new Event(Event.CHANGE));
}
}
import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.events.Event;
import flash.geom.Point;
import flash.geom.Rectangle;
/**
* View
* @author YOSHIDA, Akio (Aquioux)
*/
class View extends Bitmap {
// ---------- パブリックプロパティ ----------
//
// Model の参照
public function set model(value:Model):void { _model = value; }
private var _model:Model;
// ---------- パブリックメソッド ----------
//
/**
* コンストラクタ
* @param model Model
*/
public function View(model:Model) {
_model = model;
_model.addEventListener(Event.CHANGE, changeHandler);
}
// ---------- ローカルメソッド ----------
//
// 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;
// ---------- パブリックメソッド ----------
//
/**
* コンストラクタ
* @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(MouseEvent.MOUSE_MOVE, moveHandler);
}
// イベントハンドラ
private function moveHandler(e:MouseEvent):void {
_model.notifyFromController(mouseX);
}
}
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 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.display.BitmapDataChannel;
import flash.filters.ConvolutionFilter;
import flash.geom.Point;
import flash.geom.Rectangle;
/**
* 今回のメインエフェクタ
* @author YOSHIDA, Akio (Aquioux)
*/
class Effector implements IEffector {
// ---------- ローカルプロパティ ----------
//
private var bufferBmd_:BitmapData;
private var rBmd_:BitmapData;
private var gBmd_:BitmapData;
private var bBmd_:BitmapData;
private var rect_:Rectangle;
private var edge_:Edge2 = new Edge2();
private const ZERO_POINT:Point = EffectorUtils.ZERO_POINT;
// ---------- パブリックメソッド ----------
//
/*
* 効果適用
* @param value 効果対象 BitmapData
*/
public function applyEffect(value:BitmapData):BitmapData {
if (!rBmd_) {
rBmd_ = new BitmapData(value.width, value.height, false, 0x000000);
gBmd_ = rBmd_.clone();
bBmd_ = rBmd_.clone();
rect_ = value.rect;
}
rBmd_.copyChannel(value, rect_, ZERO_POINT, BitmapDataChannel.RED, BitmapDataChannel.RED);
gBmd_.copyChannel(value, rect_, ZERO_POINT, BitmapDataChannel.GREEN, BitmapDataChannel.GREEN);
bBmd_.copyChannel(value, rect_, ZERO_POINT, BitmapDataChannel.BLUE, BitmapDataChannel.BLUE);
edge_.applyEffect(rBmd_);
edge_.applyEffect(gBmd_);
edge_.applyEffect(bBmd_);
value.fillRect(value.rect, 0xFF000000);
value.copyChannel(rBmd_, rBmd_.rect, ZERO_POINT, BitmapDataChannel.RED, BitmapDataChannel.RED);
value.copyChannel(gBmd_, gBmd_.rect, ZERO_POINT, BitmapDataChannel.GREEN, BitmapDataChannel.GREEN);
value.copyChannel(bBmd_, bBmd_.rect, ZERO_POINT, BitmapDataChannel.BLUE, BitmapDataChannel.BLUE);
return value;
}
}
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.geom.Point;
/**
* paletteMap による BitmapData の減色
* 参考:「実践画像処理入門」 培風館 内村圭一・上瀧剛 P16 「2.5 濃度値の量子化による減色処理」
* @author YOSHIDA, Akio (Aquioux)
*/
class Posterize implements IEffector {
// ---------- パブリックプロパティ ----------
//
/*
* 減色の段階
* @param value 段階
*/
public function set degree(value:uint):void {
// value の有効範囲は 2 ~ 256
if (value < 2) value = 2;
if (value > 256) value = 256;
for (var i:int = 0; i < 256; i++) {
var val:uint = uint(i / (256 / value)) * 255 / (value - 1);
rArray_[i] = val << 16;
gArray_[i] = val << 8;
bArray_[i] = val;
}
}
// ---------- ローカルプロパティ ----------
//
// paletteMap の引数となる各 Channel 用の Array
private var rArray_:Array = [];
private var gArray_:Array = [];
private var bArray_:Array = [];
private const ZERO_POINT:Point = EffectorUtils.ZERO_POINT;
// ---------- パブリックメソッド ----------
//
/*
* コンストラクタ
*/
public function Posterize() {
degree = 8; // degree のデフォルト
}
/*
* 効果適用
* @param value 効果対象 BitmapData
*/
public function applyEffect(value:BitmapData):BitmapData {
value.paletteMap(value, value.rect, ZERO_POINT, rArray_, gArray_, bArray_);
return value;
}
}
import flash.display.BitmapData;
import flash.filters.ConvolutionFilter;
import flash.geom.Point;
/**
* ConvolutionFilter によるエッジ検出 2次微分
* 「OpenGL+GLSLによる画像処理プログラミング」 工学社 酒井幸市 P104 「5.1 差分フィルタ」
* 「C言語で学ぶ実践画像処理」 オーム社 井上誠喜・他 P47 「4.7 ラプラシアンとゼロ交差により輪郭線を求める」
* http://msdn.microsoft.com/ja-jp/academic/cc998604.aspx
* @author YOSHIDA, Akio (Aquioux)
*/
class Edge2 implements IEffector {
// ---------- ローカルプロパティ ----------
//
private const MATRIX:Array = [
-1, -1, -1,
-1, 8, -1,
-1, -1, -1
];
private const FILTER:ConvolutionFilter = new ConvolutionFilter(3, 3, 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;
}
}