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

Flickr × アハ体験

「Flickr × アハ体験」

 概要
 ・Flickrの画像を使って「アハ体験」のアレ
  (=画像を一部だけゆっくり変化させるやつ)を連続で見せる
  ・変化は色だけ。モノが出てきたり消えたり大きくなったりはしない。
 ・5秒かけて変化し、3秒静止した後に次の画像に移る
 ・画面をクリックしてる間は変化前の画像が見れる
 ・クリックによる成功・失敗判定などはなし
  ・m_BitmapData_Overのαを見れば判定自体は可能なはず

 アルゴリズム概要
 ・輝度による2値化→エッジ検出→エッジで囲まれた部分を変化させる

 既知の問題点
 ・範囲選択処理がわりと手抜き
  ・今は輝度しか考慮していない
  ・できれば「色相」の近さまで考慮してグループ化したいところ
   →追記:色相を少し考慮してみた。以前よりはマシになったがまだまだ。
 ・選択範囲が広すぎることがある
  ・「数ドット」レベルの範囲を選ばないようにはしているが、大きすぎるかは見ていない
  ・選択範囲が大きすぎたり小さすぎる場合はスキップする処理などを入れたいところ
 ・彩度を考慮してないので難しすぎたり簡単すぎたりする
  ・例えば白っぽい色が選ばれると、同じく白っぽい色に変化するので難しい
   ・モノクロ画像だと変化すらしない
  ・原色に近いと赤→青みたいな変化になるので簡単すぎる
/**
 * Copyright o_healer ( http://wonderfl.net/user/o_healer )
 * MIT License ( http://www.opensource.org/licenses/mit-license.php )
 * Downloaded from: http://wonderfl.net/c/iwgQ
 */

// forked from Event's Simple MediaRSS Viewer
/*
 「Flickr × アハ体験」

 概要
 ・Flickrの画像を使って「アハ体験」のアレ
  (=画像を一部だけゆっくり変化させるやつ)を連続で見せる
  ・変化は色だけ。モノが出てきたり消えたり大きくなったりはしない。
 ・5秒かけて変化し、3秒静止した後に次の画像に移る
 ・画面をクリックしてる間は変化前の画像が見れる
 ・クリックによる成功・失敗判定などはなし
  ・m_BitmapData_Overのαを見れば判定自体は可能なはず

 アルゴリズム概要
 ・輝度による2値化→エッジ検出→エッジで囲まれた部分を変化させる

 既知の問題点
 ・範囲選択処理がわりと手抜き
  ・今は輝度しか考慮していない
  ・できれば「色相」の近さまで考慮してグループ化したいところ
   →追記:色相を少し考慮してみた。以前よりはマシになったがまだまだ。
 ・選択範囲が広すぎることがある
  ・「数ドット」レベルの範囲を選ばないようにはしているが、大きすぎるかは見ていない
  ・選択範囲が大きすぎたり小さすぎる場合はスキップする処理などを入れたいところ
 ・彩度を考慮してないので難しすぎたり簡単すぎたりする
  ・例えば白っぽい色が選ばれると、同じく白っぽい色に変化するので難しい
   ・モノクロ画像だと変化すらしない
  ・原色に近いと赤→青みたいな変化になるので簡単すぎる
*/

