[WebCam] THINK ART のパチモン
[WebCam] THINK ART のパチモン
説明: http://aquioux.blog48.fc2.com/blog-entry-712.html
参照:THINK ART http://www.marunouchi.com/think_art/
@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/koFB
*/
package {
import flash.display.Sprite;
import net.hires.debug.Stats;
[SWF(width = "465", height = "465", frameRate = "30", backgroundColor = "#000000")]
/**
* [WebCam] THINK ART のパチモン
* 説明: http://aquioux.blog48.fc2.com/blog-entry-712.html
* 参照:THINK ART http://www.marunouchi.com/think_art/
* @author Aquioux(Yoshida, Akio)
*/
public class Main extends Sprite {
public function Main():void {
Wonderfl.capture_delay(25);
// 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.events.MouseEvent;
import flash.geom.Matrix;
import flash.geom.Point;
import flash.geom.Rectangle;
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;
// 各エフェクタ
private var smooth_:Smooth; // 平滑化
private var grayscale_:GrayScale; // グレイスケール
private var contrast_:Contrast; // コントラスト
private var posterize_:Posterize; // 減色
private var divider_:SubDivider; // 平面分割
private var gray_:BitmapData;
private var rect_:Rectangle;
private const ZERO_POINT:Point = EffectorUtils.ZERO_POINT;
// ---------- パブリックメソッド ----------
//
/**
* コンストラクタ
* @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);
gray_ = new BitmapData(w, h, false, 0x000000);
rect_ = _data.rect;
// 鏡像になるよう、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("カメラがありません。");
}
// エフェクタ
smooth_ = new Smooth(); // 平滑化
smooth_.strength = 1;
grayscale_ = new GrayScale(); // グレイスケール
contrast_ = new Contrast(); // コントラスト
contrast_.strength = 1;
posterize_ = new Posterize(); // 減色
posterize_.degree = 3;
divider_ = new SubDivider(); // 平面分割
}
// ---------- ローカルメソッド ----------
//
// 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_);
smooth_.applyEffect(_data); // 平滑化
grayscale_.applyEffect(_data); // グレイスケール
gray_.copyPixels(_data, rect_, ZERO_POINT);
contrast_.applyEffect(gray_); // コントラスト
posterize_.applyEffect(gray_); // 減色
_data.fillRect(rect_, 0xFFFFFF);
divider_.applyEffect(rect_, gray_, _data, 8, 10);
dispatchEvent(new Event(Event.CHANGE));
}
}
import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.events.Event;
import flash.geom.Point;
import flash.geom.Rectangle;
/**
* View
* @author YOSHIDA, Akio (Aquioux)
*/
class View extends Bitmap {
// ---------- パブリックプロパティ ----------
//
// Model の参照
public function set model(value:Model):void { _model = value; }
private var _model:Model;
// ---------- パブリックメソッド ----------
//
/**
* コンストラクタ
* @param model Model
*/
public function View(model:Model) {
_model = model;
_model.addEventListener(Event.CHANGE, changeHandler);
}
// ---------- ローカルメソッド ----------
//
// Model から Event.CHANGE が発行されたときの処理
private function changeHandler(e:Event):void {
bitmapData = _model.data;
}
}
import flash.display.BitmapData;
import flash.display.Sprite;
import flash.geom.Matrix;
import flash.geom.Rectangle;
/**
* 画面分割
* @author YOSHIDA, Akio (Aquioux)
*/
class SubDivider {
// ---------- ローカルプロパティ ----------
//
// 共用 Rectangle
private var nextRect_:Rectangle = new Rectangle(); // 4分木再帰処理に使用
private var drawRect_:Rectangle = new Rectangle(); // 描画に使用
// ヒストグラムから濃度の合計値を求める関数
private const GET_BRIGHTNESS:Function = EffectorUtils.getSumOfBrightness1;
private const DARK_LETTERS:Array = [
new LetterO(AbstractLetter.DARK1),
new LetterO(AbstractLetter.DARK2),
new LetterX(AbstractLetter.DARK1),
new LetterX(AbstractLetter.DARK2)
];
private const LIGHT_LETTERS:Array = [
new LetterO(AbstractLetter.LIGHT1),
new LetterO(AbstractLetter.LIGHT2),
new LetterX(AbstractLetter.LIGHT1),
new LetterX(AbstractLetter.LIGHT2),
];
private const MIDDLE_LETTERS:Array = [
new LetterO(AbstractLetter.MIDDLE1),
new LetterO(AbstractLetter.MIDDLE2),
new LetterX(AbstractLetter.MIDDLE1),
new LetterX(AbstractLetter.MIDDLE2),
];
private const LETTERS_LENGTH:uint = DARK_LETTERS.length;
private var letters:Array;
private var matrix_:Matrix = new Matrix();
// ---------- パブリックメソッド ----------
//
/*
* コンストラクタ
*/
public function SubDivider() {
}
/*
* 効果適用
* @param rect 現在の対象 Rectangle
* @param gray 効果対象 BitmapData
* @param canvas 描画 BitmapData
* @param divideThreshold 分割閾値
* @param minSize 分割サイズ最小値
*/
public function applyEffect(rect:Rectangle, gray:BitmapData, canvas:BitmapData, divideThreshold:uint, minSize:uint):void {
// 現在のチェック対象矩形の縦または横の長さが指定より小さい場合、再帰を停止
var isDivide:Boolean = rect.width > minSize && rect.height > minSize;
// 走査範囲のピクセル数
var numOfPixel:uint = rect.width * rect.height;
// 走査範囲のヒストグラム
var hist:Vector.<Vector.<Number>> = gray.histogram(rect);
// 再帰判定
if (isDivide) {
// 濃度の標準偏差
var deviation:Number = getStandardDeviation(hist[0], numOfPixel);
if (deviation > divideThreshold) {
// 標準偏差が指定より大きい場合、4分木再帰を続行
// Rectangle の x、y、width、height が uint になるようにする
// (numOfPixel と齟齬が出ないようにするため)
var nextWidth1:uint = rect.width / 2 >> 0;
var nextHeight1:uint = rect.height / 2 >> 0;
var nextWidth2:uint = rect.width - nextWidth1;
var nextHeight2:uint = rect.height - nextHeight1;
var left:Number = rect.x;
var top:Number = rect.y;
var center:Number = left + nextWidth1;
var middle:Number = top + nextHeight1;
var xs:Vector.<uint> = Vector.<uint>([left, center, left, center]);
var ys:Vector.<uint> = Vector.<uint>([top, top, middle, middle]);
var ws:Vector.<uint> = Vector.<uint>([nextWidth1, nextWidth2, nextWidth1, nextWidth2]);
var hs:Vector.<uint> = Vector.<uint>([nextHeight1, nextHeight1, nextHeight2, nextHeight2]);
// 4分木再帰
for (var i:int = 0; i < 4; i++) {
nextRect_.x = xs[i];
nextRect_.y = ys[i];
nextRect_.width = ws[i];
nextRect_.height = hs[i];
applyEffect(nextRect_, gray, canvas, divideThreshold, minSize);
}
} else {
// 標準偏差が指定より小さい場合、再帰を停止し、描画をおこなう
isDivide = false;
}
}
// 描画
if (!isDivide) draw(rect, hist[0], numOfPixel, canvas);
}
// ---------- ローカルメソッド ----------
//
// 対象 BitmapData の指定矩形範囲の標準偏差を求める
private function getStandardDeviation(hist:Vector.<Number>, numOfPixel:uint):Number {
// 平均値
var average:Number = (hist[127] * 127 + hist[255] * 255) / numOfPixel;
// 平均との差の二乗を累積
var diff:Number = 0;
var sum:uint = 0;
diff = -average;
sum += diff * diff * hist[0];
diff = 127 - average;
sum += diff * diff * hist[127];
diff = 255 - average;
sum += diff * diff * hist[255];
return Math.sqrt(sum / numOfPixel);
}
// キャンバスに描画
private function draw(rect:Rectangle, hist:Vector.<Number>, numOfPixel:uint, canvas:BitmapData):void {
var sum:uint = GET_BRIGHTNESS(hist);
var brightness:uint = sum / numOfPixel;
if (brightness <= 0x33) {
letters = DARK_LETTERS;
} else if (brightness >= 0xCC) {
letters = LIGHT_LETTERS;
} else {
letters = MIDDLE_LETTERS;
}
matrix_.a = matrix_.d = rect.width / 100;
matrix_.tx = rect.x;
matrix_.ty = rect.y;
var idx:uint = Math.random() * LETTERS_LENGTH;
canvas.draw(letters[idx], matrix_);
}
}
import flash.display.Graphics;
import flash.display.Shape;
/**
* @author YOSHIDA, Akio (Aquioux)
*/
class AbstractLetter extends Shape {
// 色種別
static public const DARK1:String = "dark1";
static public const DARK2:String = "dark2";
static public const LIGHT1:String = "light1";
static public const LIGHT2:String = "light2";
static public const MIDDLE1:String = "middle1";
static public const MIDDLE2:String = "middle2";
// 大きさ
protected const SIZE:uint = 100;
protected const HALF_SIZE:uint = SIZE / 2;
// 紋様の太さ
protected const THICKNESS:uint = 18;
// 既定値は MIDDLE2
protected var baseColor_:uint = 0x000000;
protected var patternColor_:uint = 0xFFFFFF;
public function AbstractLetter(colorType:String) {
switch(colorType) {
case DARK1: {
baseColor_ = 0xCC0033;
patternColor_ = 0x000000;
break;
}
case DARK2: {
baseColor_ = 0x000000;
patternColor_ = 0xCC0033;
break;
}
case LIGHT1: {
baseColor_ = 0xCC0033;
patternColor_ = 0xFFFFFF;
break;
}
case LIGHT2: {
baseColor_ = 0xFFFFFF;
patternColor_ = 0xCC0033;
break;
}
case MIDDLE1: {
baseColor_ = 0xFFFFFF;
patternColor_ = 0x000000;
break;
}
}
}
protected function draw():void {
}
}
import flash.display.Graphics;
import flash.display.Shape;
/**
* @author YOSHIDA, Akio (Aquioux)
*/
class LetterO extends AbstractLetter {
public function LetterO(colorType:String) {
super(colorType);
draw();
}
override protected function draw():void {
var g:Graphics = graphics;
// 地
g.beginFill(baseColor_);
g.drawRect(0, 0, SIZE, SIZE);
// 紋
g.beginFill(patternColor_);
g.drawCircle(HALF_SIZE, HALF_SIZE, HALF_SIZE);
g.drawCircle(HALF_SIZE, HALF_SIZE, HALF_SIZE - THICKNESS);
g.endFill();
}
}
import flash.display.Graphics;
import flash.display.Shape;
/**
* @author YOSHIDA, Akio (Aquioux)
*/
class LetterX extends AbstractLetter {
private const POINT:Number = THICKNESS * Math.SQRT2 * 0.5;
public function LetterX(colorType:String) {
super(colorType);
draw();
}
override protected function draw():void {
var g:Graphics = graphics;
// 地
g.beginFill(baseColor_);
g.drawRect(0, 0, SIZE, SIZE);
// 紋
g.beginFill(patternColor_);
g.lineTo(POINT, 0);
g.lineTo(HALF_SIZE, HALF_SIZE - POINT);
g.lineTo(SIZE - POINT, 0);
g.lineTo(SIZE, 0);
g.lineTo(SIZE, POINT);
g.lineTo(HALF_SIZE + POINT, HALF_SIZE);
g.lineTo(SIZE, SIZE - POINT);
g.lineTo(SIZE, SIZE);
g.lineTo(SIZE - POINT, SIZE);
g.lineTo(HALF_SIZE, HALF_SIZE + POINT);
g.lineTo(POINT, SIZE);
g.lineTo(0, SIZE);
g.lineTo(0, SIZE - POINT);
g.lineTo(HALF_SIZE - POINT, HALF_SIZE);
g.lineTo(0, POINT);
g.lineTo(0, 0);
g.endFill();
}
}
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);
// グレイスケール用の各チャンネルの重みづけ
static public const LUM_R:Number = 0.298912;
static public const LUM_G:Number = 0.586611;
static public const LUM_B:Number = 0.114478;
// ---------- パブリックメソッド ----------
//
// 一つのチャンネルにおける濃度の平均値を求める(引数のヒストグラムで調整する)
static public function getAverageOfBrightness1(hist:Vector.<Number>):uint {
var sum:uint = 0;
var numOfPixel:uint = 0;
for (var i:int = 0; i < 256; i++) {
sum += i * hist[i];
numOfPixel += hist[i];
}
return sum / numOfPixel >> 0;
}
// RGB チャンネルにおける濃度の各平均値を求める
static public function getAverageOfBrightness3(hist:Vector.<Vector.<Number>>):Vector.<uint> {
var rSum:uint = 0;
var gSum:uint = 0;
var bSum:uint = 0;
var numOfPixel: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];
numOfPixel += hist[0];
}
return Vector.<uint>([rSum / numOfPixel >> 0, gSum / numOfPixel >> 0, bSum / numOfPixel >> 0]);
}
// 一つのチャンネルにおける濃度の平均値を求める(引数のヒストグラムで調整する)
static public function getSumOfBrightness1(hist:Vector.<Number>):uint {
var sum:uint = 0;
for (var i:int = 0; i < 256; i++) {
sum += i * hist[i];
}
return sum;
}
// RGB チャンネルにおける濃度の各平均値を求める
static public function getSumOfBrightness3(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];
}
return Vector.<uint>([rSum, gSum, bSum]);
}
}
import flash.display.BitmapData;
import flash.filters.BitmapFilterQuality;
import flash.filters.BlurFilter;
import flash.geom.Point;
/**
* BlurFilter による平滑化
* @author YOSHIDA, Akio (Aquioux)
*/
class Smooth implements IEffector {
// ---------- パブリックプロパティ ----------
//
/*
* ぼかしの強さ
* @param value 数値
*/
public function set strength(value:Number):void {
filter_.blurX = filter_.blurY = value;
}
/*
* ぼかしの質
* @param value 数値
*/
public function set quality(value:int):void {
filter_.quality = value;
}
// ---------- ローカルプロパティ ----------
//
private var filter_:BlurFilter; // ブラーフィルタ
private const ZERO_POINT:Point = EffectorUtils.ZERO_POINT;
// ---------- パブリックメソッド ----------
//
/*
* コンストラクタ
*/
public function Smooth() {
filter_ = new BlurFilter(2, 2, BitmapFilterQuality.MEDIUM);
}
/*
* 効果適用
* @param value 効果対象 BitmapData
*/
public function applyEffect(value:BitmapData):BitmapData {
value.applyFilter(value, value.rect, ZERO_POINT, filter_);
return value;
}
}
import flash.display.BitmapData;
import flash.filters.ColorMatrixFilter;
import flash.geom.Point;
/**
* 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 GrayScale implements IEffector {
// ---------- ローカルプロパティ ----------
//
private const R:Number = EffectorUtils.LUM_R;
private const G:Number = EffectorUtils.LUM_G;
private const B:Number = EffectorUtils.LUM_B;
private const MATRIX:Array = [
R, G, B, 0, 0,
R, G, B, 0, 0,
R, G, B, 0, 0,
0, 0, 0, 1, 0
];
private const FILTER:ColorMatrixFilter = new ColorMatrixFilter(MATRIX);
private const ZERO_POINT:Point = EffectorUtils.ZERO_POINT;
// ---------- パブリックメソッド ----------
//
/*
* 効果適用
* @param value 効果対象 BitmapData
*/
public function applyEffect(value:BitmapData):BitmapData {
value.applyFilter(value, value.rect, ZERO_POINT, FILTER);
return value;
}
}
import flash.display.BitmapData;
import flash.geom.Point;
/**
* paletteMap による BitmapData の減色
* 参考:「実践画像処理入門」 培風館 内村圭一・上瀧剛 P16 「2.5 濃度値の量子化による減色処理」
* @author YOSHIDA, Akio (Aquioux)
*/
class Posterize implements IEffector {
// ---------- パブリックプロパティ ----------
//
/*
* 減色の段階
* @param value 段階
*/
public function set degree(value:uint):void {
// value の有効範囲は 2 ~ 256
if (value < 2) value = 2;
if (value > 256) value = 256;
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 の引数となる各 Channel 用の Array
private var rArray_:Array = [];
private var gArray_:Array = [];
private var bArray_:Array = [];
private const ZERO_POINT:Point = EffectorUtils.ZERO_POINT;
// ---------- パブリックメソッド ----------
//
/*
* コンストラクタ
*/
public function Posterize() {
degree = 8; // degree のデフォルト
}
/*
* 効果適用
* @param value 効果対象 BitmapData
*/
public function applyEffect(value:BitmapData):BitmapData {
value.paletteMap(value, value.rect, ZERO_POINT, rArray_, gArray_, bArray_);
return value;
}
}
import flash.display.BitmapData;
import flash.filters.ColorMatrixFilter;
import flash.geom.Point;
/**
* コントラスト
* 参考:
* @author YOSHIDA, Akio (Aquioux)
*/
class Contrast implements IEffector {
// ---------- パブリックプロパティ ----------
//
/*
* コントラストの強さ
* @param value 強さ
*/
public function set strength(value:Number):void {
// value の有効範囲は -1 ~ 1
if (value < -1) value = -1;
if (value > 1) value = 1;
value += 1;
matrix_ = [
value, 0, 0, 0, 128 * (1 - value),
0, value, 0, 0, 128 * (1 - value),
0, 0, value, 0, 128 * (1 - value),
0, 0, 1, 0, 0,
0, 0, 0, 1, 0
];
filter_.matrix = matrix_;
}
// ---------- ローカルプロパティ ----------
//
private var matrix_:Array = [
1, 0, 0, 0, 0,
0, 1, 0, 0, 0,
0, 0, 1, 0, 0,
0, 0, 0, 1, 0
];
private var filter_:ColorMatrixFilter = new ColorMatrixFilter(matrix_);
private const ZERO_POINT:Point = EffectorUtils.ZERO_POINT;
// ---------- パブリックメソッド ----------
//
/*
* コンストラクタ
*/
public function Contrast() {
}
/*
* 効果適用
* @param value 効果対象 BitmapData
*/
public function applyEffect(value:BitmapData):BitmapData {
value.applyFilter(value, value.rect, ZERO_POINT, filter_);
return value;
}
}