l'essentiel est invisible pour les yeux

Sunday, February 17, 2008

as3filters: 画像処理用フィルターライブラリの公開とPhoto BoothをAS3で作った

第二回目のActionScript記事だ。前回は、AS3の練習にとas3zerobugを作ったが、ActionScript -> Firebugプロキシは既出だったようだ。ま、誰でも考えるアイデアだ。

as3filters



あなたのPCにカメラがついているならば、そいつは、いつもあなたを撮影する機会をうかがっている。Skypeのビデオチャットにしか使わないなんてもったいない。彼らをもっと面白く使ってやろう。リアルな世界がデジタルな世界に瞬時に取り込まれる面白さは、取り込んだリアルな情報を加工した時に現れると思う。例えば、あなたの顔をリアルタイムでとても美人に映し出すなんてどうだろうか?複合現実が当たり前になり、現実と仮想の区別がつかなくなった数世紀後には、人の顔なんて今の原型をなしていないかもしれない。

ブサイクだろうが、相手に見えている顔は現実の世界にオーバーレイされた仮想のあなたの顔だからだ。

さて、今回は画像処理用のフィルターを集めたライブラリを作ってみた。
残念ながら、(今の所)貴方の顔をかっこよく映し出したり、かわいく見せたりするフィルタではない。顔を回転させたり、つぶしたり、部分的に拡大したりと、Photoshopのフィルタ群でよくみられるフィルタだ。ま、ほとんとの場合ブサイクになるだろう。

CPUは消費するが、カメラで取り込んだ映像にリアルタイムでフィルターをかける事は、AS3では十分に可能だ。ハードウェアの進歩に感謝する。ブサイクフィルタだけでなくて、もっとおもしろいアイデアを持っている人を募集している。是非、as3filtersプロジェクトに参加してほしい。

Mac OSXユーザの皆様は、Photo Boothというアプリケーションを知っていると思う。このアプリケーションは、カメラで撮影と撮影した画像に面白いエフェクトをかけることのできる、シンプルなアプリケーションである。このアプリケーションをFlashで作った。

as3filters: Photo Booth Demo


このデモを実行するには、カメラが必要なのでカメラを持っていないユーザは、下記のプロジェクトページからスナップショットが見てください。さすがに、8つのフィルタを同時に処理するとCPU負荷が高いが、一つなら問題ない。

as3filters



as3filtersは、これらのエフェクトをActionScriptで簡単に当てる事のできるフィルターライブラリだ。

インストール方法
チェックアウトして、コンパイル時にリンクする。

% svn checkout http://as3filters.googlecode.com/svn/trunk/ as3filters
% cp as3filters/bin/as3filters.swc /path/to/your-project
% mxmlc -compiler.include-libraries as3filters.swc


Flex-configファイルを使用してもOK。

<?xml version="1.0"?>
<flex-config>
<compiler>
<library-path append="true">
<path-element>../bin/as3filters.swc</path-element>
</library-path>
</compiler>
</flex-config>



使い方
Filterクラスのスタティックメソッドで、フィルタを作成して、BitmapData#applyFilterメソッドでフィルタを適用する。第一引数は、フィルタ適用対象のBitmapDataオブジェクトを渡す。regionはフィルタを適用する領域となる。


var bmd:BitmapData = new BitmapData(width, height, true);
var region:Rectangle = new Rectangle(30, 30, 90, 90)
var twirlFilter:DisplacementMapFilter = Filter.twirlFilter(bmd, region);
bmd.draw(video);
bmd.applyFilter(bmd, bmd.rect, new Point(0, 0), twirlFilter);



詳しくは、以下のPhoto Booth Demoアプリケーションのソースをご覧あれ。

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
layout="absolute"
color="#000"
themeColor="#FFFFFF"
styleName="plain"
creationComplete="init();">

