9.4 画像の変形(アフィン変換・その1)

前回までで画像の拡大と縮小ができるようになったね。
じゃあ今回からプラグインを作るの?
ん〜、そうしようかと思ったんだけど、やっぱり先に他のメソッドも一通り見とこうと思うんだ。
他のメソッドって、画像を回転したりとかするののこと?
そうそう。
ってワケだから、今回は画像をアフィン変換するメソッドを見ていくことにするね。
アフィン変換…?
アフィン変換っていうのはね、 簡単に言うと画像を拡大したり縮小したり回転したり平行移動したり上下左右を反転させたり平行四辺形に変形させたりすることだよ。
な、なんかスゴイ色々できるんだね…
それって全部1つのメソッドでできちゃうの?
うん、affineCopy っていうメソッドを使えばできるよ。
あふぃんこぴー…メソッド?
affineCopy メソッドは名前の通り、画像をアフィン変換してコピーするメソッドなんだ。
※吉里吉里2で利用可能なのは2次元アフィン変換のみですので、この講座では2次元アフィン変換を単にアフィン変換と呼ぶことにします。
へぇ…
まぁ、それだけじゃよくわからないと思うから、 実際に affineCopy メソッドを使って画像を変換してみよっか。
うん、そーだね。
んじゃまずはこのスクリプトを first.ks に書き込んで実行してみて。

affineCopy メソッドの使用例(first.ks の中身)>

; メッセージレイヤを非表示にします
[position page=fore layer=message0 visible=false]
; 表画面の 0 番の前景レイヤに画像を読み込みます
[image page=fore layer=0 storage="krkr" visible=true]

[iscript]
// 画像を見やすくするため背景レイヤを白色で塗りつぶします
kag.fore.base.fillRect(0, 0, kag.fore.base.width, kag.fore.base.height, 0xFFFFFF);

// 0 番の前景レイヤへの参照を変数 layer0 に代入します
var layer0 = kag.fore.layers[0];

// 親レイヤを 0 番の前景レイヤとして一時レイヤを作ります
var tempLayer = new Layer(kag, layer0);
// 一時レイヤに 0 番の前景レイヤの画像をコピーします
tempLayer.assignImages(layer0);

// (A)

// 0 番の前景レイヤの幅と高さを調整します
layer0.setImageSize(128, 128);
layer0.setSizeToImageSize();

// アフィン変換で画像を128×128ピクセルに拡大します
layer0.affineCopy(tempLayer, 0, 0, tempLayer.imageWidth, tempLayer.imageHeight, false, 0, 0, 128, 0, 0, 128, stFastLinear, false);

// (B)

// 一時レイヤは必要なくなったので無効化します
invalidate tempLayer;
[endscript]

実行してみるね〜。
ん。

<実行結果(変換された画像の部分だけ示しています)>

…これって画像をフツーに拡大してるように見えるんだけど?
そうだよ。画像を縦横2倍に拡大してるの。
え、画像を拡大するのって stretchCopy メソッドなんじゃ…?
stretchCopy メソッドについては §9.2 参照。
さっきも言った通り、アフィン変換すると画像を拡大することもできるの。
でもそれじゃ stretchCopy メソッドって必要なくない?
まぁ確かに affineCopy メソッドで stretchCopy メソッドと同じことはできるんだけど、 単に画像を拡大/縮小したいだけなら stretchCopy メソッドを使った方が affineCopy メソッドを使うより処理が速いみたいだね。
へぇ、そーなんだ。
それじゃ、スクリプトの方見てくね。
は〜い。
メッセージレイヤを非表示にするとこから assignImages メソッドで一時レイヤに 0 番の前景レイヤの画像をコピーするとこまでは前にもやったから大丈夫だよね?
§9.2 参照。
うん、その辺はわかってるよ〜。
じゃ次いくね。
次は 0 番の前景レイヤの幅と高さを設定してるんだけど、これも問題ないよね?
0 番の前景レイヤの幅と高さを128×128ピクセルにしてるんだよね。
そ。ちなみに元の画像のサイズが64×64ピクセルで、 縦横2倍に拡大するから128×128ピクセルにしてるんだよ。
で、次の affineCopy メソッドを呼び出してるとこが今回のメインね。
stretchCopy メソッドとかも結構引数多かったけど、 affineCopy メソッドはもっと引数が多いみたいだね…
affineCopy メソッドは色々指定しなきゃいけない要素があるからねー。
あと、引数の意味も他のメソッドよりちょっとややこしくなってるね。
そ、そーなんだ…
ま、とりあえず affineCopy メソッドの引数を見ていくことにするね。
“変換後の座標を指定する場合の” affineCopy メソッドの引数はこうなってるよ。