package {
    import flash.display.*;
    import flash.events.*;
    import flash.filters.*;
    import flash.geom.*;
    import flash.net.*;
    import flash.system.*;
    import flash.text.*;
    import flash.media.*;

    public class GameMain extends Sprite {

        //==Const==

        //元画像
/*
        [Embed(source='bg.jpg')]
         private static var Bitmap_Image: Class;
        static public var m_Bitmap_Image:Bitmap = new Bitmap_Image();
        public var m_BitmapData_Ori:BitmapData = m_Bitmap_Image.bitmapData;
/*/
//        static public const FEED:String = "http://api.flickr.com/services/feeds/photos_public.gne?tags=landscape&format=rss_200";
//        static public const FEED:String = "http://api.flickr.com/services/feeds/photos_public.gne?tags=nature&format=rss_200";
//        static public const FEED:String = "http://api.flickr.com/services/feeds/photos_public.gne?tags=city&format=rss_200";
//        static public const FEED:String = "http://api.flickr.com/services/feeds/photos_public.gne?tags=architecture&format=rss_200";
        static public const FEED:String = "http://api.flickr.com/services/feeds/photos_public.gne?tags=view&format=rss_200";
//        static public const FEED:String = "http://api.flickr.com/services/feeds/photos_public.gne?tags=scenery&format=rss_200";
        static public const MEDIA:Namespace = new Namespace("http://search.yahoo.com/mrss/");

        static public var m_Bitmap_Image_List:Array;
        static public var m_Bitmap_Image_Iter:int = 0;
//*/

        //変更にかかる時間
        static public const CHANGE_TIME:Number = 5.0;

        //変更後、待機する時間
        static public const WAIT_TIME:Number = 3.0;

        //Util
        static public const POS_ZERO:Point = new Point(0,0);


        //==Var==

        //表示用Bitmap
        public var m_BitmapData_View:BitmapData;//実際に表示する画像
        public var m_BitmapData_Base:BitmapData;//変化前の基本画像
        public var m_BitmapData_Over:BitmapData;//それにかぶせる画像

        //計算用Bitmap
        public var m_BitmapData_Edge:BitmapData;//エッジ
        public var m_BitmapData_Edge_R:BitmapData;//エッジ
        public var m_BitmapData_Edge_G:BitmapData;//エッジ
        public var m_BitmapData_Edge_B:BitmapData;//エッジ
        public var m_BitmapData_Calc:BitmapData;//計算用

        //タイマー
        public var m_Timer:Number = 0.0;

        //事前計算用フラグ
        public var m_RecalcFlag:Boolean = true;

        //マウスが押されているか
        public var m_IsMouseDown:Boolean = false;


        //==Function==

        //Init
        public function GameMain():void {
/*
            addEventListener(
                Event.ADDED_TO_STAGE,
                function(e:Event):void{
                    Init();
                }
            );
/*/
            Wonderfl.capture_delay(43);

            //Load
            var ldr:URLLoader = new URLLoader;
            ldr.addEventListener(Event.COMPLETE, function _load(e:Event):void {
                //Load Once
                ldr.removeEventListener(Event.COMPLETE, _load);

                //画像のURLをリスト化
                var img_url_list:Array = XML(ldr.data)..MEDIA::thumbnail.@url.toXMLString().split('\n');

                //画像のロード先の初期化
                m_Bitmap_Image_List = new Array(img_url_list.length);
                for(var i:int = 0; i < m_Bitmap_Image_List.length; i++){
                    m_Bitmap_Image_List[i] = new Bitmap();
                }

                //画像のロードを開始
                   var count:int = 0;
                img_url_list.forEach(function(img_url:*, index:int, arr:Array):void{
                    count++;

                    var ldr_img:Loader = new Loader;
                    ldr_img.load(new URLRequest(img_url.replace ("_s", "")), new LoaderContext(true));//大きい画像のURLに変更
                    ldr_img.contentLoaderInfo.addEventListener(Event.COMPLETE, function(e:Event):void {
                        m_Bitmap_Image_List[index].bitmapData = new BitmapData(ldr_img.content.width, ldr_img.content.height, false, 0x000000);
                        m_Bitmap_Image_List[index].bitmapData.draw(ldr_img.content);

                        //全てのロードが完了したら初期化開始
                        count--;
                        if(count <= 0){
                            Init();
                        }
                    });
                });
            });
            ldr.load(new URLRequest(FEED));
//*/
        }
        public function Init():void{
            const w:int = stage.stageWidth;
            const h:int = stage.stageHeight;

            //View
            {
                m_BitmapData_View = new BitmapData(w, h, false, 0x000000);
                addChild(new Bitmap(m_BitmapData_View));
            }

            //Graphic
            {
                m_BitmapData_Base = new BitmapData(w, h, false, 0x000000);
                m_BitmapData_Over = new BitmapData(w, h, true,  0x00000000);
            }

            //Calc
            {
                m_BitmapData_Edge = new BitmapData(w, h, false, 0x000000);
                m_BitmapData_Edge_R = new BitmapData(w, h, false, 0x000000);
                m_BitmapData_Edge_G = new BitmapData(w, h, false, 0x000000);
                m_BitmapData_Edge_B = new BitmapData(w, h, false, 0x000000);
                m_BitmapData_Calc = new BitmapData(w, h, false, 0x000000);
            }

            //Mouse
            {
                stage.addEventListener(
                    MouseEvent.MOUSE_DOWN,
                    function(event:MouseEvent):void{
                        m_IsMouseDown = true;
                    }
                );
                stage.addEventListener(
                    MouseEvent.MOUSE_UP,
                    function(event:MouseEvent):void{
                        m_IsMouseDown = false;
                    }
                );
            }

            //Update
            addEventListener(Event.ENTER_FRAME, Update);
        }

        //Update
        public function Update(e:Event=null):void{
            var DeltaTime:Number = 1/24.0;

            //Redraw
            {
                //Init
                {
                    m_BitmapData_View.lock();
                }

                //Timer
                {
                    m_Timer += DeltaTime;
                    if(m_Timer >= CHANGE_TIME+WAIT_TIME){
                        m_Timer -= CHANGE_TIME+WAIT_TIME;

                        m_RecalcFlag = true;
/*
/*/
                        m_Bitmap_Image_Iter++;
                        if(m_Bitmap_Image_List.length <= m_Bitmap_Image_Iter){
                            m_Bitmap_Image_Iter = 0;
                        }
//*/
                    }
                }

                //Calc
                {
                    if(m_RecalcFlag){
                        //Base
                        {
/*
                            var bmd:BitmapData = m_BitmapData_Ori;
                            var mtx:Matrix = new Matrix(stage.stageWidth/bmd.width, 0, 0, stage.stageHeight/bmd.height, 0,0);
                            m_BitmapData_Base.draw(bmd, mtx);
/*/
                            var bmd:BitmapData = m_Bitmap_Image_List[m_Bitmap_Image_Iter].bitmapData;
                            var mtx:Matrix = new Matrix(stage.stageWidth/bmd.width, 0, 0, stage.stageHeight/bmd.height, 0,0);
                            m_BitmapData_Base.draw(bmd, mtx);
//*/
                        }

                        //エッジ検出
                        {
/*
                            //基本表示
                            m_BitmapData_Edge.copyPixels(m_BitmapData_Base, m_BitmapData_Base.rect, POS_ZERO);

                            //ボカす
                            const filter_blur:BlurFilter = new BlurFilter(8,8);
                            m_BitmapData_Edge.applyFilter(m_BitmapData_Edge, m_BitmapData_Edge.rect, POS_ZERO, filter_blur);

                            //輝度化
                            const filter_deg:ColorMatrixFilter = new ColorMatrixFilter(
                                [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]
                            );
                            m_BitmapData_Edge.applyFilter(m_BitmapData_Edge, m_BitmapData_Edge.rect, POS_ZERO, filter_deg);

                            //2値化
                            var thr:uint = 0x00;
                            {
                                for(var x:int = 0; x < m_BitmapData_Edge.width; x++){
                                    for(var y:int = 0; y < m_BitmapData_Edge.height; y++){
                                        thr += m_BitmapData_Edge.getPixel(x, y) & 0x0000FF;
                                    }
                                }
                                thr /= m_BitmapData_Edge.width * m_BitmapData_Edge.height;
                            }
                            m_BitmapData_Edge.threshold(m_BitmapData_Edge, m_BitmapData_Edge.rect, POS_ZERO, "<", thr, 0x00000000, 0x0000FF);
                            m_BitmapData_Edge.threshold(m_BitmapData_Edge, m_BitmapData_Edge.rect, POS_ZERO, ">", thr, 0xFFFFFFFF, 0x0000FF);

                            //エッジ検出
                            const filter_conv:ConvolutionFilter = new ConvolutionFilter(
                                3,3,
                                [-1, -1, -1,
                                 -1,  8, -1,
                                 -1, -1, -1]
                            );
                            m_BitmapData_Edge.applyFilter(m_BitmapData_Edge, m_BitmapData_Edge.rect, POS_ZERO, filter_conv);
/*/
                            //RGBを分けてエッジ検出をし、結果を合成して使う(色相によるエッジ検出)

                            //Alias
                            var rect:Rectangle = m_BitmapData_Base.rect;

                            //基本表示
                            m_BitmapData_Edge.copyPixels(m_BitmapData_Base, rect, POS_ZERO);

                            //ボカす
//                            const filter_blur:BlurFilter = new BlurFilter(4,4);
                            const filter_blur:BlurFilter = new BlurFilter(2,2);
                            m_BitmapData_Edge.applyFilter(m_BitmapData_Edge, rect, POS_ZERO, filter_blur);

                            //RGBごとに分けて2値化
                            var thr_r:Number = 0x00;
                            var thr_g:Number = 0x00;
                            var thr_b:Number = 0x00;
                            {
                                for(var x:int = 0; x < m_BitmapData_Base.width; x++){
                                    for(var y:int = 0; y < m_BitmapData_Base.height; y++){
                                        //ピクセルの色
                                        var color:uint = m_BitmapData_Edge.getPixel(x, y);
                                        var r:uint = (color >> 16) & 0xFF;
                                        var g:uint = (color >>  8) & 0xFF;
                                        var b:uint = (color >>  0) & 0xFF;

/*
                                        //減色(輝度的な感じで)
                                        r *= 0.3;
                                        g *= 0.59;
                                        b *= 0.11;
//*/
/*
                                        r /= 16;
                                        g /= 16;
                                        b /= 16;
//*/

/*
                                        //平均色を求める(これより大きいか否かで2値化)
                                        thr_r += r;
                                        thr_g += g;
                                        thr_b += b;
/*/
                                        //閾値計算は輝度でしてみる(平均色だと赤っぽい箇所も分割されるため)
                                        thr_r += (r*0.3) + (g*0.59) + (b*0.11);
                                        thr_g += (r*0.3) + (g*0.59) + (b*0.11);
                                        thr_b += (r*0.3) + (g*0.59) + (b*0.11);
//*/

                                        //色ごとにエッジを検出するため、それぞれにBitmapを用意
                                        m_BitmapData_Edge_R.setPixel(x, y, r << 16);
                                        m_BitmapData_Edge_G.setPixel(x, y, g <<  8);
                                        m_BitmapData_Edge_B.setPixel(x, y, b <<  0);
                                    }
                                }
                                thr_r /= m_BitmapData_Base.width * m_BitmapData_Base.height;
                                thr_g /= m_BitmapData_Base.width * m_BitmapData_Base.height;
                                thr_b /= m_BitmapData_Base.width * m_BitmapData_Base.height;
                            }
                            m_BitmapData_Edge_R.threshold(m_BitmapData_Edge_R, rect, POS_ZERO, "<",  thr_r << 16, 0x00000000, 0xFF0000);
                            m_BitmapData_Edge_R.threshold(m_BitmapData_Edge_R, rect, POS_ZERO, ">=", thr_r << 16, 0xFFFFFFFF, 0xFF0000);
                            m_BitmapData_Edge_G.threshold(m_BitmapData_Edge_G, rect, POS_ZERO, "<",  thr_g <<  8, 0x00000000, 0x00FF00);
                            m_BitmapData_Edge_G.threshold(m_BitmapData_Edge_G, rect, POS_ZERO, ">=", thr_g <<  8, 0xFFFFFFFF, 0x00FF00);
                            m_BitmapData_Edge_B.threshold(m_BitmapData_Edge_B, rect, POS_ZERO, "<",  thr_b <<  0, 0x00000000, 0x0000FF);
                            m_BitmapData_Edge_B.threshold(m_BitmapData_Edge_B, rect, POS_ZERO, ">=", thr_b <<  0, 0xFFFFFFFF, 0x0000FF);

                            //エッジ検出
                            const filter_conv:ConvolutionFilter = new ConvolutionFilter(
                                3,3,
                                [-1, -1, -1,
                                 -1,  8, -1,
                                 -1, -1, -1]
                            );
                            m_BitmapData_Edge_R.applyFilter(m_BitmapData_Edge_R, rect, POS_ZERO, filter_conv);
                            m_BitmapData_Edge_G.applyFilter(m_BitmapData_Edge_G, rect, POS_ZERO, filter_conv);
                            m_BitmapData_Edge_B.applyFilter(m_BitmapData_Edge_B, rect, POS_ZERO, filter_conv);

                            //合成
                            m_BitmapData_Edge.fillRect(rect, 0x000000);
                            m_BitmapData_Edge.draw(m_BitmapData_Edge_R, null, null, BlendMode.ADD);
                            m_BitmapData_Edge.draw(m_BitmapData_Edge_G, null, null, BlendMode.ADD);
                            m_BitmapData_Edge.draw(m_BitmapData_Edge_B, null, null, BlendMode.ADD);
//*/
                        }

                        //グループのサンプリング
                        {
                            //エッジをコピー
                            m_BitmapData_Calc.copyPixels(m_BitmapData_Edge, m_BitmapData_Edge.rect, POS_ZERO);

                            //小さいグループはつぶれるようにする
/*
                            const filter_big:ConvolutionFilter = new ConvolutionFilter(
                                3,3,
                                [1, 1, 1,
                                 1, 1, 1,
                                 1, 1, 1]
                            );
/*/
                            const filter_big:BlurFilter = new BlurFilter(8,8);
//*/
                            m_BitmapData_Calc.applyFilter(m_BitmapData_Calc, m_BitmapData_Calc.rect, POS_ZERO, filter_big);

                            //ランダムにピクセルを見て、変更するグループを見つける
                            var TrgX:int = 0;
                            var TrgY:int = 0;
                            for(var i:int = 0; i < 100; i++){
                                TrgX = Math.random() * m_BitmapData_Calc.width;
                                TrgY = Math.random() * m_BitmapData_Calc.height;

                                if(m_BitmapData_Calc.getPixel(TrgX, TrgY) == 0x000000){//エッジ部分でないなら
                                    break;//採用
                                }
                            }
                            if(i >= 100){//見つからなかった
                                m_Timer = CHANGE_TIME+WAIT_TIME;//次にいってもらう
                            }

                            //再びエッジをコピー
                            m_BitmapData_Calc.copyPixels(m_BitmapData_Edge, m_BitmapData_Edge.rect, POS_ZERO);

                            //グループを白で塗りつぶす
                            m_BitmapData_Calc.floodFill(TrgX, TrgY, 0xFFFFFF);

                            //エッジ部分を削除
                            m_BitmapData_Calc.draw(m_BitmapData_Edge, null, null, BlendMode.SUBTRACT);

                            //グループの縁を少しぼかす
//                            const filter_b:BlurFilter = new BlurFilter(2,2);
//                            m_BitmapData_Calc.applyFilter(m_BitmapData_Calc, m_BitmapData_Calc.rect, POS_ZERO, filter_b);
                        }

                        //Over
                        {//かぶせて変化させる画像の作成
                            //まずは元の画像をコピー
                            m_BitmapData_Over.copyPixels(m_BitmapData_Base, m_BitmapData_Base.rect, POS_ZERO);
                            //m_BitmapData_Over.draw(m_BitmapData_Base);

                            //αの部分をいじって、見つけたグループだけ表示する
                            m_BitmapData_Over.copyChannel(m_BitmapData_Calc, m_BitmapData_Calc.rect, POS_ZERO, BitmapDataChannel.RED, BitmapDataChannel.ALPHA);

                            //色相を変更
                            const filter_hue:ColorMatrixFilter = new ColorMatrixFilter(
                                [0, 1, 0, 0, 0,
                                 0, 0, 1, 0, 0,
                                 1, 0, 0, 0, 0,
                                 0, 0, 0, 1, 0]
                            );
                            m_BitmapData_Over.applyFilter(m_BitmapData_Over, m_BitmapData_Over.rect, POS_ZERO, filter_hue);
                        }
                    }

                    m_RecalcFlag = false;
                }

                //Draw
                {
                    //Base
                    m_BitmapData_View.draw(m_BitmapData_Base);

                    //Over
                    var ratio:Number = Math.min(m_Timer / CHANGE_TIME, 1.0);
                    if(m_IsMouseDown){ratio = 0.0;}//マウスが押されていたら以前の画像を表示する
                    var ct:ColorTransform = new ColorTransform(1,1,1,ratio);
                    m_BitmapData_View.draw(m_BitmapData_Over, null, ct);
                    //m_BitmapData_View.draw(m_BitmapData_Edge, null, ct);//debug:エッジの可視化
                    //m_BitmapData_View.draw(m_BitmapData_Calc, null, ct);//debug:選択範囲の可視化
                }

                //Fin
                {
                    m_BitmapData_View.unlock();
                }
            }
        }
    }
}