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

forked from: ウェブカム・エフェクト forked from: 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 に若干の説明を書きましたので、よろしければご参照ください。

各種フィルタを使ったウェブカム画像の加工
@author YOSHIDA, Akio (Aquioux)
/**
 * Copyright Good ( http://wonderfl.net/user/Good )
 * MIT License ( http://www.opensource.org/licenses/mit-license.php )
 * Downloaded from: http://wonderfl.net/c/hmD3
 */

// forked from Aquioux's ウェブカム・エフェクト forked from: nengafl
// 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 = 20;
            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;
        }
    }