<mx:Script> <![CDATA[
import flash.display.*;
import flash.filters.*;
import flash.media.Camera;
import as3.Filter;
import mx.controls.*;
import mx.core.*;

private var camera:Camera;
private var fps:int = 18;
private const VIDEO_WIDTH:int = 150,
VIDEO_HEIGHT:int = 150;

private function setupVideo():void
{
bulge_video.width = dent_video.width = twirl_video.width = VIDEO_WIDTH;
squeeze_video.width = video.width = mirror_video.width = VIDEO_WIDTH;
tunnel_video.width = fisheye_video.width = strech_video.width = VIDEO_WIDTH;
bulge_video.height = dent_video.height = twirl_video.height = VIDEO_HEIGHT;
squeeze_video.height = video.height = mirror_video.height = VIDEO_HEIGHT;
tunnel_video.height = fisheye_video.height = strech_video.height = VIDEO_HEIGHT;
}

public function init():void
{
// setup camera
if(connectCamera()) {
// Setup video
setupVideo();
video.attachCamera(camera);

setInterval((function():Function {
// setup filters
var width:int = video.width;
var height:int = video.height;
var bmd:BitmapData = new BitmapData(width, height, true);
var region:Rectangle = new Rectangle(30, 30, 90, 90);
var bulgeFilter:DisplacementMapFilter = Filter.bulgeFilter(bmd, region);
var twirlFilter:DisplacementMapFilter = Filter.twirlFilter(bmd, region);
var squeezeFilter:DisplacementMapFilter = Filter.squeezeFilter(bmd);
var pinchFilter:DisplacementMapFilter = Filter.pinchFilter(bmd, region);
var tunnelFilter:DisplacementMapFilter = Filter.photicTunnelFilter(bmd, region);
var fisheyeFilter:DisplacementMapFilter = Filter.fisheyeFilter(bmd);
var strechFilter:DisplacementMapFilter = Filter.strechFilter(bmd);

// TODO: Dent filter is not supported yet.
var dentFilter:DisplacementMapFilter = Filter.squeezeFilter(bmd, region, 0.3);

return function():void {
// original
bmd.draw(video);

// bulge effect
var bulgeBmd:BitmapData = new BitmapData(width, height, false);
bulgeBmd.applyFilter(bmd, bmd.rect, new Point(0, 0), bulgeFilter);
bulge_video.addChild(new Bitmap(bulgeBmd));

// twirl effect
var twirlBmd:BitmapData = new BitmapData(width, height, false);
twirlBmd.applyFilter(bmd, bmd.rect, new Point(0, 0), twirlFilter);
twirl_video.addChild(new Bitmap(twirlBmd));

// squeeze effect
var squeezeBmd:BitmapData = new BitmapData(width, height, false);
squeezeBmd.applyFilter(bmd, bmd.rect, new Point(0, 0), squeezeFilter);
squeeze_video.addChild(new Bitmap(squeezeBmd));

// pinch effect
var tunnelBmd:BitmapData = new BitmapData(width, height, false);
tunnelBmd.applyFilter(bmd, bmd.rect, new Point(0, 0), tunnelFilter);
tunnel_video.addChild(new Bitmap(tunnelBmd));

// mirror effect
// NOTE: A little tricky because return BitmapData object directly.
mirror_video.addChild(new Bitmap(Filter.mirror(bmd)));

// fisheye effect
var fisheyeBmd:BitmapData = new BitmapData(width, height, false);
fisheyeBmd.applyFilter(bmd, bmd.rect, new Point(0, 0), fisheyeFilter);
fisheye_video.addChild(new Bitmap(fisheyeBmd));

// strech effect
var strechBmd:BitmapData = new BitmapData(width, height, false);
strechBmd.applyFilter(bmd, bmd.rect, new Point(0, 0), strechFilter);
strech_video.addChild(new Bitmap(strechBmd));

// dent effect
// TODO: Dent effect is not supported yet
var dentBmd:BitmapData = new BitmapData(width, height, false);
dentBmd.applyFilter(bmd, bmd.rect, new Point(0, 0), dentFilter);
dent_video.addChild(new Bitmap(dentBmd));
};
})(), 1000 / fps);
}
}
// {{{
private function rotate(source:BitmapData):BitmapData
{
var width:int = source.width;
var height:int = source.height;
var matrix:Matrix = new Matrix();
var rotation:Number = Math.PI / 2;
var dx:int = Math.floor(width / 2);
var dy:int = Math.floor(height / 2);
matrix.rotate(rotation);
matrix.translate(width, 0);
var bmd:BitmapData = new BitmapData(width, height, false);
bmd.draw(source, matrix);

return bmd;
}
// }}}

private function connectCamera():Boolean
{
camera = Camera.getCamera();
if(camera) {
camera.setMode(VIDEO_WIDTH, VIDEO_HEIGHT, fps);
return true;
} else {
return false;
}
}
]]></mx:Script>
<mx:VBox>
<mx:Label text="AS3 Photo Booth." fontSize="16"/>
<mx:HBox>
<mx:VideoDisplay id="bulge_video"/>
<mx:VideoDisplay id="dent_video"/>
<mx:VideoDisplay id="twirl_video"/>
</mx:HBox>
<mx:HBox>
<mx:VideoDisplay id="squeeze_video"/>
<mx:VideoDisplay id="video"/>
<mx:VideoDisplay id="mirror_video"/>
</mx:HBox>
<mx:HBox>
<mx:VideoDisplay id="tunnel_video"/>
<mx:VideoDisplay id="fisheye_video"/>
<mx:VideoDisplay id="strech_video"/>
</mx:HBox>
</mx:VBox>
</mx:Application>



