forked from: [WebCam]ジャギ男

by tjoen forked from [WebCam]ジャギ男 (diff: 1)
♥0 | Line 322 | Modified 2015-01-17 05:05:18 | MIT License
play

ActionScript3 source code

/**
 * Copyright tjoen ( http://wonderfl.net/user/tjoen )
 * MIT License ( http://www.opensource.org/licenses/mit-license.php )
 * Downloaded from: http://wonderfl.net/c/7Sd4
 */

// forked from Aquioux's [WebCam]ジャギ男
package {
    import flash.display.Sprite;
    import net.hires.debug.Stats;
    [SWF(width = "465", height = "465", frameRate = "30", backgroundColor = "#000000")]
    /**
     * [WebCam]ジャギ男
     * 参考:http://wonderfl.net/c/sJ7a
     * 説明:http://aquioux.blog48.fc2.com/blog-entry-706.html
     * @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);
            
            //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.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 grayscale_:GrayScale;    // グレイスケール
        private var nega_:Negative;            // 反転
        private var binarize_:Binarize;        // 二値
        private var tone_:HalftoneMono;        // ハーフトーン
        

        // ---------- パブリックメソッド ----------
        //
        /**
         * コンストラクタ
         * @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, false, 0x000000);
            
            // 鏡像になるよう、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("カメラがありません。");
            }
            
            // エフェクタ
            grayscale_ = new GrayScale();    // グレイスケール
            nega_      = new Negative();    // 反転
            binarize_  = new Binarize();    // 二値
            binarize_.auto = true;
            tone_      = new HalftoneMono();// ハーフトーン
            tone_.size = 15;
        }
        
        
        // ---------- ローカルメソッド ----------
        //
        // 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_);
            
            // エフェクト適用
            grayscale_.applyEffect(_data);    // グレイスケール
            nega_.applyEffect(_data);        // 反転
            binarize_.applyEffect(_data);    // 二値
            tone_.applyEffect(_data);        // ハーフトーン

            dispatchEvent(new Event(Event.CHANGE));
        }
    }


    import flash.display.Bitmap;
    import flash.display.BitmapData;
    import flash.events.Event;
    import flash.filters.BitmapFilterQuality;
    import flash.filters.BlurFilter;
    import flash.filters.DropShadowFilter;
    /**
     * 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);
            
            filters = [new BlurFilter(2, 2, BitmapFilterQuality.HIGH)];
        }
        

        // ---------- ローカルメソッド ----------
        //
        // Model から Event.CHANGE が発行されたときの処理
        private function changeHandler(e:Event):void {
            bitmapData = _model.data;
        }
    }


    import flash.display.BitmapData;
    import flash.filters.ColorMatrixFilter;
    import flash.geom.Point;
    /**
     * 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 GrayScale implements IEffector {
        // ---------- ローカルプロパティ ----------
        //
        private const 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,   0,    0,    1, 0
        ];
        private const FILTER:ColorMatrixFilter = new ColorMatrixFilter(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;
        }
    }


    import flash.display.BitmapData;
    import flash.filters.ConvolutionFilter;
    import flash.geom.Point;
    /**
     * ConvolutionFilter による BitmapData の色反転
     * 参考:http://www40.atwiki.jp/spellbound/pages/231.html
     * @author YOSHIDA, Akio (Aquioux)
     */
    class Negative implements IEffector {
        // ---------- ローカルプロパティ ----------
        //
        private const FILTER:ConvolutionFilter = new ConvolutionFilter(1, 1, [ -1], 1, 255);
        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;
        }
    }


    import flash.display.BitmapData;
    import flash.geom.Point;
    import flash.geom.Rectangle;
    /**
     * 二値化
     * 閾値より大きな値のピクセルは白、それ以外は黒に置き換える
     * @author YOSHIDA, Akio (Aquioux)
     */
    class Binarize implements IEffector {
        // ---------- パブリックプロパティ ----------
        //
        // 閾値
        public function get threshold():int { return _threshold; }
        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
        
        public function get auto():Boolean { return _auto; }
        public function set auto(value:Boolean):void { _auto = value; }
        private var _auto:Boolean = false;


        // ---------- ローカルプロパティ ----------
        //
        private const ZERO_POINT:Point = EffectorUtils.ZERO_POINT;

        
        // ---------- パブリックメソッド ----------
        //
        /*
         * 効果適用
         * @param    value    効果対象 BitmapData
         */
        public function applyEffect(value:BitmapData):BitmapData {
            var bufferBmd:BitmapData = new BitmapData(value.width, value.height);
            var rect:Rectangle = bufferBmd.rect;
            bufferBmd = value.clone();

            if (_auto) {
                var hist:Vector.<Vector.<Number>> = value.histogram();
                var numOfPixel:uint = rect.width * rect.height;
                _threshold = calcThresholdRough(hist[2], numOfPixel);
            }
            
            value.fillRect(rect, 0xFF000000);
            value.threshold(bufferBmd, rect, ZERO_POINT, ">", _threshold, 0xFFFFFFFF, 0x000000FF, false);
            return value;
        }

        
        // ---------- ローカルメソッド ----------
        //
        // 閾値の判別分析
        private function calcThresholdRough(hist:Vector.<Number>, numOfPixel:uint):uint {
            var total:uint;
            for (var i:int = 0; i < 256; i++) {
                total += hist[i] * i;
            }
            return total / numOfPixel >> 0;
        }
        
        // 閾値の判別分析
        private function calcThresholdStrict(hist:Vector.<Number>, numOfPixel:uint):uint {
            var maxSeparability:Number  = 0;    // 最大分離値を待避させる変数
            var maxDegree:uint = 0;                // そのときの階調を待避させる変数
            for (var i:int = 1; i < 255; i++) {
                // 1~254 を閾値としたときの分離度を計算し、最大値を待避する
                var Separability:Number = calcSeparability(i, hist, numOfPixel);
                if (Separability > maxSeparability) {
                    maxSeparability = Separability;
                    maxDegree       = i;
                }
            }
            return maxDegree;
        }
        // 分離度の計算
        private function calcSeparability(threshold:uint, hist:Vector.<Number>, numOfPixel:uint):Number {
            var i:uint;                                // ループカウンター
            var num1:uint = 0, num2:uint = 0;        // 各領域の画素数
            var con1:Number = 0, con2:Number = 0;    // 各領域の濃度(濃度平均値)
            var con:Number = 0;                        // 濃度中間値
            var dis1:Number, dis2:Number;            // 分散計算用
            var within:Number = 0;                    // クラス内分散値
            var between:Number = 0;                    // クラス間分散値
            
            // 二つの領域の画素数と濃度を計算
            for (i = 0; i < threshold; i++) {
                num1 += hist[i];
                con1 += i * hist[i];
            }
            for (i = threshold; i < 256; i++) {
                num2 += hist[i];
                con2 += i * hist[i];
            }
            con = (con1 + con2) / numOfPixel;    // 濃度中間値
            con1 /= num1;    // 領域1の濃度平均値
            con2 /= num2;    // 領域2の濃度平均値

            if (num1 == 0 || num2 == 0) return 0;
            
            // 分散を計算
            // クラス内分散
            for (i = 0; i < threshold; i++) {
                dis1 = i - con1;
                within += dis1 * dis1 * hist[i];
            }
            for (i = threshold; i < 256; i++) {
                dis2 = i - con2;
                within += dis2 * dis2 * hist[i];
            }
            within /= numOfPixel;
            // クラス間分散
            for (i = 0; i < threshold; i++) {
                dis1 = con - con1;
                between += dis1 * dis1 * hist[i];
            }
            for (i = threshold; i < 256; i++) {
                dis2 = con - con2;
                between += dis2 * dis2 * hist[i];
            }
            between /= numOfPixel;
            
            return between / within;
        }
    }


    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.geom.Point;
    import flash.geom.Rectangle;
    /**
     * ハーフトーン(モノクロ)
     * @author YOSHIDA, Akio (Aquioux)
     */
    class HalftoneMono implements IEffector {
        // ---------- パブリックプロパティ ----------
        //
        public function set size(value:uint):void {
            _size = value;

            wNum_ = Math.ceil(width_  / _size);    // width / _size が割り切れない場合は切り上げ
            hNum_ = Math.ceil(height_ / _size);    // height / _size が割り切れない場合は切り上げ
            
            if (blockBmd_) blockBmd_.dispose();
            blockBmd_ = new BitmapData(_size, _size);
            
            blockRect_.width = blockRect_.height = _size;
            
            blockPixels_ = _size * _size;
        }
        private var _size:uint = 16;                // モザイクの1辺の長さ(縦横同じ長さとする)
        

        // ---------- ローカルプロパティ ----------
        //
        private var width_:Number;            // 対象幅
        private var height_:Number;            // 対象高

        private var wNum_:uint;                // 横方向のモザイク数
        private var hNum_:uint;                // 縦方向のモザイク数
        
        private var blockBmd_:BitmapData;    // モザイク1つ分の BitmapData
        private var blockRect_:Rectangle;    // モザイク1つ分の Rectangle(モザイク領域読込時とトーン描画時で使い回す)
        private var blockPixels_:uint;        // モザイク1つ分の pixel 数
        
        private const BASE_COLOR:uint = 0xFFFFFFFF;
        private const DOT_COLOR:uint  = 0xFF000000;
        
        private const ZERO_POINT:Point = EffectorUtils.ZERO_POINT;
        private const GET_BRIGHTNESS:Function = EffectorUtils.getSumOfBrightness1;

        
        // ---------- パブリックメソッド ----------
        //
        /*
         * コンストラクタ
         */
        public function HalftoneMono(width:Number = 0.0, height:Number = 0.0) {
            width_  = width;    // 対象幅を待避
            height_ = height;    // 対象高を待避
            blockRect_ = new Rectangle();
            if (width_ != 0.0 && height_ != 0.0) init(width, height);
        }
        
        /*
         * 効果適用
         * @param    value    効果対象 BitmapData
         */
        public function applyEffect(value:BitmapData):BitmapData {
            if (width_ != value.width || height_ != value.height) init(value.width, value.height);

            var saveBmd:BitmapData = value.clone();    // 入力画像を待避
            value.fillRect(value.rect, BASE_COLOR);    // 塗りつぶす

            // モザイクブロックごとの走査
            value.lock();
            for (var i:int = 0; i < hNum_; i++) {
                for (var j:int = 0; j < wNum_; j++) {
                    // モザイク領域読込用としての blockRect_ 設定
                    blockRect_.x = j * _size;
                    blockRect_.y = i * _size;
                    blockRect_.width  = blockRect_.height = _size;
                    // モザイク領域を全体から切り取る
                    blockBmd_.copyPixels(saveBmd, blockRect_, ZERO_POINT);
                    // モザイク領域の対象カラーチャンネルの平均輝度取得
                    var brightness:uint = GET_BRIGHTNESS(blockBmd_.histogram()[0]) / blockPixels_;
                    // 描画サイズの設定
                    var size:Number = _size * (brightness / 255);
                    if (size >= 2) {
                        // 描画開始位置オフセット計算
                        var offset:Number = (_size - size) / 2;
                        // トーン描画用としての blockRect_ 設定
                        blockRect_.x += offset;
                        blockRect_.y += offset;
                        blockRect_.width = blockRect_.height = size;
                        // 描画
                        value.fillRect(blockRect_, DOT_COLOR);
                    }
                }
            }
            value.unlock();
            return value;
        }
        

        // ---------- ローカルメソッド ----------
        //
        // 初期化
        private function init(width:Number, height:Number):void {
            width_  = width;    // 対象幅を待避
            height_ = height;    // 対象高を待避
            size    = _size;    // _size の設定
        }
    }