// forked from nengafl's nengafl // other license, write as code comments // 求む!ウェブカム作品 // 新しいエフェクタを作って、クールなウェブカム作品で萌えませんか? // // 書き換える場所はふたつです // 1. ドキュメントクラス(Nengafl)のコンストラクタの最初の部分 // chain.addEffector() メソッドで新しいエフェクタ・インスタンスを登録する // 2. 独自 EffectorFormat の生成 // 新しい Effector クラスは EffectorFormat を書き換える // // 注意:EffectorFormat は AbstractEffector の後に記述してあります。 // Nengafl の直後じゃなくてスイマセン(そうしないと参照問題でエラーになるので)。 // // http://aquioux.blog48.fc2.com/blog-entry-670.html に若干の説明を書きましたので、よろしければご参照ください。 // package { import flash.display.Sprite; import flash.events.Event; /** * 各種フィルタを使ったウェブカム画像の加工 * @author YOSHIDA, Akio (Aquioux) */ [SWF(width = "465", height = "465", frameRate = "30", backgroundColor = "#000000")] public class Nengafl extends Sprite { public function Nengafl() { // エフェクタ連鎖 var chain:ChainEffectors = new ChainEffectors(); // ===== 書き換える場所 ここから ===== // ここで各エフェクタ・インスタンスを生成 var smooth:EffectorSmoothing = new EffectorSmoothing(); smooth.strength = 16; var posterize:EffectorPosterization = new EffectorPosterization(); posterize.degree = 8; var sharp:EffectorSharp = new EffectorSharp(); sharp.strength = 5; //var mosaic:EffectorPixelization = new EffectorPixelization(465, 465); //mosaic.size = 31; // addEffector で登録した順番で映像は加工される // 同じフィルタを使っても、順番を入れ替えるだけでずいぶん変わる chain.addEffector(smooth); chain.addEffector(posterize); //chain.addEffector(mosaic); chain.addEffector(sharp); //chain.addEffector(new EffectorNegative()); //chain.addEffector(new EffectorGrayScale()); // ===== 書き換える場所 ここまで ===== // Model を生成 try { var model:Model = new Model(stage); } catch (err:Error) { trace(err.message); return; } // View を生成 var view:View = new View(model); addChild(view); // 開始 model.chain = chain; model.start(); Wonderfl.capture_delay(30); } } } 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; /** * Effector のフォーマット * 必ず AbstractEffector を継承する */ class EffectorFormat extends AbstractEffector { // ===== 書き換える場所 ここから ===== // メンバー変数が必要な場合はここで記述 // すべての Effector 参照 // ===== 書き換える場所 ここまで ===== /* * コンストラクタ */ public function EffectorFormat() { // ===== 書き換える場所 ここから ===== // このクラス生成時に初期化処理が必要な場合はここで記述。なければ不要 // EffectorPosterization, EffectorSharp, EffectorSmoothing 参照 // ===== 書き換える場所 ここまで ===== } /* * エフェクト実行 * @param value 効果をかける BitmapData */ override protected function effect(value:BitmapData):BitmapData { // ===== 書き換える場所 ここから ===== // value.applyFilter(value, value.rect, ZERO_POINT, xxxFilter); // value.applyFilter を使う場合、第3引数までは固定 // すべての Effector 参照 // ===== 書き換える場所 ここまで ===== return value; } } import flash.display.BitmapData; import flash.filters.BitmapFilterQuality; import flash.filters.BlurFilter; /** * BlurFilter による平滑化 * @author YOSHIDA, Akio (Aquioux) */ class EffectorSmoothing extends AbstractEffector { // BlurFilter 用変数(強さ) public function set strength(value:Number):void { blurFilter.blurX = blurFilter.blurY = value; } // BlurFilter 用変数(質) public function set quality(value:int):void { blurFilter.quality = value; } // BlurFilter 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; } } import flash.display.BitmapData; /** * paletteMap による BitmapData の減色 * 参考:「実践画像処理入門」 培風館 内村圭一・上瀧剛 P16 「2.5 濃度値の量子化による減色処理」 * @author YOSHIDA, Akio (Aquioux) */ class EffectorPosterization extends AbstractEffector { /* * 減色の段階(1、および 256 より大きい値以外) */ public function set degree(value:uint):void { value = value < 2 ? 2 : value; value = value > 256 ? 256 : value; 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 の引数となる各色要素用の Array private var rArray:Array = []; private var gArray:Array = []; private var bArray:Array = []; /* * コンストラクタ */ public function EffectorPosterization() { degree = 8; // degree のデフォルト } /* * 減色実行 * @param value 効果をかける BitmapData */ override protected function effect(value:BitmapData):BitmapData { value.paletteMap(value, value.rect, ZERO_POINT, rArray, gArray, bArray); return value; } } import flash.display.BitmapData; import flash.geom.Matrix; import flash.geom.Rectangle; /** * モザイク(原画像のブロック内部の画素値の平均値による) * 「OpenGL+GLSLによる画像処理プログラミング」 工学社 酒井幸市 P46 「2.1 モザイク処理」 * @author YOSHIDA, Akio (Aquioux) */ class EffectorPixelization extends AbstractEffector { public function set size(value:uint):void { _size = value; wNum = Math.ceil(width / _size); // width / _size が割り切れない場合は切り上げ hNum = Math.ceil(height / _size); // height / _size が割り切れない場合は切り上げ pixelizeRect.width = _size; pixelizeRect.height = _size; if (pixelizeBitmapData) pixelizeBitmapData.dispose(); pixelizeBitmapData = new BitmapData(_size, _size); } private var _size:uint; // モザイクの1辺の長さ(縦横同じ長さとする) private var width:Number; // 対象幅 private var height:Number; // 対象高 private var wNum:uint; // 横方向のモザイク数 private var hNum:uint; // 縦方向のモザイク数 private var pixelizeRect:Rectangle = new Rectangle(); // モザイク1つ分の Rectangle private var pixelizeBitmapData:BitmapData; // モザイク1つ分の BitmapData private var matrix:Matrix = new Matrix(); // BitmapData.draw 用 public function EffectorPixelization(width:Number, height:Number) { this.width = width; // 対象幅を待避 this.height = height; // 対象高を待避 size = 8; // _size のデフォルト(setter メソッドを発動させる) } override protected function effect(value:BitmapData):BitmapData { // モザイクブロックごとの走査 for (var i:int = 0; i < hNum; i++) { for (var j:int = 0; j < wNum; j++) { pixelizeRect.x = j * _size; pixelizeRect.y = i * _size; matrix.tx = -pixelizeRect.x; matrix.ty = -pixelizeRect.y; pixelizeBitmapData.draw(value, matrix); // モザイクの1辺の長さが 16 より大きい場合はヒストグラムを使って、そうでない場合は getPixel を使って平均色を求める // 16 ^ 2 = 256 // 走査ループ回数がヒストグラムでのそれの未満なら getPixel に切り替える var color:uint = (_size > 16) ? getAverageColorHistogram(pixelizeBitmapData) : getAverageColor1GetPixel(pixelizeBitmapData); value.fillRect(pixelizeRect, color); } } return value; } // 平均色を求める(モザイク1ブロック内のヒストグラムから計算) private function getAverageColorHistogram(bitmapData:BitmapData):uint { var hist:Vector.<Vector.<Number>> = bitmapData.histogram(); 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]; } var numOfPixel:uint = _size * _size; var r:uint = rSum / numOfPixel >> 0; var g:uint = gSum / numOfPixel >> 0; var b:uint = bSum / numOfPixel >> 0; return 0xFF << 24 | r << 16 | g << 8 | b; } // 平均色を求める(モザイク1ブロック内のピクセルを走査) private function getAverageColor1GetPixel(bitmapData:BitmapData):uint { var rSum:uint = 0; var gSum:uint = 0; var bSum:uint = 0; for (var i:int = 0; i < _size; i++) { for (var j:int = 0; j < _size; j++) { var color:uint = bitmapData.getPixel(j, i); var r:uint = (color >> 16) & 0xFF; var g:uint = (color >> 8) & 0xFF; var b:uint = color & 0xFF; rSum += r; gSum += g; bSum += b; } } var numOfPixel:uint = _size * _size; r = rSum / numOfPixel >> 0; g = gSum / numOfPixel >> 0; b = bSum / numOfPixel >> 0; return 0xFF << 24 | r << 16 | g << 8 | b; } } import flash.display.BitmapData; import flash.filters.ConvolutionFilter; /** * 鮮鋭化 * 参考:「実践画像処理入門」 培風館 内村圭一・上瀧剛 P48 「6.2 高域強調フィルタ」 * 参考:「OpenGL+GLSLによる画像処理プログラミング」 工学社 酒井幸市 P111 「5.2 鮮鋭化フィルタ」 * @author YOSHIDA, Akio (Aquioux) */ class EffectorSharp extends AbstractEffector { /* * 鮮鋭化の強さ */ public function set strength(value:Number):void { sharpMatrix = [ 0, -value, 0, -value, 1 + 4 * value, -value, 0, -value, 0 ]; } // ConvolutionFilter 用マトリクス private var sharpMatrix:Array = []; /* * コンストラクタ */ public function EffectorSharp() { strength = 1.0; // strength のデフォルト } /* * 鮮鋭化実行 * @param value 効果をかける BitmapData */ override protected function effect(value:BitmapData):BitmapData { value.applyFilter(value, value.rect, ZERO_POINT, new ConvolutionFilter(3, 3, sharpMatrix)); 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 ]; // 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.ConvolutionFilter; /** * ConvolutionFilter による BitmapData の色反転 * 参考:http://www40.atwiki.jp/spellbound/pages/231.html * @author YOSHIDA, Akio (Aquioux) */ class EffectorNegative extends AbstractEffector { // ConvolutionFilter 用マトリクス private const NEGATIVE_MATRIX:Array = [-1]; // ConvolutionFilter private const NEGA_FILTER:ConvolutionFilter = new ConvolutionFilter(1, 1, NEGATIVE_MATRIX, 1, 255); /* * 色反転実行 * @param value 効果をかける BitmapData */ override protected function effect(value:BitmapData):BitmapData { value.applyFilter(value, value.rect, ZERO_POINT, NEGA_FILTER); return value; } } 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.display.Stage; import flash.events.Event; import flash.events.EventDispatcher; 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); _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; 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); // カメラ 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; } } ウェブカム・エフェクト forked from: nengafl