[WebCam]ジャギ男
[WebCam]ジャギ男
参考:http://wonderfl.net/c/sJ7a
説明:http://aquioux.blog48.fc2.com/blog-entry-706.html
@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/oNS2
*/
package {
import flash.display.Sprite;
import net.hires.debug.Stats;
[SWF(width = "465", height = "465", frameRate = "30", backgroundColor = "#000000")]
/**
* [WebCam]ジャギ男
* 参考:http://wonderfl.net/c/sJ7a
* 説明:http://aquioux.blog48.fc2.com/blog-entry-706.html
* @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;
// 各エフェクタ
private var grayscale_:GrayScale; // グレイスケール
private var nega_:Negative; // 反転
private var binarize_:Binarize; // 二値
private var tone_:HalftoneMono; // ハーフトーン
// ---------- パブリックメソッド ----------
//
/**
* コンストラクタ
* @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("カメラがありません。");
}
// エフェクタ
grayscale_ = new GrayScale(); // グレイスケール
nega_ = new Negative(); // 反転
binarize_ = new Binarize(); // 二値
binarize_.auto = true;
tone_ = new HalftoneMono();// ハーフトーン
tone_.size = 15;
}
// ---------- ローカルメソッド ----------
//
// 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_);
// エフェクト適用
grayscale_.applyEffect(_data); // グレイスケール
nega_.applyEffect(_data); // 反転
binarize_.applyEffect(_data); // 二値
tone_.applyEffect(_data); // ハーフトーン
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;
import flash.filters.DropShadowFilter;
/**
* 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);
filters = [new BlurFilter(2, 2, BitmapFilterQuality.HIGH)];
}
// ---------- ローカルメソッド ----------
//
// Model から Event.CHANGE が発行されたときの処理
private function changeHandler(e:Event):void {
bitmapData = _model.data;
}
}
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 MATRIX:Array = [
0.3, 0.59, 0.11, 0, 0,
0.3, 0.59, 0.11, 0, 0,
0.3, 0.59, 0.11, 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.filters.ConvolutionFilter;
import flash.geom.Point;
/**
* ConvolutionFilter による BitmapData の色反転
* 参考:http://www40.atwiki.jp/spellbound/pages/231.html
* @author YOSHIDA, Akio (Aquioux)
*/
class Negative implements IEffector {
// ---------- ローカルプロパティ ----------
//
private const FILTER:ConvolutionFilter = new ConvolutionFilter(1, 1, [ -1], 1, 255);
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;
import flash.geom.Rectangle;
/**
* 二値化
* 閾値より大きな値のピクセルは白、それ以外は黒に置き換える
* @author YOSHIDA, Akio (Aquioux)
*/
class Binarize implements IEffector {
// ---------- パブリックプロパティ ----------
//
// 閾値
public function get threshold():int { return _threshold; }
public function set threshold(value:int):void {
if (value < 0) value = 0;
if (value > 255) value = 255;
_threshold = value;
}
private var _threshold:int = 127; // 閾値:0 ~ 255
public function get auto():Boolean { return _auto; }
public function set auto(value:Boolean):void { _auto = value; }
private var _auto:Boolean = false;
// ---------- ローカルプロパティ ----------
//
private const ZERO_POINT:Point = EffectorUtils.ZERO_POINT;
// ---------- パブリックメソッド ----------
//
/*
* 効果適用
* @param value 効果対象 BitmapData
*/
public function applyEffect(value:BitmapData):BitmapData {
var bufferBmd:BitmapData = new BitmapData(value.width, value.height);
var rect:Rectangle = bufferBmd.rect;
bufferBmd = value.clone();
if (_auto) {
var hist:Vector.<Vector.<Number>> = value.histogram();
var numOfPixel:uint = rect.width * rect.height;
_threshold = calcThresholdRough(hist[2], numOfPixel);
}
value.fillRect(rect, 0xFF000000);
value.threshold(bufferBmd, rect, ZERO_POINT, ">", _threshold, 0xFFFFFFFF, 0x000000FF, false);
return value;
}
// ---------- ローカルメソッド ----------
//
// 閾値の判別分析
private function calcThresholdRough(hist:Vector.<Number>, numOfPixel:uint):uint {
var total:uint;
for (var i:int = 0; i < 256; i++) {
total += hist[i] * i;
}
return total / numOfPixel >> 0;
}
// 閾値の判別分析
private function calcThresholdStrict(hist:Vector.<Number>, numOfPixel:uint):uint {
var maxSeparability:Number = 0; // 最大分離値を待避させる変数
var maxDegree:uint = 0; // そのときの階調を待避させる変数
for (var i:int = 1; i < 255; i++) {
// 1~254 を閾値としたときの分離度を計算し、最大値を待避する
var Separability:Number = calcSeparability(i, hist, numOfPixel);
if (Separability > maxSeparability) {
maxSeparability = Separability;
maxDegree = i;
}
}
return maxDegree;
}
// 分離度の計算
private function calcSeparability(threshold:uint, hist:Vector.<Number>, numOfPixel:uint):Number {
var i:uint; // ループカウンター
var num1:uint = 0, num2:uint = 0; // 各領域の画素数
var con1:Number = 0, con2:Number = 0; // 各領域の濃度(濃度平均値)
var con:Number = 0; // 濃度中間値
var dis1:Number, dis2:Number; // 分散計算用
var within:Number = 0; // クラス内分散値
var between:Number = 0; // クラス間分散値
// 二つの領域の画素数と濃度を計算
for (i = 0; i < threshold; i++) {
num1 += hist[i];
con1 += i * hist[i];
}
for (i = threshold; i < 256; i++) {
num2 += hist[i];
con2 += i * hist[i];
}
con = (con1 + con2) / numOfPixel; // 濃度中間値
con1 /= num1; // 領域1の濃度平均値
con2 /= num2; // 領域2の濃度平均値
if (num1 == 0 || num2 == 0) return 0;
// 分散を計算
// クラス内分散
for (i = 0; i < threshold; i++) {
dis1 = i - con1;
within += dis1 * dis1 * hist[i];
}
for (i = threshold; i < 256; i++) {
dis2 = i - con2;
within += dis2 * dis2 * hist[i];
}
within /= numOfPixel;
// クラス間分散
for (i = 0; i < threshold; i++) {
dis1 = con - con1;
between += dis1 * dis1 * hist[i];
}
for (i = threshold; i < 256; i++) {
dis2 = con - con2;
between += dis2 * dis2 * hist[i];
}
between /= numOfPixel;
return between / within;
}
}
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 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.geom.Point;
import flash.geom.Rectangle;
/**
* ハーフトーン(モノクロ)
* @author YOSHIDA, Akio (Aquioux)
*/
class HalftoneMono 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 数
private const BASE_COLOR:uint = 0xFFFFFFFF;
private const DOT_COLOR:uint = 0xFF000000;
private const ZERO_POINT:Point = EffectorUtils.ZERO_POINT;
private const GET_BRIGHTNESS:Function = EffectorUtils.getSumOfBrightness1;
// ---------- パブリックメソッド ----------
//
/*
* コンストラクタ
*/
public function HalftoneMono(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 {
if (width_ != value.width || height_ != value.height) init(value.width, value.height);
var saveBmd:BitmapData = value.clone(); // 入力画像を待避
value.fillRect(value.rect, BASE_COLOR); // 塗りつぶす
// モザイクブロックごとの走査
value.lock();
for (var i:int = 0; i < hNum_; i++) {
for (var j:int = 0; j < wNum_; j++) {
// モザイク領域読込用としての blockRect_ 設定
blockRect_.x = j * _size;
blockRect_.y = i * _size;
blockRect_.width = blockRect_.height = _size;
// モザイク領域を全体から切り取る
blockBmd_.copyPixels(saveBmd, blockRect_, ZERO_POINT);
// モザイク領域の対象カラーチャンネルの平均輝度取得
var brightness:uint = GET_BRIGHTNESS(blockBmd_.histogram()[0]) / blockPixels_;
// 描画サイズの設定
var size:Number = _size * (brightness / 255);
if (size >= 2) {
// 描画開始位置オフセット計算
var offset:Number = (_size - size) / 2;
// トーン描画用としての blockRect_ 設定
blockRect_.x += offset;
blockRect_.y += offset;
blockRect_.width = blockRect_.height = size;
// 描画
value.fillRect(blockRect_, DOT_COLOR);
}
}
}
value.unlock();
return value;
}
// ---------- ローカルメソッド ----------
//
// 初期化
private function init(width:Number, height:Number):void {
width_ = width; // 対象幅を待避
height_ = height; // 対象高を待避
size = _size; // _size の設定
}
}