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

遠くから見ると別画像(Hybrid Image)

遠くから見ると別画像(Hybrid Image)

 概要
 ・Flickrから画像を取得して、
  「近くで見ると左の画像」
  「遠くで見ると右の画像」
  となるような画像を作成する
 ・クリックで次の画像に移る
  ・前回の「遠くから見た時用の画像」が
   次回の「近くから見た時用の画像」として使われる

 アルゴリズム解説
 ・空間周波数やら高速フーリエ変換がどーたらこーたら
 ・まぁ要はそういう変換をすると「近くから見たときに目立つ部分」と
  「遠くから〃」を分離できるので、それを混ぜたというお話
 ・詳しくは「Hybrid Image」や「Mr Angry and Mrs Calm」で検索!

 備考
 ・高速フーリエ変換(FFT)はwellflatさんの作成したクラスを使わせてもらっています
  ・http://rest-term.com/archives/1289/
/**
 * 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/srIg
 */

// forked from Event's Simple MediaRSS Viewer
/*
 遠くから見ると別画像(Hybrid Image)

 概要
 ・Flickrから画像を取得して、
  「近くで見ると左の画像」
  「遠くで見ると右の画像」
  となるような画像を作成する
 ・クリックで次の画像に移る
  ・前回の「遠くから見た時用の画像」が
   次回の「近くから見た時用の画像」として使われる

 アルゴリズム解説
 ・空間周波数やら高速フーリエ変換がどーたらこーたら
 ・まぁ要はそういう変換をすると「近くから見たときに目立つ部分」と
  「遠くから〃」を分離できるので、それを混ぜたというお話
 ・詳しくは「Hybrid Image」や「Mr Angry and Mrs Calm」で検索!

 備考
 ・高速フーリエ変換(FFT)はwellflatさんの作成したクラスを使わせてもらっています
  ・http://rest-term.com/archives/1289/
*/


