[WebCam] 誰でもスーラ -Anyone Seurat-
誰でもスーラ -Anyone Seurat-
点描派風ピクセレート・ウェブカム版
ネタ元:Beyond Interaction -メディアアートのための openFrameworks プログラミング入門 P169
http://www.amazon.co.jp/gp/product/4861006708?ie=UTF8&tag=laxcomplex-22&linkCode=as2&camp=247&creative=1211&creativeASIN=4861006708
参考:http://wonderfl.net/code/2bfaa6e6b54bc46b3aa9b0210103f3812aa00376
@author Aquioux(Yoshida, Akio)
/**
* Copyright Aquioux ( http://wonderfl.net/user/Aquioux )
* MIT License ( http://www.opensource.org/licenses/mit-license.php )
* Downloaded from: http://wonderfl.net/c/sJ7a
*/
package {
import flash.display.Sprite;
import net.hires.debug.Stats;
[SWF(width = "465", height = "465", frameRate = "30", backgroundColor = "#000000")]
/**
* 誰でもスーラ -Anyone Seurat-
* 点描派風ピクセレート・ウェブカム版
* ネタ元:Beyond Interaction -メディアアートのための openFrameworks プログラミング入門 P169
* http://www.amazon.co.jp/gp/product/4861006708?ie=UTF8&tag=laxcomplex-22&linkCode=as2&camp=247&creative=1211&creativeASIN=4861006708
* 参考:http://wonderfl.net/code/2bfaa6e6b54bc46b3aa9b0210103f3812aa00376
* @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;
// ---------- パブリックメソッド ----------
//
/**
* コンストラクタ
* @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("カメラがありません。");
}
}
// ---------- ローカルメソッド ----------
//
// 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_);
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;
/**
* View
* @author YOSHIDA, Akio (Aquioux)
*/
class View extends Bitmap {
// ---------- パブリックプロパティ ----------
//
// Model の参照
public function set model(value:Model):void { _model = value; }
private var _model:Model;
// Controller の参照
//public function set controller(value:Controller):void { _controller = value; }
//private var _controller:Controller;
// ---------- ローカルプロパティ ----------
//
// 各エフェクタ
private var smooth:Smooth; // 平滑化のためのエフェクタ
//private var smooth2:Smooth; // 最後にぼかすためのエフェクタ
private var tone:HalftoneColor; // ハーフトーン化
// ---------- パブリックメソッド ----------
//
/**
* コンストラクタ
* @param model Model
*/
public function View(model:Model) {
_model = model;
_model.addEventListener(Event.CHANGE, changeHandler);
smooth = new Smooth(); // 平滑化
smooth.strength = 2;
tone = new HalftoneColor(); // ハーフトーン
tone.size = 20;
filters = [new BlurFilter(8, 8, BitmapFilterQuality.MEDIUM)];
}
// ---------- ローカルメソッド ----------
//
// Model から Event.CHANGE が発行されたときの処理
private function changeHandler(e:Event):void {
// Model からデータを受け取り、視覚化
var bmd:BitmapData = _model.data;
smooth.applyEffect(bmd); // 平滑化
tone.applyEffect(bmd); // ハーフトーン
bitmapData = bmd;
}
}
import flash.display.BitmapData;
import flash.display.BitmapDataChannel;
import flash.geom.Rectangle;
/**
* ハーフトーン(カラー)
* @author YOSHIDA, Akio (Aquioux)
*/
class HalftoneColor 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 数
// バッファ
static private var rBmd_:BitmapData; // for RED Channel
static private var gBmd_:BitmapData; // for GREEN Channel
static private var bBmd_:BitmapData; // for BLUE Channel
static private var bufferBmds_:Array;
static private var totalRect_:Rectangle;// 全体の Rectangle
// ---------- パブリックメソッド ----------
//
/*
* コンストラクタ
*/
public function HalftoneColor(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 {
// 2回目以降
if (rBmd_){
rBmd_.fillRect(totalRect_, 0x00000000);
gBmd_.fillRect(totalRect_, 0x00000000);
bBmd_.fillRect(totalRect_, 0x00000000);
}
// 初回
if (width_ == 0.0 || height_ == 0.0) init(value.width, value.height);
var saveBmd:BitmapData = value.clone(); // カメラ画像を待避
value.fillRect(value.rect, 0xFF000000); // 塗りつぶす
// モザイクブロックごとの走査
value.lock();
for (var i:int = 0; i < hNum_; i++) {
for (var j:int = 0; j < wNum_; j++) {
var px:Number = j * _size;
var py:Number = i * _size;
// モザイク領域読込用としての blockRect_ 設定
blockRect_.x = px;
blockRect_.y = py;
blockRect_.width = blockRect_.height = _size;
// モザイク領域を全体から切り取る
blockBmd_.copyPixels(saveBmd, blockRect_, EffectorUtils.ZERO_POINT);
// モザイク領域の各カラーチャンネルの平均輝度取得
var brightness:Vector.<uint> = getAverageBrightness(blockBmd_.histogram());
for (var k:int = 0; k < 3; k++) {
// 描画サイズの設定
var blockSize:Number = _size * (brightness[k] / 255) * 0.9; // 90% 補正
// 描画開始位置オフセット計算
var offset:Number = (_size - blockSize) / 2;
// トーン描画用としての blockRect_ 設定
blockRect_.x = px + offset + Math.random() - 0.5;
blockRect_.y = py + offset + Math.random() - 0.5;
blockRect_.width = blockRect_.height = blockSize;
// バッファに描画
bufferBmds_[k].fillRect(blockRect_, 0xFFFFFFFF);
}
}
}
value.copyChannel(rBmd_, totalRect_, EffectorUtils.ZERO_POINT, BitmapDataChannel.RED, BitmapDataChannel.RED);
value.copyChannel(gBmd_, totalRect_, EffectorUtils.ZERO_POINT, BitmapDataChannel.GREEN, BitmapDataChannel.GREEN);
value.copyChannel(bBmd_, totalRect_, EffectorUtils.ZERO_POINT, BitmapDataChannel.BLUE, BitmapDataChannel.BLUE);
value.unlock();
return value;
}
// ---------- ローカルメソッド ----------
//
// 初期化
private function init(width:Number, height:Number):void {
width_ = width; // 対象幅を待避
height_ = height; // 対象高を待避
size = _size; // _size の設定
rBmd_ = new BitmapData(width_, height_, true, 0x00000000);
gBmd_ = rBmd_.clone();
bBmd_ = rBmd_.clone();
bufferBmds_ = [rBmd_, gBmd_, bBmd_];
totalRect_ = new Rectangle(0, 0, width_, height_);
}
// 平均輝度を求める
private function getAverageBrightness(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];
}
var r:uint = rSum / blockPixels_ >> 0;
var g:uint = gSum / blockPixels_ >> 0;
var b:uint = bSum / blockPixels_ >> 0;
return Vector.<uint>([r, g, b]);
}
}
import flash.display.BitmapData;
import flash.filters.BitmapFilterQuality;
import flash.filters.BlurFilter;
/**
* BlurFilter による平滑化
* @author YOSHIDA, Akio (Aquioux)
*/
class Smooth implements IEffector {
// ---------- パブリックプロパティ ----------
//
/*
* ぼかしの強さ
* @param value 数値
*/
public function set strength(value:Number):void {
blurFilter_.blurX = blurFilter_.blurY = value;
}
/*
* ぼかしの質
* @param value 数値
*/
public function set quality(value:int):void {
blurFilter_.quality = value;
}
// ---------- ローカルプロパティ ----------
//
// ブラーフィルタ
private var blurFilter_:BlurFilter;
// ---------- パブリックメソッド ----------
//
/*
* コンストラクタ
*/
public function Smooth() {
blurFilter_ = new BlurFilter(2, 2, BitmapFilterQuality.MEDIUM);
}
/*
* 効果適用
* @param value 効果対象 BitmapData
*/
public function applyEffect(value:BitmapData):BitmapData {
value.applyFilter(value, value.rect, EffectorUtils.ZERO_POINT, blurFilter_);
return value;
}
}
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);
}