Filter.asのソース

package as3 {
import flash.display.*;
import flash.geom.*;
import flash.filters.*;

/**
* as.Filter
*
* as3.Filter class includes methods generate variety of filters for image processing.
*/
public class Filter
{
/**
* Generate the DisplacementMapFilter for twirling effect.
*
* You can apply the filter is as follows:
*
* var bmd:BitmapData = new BitmapData(width, height, false);
* var filter:DisplacementMapFilter = Filter.twirlFilter(bmd);
* bmd.draw(video);
* bmd.applyFilter(bmd, bmd.rect, new Point(0, 0), filter);
*
* You can also apply the filter specified region:
*
* var region:Rectangle = new Rectangle(100, 100, 100, 100);
* var filter:DisplacementMapFilter = Filter.twirlFilter(bmd, rect);
*
* @params source BitmapData The input bitmap data to apply the twirling effect.
* @params region Rectangle The region to apply the twirling effect.
* @params rotation Number The max amount to rotate, in radius.
* Default is Math.PI / 2.
*
* @return DisplacementMapFilter Filter to apply twirl effect
*/
static public function twirlFilter(source:BitmapData, region:Rectangle=null,
rotation:Number=0):DisplacementMapFilter
{
var width:int = source.width;
var height:int = source.height;
region ||= new Rectangle(0, 0, width, height);
rotation ||= Math.PI / 2;
var dbmd:BitmapData = new BitmapData(width, height, false, 0x8080);
var radius:Number = Math.min(region.width, region.height) / 2;
var centerX:int = region.x + region.width / 2;
var centerY:int = region.y + region.height / 2;
for(var y:int=0;y<height;++y) {
var ycoord:int = y - centerY;
for(var x:int=0;x<width;++x) {
var xcoord:int = x - centerX;
var dr:Number = radius - Math.sqrt(xcoord * xcoord + ycoord * ycoord);
if(dr > 0) {
var angle:Number = dr / radius * rotation;
var dx:Number = xcoord * Math.cos(angle) - ycoord * Math.sin(angle) - xcoord;
var dy:Number = xcoord * Math.sin(angle) + ycoord * Math.cos(angle) - ycoord;
var blue:int = 0x80 + Math.round(dx / width * 0xff);
var green:int = 0x80 + Math.round(dy / height * 0xff);
dbmd.setPixel(x, y, green << 8 | blue);
}
}
}
return new DisplacementMapFilter(dbmd,
new Point(0, 0),
BitmapDataChannel.BLUE,
BitmapDataChannel.GREEN,
width,
height,
DisplacementMapFilterMode.IGNORE);
}

/**
* Generate the BitmapData which applied mirror effect.
*
* You can create the mirrored BitmapData is as follows:
*
* var bmd = new BitmapData(video.width, video.height, false);
* bmd.draw(video);
* var mirroredBmd = Effect.mirror(bmd);
*
* @params source BitmapData The input bitmap data to apply the mirror effect.
* @params region Rectangle The region to apply the twirling effect. Default is entire region.
* @return BitmapData BitmapData which applied the mirror effect
*/
static public function mirror(source:BitmapData):BitmapData
{
var bmd:BitmapData = new BitmapData(source.width, source.height, false);
var halfWidth:int = Math.round(source.width / 2);
source.lock();
bmd.copyPixels(source, new Rectangle(0, 0, halfWidth, source.height), new Point(0,0));
for(var i:int=0;i<source.height;++i) {
for(var j:int=0;j<halfWidth;++j) {
bmd.setPixel32(halfWidth + j, i, source.getPixel32(halfWidth - j, i));
}
}
source.unlock();
return bmd;
}

/**
* Generate the DisplacementMapFilterMode for pinch effect.
*
* You can apply the filter is as follows:
*
* var bmd:BitmapData = new BitmapData(width, height, false);
* var filter:DisplacementMapFilter = Effect.pinchFilter(bmd);
* bmd.draw(video);
* bmd.applyFilter(bmd, bmd.rect, new Point(0, 0), filter);
*
* You can also apply the filter specified region:
*
* var region:Rectangle = new Rectangle(100, 100, 100, 100);
* var amount:Number = 0.5;
* var filter:DisplacementMapFilter = Effect.pinchFilter(bmd, rect, amount);
*
* @params source BitmapData The input bitmap data to apply the twirling effect.
* @params region Rectangle The region to apply the twirling effect.
* @params amount Number Amount of pinch. (-1 <= x <= 1)
* Default is 0.35.
*/
static public function pinchFilter(source:BitmapData, region:Rectangle=null,
amount:Number=0.35):DisplacementMapFilter
{
var width:int = source.width;
var height:int = source.height;
region ||= new Rectangle(0, 0, width, height);
var radius:Number = Math.min(region.width, region.height) / 2;
var centerX:int = region.x + region.width / 2;
var centerY:int = region.y + region.height / 2;
var dbmd:BitmapData = new BitmapData(width, height, false, 0x8080);
for(var y:int=0;y<height;++y) {
var ycoord:int = y - centerY;
for(var x:int=0;x<width;++x) {
var xcoord:int = x - centerX;
var d:Number = Math.sqrt(xcoord * xcoord + ycoord * ycoord);
if(d < radius) {
var t:Number = d == 0 ? 0 : Math.pow(Math.sin(Math.PI / 2 * d / radius), -amount);
var dx:Number = xcoord * (t - 1) / width;
var dy:Number = ycoord * (t - 1) / height;
var blue:int = 0x80 + dx * 0xff;
var green:int = 0x80 + dy * 0xff;
dbmd.setPixel(x, y, green << 8 | blue);
}
}
}
return new DisplacementMapFilter(dbmd,
new Point(0, 0),
BitmapDataChannel.BLUE,
BitmapDataChannel.GREEN,
width,
height,
DisplacementMapFilterMode.CLAMP);
}

/**
* Generate the DisplacementMapFilter for photic tunnel effect.
* Photic tunnel effect is as same as effect of Photo Booth application in Mac OS.
*
* You can apply the filter is as follows:
*
* var bmd:BitmapData = new BitmapData(width, height, false);
* var filter:DisplacementMapFilter = Effect.pinchFilter(bmd);
* bmd.draw(video);
* bmd.applyFilter(bmd, bmd.rect, new Point(0, 0), filter);
*
* @params source BitmapData The input bitmap data to apply the twirling effect.
* @params region Rectangle The region to apply the twirling effect.
* @return DisplacementMapFilter Filter to apply photic tunnel effect.
*/
static public function photicTunnelFilter(source:BitmapData, region:Rectangle=null):DisplacementMapFilter
{
var width:int = source.width;
var height:int = source.height;
region ||= new Rectangle(0, 0, width, height);
var centerX:int = region.x + region.width / 2;
var centerY:int = region.y + region.height / 2;
var dbmd:BitmapData = new BitmapData(width, height, false, 0x8080);
var radius:Number = Math.min(region.width, region.height) / 2;
for(var y:int=0;y<height;++y) {
var ycoord:int = y - centerY;
for(var x:int=0;x<width;++x) {
var xcoord:int = x - centerX;
var d:Number = Math.sqrt(xcoord * xcoord + ycoord * ycoord);
if(radius < d) {
var angle:Number = Math.atan2(Math.abs(ycoord), Math.abs(xcoord));
var dx:Number = (xcoord > 0? -1 : 1) * (d - radius) * Math.cos(angle) / width;
var dy:Number = (ycoord > 0? -1 : 1) * (d - radius) * Math.sin(angle) / height;
var blue:int = 0x80 + dx * 0xff;
var green:int = 0x80 + dy * 0xff;
dbmd.setPixel(x, y, green << 8 | blue);
}
}
}
return new DisplacementMapFilter(dbmd,
new Point(0, 0),
BitmapDataChannel.BLUE,
BitmapDataChannel.GREEN,
width,
height,
DisplacementMapFilterMode.CLAMP);
}

/**
* Generate the DisplacementMapFilter for bulge effect.
* Bulge effect is wrapper of pinchFilter method.
*
* @params source BitmapData The input bitmap data to apply the effect.
* @params region Rectangle The region to apply the bulge effect.
* @params amount Number Amount of bulge. (0 <= x <= 1)
* @return DisplacementMapFilter The filter to apply bulge effect.
*/
static public function bulgeFilter(source:BitmapData, region:Rectangle=null,
amount:Number=0.5):DisplacementMapFilter
{
// wrapper method of pinchFilter
return pinchFilter(source, region, Math.min(-amount, -1));
}
/**
* Generate the DisplacementMapFilter for squeeze effect.
* Dent effect is wrapper of pinchFilter method.
*
* @params source BitmapData The input bitmap data to apply the effect.
* @params region Rectangle The region to apply the bulge effect
* @params amount Number Amount of squeeze. (0 <= x <= 1)
*/
static public function squeezeFilter(source:BitmapData, region:Rectangle=null,
amount:Number=0.5):DisplacementMapFilter
{
// wrapper method of bulge filter
return pinchFilter(source, region, amount);
}

/**
* Generate the DisplacementMapFilter for fisheye effect.
*
* @params source BitmapData The input bitmap data to apply the twirling effect.
* @params amount Number Amount of fisheye (0 <= x <= 1)
* @return DisplacementMapFilter The filter to apply the fisheye effect.
*/
static public function fisheyeFilter(source:BitmapData, amount:Number=0.8):DisplacementMapFilter
{
var width:int = source.width;
var height:int = source.height;
var dbmd:BitmapData = new BitmapData(width, height, false, 0x8080);
var centerX:int = width / 2;
var centerY:int = height / 2;
var radius:Number = Math.sqrt(Math.pow(width, 2) + Math.pow(height, 2));
for(var y:int=0;y<height;++y) {
var ycoord:int = y - centerY;
for(var x:int=0;x<width;++x) {
var xcoord:int = x - centerX;
var d:Number = Math.sqrt(xcoord * xcoord + ycoord * ycoord);
if(d < radius) {
var t:Number = d == 0 ? 0 : Math.pow(Math.sin(Math.PI / 2 * d / radius), amount);
var dx:Number = xcoord * (t - 1) / width;
var dy:Number = ycoord * (t - 1) / height;
var blue:int = 0x80 + dx * 0xff;
var green:int = 0x80 + dy * 0xff;
dbmd.setPixel(x, y, green << 8 | blue);
}
}
}
return new DisplacementMapFilter(dbmd,
new Point(0, 0),
BitmapDataChannel.BLUE,
BitmapDataChannel.GREEN,
width,
height,
DisplacementMapFilterMode.CLAMP);
}

/**
* Generate the DisplacementMapFilter for strech effect.
*
* @params source BitmapData The input bitmap data to apply the twirling effect.
* @params amount Number Amount of strech (0 <= x <= 1), default is 0.6;
* @return DisplacementMapFilter The filter to apply the strech effect.
*/
static public function strechFilter(source:BitmapData, amount:Number=0.6):DisplacementMapFilter
{
var width:int = source.width;
var height:int = source.height;
var dbmd:BitmapData = new BitmapData(width, height, false, 0x8080);
var centerX:int = width / 2;
var centerY:int = height / 2;
var vregion:Rectangle = new Rectangle(0, 0 , width / 3, height);
var hregion:Rectangle = new Rectangle(0, 0, width, height / 3);
var blue:int;
var green:int;
for(var y:int=0;y<height;++y) {
var ycoord:int = y - centerY;
for(var x:int=0;x<width;++x) {
var xcoord:int = x - centerX;
var dx:int = (Math.abs(xcoord) < vregion.width)?
xcoord * (Math.pow(Math.abs(xcoord) / vregion.width, amount) - 1) : 0x0;
var dy:int = (Math.abs(ycoord) < hregion.height)?
ycoord * (Math.pow(Math.abs(ycoord) / hregion.height, amount) - 1) : 0x0;
blue = 0x80 + 0xff * dx / width;
green = 0x80 + 0xff * dy / height;
dbmd.setPixel(x, y, green << 8 | blue);
}
}
return new DisplacementMapFilter(dbmd,
new Point(0, 0),
BitmapDataChannel.BLUE,
BitmapDataChannel.GREEN,
width,
height,
DisplacementMapFilterMode.CLAMP);
}
}
}



