// http://aquioux.blog48.fc2.com/blog-entry-680.html の内容を WebCam に適用したものです。 package { import flash.display.Sprite; import flash.events.Event; /** * 各種フィルタを使ったウェブカム画像の加工 * @author YOSHIDA, Akio (Aquioux) */ [SWF(width = "465", height = "465", frameRate = "30", backgroundColor = "#FFFFFF")] public class Main extends Sprite { public function Main() { 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); view.commands = model.commands; // 開始 model.start(); } } } import flash.display.BitmapData; import flash.display.GraphicsPathCommand; import flash.display.Stage; import flash.events.Event; import flash.events.EventDispatcher; import flash.filters.ConvolutionFilter; import flash.geom.Matrix; import flash.geom.Point; import flash.geom.Rectangle; import flash.media.Camera; import flash.media.Video; /** * Web Camera の映像にエフェクトをかける(MVC の Model) * エフェクトロジックは effector クラスとして外部で定義する * @author YOSHIDA, Akio (Aquioux) */ class Model extends EventDispatcher { // -------------------------------------------------- // 定数 // -------------------------------------------------- // 描画間隔 private const INTERVAL:uint = 6; // エッジ検出の強さ private const DEPTH:Number = 5.0; // -------------------------------------------------- // View へ渡すデータ(プロパティ) // -------------------------------------------------- public function get commands():Vector.<int> { return _commands; } private var _commands:Vector.<int>; public function get data():Vector.<Number> { return _data; } private var _data:Vector.<Number>; // -------------------------------------------------- // 外部との通信をおこなうメソッド // -------------------------------------------------- /** * 対 View 用メソッド * このメソッドの終了時にイベントを発行するので、View との通信手段となる * @private */ // ConvolutionFilter 用マトリクス private const HORISON_MATRIX:Array = [ -1, 0, 1, -DEPTH, 0, DEPTH, -1, 0, 1 ]; private const VERTICAL_MATRIX:Array = [ -1, -DEPTH, -1, 0, 0, 0, 1, DEPTH, 1 ]; private const HORISON_FILTER:ConvolutionFilter = new ConvolutionFilter(3, 3, HORISON_MATRIX); private const VERTICAL_FILTER:ConvolutionFilter = new ConvolutionFilter(3, 3, VERTICAL_MATRIX); private const ZERO_POINT:Point = new Point(0, 0); private function update():void { bmd.draw(video, matrix); // ウェブカム映像取り込み grayscale.applyEffect(bmd); // グレイスケール適用 cloneBmd = bmd.clone(); // 複製 // ConvolutionFilter 適用 bmd.applyFilter(bmd, rect, ZERO_POINT, HORISON_FILTER); cloneBmd.applyFilter(cloneBmd, rect, ZERO_POINT, VERTICAL_FILTER); // 平滑化適用 smoothing.applyEffect(bmd); smoothing.applyEffect(cloneBmd); // View 用データ data の決定 var cnt:uint = 0; for (var i:int = 0; i < numY; i++) { for (var j:int = 0; j < numX; j++) { var startX:uint = j * INTERVAL; var startY:uint = i * INTERVAL; var colorX:uint = bmd.getPixel(startX, startY); var colorY:uint = cloneBmd.getPixel(startX, startY); var xx:uint = colorX & 0xFF; var yy:uint = colorY & 0xFF; var radian:Number = Math.atan2(yy, xx); var strength:Number = Math.sqrt(xx * xx + yy * yy); var endX:Number = Math.cos(radian) * strength / INTERVAL + startX; var endY:Number = Math.sin(radian) * strength / INTERVAL + startY; _data[cnt * 4] = startX; _data[cnt * 4 + 1] = startY; _data[cnt * 4 + 2] = endX; _data[cnt * 4 + 3] = endY; cnt++; } } 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 bmd:BitmapData; private var cloneBmd:BitmapData; private var matrix:Matrix; private var rect:Rectangle; private var numX:uint; private var numY:uint; 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; bmd = new BitmapData(cameraWidth, cameraHeight, false); cloneBmd = bmd.clone(); matrix = new Matrix( -1, 0, 0, 1, cameraWidth, 0); rect = bmd.rect; numX = cameraWidth / INTERVAL; numY = cameraHeight / INTERVAL; // View 用データの生成 _commands = new Vector.<int>(numX * numY * 2, true); _data = new Vector.<Number>(numX * numY * 4, true); // View 用データ commands の決定 var n:uint = _commands.length / 2; for (var i:int = 0; i < n; i++) { _commands[i * 2] = GraphicsPathCommand.MOVE_TO; _commands[i * 2 + 1] = GraphicsPathCommand.LINE_TO; } // カメラ 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("カメラがありません。"); } } /** * 処理開始 */ private var grayscale:EffectorGrayScale; private var smoothing:EffectorSmoothing; public function start():void { grayscale = new EffectorGrayScale(); smoothing = new EffectorSmoothing(); smoothing.strength = 8; stage.addEventListener(Event.ENTER_FRAME, enterFrameHandler); } /** * イベントハンドラ * @private */ private function enterFrameHandler(event:Event):void { update(); } } import flash.display.Graphics; import flash.display.GraphicsEndFill; import flash.display.GraphicsPath; import flash.display.GraphicsPathCommand; import flash.display.GraphicsSolidFill; import flash.display.GraphicsStroke; import flash.display.IGraphicsData; import flash.display.Sprite; import flash.events.Event; /** * Web Camera のスクリーン(MVC の View) * @author YOSHIDA, Akio (Aquioux) */ class View extends Sprite { /** * コンストラクタ * @param model Model */ private var model:Model; public function View(model:Model) { init(); this.model = model; this.model.addEventListener(Event.CHANGE, changeHandler); } private var graphicsData:Vector.<IGraphicsData>; private var path:GraphicsPath; private function init():void{ var stroke:GraphicsStroke = new GraphicsStroke(); stroke.thickness = 0; stroke.fill = new GraphicsSolidFill(0x000000); var commands:Vector.<int> = new Vector.<int>(); var data:Vector.<Number> = new Vector.<Number>(); path = new GraphicsPath(commands, data); var endfill:GraphicsEndFill = new GraphicsEndFill(); graphicsData = new Vector.<IGraphicsData>(); graphicsData.push(stroke); graphicsData.push(path); graphicsData.push(endfill); } public function set commands(commands:Vector.<int>):void { path.commands = commands; } /** * Model との通信手段 * @param event 発生したイベント */ private function changeHandler(event:Event):void { // Model からデータを受け取り、視覚化 path.data = model.data; var g:Graphics = this.graphics; g.clear(); g.drawGraphicsData(graphicsData); } } 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.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.6, 0.1, 0, 0, 0.3, 0.6, 0.1, 0, 0, 0.3, 0.6, 0.1, 0, 0, 0, 0, 0, 1, 0 ]; 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; } } [WebCam]エッジの方向