affineCopy メソッドの引数(第6引数の affinefalse を指定した場合)>
引数名引数の意味デフォルト値
第1引数srcコピー元のレイヤ
第2引数sleftコピー元の四角形の左端の位置
第3引数stopコピー元の四角形の上端の位置
第4引数swidthコピー元の四角形の幅
第5引数sheightコピー元の四角形の高さ
第6引数affine変換後の3点の座標を指定する場合は false
第7引数Aコピー元四角形の左上の点のコピー先レイヤでの x 座標
第8引数Bコピー元四角形の左上の点のコピー先レイヤでの y 座標
第9引数Cコピー元四角形の右上の点のコピー先レイヤでの x 座標
第10引数Dコピー元四角形の右上の点のコピー先レイヤでの y 座標
第11引数Eコピー元四角形の右下の点のコピー先レイヤでの x 座標
第12引数Fコピー元四角形の右下の点のコピー先レイヤでの y 座標
第13引数type補間のタイプstNearest(最近傍補間)
第14引数clear変換後の画像の周囲をクリアする場合は true、しない場合は falsefalse
affinetrue の場合は、A, B, C, D, E, F はアフィン変換行列の各要素になります。

14個もあるんだね…
最初の方の引数の意味は stretchCopy メソッドとかと同じだからわかるんじゃないかと思うんだけど、どうかな?
えーっと、第5引数までは確かに stretchCopy メソッドとかにも同じ引数があったよね。
ん、affineCopy メソッドの第1引数〜第5引数は stretchCopy メソッドの第5引数〜第9引数と同じ意味だよ。
じゃ第6引数から説明してくね。
うん。
…と思ったんだけど第6引数はちょっと後回しにして、 第7引数〜第12引数から先に見てくね。
えっ!?
いやー、第6引数はちょっと説明が難しいから、 先に第7引数〜第12引数を見といた方が説明しやすくなりそーかなと思って。
そ、そーなの?
だったら別にそれでもいーけど…
第7引数〜第12引数は、元々の画像の左上、右上、左下の点が、 アフィン変換することでどの座標に移動するかを指定する引数なんだ。
えっと、それってどーゆーこと?
ん〜、この辺は言葉で説明するのはちょっと難しいから、図にしてみるね。
ここaffineCopy メソッドを呼び出すと、 こんなふうに画像が変換されるんだ。

affineCopy メソッドによる画像の変換(拡大)>

なんかこんな感じの図って前にも見た気がするんだけど…?
affineCopy メソッドで画像をアフィン変換する時の座標の指定の仕方は §9.1 でちょっと説明してるからね。
あ、画像を変形したときだね。
えっと、あの時は x1, y1, x2, y2, x3, y3 っていう属性で、 左上と右上と左下の点の座標を指定してたよね。
affineCopy メソッドだと、 第7引数〜第12引数がその座標になってるの。
つまり、affineCopy メソッドの A, B, C, D, E, F っていう引数が、 それぞれ eximage マクロの x1, y1, x2, y2, x3, y3 属性に対応してるわけね。
なるほどね。
これで第7引数〜第12引数は OK かな?
…あ、ちょっと待って。
縦横2倍に拡大するんだから、変換後の画像のサイズって 128×128 ピクセルになるよね?
うん、元の画像は 64×64 だから、そうなるね。
128×128 ってことは、右上の点の座標って (128, 0) じゃなくて (127, 0) になるんじゃない?
あと左下の点も (0, 127) になると思うんだけど…?
確かに 128×128 ピクセルの画像の右上の点の座標は (127, 0) だよ。
でしょ?
じゃあ CF128 じゃなくって 127 にすればいーんだよね?
ううん、そこは 128 でいいの。
えっ、なんで??
ちょっとこの図を見てみて。

<右上の点の座標の求め方>

※厳密には吉里吉里2でのアフィン変換において、 各ピクセルは座標値±0.5ピクセルの大きさを持つと定義されています(例えば (127, 0) の位置にあるピクセルは (126.5, -0.5) - (127.5, 0.5) の範囲にあると見なされます)が、 通常 0.5 ピクセルのずれは変換結果に殆ど影響を及ぼさないことから、 ここでは図を見やすくするため (127, 0) の位置にあるピクセルが (127, 0) - (128, 1) の範囲にあるように図を描いています。

