package { import com.bit101.components.Label; import com.bit101.components.Slider; import flash.display.Sprite; import flash.events.Event; import flash.geom.ColorTransform; /** * 似而非キャニーエッジ Fake canny edge * 解説:http://aquioux.blog48.fc2.com/blog-entry-693.html * @author YOSHIDA, Akio (Aquioux) */ [SWF(width = "465", height = "465", frameRate = "30", backgroundColor = "#000000")] public class Main extends Sprite { private var smoothing:EffectorSmoothing; private var binarization:EffectorBinarization; private var sliderBinarization:Slider; private var labelBinarization:Label; private var prevThreshold:Number; private var sliderSmoothing:Slider; private var labelSmoothing:Label; private var prevSmooth:Number; public function Main() { Wonderfl.capture_delay(20); // エフェクタ連鎖 var chain:ChainEffectors = new ChainEffectors(); smoothing = new EffectorSmoothing(); binarization = new EffectorBinarization(); chain.addEffector(smoothing); chain.addEffector(binarization); chain.addEffector(new EffectorEdge()); // 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 1 var min:uint = 0; var max:uint = 255; sliderBinarization = new Slider(Slider.HORIZONTAL, this, 0, 0, sliderBinarizationHandler); sliderBinarization.width = 200; sliderBinarization.setSliderParams(min, max, uint(max / 2)); labelBinarization = new Label(this, sliderBinarization.width + 3, sliderBinarization.y - 3); labelBinarization.transform.colorTransform = new ColorTransform(0, 0, 0, 1, 255); sliderBinarizationHandler(null); // Controller 2 min = 0; max = 16; sliderSmoothing = new Slider(Slider.HORIZONTAL, this, 0, sliderBinarization.height + 5, sliderSmoothingHandler); sliderSmoothing.width = 200; sliderSmoothing.setSliderParams(min, max, 2); labelSmoothing = new Label(this, sliderSmoothing.width + 3, sliderSmoothing.y - 3); labelSmoothing.transform.colorTransform = new ColorTransform(0, 0, 0, 1, 255); sliderSmoothingHandler(null); // 開始 model.chain = chain; model.start(); } private function sliderBinarizationHandler(event:Event):void{ var value:uint = (event) ? event.target.value : sliderBinarization.value; labelBinarization.text = "Threaold : " + String(value); if (prevThreshold != value) { binarization.threshold = value; prevThreshold = value; } } private function sliderSmoothingHandler(event:Event):void{ var value:uint = (event) ? event.target.value : sliderSmoothing.value; labelSmoothing.text = "Smooth strength : " + String(value); if (prevSmooth != value) { smoothing.strength = value; prevSmooth = value; } } } } import flash.display.BitmapData; import flash.display.Stage; import flash.events.Event; import flash.events.EventDispatcher; import flash.geom.Matrix; import flash.media.Camera; import flash.media.Video; /** * Web Camera の映像にエフェクトをかける(MVC の Model) * エフェクトロジックは effector クラスとして外部で定義する * @author YOSHIDA, Akio (Aquioux) */ class Model extends EventDispatcher { // -------------------------------------------------- // View へ渡すデータ(プロパティ) // -------------------------------------------------- /** * 加工済みのカメラ画像 */ public function get data():BitmapData { return _data; } private var _data:BitmapData; // -------------------------------------------------- // 外部から受け取るデータ(プロパティ) // -------------------------------------------------- /** * エフェクタ連鎖 */ public function set chain(value:ChainEffectors):void { _chain = value; } private var _chain:ChainEffectors; // -------------------------------------------------- // 外部との通信をおこなうメソッド // -------------------------------------------------- /** * 対 View 用メソッド * このメソッドの終了時にイベントを発行するので、View との通信手段となる * @private */ private function update():void { _data.draw(video, matrix); _data = _chain.applyEffect(_data); dispatchEvent(new Event(Event.CHANGE)); } // -------------------------------------------------- // その他のメソッド // -------------------------------------------------- /** * コンストラクタ * コンストラクタの引数はステージとする。各種データはアクセサーによって取り込むものとする * @param stage ステージ */ private var stage:Stage; // カメラが表示するサイズ private var cameraWidth:uint; private var cameraHeight:uint; // カメラ private var camera:Camera; private var video:Video; private var matrix:Matrix; public function Model(stage:Stage, cw:Number = 0, ch:Number = 0) { this.stage = stage; this.cameraWidth = (cw == 0) ? stage.stageWidth : cw; this.cameraHeight = (ch == 0) ? stage.stageHeight : ch; _data = new BitmapData(cameraWidth, cameraHeight, false); matrix = new Matrix( -1, 0, 0, 1, cameraWidth, 0); // カメラ camera = Camera.getCamera(); if (camera) { // camera のセットアップ camera.setMode(cameraWidth, cameraHeight, stage.frameRate); // video のセットアップ video = new Video(cameraWidth, cameraHeight); video.attachCamera(camera); } else { throw new Error("カメラがありません。"); } } /** * 処理開始 * Event.ENTER_FRAME を使う場合、このメソッドを設定する。 * Controller から通知されるイベントだけで処理する場合、このメソッドは不要。 */ public function start():void { stage.addEventListener(Event.ENTER_FRAME, enterFrameHandler); } /** * イベントハンドラ * @private */ private function enterFrameHandler(event:Event):void { update(); } } import flash.display.Bitmap; import flash.events.Event; /** * Web Camera のスクリーン(MVC の View) * @author YOSHIDA, Akio (Aquioux) */ class View extends Bitmap { /** * コンストラクタ * @param model Model */ private var model:Model; public function View(model:Model) { this.model = model; this.model.addEventListener(Event.CHANGE, changeHandler); } /** * Model との通信手段 * @param event 発生したイベント */ private function changeHandler(event:Event):void { // Model からデータを受け取り、視覚化 this.bitmapData = model.data; } } import flash.display.BitmapData; /** * エフェクタ連鎖 * @author YOSHIDA, Akio (Aquioux) */ class ChainEffectors { private var effectors:Array = []; /** * コンストラクタ * @param model Model */ public function ChainEffectors() { } /** * エフェクタの追加 * @param effector エフェクタ */ public function addEffector(effector:AbstractEffector):void { effectors.push(effector); } /** * エフェクタの適用 * @param value エフェクタをかける BitmapData */ public function applyEffect(value:BitmapData):BitmapData { var n:uint = effectors.length; for(var i:int=0; i < n; i++){ var effector:AbstractEffector = effectors[i]; value = effector.applyEffect(value); } return value; } } import flash.display.BitmapData; import flash.geom.Point; /** * BitmapData エフェクト用抽象クラス * @author YOSHIDA, Akio */ class AbstractEffector { /* * BitmapData.applyFilter で destPoint として使用する Point オブジェクト */ protected const ZERO_POINT:Point = new Point(0, 0); /* * コンストラクタ */ public function AbstractEffector() {} /* * 効果の適用 * @param value 効果をかける BitmapData */ public function applyEffect(value:BitmapData):BitmapData { return effect(value); } /* * 効果内容、具体的なコードはサブクラスで定義する * @param value 効果をかける BitmapData */ protected function effect(value:BitmapData):BitmapData { return value; } } import flash.display.BitmapData; import flash.geom.Rectangle; /** * 二値化 * @author YOSHIDA, Akio (Aquioux) */ class EffectorBinarization extends AbstractEffector { 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 private var cloneBitmapData:BitmapData; // 一時処理用 BitmapData private var rect:Rectangle; /* * 二値化実行 * @param value 効果をかける BitmapData */ override protected function effect(value:BitmapData):BitmapData { if (cloneBitmapData) cloneBitmapData.dispose(); cloneBitmapData = new BitmapData(value.width, value.height); cloneBitmapData = value.clone(); rect = cloneBitmapData.rect; value.fillRect(rect, 0xFF000000); value.threshold(cloneBitmapData, rect, ZERO_POINT, ">", _threshold, 0xFFFFFFFF, 0x000000FF, false); return value; } } import flash.display.BitmapData; import flash.display.BlendMode; import flash.filters.ConvolutionFilter; import flash.geom.Rectangle; /** * エッジ検出(ラプラシアン・オペレータ) * 参考: 「OpenGL+GLSLによる画像処理プログラミング」 工学社 酒井幸市 P107 「5.1.2 エッジ検出フィルタ」 * @author YOSHIDA, Akio (Aquioux) */ class EffectorEdge extends AbstractEffector { // ConvolutionFilter のマトリクス(ラプラシアン) private var laplacian:Array = [ 0, 1, 0, 1, -4, 1, 0, 1, 0 ]; /* * エッジ検出実行 * @param value 効果をかける BitmapData */ override protected function effect(value:BitmapData):BitmapData { value.applyFilter(value, value.rect, ZERO_POINT, new ConvolutionFilter(3, 3, laplacian)); return value; } } import flash.display.BitmapData; import flash.filters.ColorMatrixFilter; /** * 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 EffectorGrayScale extends AbstractEffector { // ColorMatrixFilter 用マトリクス private const GRAYSCALE_MATRIX:Array = [ 0.3, 0.59, 0.11, 0, 0, 0.3, 0.59, 0.11, 0, 0, 0.3, 0.59, 0.11, 0, 0, /* 0.3375, 0.66375, 0.12375, 0, 0, // 0.33*1.125, 0.59*1.125, 0.11*1.125 0.3375, 0.66375, 0.12375, 0, 0, 0.3375, 0.66375, 0.12375, 0, 0, */ 0, 0, 0, 1, 0 ]; // ColorMatrixFilter private const GRAYSCALE_FILTER:ColorMatrixFilter = new ColorMatrixFilter(GRAYSCALE_MATRIX); /* * グレイスケール実行 * @param value 効果をかける BitmapData */ override protected function effect(value:BitmapData):BitmapData { value.applyFilter(value, value.rect, ZERO_POINT, GRAYSCALE_FILTER); return value; } } import flash.display.BitmapData; import flash.filters.BitmapFilterQuality; import flash.filters.BlurFilter; /** * BlurFilter による平滑化 * @author YOSHIDA, Akio (Aquioux) */ class EffectorSmoothing extends AbstractEffector { /* * ぼかしの量 * @param value 数値 */ public function set strength(value:Number):void { blurFilter.blurX = blurFilter.blurY = value; } /* * ぼかしの質 * @param value 数値 */ public function set quality(value:int):void { blurFilter.quality = value; } // ブラーフィルタ private var blurFilter:BlurFilter; public function EffectorSmoothing() { blurFilter = new BlurFilter(2, 2, BitmapFilterQuality.MEDIUM); } /* * 平滑化実行 * @param value 効果をかける BitmapData */ override protected function effect(value:BitmapData):BitmapData { value.applyFilter(value, value.rect, ZERO_POINT, blurFilter); return value; } } 似而非キャニーエッジ