油絵フィルターと「マウスごしごし」インターフェイス

 先日ここで公開した油絵フィルター。参考にさせていただいた「.fla」の絵画フィルターの作者である、fladdictさん御本人からコメントをいただき、がぜんやる気を出した私。朝5時に日の出とともに目を覚ましてしまったのを幸いに、早朝プログラミングで幾つかの改良を加えた(これぞ本当の朝飯前^^)。

 まずは、カレー屋の写真を処理したときに黄色い看板の直線部分が汚くなってしまう問題。ソースコードを読み直して見ると、近似色の判定の部分に一つタイポがあったため、茶色と黄色の区別がちゃんと出来ていなかったことが判明。そこを直すとすっきりと修正された。

Kanban1Kanban2

 次に挑戦したのが、犬の毛並みがうまく表現できていない点。楕円形のブラシを毛並みの方向に走らせてやれば良いのだが、画像から自動的にそれを抽出するのは容易ではないし、場所によってはブラシの方向がランダムな方が味が出るので、中途半端なアルゴリズムでは逆効果だ。

 そこで思いついたのが、「人力(じんりき)レタッチ」。自動的にフィルターをかけた後に、マウスで(ボタンを押したまま)ゴシゴシとこすると、こすった方向に楕円形の「塗り」を追加するようにした。「マウスでごしごし」というユーザーインターフェイスは見たことがないが、けっこう直感的なのではないだろうか(フィードバック歓迎)。

 犬の写真に「ごしごしレタッチ」を施す前と後ではこんなに違う。

Goma1

Goma2

 で、当のFlashアプリはこれ。


Flashで「油絵フィルター」を作ってみた

 サンディエゴから帰ってくる飛行機の中で「.fla」を読んでいて猛烈に作りたくなってしまったのが、写真を人が描いた絵のように変換する「絵画フィルター」。それも思いっきりCPUをふんだんに使った「富豪プログラミング」で数秒から数分かかっても良いからPhotoShopのフィルターなんかよりも遥かに出来の良いものを作る、というのは楽しいかも知れない。

 手始めに作ったのは、油絵フィルター。楕円形の筆を使い、最初は方向を意識せずにランダムに絵の具を置いて行き、だんだんと隣接するピクセルとの色合いも意識しながら筆の方向を調節して行くというアルゴリズムだ(サムネイルをクリックすると、その写真を元に油絵風の絵を生成するように出来ている)。果物とか夕焼けは結構良く出来ていると思うが、コントラストの低い犬の写真や、直線部分の多いカレー屋の写真はイマイチだ。


習作UI: 縁日の金魚を再現してみた

 Flashでプログラムを書く機会があったら一度は作らねばと思っていたのが、鳥や魚の群れ(flock)のシミュレーション。そこでカスタムクラスのプログラミングの練習も兼ねて作ったのがこれ。アルゴリズムそのものは、良く知られた(1)仲間と同じ方向に泳ごうとする気持ち、(2)仲間と一緒に泳ごうとする気持ち、(3)衝突を避ける気持ち、をそれぞれの魚に持たせて泳ぐ方向を少しずつ変化させる、というものである。

 プログラムはそれほど時間をかけずに作ることができたのだが、苦労したのがそういった「気持ち」の部分を表現するのに必用な具体的なパラメーター(どのくらい離れた仲間まで認識しているか、どのくらいの距離までの接近を許すか、など)を見つけ出す部分。金魚すくいの水槽の中の金魚の動きをちょうど良い感じで再現するためのパラメーターを見つけるのにかなりの試行錯誤が必要であった。

 しかし、これを作って思ったのは、Flashの開発環境は水と油で作ったドレッシングのようだということ。デザイナー向けの機能(keyframeとtweenを使ってmovie clipを作る部分)と、プログラマー向けの機能(ActionScriptを使ってインタラクティビティを実現する部分)の両方をキチンと使いこなすのは結構難しい(この例では、個々の金魚の尾びれの動きはmovie clipを使って実現しているが、泳ぎの方向の決定とフレームごとの位置の移動はActionScriptを使って実現している)。

 本来なら、こういったインタラクティビティの部分こそUIデザイナー自身がデザインしながら作るべきなのだが、この開発環境だとその部分はプログラマーに頼らねばならないのが残念なところだ。まあ、そのおかげで私みたいな「UIプログラマー」が職にありつけるのだから個人的には悪くないのだが、やはり開発ツールを作る仕事をしている私としては、「デザイナー自身がプログラムを書かずにインタラクティブなアプリケーションを作れる環境を提供せねば」とつくづく思ったしだいである。

 ちなみに、金魚を実際にすくうことは出来ないが、ちょっとしたお遊びインタラクティビティの機能も追加してみたので、マウスを水槽の上で動かしてみて欲しい。


