ウェブカム・エフェクト forked from: nengafl
other license, write as code comments
求む!ウェブカム作品
新しいエフェクタを作って、クールなウェブカム作品で萌えませんか?
書き換える場所はふたつです
1. ドキュメントクラス(Nengafl)のコンストラクタの最初の部分
chain.addEffector() メソッドで新しいエフェクタ・インスタンスを登録する
2. 独自 EffectorFormat の生成
新しい Effector クラスは EffectorFormat を書き換える
注意:EffectorFormat は AbstractEffector の後に記述してあります。
Nengafl の直後じゃなくてスイマセン(そうしないと参照問題でエラーになるので)。
http://aquioux.blog48.fc2.com/blog-entry-670.html に若干の説明を書きましたので、よろしければご参照ください。
/**
* Copyright Aquioux ( http://wonderfl.net/user/Aquioux )
* MIT License ( http://www.opensource.org/licenses/mit-license.php )
* Downloaded from: http://wonderfl.net/c/tSFn
*/
// forked from nengafl's nengafl
// other license, write as code comments
// 求む!ウェブカム作品
// 新しいエフェクタを作って、クールなウェブカム作品で萌えませんか?
//
// 書き換える場所はふたつです
// 1. ドキュメントクラス(Nengafl)のコンストラクタの最初の部分
// chain.addEffector() メソッドで新しいエフェクタ・インスタンスを登録する
// 2. 独自 EffectorFormat の生成
// 新しい Effector クラスは EffectorFormat を書き換える
//
// 注意:EffectorFormat は AbstractEffector の後に記述してあります。
// Nengafl の直後じゃなくてスイマセン(そうしないと参照問題でエラーになるので)。
//
// http://aquioux.blog48.fc2.com/blog-entry-670.html に若干の説明を書きましたので、よろしければご参照ください。
//
package {
import flash.display.Sprite;
import flash.events.Event;
/**
* 各種フィルタを使ったウェブカム画像の加工
* @author YOSHIDA, Akio (Aquioux)
*/
[SWF(width = "465", height = "465", frameRate = "30", backgroundColor = "#000000")]
public class Nengafl extends Sprite {
public function Nengafl() {
// エフェクタ連鎖
var chain:ChainEffectors = new ChainEffectors();
// ===== 書き換える場所 ここから =====
// ここで各エフェクタ・インスタンスを生成
var smooth:EffectorSmoothing = new EffectorSmoothing();
smooth.strength = 16;
var posterize:EffectorPosterization = new EffectorPosterization();
posterize.degree = 8;
var sharp:EffectorSharp = new EffectorSharp();
sharp.strength = 5;
//var mosaic:EffectorPixelization = new EffectorPixelization(465, 465);
//mosaic.size = 31;
// addEffector で登録した順番で映像は加工される
// 同じフィルタを使っても、順番を入れ替えるだけでずいぶん変わる
chain.addEffector(smooth);
chain.addEffector(posterize);
//chain.addEffector(mosaic);
chain.addEffector(sharp);
//chain.addEffector(new EffectorNegative());
//chain.addEffector(new EffectorGrayScale());
// ===== 書き換える場所 ここまで =====
// Model を生成
try {
var model:Model = new Model(stage);
} catch (err:Error) {
trace(err.message);
return;
}
// View を生成
var view:View = new View(model);
addChild(view);
// 開始
model.chain = chain;
model.start();
Wonderfl.capture_delay(30);
}
}
}
import flash.display.BitmapData;
import flash.geom.Point;
/**
* BitmapData エフェクト用抽象クラス
* @author YOSHIDA, Akio
*/
class AbstractEffector {
/*
* BitmapData.applyFilter で destPoint として使用する Point オブジェクト
*/
protected const ZERO_POINT:Point = new Point(0, 0);
/*
* コンストラクタ
*/
public function AbstractEffector() {}
/*
* 効果の適用
* @param value 効果をかける BitmapData
*/
public function applyEffect(value:BitmapData):BitmapData {
return effect(value);
}
/*
* 効果内容、具体的なコードはサブクラスで定義する
* @param value 効果をかける BitmapData
*/
protected function effect(value:BitmapData):BitmapData {
return value;
}
}
import flash.display.BitmapData;
/**
* Effector のフォーマット
* 必ず AbstractEffector を継承する
*/
class EffectorFormat extends AbstractEffector {
// ===== 書き換える場所 ここから =====
// メンバー変数が必要な場合はここで記述
// すべての Effector 参照
// ===== 書き換える場所 ここまで =====
/*
* コンストラクタ
*/
public function EffectorFormat() {
// ===== 書き換える場所 ここから =====
// このクラス生成時に初期化処理が必要な場合はここで記述。なければ不要
// EffectorPosterization, EffectorSharp, EffectorSmoothing 参照
// ===== 書き換える場所 ここまで =====
}
/*
* エフェクト実行
* @param value 効果をかける BitmapData
*/
override protected function effect(value:BitmapData):BitmapData {
// ===== 書き換える場所 ここから =====
// value.applyFilter(value, value.rect, ZERO_POINT, xxxFilter);
// value.applyFilter を使う場合、第3引数までは固定
// すべての Effector 参照
// ===== 書き換える場所 ここまで =====
return value;
}
}
import flash.display.BitmapData;
import flash.filters.BitmapFilterQuality;
import flash.filters.BlurFilter;
/**
* BlurFilter による平滑化
* @author YOSHIDA, Akio (Aquioux)
*/
class EffectorSmoothing extends AbstractEffector {
// BlurFilter 用変数(強さ)
public function set strength(value:Number):void {
blurFilter.blurX = blurFilter.blurY = value;
}
// BlurFilter 用変数(質)
public function set quality(value:int):void {
blurFilter.quality = value;
}
// BlurFilter
private var blurFilter:BlurFilter;
/*
* コンストラクタ
*/
public function EffectorSmoothing() {
blurFilter = new BlurFilter(2, 2, BitmapFilterQuality.MEDIUM);
}
/*
* 平滑化実行
* @param value 効果をかける BitmapData
*/
override protected function effect(value:BitmapData):BitmapData {
value.applyFilter(value, value.rect, ZERO_POINT, blurFilter);
return value;
}
}
import flash.display.BitmapData;
/**
* paletteMap による BitmapData の減色
* 参考:「実践画像処理入門」 培風館 内村圭一・上瀧剛 P16 「2.5 濃度値の量子化による減色処理」
* @author YOSHIDA, Akio (Aquioux)
*/
class EffectorPosterization extends AbstractEffector {
/*
* 減色の段階(1、および 256 より大きい値以外)
*/
public function set degree(value:uint):void {
value = value < 2 ? 2 : value;
value = value > 256 ? 256 : value;
for (var i:int = 0; i < 256; i++) {
var val:uint = uint(i / (256 / value)) * 255 / (value - 1);
rArray[i] = val << 16;
gArray[i] = val << 8;
bArray[i] = val;
}
}
// paletteMap の引数となる各色要素用の Array
private var rArray:Array = [];
private var gArray:Array = [];
private var bArray:Array = [];
/*
* コンストラクタ
*/
public function EffectorPosterization() {
degree = 8; // degree のデフォルト
}
/*
* 減色実行
* @param value 効果をかける BitmapData
*/
override protected function effect(value:BitmapData):BitmapData {
value.paletteMap(value, value.rect, ZERO_POINT, rArray, gArray, bArray);
return value;
}
}
import flash.display.BitmapData;
import flash.geom.Matrix;
import flash.geom.Rectangle;
/**
* モザイク(原画像のブロック内部の画素値の平均値による)
* 「OpenGL+GLSLによる画像処理プログラミング」 工学社 酒井幸市 P46 「2.1 モザイク処理」
* @author YOSHIDA, Akio (Aquioux)
*/
class EffectorPixelization extends AbstractEffector {
public function set size(value:uint):void {
_size = value;
wNum = Math.ceil(width / _size); // width / _size が割り切れない場合は切り上げ
hNum = Math.ceil(height / _size); // height / _size が割り切れない場合は切り上げ
pixelizeRect.width = _size;
pixelizeRect.height = _size;
if (pixelizeBitmapData) pixelizeBitmapData.dispose();
pixelizeBitmapData = new BitmapData(_size, _size);
}
private var _size:uint; // モザイクの1辺の長さ(縦横同じ長さとする)
private var width:Number; // 対象幅
private var height:Number; // 対象高
private var wNum:uint; // 横方向のモザイク数
private var hNum:uint; // 縦方向のモザイク数
private var pixelizeRect:Rectangle = new Rectangle(); // モザイク1つ分の Rectangle
private var pixelizeBitmapData:BitmapData; // モザイク1つ分の BitmapData
private var matrix:Matrix = new Matrix(); // BitmapData.draw 用
public function EffectorPixelization(width:Number, height:Number) {
this.width = width; // 対象幅を待避
this.height = height; // 対象高を待避
size = 8; // _size のデフォルト(setter メソッドを発動させる)
}
override protected function effect(value:BitmapData):BitmapData {
// モザイクブロックごとの走査
for (var i:int = 0; i < hNum; i++) {
for (var j:int = 0; j < wNum; j++) {
pixelizeRect.x = j * _size;
pixelizeRect.y = i * _size;
matrix.tx = -pixelizeRect.x;
matrix.ty = -pixelizeRect.y;
pixelizeBitmapData.draw(value, matrix);
// モザイクの1辺の長さが 16 より大きい場合はヒストグラムを使って、そうでない場合は getPixel を使って平均色を求める
// 16 ^ 2 = 256
// 走査ループ回数がヒストグラムでのそれの未満なら getPixel に切り替える
var color:uint = (_size > 16) ? getAverageColorHistogram(pixelizeBitmapData) : getAverageColor1GetPixel(pixelizeBitmapData);
value.fillRect(pixelizeRect, color);
}
}
return value;
}
// 平均色を求める(モザイク1ブロック内のヒストグラムから計算)
private function getAverageColorHistogram(bitmapData:BitmapData):uint {
var hist:Vector.<Vector.<Number>> = bitmapData.histogram();
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 numOfPixel:uint = _size * _size;
var r:uint = rSum / numOfPixel >> 0;
var g:uint = gSum / numOfPixel >> 0;
var b:uint = bSum / numOfPixel >> 0;
return 0xFF << 24 | r << 16 | g << 8 | b;
}
// 平均色を求める(モザイク1ブロック内のピクセルを走査)
private function getAverageColor1GetPixel(bitmapData:BitmapData):uint {
var rSum:uint = 0;
var gSum:uint = 0;
var bSum:uint = 0;
for (var i:int = 0; i < _size; i++) {
for (var j:int = 0; j < _size; j++) {
var color:uint = bitmapData.getPixel(j, i);
var r:uint = (color >> 16) & 0xFF;
var g:uint = (color >> 8) & 0xFF;
var b:uint = color & 0xFF;
rSum += r;
gSum += g;
bSum += b;
}
}
var numOfPixel:uint = _size * _size;
r = rSum / numOfPixel >> 0;
g = gSum / numOfPixel >> 0;
b = bSum / numOfPixel >> 0;
return 0xFF << 24 | r << 16 | g << 8 | b;
}
}
import flash.display.BitmapData;
import flash.filters.ConvolutionFilter;
/**
* 鮮鋭化
* 参考:「実践画像処理入門」 培風館 内村圭一・上瀧剛 P48 「6.2 高域強調フィルタ」
* 参考:「OpenGL+GLSLによる画像処理プログラミング」 工学社 酒井幸市 P111 「5.2 鮮鋭化フィルタ」
* @author YOSHIDA, Akio (Aquioux)
*/
class EffectorSharp extends AbstractEffector {
/*
* 鮮鋭化の強さ
*/
public function set strength(value:Number):void {
sharpMatrix = [
0, -value, 0,
-value, 1 + 4 * value, -value,
0, -value, 0
];
}
// ConvolutionFilter 用マトリクス
private var sharpMatrix:Array = [];
/*
* コンストラクタ
*/
public function EffectorSharp() {
strength = 1.0; // strength のデフォルト
}
/*
* 鮮鋭化実行
* @param value 効果をかける BitmapData
*/
override protected function effect(value:BitmapData):BitmapData {
value.applyFilter(value, value.rect, ZERO_POINT, new ConvolutionFilter(3, 3, sharpMatrix));
return value;
}
}
import flash.display.BitmapData;
import flash.filters.ColorMatrixFilter;
/**
* ColorMatrixFilter による BitmapData のグレイスケール化(NTSC 系加重平均による)
* 参考:Foundation ActionScript 3.0 Image Effects(P106)
* http://www.amazon.co.jp/gp/product/1430218711?ie=UTF8&tag=laxcomplex-22
* @author YOSHIDA, Akio (Aquioux)
*/
class EffectorGrayScale extends AbstractEffector {
// ColorMatrixFilter 用マトリクス
private const GRAYSCALE_MATRIX:Array = [
0.3, 0.6, 0.1, 0, 0,
0.3, 0.6, 0.1, 0, 0,
0.3, 0.6, 0.1, 0, 0,
0, 0, 0, 1, 0
];
// ColorMatrixFilter
private const GRAYSCALE_FILTER:ColorMatrixFilter = new ColorMatrixFilter(GRAYSCALE_MATRIX);
/*
* グレイスケール実行
* @param value 効果をかける BitmapData
*/
override protected function effect(value:BitmapData):BitmapData {
value.applyFilter(value, value.rect, ZERO_POINT, GRAYSCALE_FILTER);
return value;
}
}
import flash.display.BitmapData;
import flash.filters.ConvolutionFilter;
/**
* ConvolutionFilter による BitmapData の色反転
* 参考:http://www40.atwiki.jp/spellbound/pages/231.html
* @author YOSHIDA, Akio (Aquioux)
*/
class EffectorNegative extends AbstractEffector {
// ConvolutionFilter 用マトリクス
private const NEGATIVE_MATRIX:Array = [-1];
// ConvolutionFilter
private const NEGA_FILTER:ConvolutionFilter = new ConvolutionFilter(1, 1, NEGATIVE_MATRIX, 1, 255);
/*
* 色反転実行
* @param value 効果をかける BitmapData
*/
override protected function effect(value:BitmapData):BitmapData {
value.applyFilter(value, value.rect, ZERO_POINT, NEGA_FILTER);
return value;
}
}
import flash.display.BitmapData;
/**
* エフェクタ連鎖(責任の連鎖パターンってこういうの?)
* @author YOSHIDA, Akio (Aquioux)
*/
class ChainEffectors {
private var effectors:Array = [];
/**
* コンストラクタ
* @param model Model
*/
public function ChainEffectors() {
}
/**
* エフェクタの追加
* @param effector エフェクタ
*/
public function addEffector(effector:AbstractEffector):void {
effectors.push(effector);
}
/**
* エフェクタの適用
* @param value エフェクタをかける BitmapData
*/
public function applyEffect(value:BitmapData):BitmapData {
var n:uint = effectors.length;
for(var i:int=0; i < n; i++){
var effector:AbstractEffector = effectors[i];
value = effector.applyEffect(value);
}
return value;
}
}
import flash.display.BitmapData;
import flash.display.Stage;
import flash.events.Event;
import flash.events.EventDispatcher;
import flash.media.Camera;
import flash.media.Video;
/**
* Web Camera の映像にエフェクトをかける(MVC の Model)
* エフェクトロジックは effector クラスとして外部で定義する
* @author YOSHIDA, Akio (Aquioux)
*/
class Model extends EventDispatcher {
// --------------------------------------------------
// View へ渡すデータ(プロパティ)
// --------------------------------------------------
/**
* 加工済みのカメラ画像
*/
public function get data():BitmapData { return _data; }
private var _data:BitmapData;
// --------------------------------------------------
// 外部から受け取るデータ(プロパティ)
// --------------------------------------------------
/**
* エフェクタ連鎖
*/
public function set chain(value:ChainEffectors):void {
_chain = value;
}
private var _chain:ChainEffectors;
// --------------------------------------------------
// 外部との通信をおこなうメソッド
// --------------------------------------------------
/**
* 対 View 用メソッド
* このメソッドの終了時にイベントを発行するので、View との通信手段となる
* @private
*/
private function update():void {
_data.draw(video);
_data = _chain.applyEffect(_data);
dispatchEvent(new Event(Event.CHANGE));
}
// --------------------------------------------------
// その他のメソッド
// --------------------------------------------------
/**
* コンストラクタ
* コンストラクタの引数はステージとする。各種データはアクセサーによって取り込むものとする
* @param stage ステージ
*/
private var stage:Stage;
// カメラが表示するサイズ
private var cameraWidth:uint;
private var cameraHeight:uint;
// カメラ
private var camera:Camera;
private var video:Video;
public function Model(stage:Stage, cw:Number = 0, ch:Number = 0) {
this.stage = stage;
this.cameraWidth = (cw == 0) ? stage.stageWidth : cw;
this.cameraHeight = (ch == 0) ? stage.stageHeight : ch;
_data = new BitmapData(cameraWidth, cameraHeight, false);
// カメラ
camera = Camera.getCamera();
if (camera) {
// camera のセットアップ
camera.setMode(cameraWidth, cameraHeight, stage.frameRate);
// video のセットアップ
video = new Video(cameraWidth, cameraHeight);
video.attachCamera(camera);
} else {
throw new Error("カメラがありません。");
}
}
/**
* 処理開始
* Event.ENTER_FRAME を使う場合、このメソッドを設定する。
* Controller から通知されるイベントだけで処理する場合、このメソッドは不要。
*/
public function start():void {
stage.addEventListener(Event.ENTER_FRAME, enterFrameHandler);
}
/**
* イベントハンドラ
* @private
*/
private function enterFrameHandler(event:Event):void {
update();
}
}
import flash.display.Bitmap;
import flash.events.Event;
/**
* Web Camera のスクリーン(MVC の View)
* @author YOSHIDA, Akio (Aquioux)
*/
class View extends Bitmap {
/**
* コンストラクタ
* @param model Model
*/
private var model:Model;
public function View(model:Model) {
this.model = model;
this.model.addEventListener(Event.CHANGE, changeHandler);
}
/**
* Model との通信手段
* @param event 発生したイベント
*/
private function changeHandler(event:Event):void {
// Model からデータを受け取り、視覚化
this.bitmapData = model.data;
}
}