アフィン変換で拡大した後の画像の幅は (C - A) ピクセルになるから、 C127 にすると画像の幅は (127 - 0) = 127 ピクセルになるんだ。
えと、それってつまり C127 にすると、 (127, 0) の位置にあるピクセル(右上のピクセルだね)の幅が計算に入らなくなっちゃうから、 幅が 127 ピクセルになるってコト?
ん、そういうことだね。
まぁ細かい話だから、きっちり2倍に拡大する必要があるって時以外はそんなにこだわらなくてもいいかもだけどね。
ふぅん、なるほどねー…
じゃ次はさっき飛ばした第6引数を説明するね。
そうそう、そーいえば何で第6引数の説明を後回しにしたの?
第6引数の affine はね、 第7引数〜第12引数の意味を決める引数なんだ。
え? どーゆーこと?
第7引数〜第12引数にアフィン変換後の画像の左上・右上・左下の点の座標を指定する時は affinefalse にするの。
じゃあ affinetrue にしたらどーなるの?
その時は第7引数〜第12引数はアフィン変換行列の要素を指定するの。
アフィン変換行列の要素……ってなに?
ん〜、それを説明するのはちょっと大変なんだよねー。
スゴーク数学的な話になっちゃうから。
そ、そーなんだ…
でもね、画像を回転する時には affinetrue にしてアフィン変換行列を使うと便利なんだよ。
へぇ…
ってワケだから、affinetrue にした時の affineCopy メソッドの使い方は次回説明することにするね。
え、それってもしかして次回は数学の話になっちゃうってこと?
まぁある程度はそうなっちゃうと思うけど、別に数学の授業みたいにするつもりはないから。
それに、アフィン変換行列が理解できないと画像を回転できないってワケでもないしね。
あ、そーなんだ。よかった…
じゃ今回は第6引数の affine の話はこれくらいにしとくね。
次は第13引数の type なんだけど、これは stretchCopy メソッドとかの時にも出てきたから大丈夫だよね?
§9.2 参照。
画像を補間する時のやり方だよね。
そうそう。
ちなみに affineCopy メソッドで使えるのは stNeareststFastLinear だけで、 stLinearstCubic は使えないから注意してね。
まぁ大抵の場合は stFastLinear で十分だと思うけどね。
※吉里吉里2 ver 2.30 RC 1 現在。
 stLinearstCubic は指定することはできますが、実際に線形補間・3次元補間は行われません。
確かに stFastLinear もキレイに補間できてたもんね。
ん。それじゃ、最後は第14引数の clear だね。
これってどんな引数なの?
この引数はね、アフィン変換された画像の周りを neutralColor プロパティで指定されてる色でクリアするかどうかを指定する引数なんだ。
えっと、よくわかんないんだけど…?
んー、まぁそうだよね。
んじゃ cleartrue にした時と false にした時でどう違うか実際にやってみよっか。
そだね。実際にやってみるとわかりやすいもんね。
じゃあ、さっきのスクリプト(A)(B) の部分(ここの事ね)をこのスクリプトで置き換えて実行してみて。

<画像を平行四辺形に変形するスクリプト>

// (A)

// 0 番の前景レイヤの幅と高さを調整します
layer0.setImageSize(192, 96);
layer0.setSizeToImageSize();

// アフィン変換で画像を平行四辺形に変形します
layer0.affineCopy(tempLayer, 0, 0, tempLayer.imageWidth, tempLayer.imageHeight, false, 96, 0, 192, 48, 0, 48, stFastLinear, false);

// (B)

実行してみるね。
ん。

<実行結果(変換された画像の部分だけ示しています)>

あれ、なんか画像が2つ表示されてるけど…?
奥の方に表示されてるのが元々の画像で、手前に表示されてるのがアフィン変換後の画像だよ。
今回はアフィン変換で画像を平行四辺形に変形してるの。こんな感じにね。

affineCopy メソッドによる画像の変形>

§9.1 でもこんな感じの変形をやったから大体解るよね?
うん、まーね。
ところで、なんで奥の方に元々の画像が表示されるのかは前回 operateRect メソッドを使った時に説明したんだけど、覚えてる?
え〜っと、確か fillRect メソッドを呼び出してレイヤを透明色で塗りつぶさずに operateRect メソッドを呼び出すと、 最初に image タグで読み込んだ画像が残っちゃうんだったよね?
そうそう。
それと同じことが affineCopy メソッドでも起きるの。
え、じゃあ affineCopy メソッドって画像を重ね合わせてるの?
んーん、そうじゃないよ。
基本的に affineCopy メソッドも stretchCopy メソッドとかと同じように、コピー先に元々あった画像は消えちゃうの。
じゃあどーしてこんなふうになっちゃうの?
それはね、コピー先のレイヤにアフィン変換後の画像しかコピーされてないからなんだ。
? どーゆーこと?
ちょっとこの図を見てみて。