習作UI: 初めてのFlash その4

 ひさしぶりの連続エントリーとなってしまったが、最初に自分にあたえた「24時間以内にそれなりに遊べるパズルゲームを作る」というゴールをこれで一応達成。昨日の写真を移動・完成させるPicture Controlを再利用してパズルを作っただけのことである。

 パズルの目的は、この4つのピースを並び換えて「大文字のT」を作ることである。先日とあるところでこのパズル(手で触ることができるもの)で遊んだのだが、それがとても楽しかったので、今回の習作の課題に選んだのである。

 正解にたどり着いたかどうかの判定ロジックはついていない。「作るのが面倒だった」という事情もあるが、あえて判定のロジックなどつけずにどんどんコンテンツ(つまり、並び換えるべきピース)を増やしていく方が今の時代に合っているのではないかと思っている…それもユーザーの力を借りて(CGM!)。リアルの世界のパズルやゲームもこの「ルール判定」や「終了判定」の部分は人間に頼っているものが大半であるし。

 ちなみに、今後これをどうやってCGM的なサービスとして展開させていくかに関してはこれから考えて行きたい。「ソースコードは公開したので後は自分で作ってください」では敷居が高すぎるので、何とかして誰にでも簡単にコンテンツ作りができてしまう仕組みを作りたいと考えている。


習作UI:初めてのFlash その3

 「ソース公開希望」というコメントをいただいたので、さっそくソースを公開。なにしろ、ActionScriptをさわったのは今日が初めてなので、何かとんでもなくまとはずれなことをしている可能性もあるので、そこは御容赦のほどを。

 ソースコードを公開するだけでは十分ではないところがFlashの悩ましいところだが、とりあえず作り方を説明しておく。

1.移動用の丸を描き、Moverという名前のButton Symbolに変換する。そして、Overのkeyframeの時のみその丸を表示するようにしておく。ちなみに、アンカーが中心になるようにしておく。
2.回転用のドーナッツを描き、Rotatorという名前のButton Symbolに変換する。同じく、Overのkeyframeの時のみそのドーナッツを表示するようにしておく。やはり、アンカーが中心になるようにしておく。
3.Mover、Rotatorのインスタンスを一つづつ作り、それを二つ合わせてPicture Controlという名前のMovie Symbolにする。やはり、アンカーが中心になるように配置しておく。
4.Moverのインスタンスを"mover"、Rotatorのインスタンスを"rotator"という名前にしておく。
5.Picture Controlのインスタンスをダブルクリックして、Edit in Place状態にする。
6.Scriptという名前のレイヤーを追加し、下のソースコードをActionとして貼り付ける。
7.トップのステージに戻り、適当な大きさの画像をインポートする。
8.インポートした画像と、Picture Controlのインスタンスを合わせてMovie Symbolに変換する。
9.それ(インスタンス)をダブルクリックして、Edit in Place状態にし、アンカーが中心になるように配置する。
10.他の画像も貼り付けたければ7~9をさらに繰り返す。

 これで完成である。アンカーの位置がちゃんと中心になっていないと、座標計算が狂うのでそこだけは注意していただきたい。ActionScript3.0で書かれているので、CS3以前のものでは動かない(はず)。私はAdobe Flash CS3 Professionalを使っているので、Basic版で同じことが可能かどうかは未確認である。

//----------------------------------------------------------------------
// Global variables for "Mover" (some of them are shared)
//----------------------------------------------------------------------
var dragging = false;
var anchorX, anchorY;
var relX, relY;
var pictureFrame;