TODO
Dent Filter(Photoshopのフィルタ -> 球面でマイナス指定と同じ)が正しくないのに気づかれたかもしれません。屈折率や曲座標の式などを使えばできると思うのですが、数式モデルがまだわかっていません。as3filtersにコミッタとして参加してもらえるか、数式を教えてください。

2 comments:

Anonymous said...

Culture is another great reason to visit the state gaia online gold of Delaware. Not only are visitors provided with a number of cultural festivals throughout the year but visitors also have the opportunity GW gold to visit such places as Wilmington’s “Little Italy”. Here Lord of the Rings online gold visitors can stroll through a historic MS mesos Italian urban neighborhood and shop the quaint shops and dine at one of the areas many fine dining restaurants. The cultural possibilities in SilkRoad Online gold Delaware are endless and exciting. Whether you want to find that special piece of art for your home décor or dine in authentic Italian restaurant you are sure to Sword of the New World vis find what you’re looking for in Delaware.

goodeda1122 said...

自慰套,自慰器,情趣,自慰套,情趣,視訊交友,充氣娃娃,AV,按摩棒,電動按摩棒,情趣按摩棒,按摩棒,跳蛋,跳蛋,跳蛋,
男女,潤滑液,
SM,情趣內衣,內衣,性感內衣,角色扮演,角色扮演服,吊帶襪,丁字褲,情趣用品,情趣用品,飛機杯,自慰套