2009 07 22 ECLIPSE!!!! に勝手にコメントをつけてみた
Happy Eclipse! を勝手に解説する
ソースが長いので途中で力尽きたけど、だいたいの雰囲気は分かるはず。
下記コメントの「@see http://www.flickr.com/photos/auroracrowley/」
は Fork 元「Flickr Tricks For Aurora Crowley!!」の参照ページの
模様です。
Fork 元:
- http://wonderfl.net/code/448a0195da618461603cb61f9172fc6aff8420cf
以下、オリジナルのソースコードのはじまりはじまり。
Happy Eclipse!!
ActionScript3 Thread library のサンプルになれば幸いです :-)
*
* @see http://www.flickr.com/photos/auroracrowley/
* @see http://www.libspark.org/htdocs/as3/thread-files/document/
/**
* Copyright nitoyon ( http://wonderfl.net/user/nitoyon )
* MIT License ( http://www.opensource.org/licenses/mit-license.php )
* Downloaded from: http://wonderfl.net/c/b06l
*/
// Happy Eclipse! を勝手に解説する
// ソースが長いので途中で力尽きたけど、だいたいの雰囲気は分かるはず。
//
// 下記コメントの「@see http://www.flickr.com/photos/auroracrowley/」
// は Fork 元「Flickr Tricks For Aurora Crowley!!」の参照ページの
// 模様です。
//
// Fork 元:
// - http://wonderfl.net/code/448a0195da618461603cb61f9172fc6aff8420cf
//
// 以下、オリジナルのソースコードのはじまりはじまり。
//Happy Eclipse!!
// forked from soundkitchen's Flickr Tricks For Aurora Crowley!!
/**
* ActionScript3 Thread library のサンプルになれば幸いです :-)
*
* @see http://www.flickr.com/photos/auroracrowley/
* @see http://www.libspark.org/htdocs/as3/thread-files/document/
*/
package
{
import flash.display.Sprite;
import flash.display.StageAlign;
import flash.display.StageQuality;
import flash.display.StageScaleMode;
import flash.display.StageDisplayState;
import flash.events.Event;
import flash.events.MouseEvent;
import flash.geom.Rectangle;
import org.libspark.thread.EnterFrameThreadExecutor;
import org.libspark.thread.Thread;
[SWF(frameRate=60, width=465, height=465, backgroundColor=0x000000)]
// ドキュメントクラス
public class Eclipse extends Sprite
{
// コンストラクタ
public function Eclipse()
{
// ステージの設定
stage.align = StageAlign.TOP_LEFT;
stage.quality = StageQuality.HIGH;
stage.scaleMode = StageScaleMode.NO_SCALE;
stage.fullScreenSourceRect = new Rectangle(0, 0, 465 , 465);
// クリック時にはフルスクリーンに
stage.addEventListener(MouseEvent.CLICK,function(e:Event):void{
stage.displayState = (StageDisplayState.FULL_SCREEN==stage.displayState)?StageDisplayState.NORMAL:StageDisplayState.FULL_SCREEN
})
// ステージに追加完了時に initialize 関数を呼ぶ
addEventListener(Event.ADDED_TO_STAGE, initialize);
}
private function initialize(evt:Event):void
{
// 即座に removeEventListener する。素敵な癖ですね。
removeEventListener(Event.ADDED_TO_STAGE, initialize);
// そうめん 初期化
if (!Thread.isReady)
{
Thread.initialize(new EnterFrameThreadExecutor());
}
// MainThread を開始する
new MainThread(this).start();
}
}
}
// import いっぱい!
// まずは flash.***
import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.display.BlendMode;
import flash.display.DisplayObject;
import flash.display.DisplayObjectContainer;
import flash.display.Stage;
import flash.events.MouseEvent;
import flash.events.TimerEvent;
import flash.filters.BitmapFilter;
import flash.filters.BitmapFilterQuality;
import flash.filters.BlurFilter;
import flash.geom.ColorTransform;
import flash.geom.Point;
import flash.net.URLRequest;
import flash.net.URLVariables;
import flash.system.LoaderContext;
import flash.text.TextField;
import flash.text.TextFieldAutoSize;
import flash.text.TextFormat;
import flash.ui.Mouse;
import flash.utils.Timer;
// JSON シリアライズ用のライブラリ(as3corelibより)
import com.adobe.serialization.json.JSON;
// そうめん
import org.libspark.thread.Thread;
import org.libspark.thread.threads.display.LoaderThread;
import org.libspark.thread.threads.net.URLLoaderThread;
import org.libspark.thread.utils.IProgress;
import org.libspark.thread.utils.IProgressNotifier;
import org.libspark.thread.utils.MultiProgress;
import org.libspark.thread.utils.ParallelExecutor;
import org.libspark.thread.utils.SerialExecutor;
// フィルタエフェクト
internal var FILTER_BLUR:BlurFilter = new BlurFilter(4, 4, BitmapFilterQuality.MEDIUM);
// 高速化用の(0,0)を指す Point オブジェクト
internal var POINT_ZERO:Point = new Point();
import flash.display.Shape;
// メインの処理を実行するスレッド
// 実行順は次の通り:
// 1. run() メソッド
// WaitAnimationThread を実行・待機
// 2. loadImage() メソッド
// LoadImageThread, LoadImageProgressThread を実行・待機
// 3. loadComplete() メソッド
// 画像表示用の canvas を準備
// 4. changeImage() メソッド
// HandleImageThread を実行・待機
// その後、再び changeImage() メソッドを実行
internal class MainThread extends Thread
{
private var layer:DisplayObjectContainer;
private var imageLoader:LoadImageThread;
private var contents:Array;
private var canvas:Bitmap;
private var currentIndex:uint;
// コンストラクタ
// DocumentRoot を layter に記録しておく
public function MainThread(layer:DisplayObjectContainer)
{
this.layer = layer;
}
// スレッドのエントリポイント
override protected function run():void
{
var t:Thread;
// ローディングを開始して、終了まで待機する
t = new WaitAnimationThread(layer);
t.start();
t.join();
// WaitAnimationThread が終了したら、loadImage を呼ぶ
next(loadImage);
}
// 画像をロードする
private function loadImage():void
{
// 並列実行を行う
var executor:ParallelExecutor;
executor = new ParallelExecutor();
// 画像ロード用のスレッドを追加
imageLoader = new LoadImageThread();
executor.addThread(imageLoader);
// ロード状況を表示するスレッドを追加
executor.addThread(new LoadImageProgressThread(layer, imageLoader.progress));
// 両方が終了するまで待機
executor.start();
executor.join();
// 終了後は loadComplete を実行する
next(loadComplete);
}
// ロード完了時の処理
private function loadComplete():void
{
var s:Stage,
data:BitmapData;
contents = imageLoader.contents.concat();
imageLoader.contents.length = 0;
imageLoader.contents = null;
currentIndex = 0;
s = layer.stage;
data = new BitmapData(450, 450, true, 0);
canvas = new Bitmap(data);
canvas.x = (s.stageWidth - canvas.width) / 2;
canvas.y = (s.stageHeight - canvas.height) / 2;
layer.addChild(canvas);
next(changeImage);
}
// 表示する画像を変更する
private function changeImage():void
{
if (currentIndex >= contents.length)
{
currentIndex = 0;
}
var image:Bitmap,
th:Thread;
// イメージ表示のスレッドを準備する
image = contents[currentIndex++] as Bitmap;
th = new HandleImageThread(image, canvas);
// 終了まで待機する
th.start();
th.join();
// 次は再びこのメソッド
next(changeImage);
}
// 終了時の処理
override protected function finalize():void
{
layer = null;
imageLoader = null;
}
}
// 開始時の表示を行うスレッドクラス
// クラス名に「Animation」と入っているが、実際にはアニメーションは
// 行わず、文字を表示後、即座に終了する。
internal class WaitAnimationThread extends Thread
{
private var layer:DisplayObjectContainer;
private var message:Bitmap;
// コンストラクタ
// layer はドキュメントクラスのインスタンス
public function WaitAnimationThread(layer:DisplayObjectContainer)
{
this.layer = layer;
}
// スレッドのエントリポイント
override protected function run():void
{
var s:Stage,
txt:TextField,
fmt:TextFormat,
bmd:BitmapData;
// ステージ
s = layer.stage;
// 文字列を準備
fmt = new TextFormat();
fmt.color = 0xFFFFFF;
fmt.size = 24;
fmt.font = 'Trebuchet MS';
txt = new TextField();
txt.autoSize = TextFieldAutoSize.LEFT;
txt.defaultTextFormat = fmt;
txt.text = '2009 07 22 ECLIPSE PHOTO STREAM.';
// BitmapData に文字列を描画
bmd = new BitmapData(txt.textWidth, txt.textHeight, true, 0);
bmd.draw(txt);
// ビットマップを作成し、ステージの真ん中に配置
message = new Bitmap(bmd);
message.blendMode = BlendMode.INVERT;
message.x = (s.stageWidth - message.width) / 2;
message.y = (s.stageHeight - message.height) / 2;
// ドキュメントクラスのインスタンスに追加する
layer.addChild(message);
// next() がないのでスレッドは終了する
}
// 終了処理
// message の参照は切るが、テキストは表示されたまま
override protected function finalize():void
{
layer = null;
message = null;
}
}
// 画像をロードするスレッド
// 「進捗通知機構」を備えるために IProgressNotifier を実装している
// 進捗状況は LoadImageProgressThread に伝えられる
internal class LoadImageThread extends Thread
implements IProgressNotifier
{
public var contents:Array;
private var mainProgress:MultiProgress;
private var subProgress:MultiProgress;
private var jsonLoader:URLLoaderThread;
private var imageLoader:SerialExecutor;
// IProgressNotifier で実装すべき唯一のメソッド
public function get progress():IProgress
{
return mainProgress;
}
// コンストラクタ
public function LoadImageThread()
{
var req:URLRequest,
data:URLVariables;
// 写真検索の URLRequest を準備する
// (YQL を利用して Flickr の写真を検索する)
req = new URLRequest('http://query.yahooapis.com/v1/public/yql');
data = new URLVariables();
data['q'] = "select * from flickr.photos.search where text='eclipse or 日食' and max_taken_date='2009-07-22'";
data['format'] = 'json';
req.data = data;
// Loader 用のスレッドを準備しておく
jsonLoader = new URLLoaderThread(req);
mainProgress = new MultiProgress();
subProgress = new MultiProgress();
mainProgress.addProgress(jsonLoader.progress, 0.1);
mainProgress.addProgress(subProgress);
}
// スレッドのエントリポイント
override protected function run():void
{
// 写真検索を実施して、完了を待機する
jsonLoader.start();
jsonLoader.join();
// ロードが完了したら loadDataComplete を呼び出す
next(loadDataComplete);
}
// 写真検索完了時の処理
private function loadDataComplete():void
{
var json:Object,
row:Object,
th:LoaderThread,
req:URLRequest,
ctx:LoaderContext,
flickr:FlickrPhoto;
json = JSON.decode(jsonLoader.loader.data);
ctx = new LoaderContext(true);
// 順次実行のスレッドを作成する
imageLoader = new SerialExecutor();
// 順次実行スレッドに画像ロードのスレッドを追加していく
for each (row in json.query.results.photo)
{
flickr = new FlickrPhoto(row);
req = new URLRequest(flickr.thumbnailURL);
th = new LoaderThread(req, ctx);
imageLoader.addThread(th);
subProgress.addProgress(th.progress);
}
// 画像ロード処理を開始
imageLoader.start();
imageLoader.join();
// 全部終わったら loadImageComplete を呼び出す
next(loadImageComplete);
}
// 全ての画像のロードが完了したときの処理
private function loadImageComplete():void
{
var i:uint,
l:uint,
th:LoaderThread;
contents = [];
l = imageLoader.numThreads;
for (i=0; i<l; i++)
{
th = imageLoader.getThreadAt(i) as LoaderThread;
contents.push(th.loader.content);
}
}
override protected function finalize():void
{
jsonLoader = null;
imageLoader = null;
}
}
// LoadImageThread の進捗情報に基づいて画面描画を行うスレッド
// 実際にはローディング表示は何もやっていないが、雛形だけは用意されている。
internal class LoadImageProgressThread extends Thread
{
private var progress:IProgress;
private var layer:DisplayObjectContainer;
private var indicator:HandleIndicatorThread;
// コンストラクタ
// progress 経由で LoadImageThread の進捗状況を取得できる
public function LoadImageProgressThread(layer:DisplayObjectContainer, progress:IProgress)
{
this.layer = layer;
this.progress = progress;
}
// スレッドのエントリポイント
override protected function run():void
{
indicator = new HandleIndicatorThread(layer);
indicator.start();
next(step);
}
private function step():void
{
if (progress.isCompleted||progress.isFailed||progress.isCanceled)
{
next(shutDown);
return;
}
// Loading の進捗状況を取得できる
// TODO: なにかしらやる。
var percent:Number = progress.percent;
// Loading 完了したら shutDown に移る
// 完了しない限りは繰り返し step を実行する
if (percent >= 1)
{
next(shutDown);
}
else
{
next(step);
}
}
private function shutDown():void
{
// HandleIndicatorThread に停止するよう要請する
// 実際に停止するかどうかは HandleIndicatorThread の実装に依存する
indicator.interrupt();
}
override protected function finalize():void
{
progress = null;
layer = null;
indicator = null;
}
}
// 画像の処理を行うスレッド
internal class HandleImageThread extends Thread
{
public static const PARTICLE_MARGIN:Number = 2;
private var original:Bitmap;
private var destination:Bitmap;
private var showParticles:Vector.<ShowImageParticle>;
private var hideParticles:Vector.<HideImageParticle>;
// コンストラクタ
public function HandleImageThread(original:Bitmap, destination:Bitmap)
{
this.original = original;
this.destination = destination;
this.showParticles = new Vector.<ShowImageParticle>();
this.hideParticles = new Vector.<HideImageParticle>();
}
// エントリポイント
override protected function run():void
{
var w:Number, h:Number, c:uint,
tx:Number, ty:Number,
sx:Number, sy:Number,
cx:Number, cy:Number,
angle:Number,
i:uint, j:uint,
s:Stage,
data:BitmapData;
// 画像の情報を取得する
s = destination.stage;
data = original.bitmapData;
w = data.width;
h = data.height;
cx = s.stageWidth / 2;
cy = s.stageHeight / 2;
// それぞれの座標について処理を行う
for (i=0; i<w; i+=2)
{
for (j=0; j<h; j+=2)
{
// 色を取得する
c = data.getPixel32(i, j);
// 真っ黒なら何もしない
if (!c) continue;
// スタート地点はランダムな角度に 500px 移動させる
angle = Math.random() * Math.PI * 2;
sx = Math.cos(angle) * 500 + cx;
sy = Math.sin(angle) * 500 + cy;
// 終了地点は PARTICLE_MARGIN 倍した場所
tx = i * PARTICLE_MARGIN;
ty = j * PARTICLE_MARGIN;
// 開始と終了のパーティクルを準備
showParticles.push(new ShowImageParticle(sx, sy, tx, ty, c));
hideParticles.push(new HideImageParticle(tx, ty, sx, sy, c, s.frameRate));
}
}
// 次は showStep
next(showStep);
}
// パーティクルが集まってくる処理
private function showStep():void
{
var i:uint,
l:uint,
c:uint,
a:Number,
f:Boolean,
p:ShowImageParticle,
data:BitmapData;
data = destination.bitmapData;
data.lock();
// ぼかしつつ
data.applyFilter(data, data.rect, POINT_ZERO, FILTER_BLUR);
// 座標を更新する
l = showParticles.length;
f = false;
for (i=0; i<l; i++)
{
p = showParticles[i];
if (p.isAlive)
{
f = true;
p.update();
}
data.setPixel32(p.x, p.y, p.color);
}
data.unlock();
// 全ての移動が完了すれば fixedImage へ
if (!f)
{
next(fixedImage);
}
// そうでなければ再度 showStep へ
else
{
next(showStep);
}
}
// 非表示にしていく処理
private function hideStep():void
{
var i:uint,
l:uint,
c:uint,
a:Number,
f:Boolean,
p:HideImageParticle,
data:BitmapData;
data = destination.bitmapData;
data.lock();
// 透明にしつつ、ぼかしつつ
data.colorTransform(data.rect, new ColorTransform(1, 1, 1, .97, 0, 0, 0, 0));
data.applyFilter(data, data.rect, POINT_ZERO, FILTER_BLUR);
l = hideParticles.length;
for (i=0; i<l; i++)
{
p = hideParticles[i];
p.update();
data.setPixel32(p.x, p.y, p.color);
// 画面外に移動したら消す
if (!data.rect.contains(p.x, p.y))
{
hideParticles.splice(i, 1);
i--;
l = hideParticles.length;
}
}
data.unlock();
//trace(p.x, p.y);
// 全部消えない限りは hideStep を繰り返す
if (l) next(hideStep);
}
// 固定のままの状態
// 3秒待機して hideStep へ
private function fixedImage():void
{
sleep(3000);
next(hideStep);
}
// 終了処理
override protected function finalize():void
{
original = null;
destination = null;
showParticles.length = 0;
showParticles = null;
hideParticles.length = 0;
hideParticles = null;
}
}
internal class HandleIndicatorThread extends Thread
{
private var layer:DisplayObjectContainer;
private var indicator:DisplayObject;
public function HandleIndicatorThread(layer:DisplayObjectContainer)
{
this.layer = layer;
}
override protected function run():void
{
Mouse.hide();
layer.stage.mouseChildren = false;
indicator = new Indicator();
indicator.x = layer.mouseX;
indicator.y = layer.mouseY;
layer.addChild(indicator);
next(step);
}
private function step():void
{
if (checkInterrupted()) return;
indicator.rotation = (indicator.rotation + 360 / indicator.stage.frameRate) % 360;
indicator.x = layer.mouseX;
indicator.y = layer.mouseY;
next(step);
}
override protected function finalize():void
{
Mouse.show();
layer.stage.mouseChildren = true;
layer.removeChild(indicator);
layer = null;
indicator = null;
}
}
internal class HideMessageParticle
{
public var x:Number;
public var y:Number;
public var ax:Number;
public var ay:Number;
public var vx:Number;
public var vy:Number;
public var color:uint;
public function HideMessageParticle(x:Number, y:Number, color:Number)
{
var len:Number,
angle:Number;
len = Math.random() * 5;
angle = Math.random() * Math.PI * 2;
this.ax = Math.cos(angle) * len;
this.ay = Math.sin(angle) * len;
this.x = x;
this.y = y;
this.color = color;
this.vx = 0;
this.vy = 0;
}
public function update():void
{
vx += ax;
vy += ay;
x += vx;
y += vy;
}
}
import flash.geom.Point;
internal class ShowImageParticle
{
public var x:Number;
public var y:Number;
public var color:uint;
private var targetX:Number;
private var targetY:Number;
private var _isAlive:Boolean;
public function get isAlive():Boolean
{
return _isAlive;
}
public function ShowImageParticle(x:Number, y:Number, targetX:Number, targetY:Number, color:uint)
{
this.x = x;
this.y = y;
this.color = color;
this.targetX = targetX;
this.targetY = targetY;
this._isAlive = true;
}
public function update():void
{
x += (targetX - x) * .05;
y += (targetY - y) * .05;
if (Math.abs(targetX - x) < .5 && Math.abs(targetY - y) < .5)
{
x = targetX;
y = targetY;
_isAlive = false;
}
}
}
internal class HideImageParticle
{
public var x:Number;
public var y:Number;
public var color:uint;
private var vx:Number;
private var vy:Number;
private var ax:Number;
private var ay:Number;
private var rate:Number;
public function HideImageParticle(x:Number, y:Number, targetX:Number, targetY:Number, color:uint, rate:Number)
{
var dx:Number,
dy:Number,
len:Number,
angle:Number;
dx = targetX - x;
dy = targetY - y;
len = 9.8;
angle = Math.atan2(dy, dx);
this.x = x;
this.y = y;
this.vx = 0;
this.vy = 0;
this.ax = Math.cos(angle) * len;
this.ay = Math.sin(angle) * len;
this.color = color;
this.rate = rate;
}
public function update():void
{
vx += ax / rate;
vy += ay / rate;
x += vx;
y += vy;
}
}
internal class FlickrPhoto
{
private var _id:String;
public function get id():String
{
return _id;
}
private var _secret:String;
public function get secret():String
{
return _secret;
}
private var _server:String;
public function get server():String
{
return _server;
}
private var _farm:String;
public function get farm():String
{
return _farm;
}
public function get thumbnailURL():String
{
return ['http://farm', farm, '.static.flickr.com/', server, '/', id, '_', secret, '_m.jpg'].join('');
}
public function FlickrPhoto(data:Object)
{
this._id = data['id'] || '';
this._secret = data['secret'] || '';
this._server = data['server'] || '';
this._farm = data['farm'] || '';
}
}
// ローディング用のカーソル
internal class Indicator extends Shape
{
public function Indicator()
{
var i:uint,
cx:Number, cy:Number,
numNeedles:uint = 12,
innerR:Number = 7,
outerR:Number = 5,
cAngle:Number = -Math.PI / 2,
nAngle:Number;
nAngle = Math.PI * 2 / numNeedles;
for (i=0; i<numNeedles; i++)
{
cAngle += nAngle;
cx = Math.cos(cAngle) * innerR;
cy = Math.sin(cAngle) * innerR;
graphics.moveTo(cx, cy);
cx = Math.cos(cAngle) * outerR;
cy = Math.sin(cAngle) * outerR;
graphics.lineStyle(2, 0xffffff, i/numNeedles);
graphics.lineTo(cx, cy);
}
}
}