affineCopy メソッドで画像がコピーされない領域>

この図はコピー先のレイヤ、つまり 0 番の前景レイヤを表してるんだけど、 affineCopy メソッドでアフィン変換した後の画像の範囲外の部分(左側の図の の部分のことね)には画像がコピーされないんだ。
えっと、それってつまりの部分には画像がコピーされないから、 の部分に元々表示されてた画像がそのまま残っちゃって、 右側の図みたいに表示されるってコト?
ん、そういうこと。
じゃあ結局 affineCopy メソッドを呼び出す前に fillRect メソッドを呼び出して元々表示されてた画像を消さなきゃいけないってことだよね?
それが affineCopy メソッドだと fillRect メソッドを呼び出さなくても元々表示されてた画像を消すことができるの。
え、そーなの?
そのために使うのが第14引数の clear なんだ。
あ、そーなんだ。
えっと、具体的にはどーやるの?
まずさっきのスクリプトをこのスクリプトで置き換えて実行してみて。

<画像を平行四辺形に変形&元々表示されていた画像を消去するスクリプト>

// (A)

// 0 番の前景レイヤの幅と高さを調整します
layer0.setImageSize(192, 96);
layer0.setSizeToImageSize();

// 0 番の前景レイヤの中性色を透明色に設定します
layer0.neutralColor = 0x00000000;

// アフィン変換で画像を平行四辺形に変形します
// 変換後の画像の範囲外の領域は中性色で塗りつぶします
layer0.affineCopy(tempLayer, 0, 0, tempLayer.imageWidth, tempLayer.imageHeight, false, 96, 0, 192, 48, 0, 48, stFastLinear, true);

// (B)

じゃあとりあえず実行してみるね。
うん。

<実行結果(変換された画像の部分だけ示しています)>

あ、今度は元々表示されてた画像が消えてるね。
でしょ。
じゃスクリプトの方を見ていくね。
layer0.neutralColor = 0x00000000; ってゆーのが増えてるね。
これってなんなの?
neutralColorLayer クラスのプロパティで、 中性色を設定したり参照したりできるプロパティなんだ。
中性色って?
中性色っていうのは、setImageSize メソッドとかでレイヤの画像サイズを大きくした時に、大きくなった部分を塗りつぶす時に使う色のことだよ。
例えばこんな感じかな。

<中性色の設定例>

この例だと中性色は不透明な黄色(0xFFFFFF00)に設定してるから、 setImageSize メソッドで画像のサイズを大きくすると、 大きくなった部分が自動的に黄色で塗りつぶされるの。
へぇ、そーなんだ。
setImageSize メソッドは今まで何回も使ったけど、全然気づかなかったよ。
まぁ neutralColor プロパティは透明色に設定されてることが多いからね。
レイヤのサイズを大きくしても、大きくなった部分は透明で見えないから気づきにくいんじゃないかな。
そっか。だから今まで気づかなかったんだね。
で、affineCopy メソッドの第14引数の cleartrue にすると、アフィン変換後の画像の範囲外の部分は中性色で塗りつぶされるの。
ってコトは…中性色を透明色にして、その後 cleartrue にして affineCopy メソッドを呼び出すと、 さっきの図 の部分が透明色で塗りつぶされるから、 元々あった画像を消せるってことなのかな?
そういうこと。
ちなみに fillRect メソッドでレイヤ全体を透明色で塗りつぶしてから clearfalse にして affineCopy メソッドを呼び出しても同じ結果になるんだけど、 cleartrue にした方が効率が良くなるから、 基本的にはこっちのやり方を使った方が良いんじゃないかな。
あ、そーなんだ。
fillRect メソッドだとレイヤ全体が塗りつぶされるけど、 cleartrue にした時に塗りつぶされる部分は さっきの図でいうと の部分だけになるから、そのぶん効率的でしょ。
あー、なるほどね〜。
あと、前景レイヤの neutralColor プロパティはデフォルトで透明色になってるから、 layer0.neutralColor = 0x00000000; は実行しなくても cleartrue にして affineCopy メソッドを呼び出すだけで元々表示されてた画像は表示されなくなるよ。
そーいえばさっき neutralColor プロパティは透明色に設定されてることが多いって言ってたもんね。
ん。
さてと、それじゃ affineCopy メソッドの引数も一通りチェックできたことだし、 今回はこれくらいにしとこっか。
そーだね。
次回は affineCopy メソッドを使って画像を回転させてみるね。
うん、りょーかい。
それじゃ、また次回ね!


前へ | TOP | 次へ