forked from: forked from: ドロネー三角分割でWEBカメラ表示
/**
* Copyright s26 ( http://wonderfl.net/user/s26 )
* MIT License ( http://www.opensource.org/licenses/mit-license.php )
* Downloaded from: http://wonderfl.net/c/wSqI
*/
// forked from Aquioux's forked from: ドロネー三角分割でWEBカメラ表示
package {
//import aquioux.display.bitmapDataEffector.Edge2nd;
//import aquioux.display.bitmapDataEffector.GrayScale;
//import aquioux.display.bitmapDataEffector.Posterize;
//import aquioux.display.bitmapDataEffector.Smooth;
import com.bit101.components.HSlider;
import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.display.Graphics;
import flash.display.Sprite;
import flash.events.Event;
import flash.geom.Point;
import flash.geom.Rectangle;
[SWF(width = "465", height = "465", frameRate = "60", backgroundColor = "#FFFFFF")]
/**
* forked from: ドロネー三角分割でWEBカメラ表示
* @see http://aquioux.net/blog/?p=2962
* @see http://wonderfl.net/c/wlLw
* @see http://wonderfl.net/c/4cr7
* @author Aquioux(Yoshida, Akio)
*/
public class Main extends Sprite {
// サイズ
private const IMAGE_WIDTH:int = 465;
private const IMAGE_HEIGHT:int = 465;
// ウェブカメラ・ユニット
private var webcam_:WebCamera;
// ウェブカメラの映像を格納する BitmapData
private var webcamBmd_:BitmapData;
// 画像処理各種フィルター
private var smooth_:Smooth; // 平滑化
private var grayscale_:GrayScale; // グレイスケール
private var posterize_:Posterize; // 減色
private var edge_:Edge2nd; // エッジ検出
// 画像処理フィルター適用に必要なもの
private const RECT:Rectangle = new Rectangle(0, 0, IMAGE_WIDTH, IMAGE_HEIGHT);
private const ZERO_POINT:Point = new Point(0, 0);
// ウェブカメラの映像 BitmapData から getVector で取得したデータを格納する Vector 配列
private var colorPixelList_:Vector.<uint>; // フルカラー
private var grayPixelList_:Vector.<uint>; // グレイスケール
// ドロネー
private var delaunay_:Delaunay;
// スライダー
private var slider_:HSlider;
// 描画精度調整値
private var accuracy_:int;
// ドロネー三角描画
private var g_:Graphics;
// コンストラクタ
public function Main() {
// ウェブカメラのセット
try {
webcam_ = new WebCamera(IMAGE_WIDTH, IMAGE_HEIGHT, this.stage.frameRate);
} catch (err:Error) {
throw new Error(err.message);
return;
}
// ウェブカメラのイベントハンドラ登録
webcam_.addEventListener(Event.ACTIVATE, setup);
}
// ウェブカメラのイベントハンドラ
private function setup(e:Event):void {
e.target.removeEventListener(Event.ACTIVATE, arguments.callee);
// ウェブカメラの映像を格納する BitmapData の生成
webcamBmd_ = new BitmapData(IMAGE_WIDTH, IMAGE_HEIGHT);
// 画像処理フィルター生成
smooth_ = new Smooth(); // 平滑化
smooth_.strength = 4;
grayscale_ = new GrayScale(); // グレイスケール
posterize_ = new Posterize(); // 減色
posterize_.degree = 6;
edge_ = new Edge2nd(); // エッジ検出
// ウェブカメラの映像 BitmapData から getVector で取得したデータを格納する Vector 配列の生成
colorPixelList_ = new Vector.<uint>(IMAGE_WIDTH * IMAGE_HEIGHT, true);
grayPixelList_ = new Vector.<uint>(IMAGE_WIDTH * IMAGE_HEIGHT, true);
// ドロネークラスの生成
delaunay_ = new Delaunay();
// スライダーの生成
var shiftValue:int = 20;
var minValue:int = 10;
var maxValue:int = 30;
var initValue:int = (maxValue-minValue) / 2 + minValue;
slider_ = new HSlider(this, shiftValue, IMAGE_HEIGHT- shiftValue, sliderHandler);
slider_.width = IMAGE_WIDTH - (shiftValue * 2);
slider_.setSliderParams(minValue, maxValue, initValue);
// 描画精度調整値を初期化
accuracy_ = initValue;
// ドロネー三角描画場所の指定
g_ = this.graphics;
// ENTER_FRAME イベントハンドラ登録
addEventListener(Event.ENTER_FRAME, update);
}
// ENTER_FRAME イベントハンドラ
private function update(event:Event):void {
// ウェブカメラの映像
webcamBmd_ = webcam_.bitmapData;
// ウェブカメラの映像のカラーピクセル情報を待避
colorPixelList_ = webcamBmd_.getVector(RECT);
// 画像処理
smooth_.applyEffect(webcamBmd_); // 平滑化
grayscale_.applyEffect(webcamBmd_); // グレイスケール
posterize_.applyEffect(webcamBmd_); // 減色
edge_.applyEffect(webcamBmd_); // 境界線抽出
// 画像処理後のウェブカメラの映像のピクセル情報を取得
grayPixelList_ = webcamBmd_.getVector(RECT);
// ドロネー母点の処理
// ドロネー母点格納 Vector 配列
if (points != null) {
points.length = 0;
points = null;
}
var points:Vector.<Point> = new Vector.<Point>();
// ステージの四隅をドロネー母点として登録
points.push(
new Point(0, 0),
new Point(IMAGE_WIDTH, 0),
new Point(0, IMAGE_HEIGHT),
new Point(IMAGE_WIDTH, IMAGE_HEIGHT)
);
// ドロネー母点生成
var len:int = grayPixelList_.length;
for (var i:int = 0; i < len; i++) {
if (grayPixelList_[i] > 0xFF000000) // 全ピクセル中、黒以外がドロネー母点となり得るが……
if (Math.random() * accuracy_ < 1) // すべてをドロネー母点とはせず、 accurary_ で間引く
points.push(new Point(i % IMAGE_WIDTH, i / IMAGE_HEIGHT >> 0));
}
// 描画
g_.clear();
delaunay_.render(g_, points, delaunay_.compute(points), colorPixelList_);
}
// スライダーのイベントハンドラ
private function sliderHandler(event:Event):void {
accuracy_ = HSlider(event.target).value >> 0;
}
}
}
//package {
import flash.display.BitmapData;
import flash.display.DisplayObject;
import flash.display.IBitmapDrawable;
import flash.events.ActivityEvent;
import flash.events.Event;
import flash.events.EventDispatcher;
import flash.geom.Matrix;
import flash.geom.Point;
import flash.geom.Rectangle;
import flash.media.Camera;
import flash.media.Video;
/**
* WebCamera Unit
* @author YOSHIDA, Akio (Aquioux)
*/
/*public*/ class WebCamera extends EventDispatcher {
/**
* ウェブカメラの映像
*/
public function get bitmapData():BitmapData {
_bitmapData.draw(video_, matrix_);
return _bitmapData;
}
private var _bitmapData:BitmapData;
private var video_:Video; // ビデオ
private var matrix_:Matrix; // 鏡像になるようにさせるための Matrix
/**
* コンストラクタ
* @param cameraWidth カメラ幅
* @param cameraHeight カメラ高
*/
public function WebCamera(cameraWidth:int, cameraHeight:int, frameRate:int = 30) {
// 外部へ渡す BitmapData の生成
_bitmapData = new BitmapData(cameraWidth, cameraHeight, true, 0x0);
// 鏡像になるよう、Matrix で左右反転
matrix_ = new Matrix(-1, 0, 0, 1, cameraWidth, 0);
// カメラ準備
var camera:Camera = Camera.getCamera();
if (camera) {
// camera のセットアップ
camera.setMode(cameraWidth, cameraHeight, 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 {
e.target.removeEventListener(ActivityEvent.ACTIVITY, arguments.callee);
dispatchEvent(new Event(Event.ACTIVATE));
}
}
//}
//package aquioux.display.bitmapDataEffector {
import flash.display.BitmapData;
import flash.filters.BitmapFilterQuality;
import flash.filters.BlurFilter;
import flash.geom.Point;
/**
* BlurFilter による平滑化
* @author YOSHIDA, Akio (Aquioux)
*/
/*public*/ 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;
}
}
//}
//package aquioux.display.bitmapDataEffector {
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)
*/
/*public*/ 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;
}
}
//}
//package aquioux.display.bitmapDataEffector {
import flash.display.BitmapData;
import flash.geom.Point;
/**
* paletteMap による BitmapData の減色
* 「実践画像処理入門」 培風館 内村圭一・上瀧剛 P16 「2.5 濃度値の量子化による減色処理」
* @author YOSHIDA, Akio (Aquioux)
*/
/*public*/ 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;
if (_gradation) {
_gradation.fixed = false;
_gradation.length = 0;
} else {
_gradation = new Vector.<uint>();
}
var prevVal:uint = 0xFF;
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;
if (prevVal != val) {
_gradation.push(val);
prevVal = val;
}
}
_gradation.fixed = true;
}
// 減色化によって計算された gradation の値を格納する Vector
// degree を set することではじめて有効になる
// length は degree になる
public function get gradation():Vector.<uint> { return _gradation; }
private var _gradation:Vector.<uint>;
// 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;
}
}
//}
//package aquioux.display.bitmapDataEffector {
import flash.display.BitmapData;
import flash.filters.ConvolutionFilter;
import flash.geom.Point;
/**
* ConvolutionFilter によるエッジ検出 2次微分
* 「OpenGL+GLSLによる画像処理プログラミング」 工学社 酒井幸市 P104 「5.1 差分フィルタ」
* 「C言語で学ぶ実践画像処理」 オーム社 井上誠喜・他 P47 「4.7 ラプラシアンとゼロ交差により輪郭線を求める」
* http://msdn.microsoft.com/ja-jp/academic/cc998604.aspx
* @author YOSHIDA, Akio (Aquioux)
*/
/*public*/ class Edge2nd implements IEffector {
private const MATRIX:Array = [
0, 1, 0,
1, -4, 1,
0, 1, 0
];
private const FILTER:ConvolutionFilter = new ConvolutionFilter(3, 3, 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;
}
}
//}
//package aquioux.display.bitmapDataEffector {
import flash.display.BitmapData;
/**
* BitmapDataEffector 用 interface
* @author YOSHIDA, Akio (Aquioux)
*/
/*public*/ interface IEffector {
function applyEffect(value:BitmapData):BitmapData;
}
//}
//package aquioux.display.bitmapDataEffector {
import flash.geom.Point;
/**
* bitmapDataEffector パッケージ内のクラスで共通に使う定数など
* @author YOSHIDA, Akio (Aquioux)
*/
/*public*/ class EffectorUtils {
// BitmapData が備える各種メソッドの destPoint 用
static public const ZERO_POINT:Point = new Point(0, 0);
// グレイスケール用の各チャンネルの重みづけ
// 単純な平均
//static public const LUM_R:Number = 1/3;
//static public const LUM_G:Number = 1/3;
//static public const LUM_B:Number = 1/3;
// NTSC系加重平均法(YIQ,YCbCr も同じ)
static public const LUM_R:Number = 0.298912;
static public const LUM_G:Number = 0.586611;
static public const LUM_B:Number = 0.114478;
// HDTV
//static public const LUM_R:Number = 0.2126;
//static public const LUM_G:Number = 0.7152;
//static public const LUM_B:Number = 0.0722;
}
//}
/**
* Copyright nicoptere ( http://wonderfl.net/user/nicoptere )
* MIT License ( http://www.opensource.org/licenses/mit-license.php )
* Downloaded from: http://wonderfl.net/c/4cr7
*/
/**
* Copyright s26 ( http://wonderfl.net/user/s26 )
* MIT License ( http://www.opensource.org/licenses/mit-license.php )
* Downloaded from: http://wonderfl.net/c/wlLw
*/
import flash.display.Graphics;
import flash.geom.Point;
class Delaunay
{
static public var EPSILON:Number = Number.MIN_VALUE;
static public var SUPER_TRIANGLE_RADIUS:Number = 1000000000;
private var indices:Vector.<int>;
private var circles:Vector.<Number>;
public function compute( points:Vector.<Point> ):Vector.<int>
{
var nv:int = points.length;
if (nv < 3) return null;
var d:Number = SUPER_TRIANGLE_RADIUS;
points.push( new Point( 0, -d ), new Point( d, d ), new Point( -d, d ) );
indices = Vector.<int>( [ points.length-3, points.length-2, points.length-1 ] );
circles = Vector.<Number>( [ 0, 0, d ] );
var edgeIds:Vector.<int> = new Vector.<int>();
var i:int, j:int, k:int, id0:int, id1:int, id2:int;
for ( i = 0; i < nv; i++)
{
for ( j = 0; j < indices.length; j+=3 )
{
if ( circles[ j + 2 ] > EPSILON && circleContains( j, points[ i ] ) )
{
id0 = indices[ j ];
id1 = indices[ j + 1 ];
id2 = indices[ j + 2 ];
edgeIds.push( id0, id1, id1, id2, id2, id0 );
indices.splice( j, 3 );
circles.splice( j, 3 );
j -= 3;
}
}
for ( j = 0; j < edgeIds.length; j+=2 )
{
for ( k = j + 2; k < edgeIds.length; k+=2 )
{
if( ( edgeIds[ j ] == edgeIds[ k ] && edgeIds[ j + 1 ] == edgeIds[ k + 1 ] )
|| ( edgeIds[ j + 1 ] == edgeIds[ k ] && edgeIds[ j ] == edgeIds[ k + 1 ] ) )
{
edgeIds.splice( k, 2 );
edgeIds.splice( j, 2 );
j -= 2;
k -= 2;
if ( j < 0 ) break;
if ( k < 0 ) break;
}
}
}
for ( j = 0; j < edgeIds.length; j+=2 )
{
indices.push( edgeIds[ j ], edgeIds[ j + 1 ], i );
computeCircle( points, edgeIds[ j ], edgeIds[ j + 1 ], i );
}
edgeIds.length = 0;
}
id0 = points.length - 3;
id1 = points.length - 2;
id2 = points.length - 1;
for ( i = 0; i < indices.length; i+= 3 )
{
if ( indices[ i ] == id0 || indices[ i ] == id1 || indices[ i ] == id2
|| indices[ i + 1 ] == id0 || indices[ i + 1 ] == id1 || indices[ i + 1 ] == id2
|| indices[ i + 2 ] == id0 || indices[ i + 2 ] == id1 || indices[ i + 2 ] == id2 )
{
indices.splice( i, 3 );
i-=3;
continue;
}
}
points.pop();
points.pop();
points.pop();
return indices;
}
private function circleContains( circleId:int, p:Point ):Boolean
{
var dx:Number = circles[ circleId ] - p.x;
var dy:Number = circles[ circleId + 1 ] - p.y;
return circles[ circleId + 2 ] > dx * dx + dy * dy;
}
private function computeCircle( points:Vector.<Point>, id0:int, id1:int, id2:int ):void
{
var p0:Point = points[ id0 ];
var p1:Point = points[ id1 ];
var p2:Point = points[ id2 ];
var A:Number = p1.x - p0.x;
var B:Number = p1.y - p0.y;
var C:Number = p2.x - p0.x;
var D:Number = p2.y - p0.y;
var E:Number = A * (p0.x + p1.x) + B * (p0.y + p1.y);
var F:Number = C * (p0.x + p2.x) + D * (p0.y + p2.y);
var G:Number = 2.0 * (A * (p2.y - p1.y) - B * (p2.x - p1.x));
var x:Number = (D * E - B * F) / G;
circles.push( x );
var y:Number = (A * F - C * E) / G;
circles.push( y );
x -= p0.x;
y -= p0.y;
circles.push( x * x + y * y );
}
public function render(graphics:Graphics, points:Vector.<Point>, indices:Vector.<int>,colorList:Vector.<uint>):void
{
var id0:uint, id1:uint, id2:uint;
for ( var i:int = 0; i < indices.length; i+=3 )
{
id0 = indices[ i ];
id1 = indices[ i + 1 ];
id2 = indices[ i + 2 ];
var cntX:int = int((points[id0].x + points[id1].x + points[id2].x) / 3);
var cntY:int = int((points[id0].y + points[id1].y + points[id2].y) / 3);
graphics.beginFill(colorList[cntY * 465 + cntX]);
graphics.moveTo( points[ id0 ].x, points[ id0 ].y );
graphics.lineTo( points[ id1 ].x, points[ id1 ].y );
graphics.lineTo( points[ id2 ].x, points[ id2 ].y );
graphics.lineTo( points[ id0 ].x, points[ id0 ].y );
graphics.endFill();
}
}
}