package {
    import __AS3__.vec.Vector;
    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 Test extends Sprite {

        //==Const==

        //元画像
//        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=architecture&format=rss_200";//○
//        static public const FEED:String = "http://api.flickr.com/services/feeds/photos_public.gne?tags=building&format=rss_200";//○
//        static public const FEED:String = "http://api.flickr.com/services/feeds/photos_public.gne?tags=stainedglass&format=rss_200";//○
//        static public const FEED:String = "http://api.flickr.com/services/feeds/photos_public.gne?tags=monochrome&format=rss_200";//○
//        static public const FEED:String = "http://api.flickr.com/services/feeds/photos_public.gne?tags=%E9%A2%A8%E6%99%AF&format=rss_200";//○:風景
        static public const FEED:String = "http://api.flickr.com/services/feeds/photos_public.gne?tags=church&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 var m_Bitmap_Vector_List:Array;

        //サイズ(FFTの関係で2の冪乗のみ)
        static public const IMG_SIZE:int = 128;


        //==Var==

        //表示用Bitmap
        public var m_BitmapData_View:BitmapData;//実際に表示する画像
        public var m_Bitmap_Near:Bitmap = new Bitmap();
        public var m_Bitmap_Far:Bitmap = new Bitmap();

        //フーリエ変換用
        public var m_FFT:FFT = new FFT(IMG_SIZE);
        public var m_Re_R_Near:Vector.<Number> = new Vector.<Number>(IMG_SIZE*IMG_SIZE, true);
        public var m_Re_G_Near:Vector.<Number> = new Vector.<Number>(IMG_SIZE*IMG_SIZE, true);
        public var m_Re_B_Near:Vector.<Number> = new Vector.<Number>(IMG_SIZE*IMG_SIZE, true);
        public var m_Im_R_Near:Vector.<Number> = new Vector.<Number>(IMG_SIZE*IMG_SIZE, true);
        public var m_Im_G_Near:Vector.<Number> = new Vector.<Number>(IMG_SIZE*IMG_SIZE, true);
        public var m_Im_B_Near:Vector.<Number> = new Vector.<Number>(IMG_SIZE*IMG_SIZE, true);
        public var m_Re_R_Far:Vector.<Number> = new Vector.<Number>(IMG_SIZE*IMG_SIZE, true);
        public var m_Re_G_Far:Vector.<Number> = new Vector.<Number>(IMG_SIZE*IMG_SIZE, true);
        public var m_Re_B_Far:Vector.<Number> = new Vector.<Number>(IMG_SIZE*IMG_SIZE, true);
        public var m_Im_R_Far:Vector.<Number> = new Vector.<Number>(IMG_SIZE*IMG_SIZE, true);
        public var m_Im_G_Far:Vector.<Number> = new Vector.<Number>(IMG_SIZE*IMG_SIZE, true);
        public var m_Im_B_Far:Vector.<Number> = new Vector.<Number>(IMG_SIZE*IMG_SIZE, true);
        public var m_ResultVec:Vector.<uint> = new Vector.<uint>(IMG_SIZE*IMG_SIZE, true);


        //==Function==

        //Init
        public function Test():void {
            //Capture Timing
            Wonderfl.capture_delay(20);

            //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);
                m_Bitmap_Vector_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(IMG_SIZE, IMG_SIZE, false, 0x000000);
                        var mtx_scl:Matrix = new Matrix(IMG_SIZE/ldr_img.content.width, 0, 0, IMG_SIZE/ldr_img.content.height, 0,0);
                        m_Bitmap_Image_List[index].bitmapData.draw(ldr_img.content, mtx_scl);

                        m_Bitmap_Vector_List[index] = m_Bitmap_Image_List[index].bitmapData.getVector(m_Bitmap_Image_List[index].bitmapData.rect);

                        //全てのロードが完了したら初期化開始
                        count--;
                        if(count <= 0){
                            Init();
                        }
                    });
                });
            });
            ldr.load(new URLRequest(FEED));
        }

        public function Init():void{
            //View
            {
                //Create & Regist
                m_BitmapData_View = new BitmapData(IMG_SIZE, IMG_SIZE, false, 0x000000);
                var bmp_view:Bitmap = new Bitmap(m_BitmapData_View);
                addChild(bmp_view);

                //Centering
                bmp_view.x = stage.stageWidth/2  - IMG_SIZE/2;
                bmp_view.y = stage.stageHeight/2 - IMG_SIZE/2;
            }

            //
            {
                m_Bitmap_Near.x = 0;
                m_Bitmap_Near.y = bmp_view.y;
                addChild(m_Bitmap_Near);

                m_Bitmap_Far.x = stage.stageWidth  - IMG_SIZE;
                m_Bitmap_Far.y = bmp_view.y;
                addChild(m_Bitmap_Far);
            }

            //最初の表示画像を作成
            {
                Redraw();
            }

            //Mouse
            {
                stage.addEventListener(
                    MouseEvent.MOUSE_DOWN,
                    function(event:MouseEvent):void{
                        //++
                        {
                            m_Bitmap_Image_Iter++;
                            if(m_Bitmap_Image_List.length <= m_Bitmap_Image_Iter){
                                m_Bitmap_Image_Iter = 0;
                            }
                        }

                        //Redraw
                        {
                            Redraw();
                        }
                    }
                );
            }
        }

        //Redraw
        public function Redraw():void{
            //Index
            var index_near:int = m_Bitmap_Image_Iter;
            var index_far:int  = m_Bitmap_Image_Iter+1; if(m_Bitmap_Image_List.length <= index_far){index_far = 0;}

            //BitmapData(Ori)
            var bmd_near:BitmapData = m_Bitmap_Image_List[index_near].bitmapData;
            var bmd_far:BitmapData  = m_Bitmap_Image_List[index_far].bitmapData;
            m_Bitmap_Near.bitmapData = bmd_near;//ついでに表示
            m_Bitmap_Far.bitmapData  = bmd_far;

            //getPixelは遅いので、一度配列として取得
/*
            var vec_near:Vector.<uint> = bmd_near.getVector(bmd_near.rect);//!!事前に取得しておけるはず
            var vec_far:Vector.<uint>  = bmd_far.getVector(bmd_far.rect);
/*/
            var vec_near:Vector.<uint> = m_Bitmap_Vector_List[index_near];//事前取得にしても速度はあんまり変わらないっぽい
            var vec_far:Vector.<uint>  = m_Bitmap_Vector_List[index_far];
//*/

            //FFTにかけるため、RGBの値に分けつつ初期化
            var num:int = vec_near.length;
            for(var i:int = 0; i < num; i++){
                m_Re_R_Near[i] = (vec_near[i] >> 16) & 0xFF;
                m_Re_G_Near[i] = (vec_near[i] >>  8) & 0xFF;
                m_Re_B_Near[i] = (vec_near[i] >>  0) & 0xFF;

                m_Re_R_Far[i] = (vec_far[i] >> 16) & 0xFF;//Farに関しては「前回のNear」が流用したいところだが
                m_Re_G_Far[i] = (vec_far[i] >>  8) & 0xFF;
                m_Re_B_Far[i] = (vec_far[i] >>  0) & 0xFF;

                m_Im_R_Near[i] = 
                m_Im_G_Near[i] = 
                m_Im_B_Near[i] = 
                m_Im_R_Far[i] = 
                m_Im_G_Far[i] = 
                m_Im_B_Far[i] = 0x00;
            }

            // 二次元離散フーリエ変換
            m_FFT.fft2d(m_Re_R_Near, m_Im_R_Near);
            m_FFT.fft2d(m_Re_G_Near, m_Im_G_Near);
            m_FFT.fft2d(m_Re_B_Near, m_Im_B_Near);
            m_FFT.fft2d(m_Re_R_Far, m_Im_R_Far);
            m_FFT.fft2d(m_Re_G_Far, m_Im_G_Far);
            m_FFT.fft2d(m_Re_B_Far, m_Im_B_Far);

            //Nearの画像にFarの画像を一部組み込む
            const Threshold:Number = 10;//小さいほどNearが強く、大きいほどFarが強く表示される
            const center:Number = IMG_SIZE/2;
            for(var y:int = center-Threshold; y < center+Threshold; y++){
                var GapY:Number = y - center;
                for(var x:int = center-Threshold; x < center+Threshold; x++){
                    var GapX:Number = x - center;
                    var distance:Number = Math.sqrt(GapX*GapX + GapY*GapY);

                    i = x+y*IMG_SIZE;
                    if(distance < Threshold){//この混ぜ方で良いのかはよくわからない
                        m_Re_R_Near[i] = m_Re_R_Far[i];
                        m_Re_G_Near[i] = m_Re_G_Far[i];
                        m_Re_B_Near[i] = m_Re_B_Far[i];
                        m_Im_R_Near[i] = m_Im_R_Far[i];
                        m_Im_G_Near[i] = m_Im_G_Far[i];
                        m_Im_B_Near[i] = m_Im_B_Far[i];
                    }
                }
            }

            //逆変換
            m_FFT.fft2d(m_Re_R_Near, m_Im_R_Near, true);
            m_FFT.fft2d(m_Re_G_Near, m_Im_G_Near, true);
            m_FFT.fft2d(m_Re_B_Near, m_Im_B_Near, true);

            //画像化
/*
            for(var y:int=0; y < IMG_SIZE; y++) {
                for(var x:int=0; x < IMG_SIZE; x++) {
                    var r:uint = m_Re_R_Near[x + y*IMG_SIZE]; if(r < 0x00){r = 0x00;} if(r > 0xFF){r = 0xFF;}
                    var g:uint = m_Re_G_Near[x + y*IMG_SIZE]; if(g < 0x00){g = 0x00;} if(g > 0xFF){g = 0xFF;}
                    var b:uint = m_Re_B_Near[x + y*IMG_SIZE]; if(b < 0x00){b = 0x00;} if(b > 0xFF){b = 0xFF;}

                    var color:uint = (r << 16) | (g << 8) | (b << 0);
                    m_BitmapData_View.setPixel(x, y, color);
                }
            }
/*/
            //こっちが早いかと思ったけど、体感的にはたいして変わらない
            for(i = 0; i < IMG_SIZE*IMG_SIZE; i++){
                var r:int = m_Re_R_Near[i]; if(r < 0x00){r = 0x00;} if(r > 0xFF){r = 0xFF;}
                var g:int = m_Re_G_Near[i]; if(g < 0x00){g = 0x00;} if(g > 0xFF){g = 0xFF;}
                var b:int = m_Re_B_Near[i]; if(b < 0x00){b = 0x00;} if(b > 0xFF){b = 0xFF;}
                //vec_near[i] = (r << 16) | (g << 8) | (b << 0);//!!事前取得の場合、いじるのが反映されるので良くない
                m_ResultVec[i] = (r << 16) | (g << 8) | (b << 0);
            }
//            m_BitmapData_View.setVector(m_BitmapData_View.rect, vec_near);
            m_BitmapData_View.setVector(m_BitmapData_View.rect, m_ResultVec);
//*/
        }
    }
}