//----------------------------------------------------------------------
// Event handlers for "Mover"
//----------------------------------------------------------------------
function dragBeginHandler(event:MouseEvent):void {
    dragging = true;
    anchorX = event.stageX;
    anchorY = event.stageY;
    pictureFrame = event.target.parent.parent;
    relX = pictureFrame.x - anchorX;
    relY = pictureFrame.y - anchorY;
    stage.addEventListener(MouseEvent.MOUSE_UP, dragEndHandler);
    stage.addEventListener(MouseEvent.MOUSE_MOVE, dragMoveHandler);
}

function dragEndHandler(event:MouseEvent):void {
    dragging = false;
    stage.removeEventListener(MouseEvent.MOUSE_UP, dragEndHandler);
    stage.removeEventListener(MouseEvent.MOUSE_MOVE, dragMoveHandler);
}

function dragMoveHandler(event:MouseEvent):void {
    if (dragging) {
        pictureFrame.x = relX + event.stageX;
        pictureFrame.y = relY + event.stageY;
    }
}

mover.addEventListener(MouseEvent.MOUSE_DOWN, dragBeginHandler);

//----------------------------------------------------------------------
// Global variables for "Rotator"
//----------------------------------------------------------------------
var rotating = false;
var anchorA, offsetA;

//----------------------------------------------------------------------
// Event handlers for "Rotator"
//----------------------------------------------------------------------
function rotBeginHandler(event:MouseEvent):void {
    rotating = true;
    pictureFrame = event.target.parent.parent;
    relX = event.stageX - pictureFrame.x;
    relY = event.stageY - pictureFrame.y;
    anchorA = - Math.atan2(relX, relY) / Math.PI * 180;
    offsetA = pictureFrame.rotation;
    stage.addEventListener(MouseEvent.MOUSE_UP, rotEndHandler);
    stage.addEventListener(MouseEvent.MOUSE_MOVE, rotMoveHandler);
}

function rotEndHandler(event:MouseEvent):void {
    rotating = false;
    stage.removeEventListener(MouseEvent.MOUSE_UP, rotEndHandler);
    stage.removeEventListener(MouseEvent.MOUSE_MOVE, rotMoveHandler);
}

function rotMoveHandler(event:MouseEvent):void {
    if (rotating) {
        relX = event.stageX - pictureFrame.x;
        relY = event.stageY - pictureFrame.y;
        var rot = - Math.atan2(relX, relY) / Math.PI * 180;
        pictureFrame.rotation = offsetA + (rot - anchorA);
    }
}

rotator.addEventListener(MouseEvent.MOUSE_DOWN, rotBeginHandler);


習作UI:初めてのFlash その2

 ネットで探してみると、マウスのキャプチャーの仕方が判明。ホットスポットへのMOUSE_DOWNのイベントをボタンで受け取ったあとは、(ホットスポットではなく)"stage"(Flashの表示領域全体)へのMOUSE_MOVEとMOUSE_UPイベントをlistenするようにすれば良いだけのことであった(ただし、これだとマウスのボタンを押したまま"stage"の外に出ると挙動が少しおかしくなる→これは別途解決する必用あり)。

 それに加えて、写真の移動・回転をつかさどっている部品を一つの独立したmovie symbolとして再利用可能にした。なんだかやたらとsymbolがネスティングしているが、これがFlash独特のプログラミング・パラダイムなのかも知れない。なぜ「Flashムービー」を作れる人はたくさんいるのに、キチンとした「インタラクティブFlashアプリ」を作れる人があまり多くないのかがだんだんと分かってきたような気がする。


習作UI:初めてのFlash

 訪問先でUIEngineとFlashの違いを良く聞かれるので、それにちゃんと答えられるようにとFlashの勉強を始めた。これが一番最初の作品。写真を動かしたり回転させたりできる。いきなりActionScriptの3.0から入る人も珍しいのかも知れないと思いつつ書いてみた。まだまだ改良の余地はあるが、とりあえず今日はここまで(マウスのキャプチャーのしかたが分からん…ととりあえず書いてみるテスト^^;)。