遠くから見ると別画像(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;
}
}
}