/*
 FFT(高速フーリエ変換)クラス
 ・author:wellflat
  ・http://rest-term.com/archives/1289/
*/

import __AS3__.vec.Vector;

class FFT{
    private var n:int; // データ数
    private var bitrev:Vector.<int>; // ビット反転テーブル
    private var cstb:Vector.<Number>; // 三角関数テーブル
    public static const HPF:String = "high";
    public static const LPF:String = "low";
    public static const BPF:String = "band"

    public function FFT(n:int) {
        if(n != 0 && (n & (n-1)) == 0) {
            this.n = n;
            this.cstb = new Vector.<Number>(n + (n>>2), true);
            this.bitrev = new Vector.<int>(n, true);
            makeCstb();
            makeBitrev();
        }     
    }
    // 1D-FFT
    public function fft(re:Vector.<Number>, im:Vector.<Number>, inv:Boolean=false):void {
        if(!inv) {
            fftCore(re, im, 1);
        }else {
            fftCore(re, im, -1);
            for(var i:int=0; i<n; i++) { 
                re[i] /= n;
                im[i] /= n;
            }
        }
    }
    // 2D-FFT
    public function fft2d(re:Vector.<Number>, im:Vector.<Number>, inv:Boolean=false):void {
        var tre:Vector.<Number> = new Vector.<Number>(re.length, true);
        var tim:Vector.<Number> = new Vector.<Number>(im.length, true);
        if(inv) cvtSpectrum(re, im);
        // x軸方向のFFT
        for(var y:int=0; y<n; y++) {
            for(var x:int=0; x<n; x++) {
                tre[x] = re[x + y*n];
                tim[x] = im[x + y*n];
            }
            if(!inv) fft(tre, tim);
            else fft(tre, tim, true);
            for(var x:int=0; x<n; x++) {
                re[x + y*n] = tre[x];
                im[x + y*n] = tim[x];
            }
        }
        // y軸方向のFFT
        for(var x:int=0; x<n; x++) {
            for(var y:int=0; y<n; y++) {
                tre[y] = re[x + y*n];
                tim[y] = im[x + y*n];
            }
            if(!inv) fft(tre, tim);
            else fft(tre, tim, true);
            for(var y:int=0; y<n; y++) {
                re[x + y*n] = tre[y];
                im[x + y*n] = tim[y];
            }
        }
        if(!inv) cvtSpectrum(re, im);
    }
    // 空間周波数フィルタ
    public function applyFilter(re:Vector.<Number>, im:Vector.<Number>, rad:uint, type:String, bandWidth:uint = 0):void {
        var r:int = 0; // 半径
        var n2:int = n>>1;
        for(var y:int=-n2; y<n2; y++) {
            for(var x:int=-n2; x<n2; x++) {
                r = Math.sqrt(x*x + y*y);
                if((type == HPF && r<rad) || (type == LPF && r>rad) || (type == BPF && (r<rad || r>(rad + bandWidth)))) {
                        re[x + n2 + (y + n2)*n] = im[x + n2 + (y + n2)*n] = 0;
                }
            }
        }
    }
    private function fftCore(re:Vector.<Number>, im:Vector.<Number>, sign:int):void {
        var h:int, d:int, wr:Number, wi:Number, ik:int, xr:Number, xi:Number, m:int, tmp:Number;
        for(var l:int=0; l<n; l++) { // ビット反転
            m = bitrev[l];
            if(l<m) {
                tmp = re[l];
                re[l] = re[m];
                re[m] = tmp;
                tmp = im[l];
                im[l] = im[m];
                im[m] = tmp;
            }
        }
        for(var k:int=1; k<n; k<<=1) { // バタフライ演算
            h = 0;
            d = n/(k<<1);
            for(var j:int=0; j<k; j++) {
                wr = cstb[h + (n>>2)];
                wi = sign*cstb[h];
                for(var i:int=j; i<n; i+=(k<<1)) {
                    ik = i+k;
                    xr = wr*re[ik] + wi*im[ik]
                    xi = wr*im[ik] - wi*re[ik];
                    re[ik] = re[i] - xr;
                    re[i] += xr;
                    im[ik] = im[i] - xi;
                    im[i] += xi;
                }
                h += d;
            }
        }
    }
    // スペクトル位置並び替え
    public function cvtSpectrum(re:Vector.<Number>, im:Vector.<Number>):void {
        var tmp:Number = 0.0, xn:int = 0, yn:int = 0;
        for(var y:int=0; y<(n>>1); y++) {
            for(var x:int=0; x<(n>>1); x++) {
                xn = x + (n>>1);
                yn = y + (n>>1);
                tmp = re[x + y*n];
                re[x + y*n] = re[xn + yn*n];
                re[xn + yn*n] = tmp;
                tmp = re[x + yn*n];
                re[x + yn*n] = re[xn + y*n];
                re[xn + y*n] = tmp;
                tmp = im[x + y*n];
                im[x + y*n] = im[xn + yn*n];
                im[xn + yn*n] = tmp;
                tmp = im[x + yn*n];
                im[x + yn*n] = im[xn + y*n];
                im[xn + y*n] = tmp;
            }
        }
    }
    // 三角関数テーブル作成
    private function makeCstb():void {
        var n2:int = n>>1, n4:int = n>>2, n8:int = n>>3;
        var t:Number = Math.sin(Math.PI/n);
        var dc:Number = 2*t*t;
        var ds:Number = Math.sqrt(dc*(2 - dc));
        var c:Number = cstb[n4] = 1;
        var s:Number = cstb[0] = 0;
        t = 2*dc;
        for(var i:int=1; i<n8; i++) {
            c -= dc;
            dc += t*c;
            s += ds;
            ds -= t*s;
            cstb[i] = s;
            cstb[n4 - i] = c;
        }
        if(n8 != 0) cstb[n8] = Math.sqrt(0.5);
        for(var j:int=0; j<n4; j++) cstb[n2 - j] = cstb[j];
        for(var k:int=0; k<(n2 + n4); k++) cstb[k + n2] = -cstb[k];
    }
    // ビット反転テーブル作成
    private function makeBitrev():void {
        var i:int = 0, j:int = 0, k:int = 0;
        bitrev[0] = 0;
        while(++i<n) {
            k = n >> 1;
            while(k<=j) {
                j -= k;
                k >>= 1;
            }
            j += k;
            bitrev[i] = j;
        }
    }
}