9.10 画像読み込み機能拡張プラグイン(その3)

今回は前回の続きってことで…
transformImage メソッドと rotateImage メソッドを見てくんだよね。
ん。じゃまずは transformImage メソッドからね。
これがスクリプト。

transformImage メソッド>

function transformImage(layer, elm)
{
    var tmpLayer = new Layer(kag, layer); // 一時レイヤを作ります
    tmpLayer.assignImages(layer); // 一時レイヤに操作対象のレイヤの画像をコピーします

    var x1 = +elm.x1, x2 = +elm.x2, x3 = +elm.x3;
    var y1 = +elm.y1, y2 = +elm.y2, y3 = +elm.y3;

    // 変形後の画像を表示するレイヤ(layer)の左端・右端・上端・下端の位置を計算します
    var left = min(x1, x2, x3, x2 - x1 + x3);
    var right = max(x1, x2, x3, x2 - x1 + x3);
    var top = min(y1, y2, y3, y2 - y1 + y3);
    var bottom = max(y1, y2, y3, y2 - y1 + y3);

    // 変形後の画像の幅と高さを計算します
    var width = right - left + 1;
    var height = bottom - top + 1;
    layer.setImageSize(width, height); // 操作対象のレイヤのサイズを変更します
    layer.setSizeToImageSize(); // 操作対象のレイヤのサイズを画像のサイズに合わせます

    // 操作対象のレイヤの中性色を一時的に透明色(0x00000000)に設定します
    var neutralColor = layer.neutralColor;
    layer.neutralColor = 0x00000000;

    // 一時レイヤの画像を変形して操作対象のレイヤにコピーします
    layer.affineCopy(tmpLayer, 0, 0, tmpLayer.imageWidth, tmpLayer.imageHeight, false,
                     x1 - left, y1 - top, x2 - left, y2 - top, x3 - left, y3 - top, stFastLinear, true);

    // 操作対象のレイヤの中性色を元に戻します
    layer.neutralColor = neutralColor;

    invalidate tmpLayer; // 一時レイヤはもう必要ないので無効化します

    // 操作対象のレイヤの位置を調整します
    if(elm.pos !== void)
        setPosition(layer, elm.pos);
    else
        layer.setPos(left, top);
}

結構長いね…
レイヤのサイズとか表示位置の調整がちょっとややこしくなってるからね。
そーなんだ。
それじゃスクリプトを見てくね。
最初の一時レイヤを作ってるとこは大丈夫だよね?
うん。もう何回もやってるもんね〜。
次の x1, x2, x3, y1, y2, y3eximage マクロの属性値を代入してるとこも特に問題ないよね。
x1, x2, x3, y1, y2, y3 っていう変数に x1, x2, x3, y1, y2, y3 属性の値を数値にして代入してるんだよね。
これもおっけーだよ。
んじゃ次いくね。
次は left, right, top, bottom にそれぞれ変形した画像を表示するレイヤ(第1引数の layer のことね)の左端、右端、上端、下端の位置を計算して代入してるの。
…それって何のために計算してるの?
x1 とか y1 とかで変形後の位置を指定してるのに。
変形後の3つの点の位置だけじゃレイヤのサイズをどう設定したらいいか判らないでしょ?
だから変形後の画像がちゃんとレイヤに収まるようにレイヤのサイズを計算しなきゃいけないんだ。
あ、なるほどね。
…でもそれだったら左端の位置とかじゃなくて幅と高さを直接計算すればいいんじゃない?
レイヤのサイズを計算するだけならそれでいいんだけどね。
ってことは、計算しなきゃいけないのってサイズだけじゃないってコト?
そういうこと。まぁそれはまた後で説明するとして、 とりあえずレイヤの左端、右端、上端、下端の位置を計算するスクリプトを見ていくね。
この min とか max とかって何かのメソッドだよね?
ん。min メソッドは引数の中で一番小さい値を返すメソッドで、 max メソッドは引数の中で一番大きい値を返すメソッドだよ。
それって元々定義されてるメソッドなの?
ううん、そーじゃないよ。
だから min メソッドと max メソッドは後で作ることにするね。
そーなんだ、りょーかい。
じゃ left, right, top, bottom がそれぞれどんな値になるか考えてみて。
えっと、まず left は…
x1x2x3x2 - x1 + x3 の中で一番小さい値になるんだよね…
そうだね。
x1x2x3 はそれぞれ左上と右上と左下の点の変形後の x 座標だと思うけど、 x2 - x1 + x3 ってなんなんだろ…?
左上、右上、左下、とくれば、次は?
えっ? えと…右下?
そ。アフィン変換を使って画像を変形するから、 変形後の画像は平行四辺形になるんだ。
だから、左上、右上、左下の点の変形後の x 座標が決まれば、 右下の点の変形後の x 座標は自動的に x2 - x1 + x3 に決まるの。
※ここで言う「平行四辺形」とは正方形・長方形などを含む広い意味での平行四辺形を指します。
また、アフィン変換については §9.4§9.5 参照。
じゃあ left って左上、右上、左下、右下の点の変形後の x 座標の中で一番小さい値になるんだね。
つまり、一番左にある点の x 座標ってことになるから、 left は変形後の画像を表示するレイヤの左端の位置になるよね。
なるほど、確かにそーなるね。
じゃ right は?
right には max メソッドの戻り値が代入してあって、 max メソッドに指定されてる引数は left の時とおんなじだから…
左上、右上、左下、右下の点の変形後の x 座標の中で一番大きい値が代入されるってことかな?
だね。つまり…?
つまり、4つの点の中で一番右にある点の x 座標ってことだから、 right は変形後の画像を表示するレイヤの右端の位置になるってことだね。
ん、そういうこと。
あと、topbottomx 座標が y 座標に変わってるだけだから…
topbottom にはそれぞれ左上、右上、左下、右下の点の中で変形後に一番上と下にある点の y 座標の値が代入されるってことだよね。
そ。じゃ min メソッドと max メソッドの中身もちょっと見とこっか。
はーい。
まず引数の中で一番小さい値を返す min メソッドからね。

min メソッド>

function min(value*)
{
    
var minval = value[0]; // 第1引数の値を現在の最小値とします
    for(var i=1;i<value.count;i++)
    {
        // 第(i+1)引数の値が現在の最小値(minval)より小さければ minval の値を更新します
        if(value[i] < minval)
            minval = value[i];
    }
    return minval; // 引数のうち最小の値を返します
}

…ねぇ、確かさっきは min メソッドに引数が4つあったよね?
ん、4つの点の座標を引数にしてたからね。
でもコレ引数が1つしかないんだけど…?
引数の value の後ろに * が付いてるでしょ?
あ、ホントだ。
これって、掛け算してるわけじゃない…よね。
さすがにこんなトコで掛け算はないでしょ。それに * の右側には何もないしね。
そ、そーだよね…
えっと、この * ってどーゆー意味なの?
この * はね、min メソッドの引数を配列として受け取るよって意味なんだ。
え、それってどういうこと?
引数の value が配列になってて、 例えば min(a, b, c, d) っていうふうにメソッドを呼び出すと、 value[0]a が代入されて、 value[1]b が代入されて、 value[2]c が代入されて、 value[3]d が代入されるの。
へぇ、そーなんだ。
…でもこれって何の役に立つの?
こうするとメソッドを呼び出す時に引数の数を自由に設定できるんだ。
つまり、min メソッドの場合は、 例えば引数を4つ指定すれば4つの引数の中から一番小さい値が返ってくるし、 10個指定すれば10個の引数の中から一番小さい値が返ってくるようにできるの。
だから引数が4つの時用の min メソッドと引数が10個の時用の min メソッドをいちいち別々に作らなくてもいいってワケ。
なるほどね〜。それってちょっと便利だね。
でしょ。
あ、でも left とか top を計算する時って引数は4つにしかならないよね?
だったら別にフツーに4つの引数で min メソッドを作っても良かったんじゃ…
ま、まぁそーなんだけど、こんなやり方もあるってのを知っててもいいんじゃないかなーと思って、ね。
う〜ん、まーそれはそーかもね。
えっと、それじゃ min メソッドの中身を見てくね。
うん。
まず最初に minval に第1引数の値を代入してるんだけど、これは OK だよね?
第1引数の値って value[1] じゃなくって value[0] になるんだね。
引数は 1, 2,… って数えるけど、配列の要素は 0, 1,… だからね。
引数の番号と配列の添え字の番号は1ずれるから注意してね。
はーい。
で、後は for ブロックの中で第2引数から1つずつ引数の値をチェックしていくの。
i1 から始まって value.count より小さい間 for ブロックの中を実行するってことだから…
第2引数から最後の引数まで順番にチェックしてくってことだよね?
count プロパティについては §1.14 参照。
ん、そうそう。
で、もし引数の値(value[i])が minval より小さかったら、 minval をその引数の値で置き換えるの。
こうやって引数の値を順番にチェックしていけば、最終的に minval は引数の中で一番小さい値になるよね。
え〜っと、そーなるのかな…?
こんなふうに minval に一旦最小値が代入されると、その後は minval の値が置き換えられることはないから、 最終的に minval が最小値になるの。

<(例)min(60, 45, 12, 20, 33) の場合の min メソッドの処理の流れ>

あ〜、なるほど。確かに最小値が返るようになってるね。
でしょ。
じゃこれを参考にして max メソッドの方を作ってみて。
max メソッドって min メソッドと逆で最大値を返すんだよねぇ…
ってコトは、min メソッドのどの部分を変えればいいと思う?
ん〜っと…全部の引数をチェックしてくとこはおんなじでいいと思うから…
if の条件が変わるのかなぁ…?
だね。じゃどんなふうに変えればいいと思う?
min メソッドと逆になるんだから…

max メソッド>

function max(value*)
{
    var maxval = value[0]; // 第1引数を現在の最大値とします
    for(var i=1;i<value.count;i++)
    {
        // 第(i+1)引数の値が現在の最大値(maxval)より大きければ maxval の値を更新します
        if(value[i] > maxval)
            maxval = value[i];
    }
    return maxval; // 引数のうち最大の値を返します
}
こんな感じになるんじゃないかな?
そうそう。今度は引数の値(value[i])が maxval より大きかったら、 maxval をその引数の値で置き換えればいいよね。
だよね!
それじゃちょっと話が飛んじゃったけど、 transformImage メソッドの話に戻るね。
うん。
これで left, right, top, bottom が計算できたから、 今度はこの4つの値を使って変形後の画像を表示するレイヤの幅と高さ(widthheight だね)を計算してるの。
幅は right - left じゃなくて right - left + 1 になるんだね。
あと高さも bottom - top + 1 だね。
ん、レイヤの四隅の点から幅とか高さを計算する時はこの『+1』を忘れないようにしてね。
レイヤのサイズを設定してるとこは大丈夫だよね?
setImageSize メソッドについては §7.3setSizeToImageSize メソッドについては §3.3 参照。
うん。
次はレイヤの中性色を透明色にして affineCopy メソッドで画像を変形してるんだけど、 これは前にも同じことやったよね。
§9.4 参照。
ん〜…確かに似たよーなことはやったと思うけど、 今回は3つの点の座標がヘンだよね?
なんで x1 とか y1 じゃなくて x1 - left とか y1 - top になってるの?
それはね、レイヤの位置を指定する時の座標系とレイヤの中に画像を(拡大したり変形したりして)コピーする時の座標系が違ってるからだよ。
? どーゆーこと?
こんなふうに、レイヤの位置を指定する時はウィンドウの左端が原点、 つまり (0, 0) の位置になるんだけど、 レイヤの中に画像をコピーする時はレイヤの左上が原点になるの。

<座標系の違い>

x1 とか y1 とかはレイヤの位置を指定する時の座標系で指定してるから、 affineCopy メソッドで画像を変形してコピーする時に x1 とか y1 をそのまま使って…

layer.affineCopy(tmpLayer, 0, 0, tmpLayer.imageWidth, tmpLayer.imageHeight, false, x1, y1, x2, y2, x3, y3, stFastLinear, true);

こうやって affineCopy メソッドを呼び出すと…

こんなふうに、変形後の画像が右に left、 下に top ずれてコピーされるから、画像の一部が見えなくなっちゃうんだ。
そーなんだ。
で、変形後の画像全体がちゃんと表示されるようにするにはどうすればいいかって言うと…
画像をもっと左上に表示するの?
そ。変形後の画像の左上の点は (left, top) になってるから、 全部の点の x 座標を left だけ小さくして、 y 座標を top だけ小さくすれば、 変形後の画像がレイヤにぴったり収まって画像全体が表示されるようになるわけね。
それで x1, x2, x3 からは left を引いてて、 y1, y2, y3 からは top を引いてたんだね。
そういうこと。
じゃ最後はレイヤの位置を調整するとこだね。
eximage マクロの pos 属性が指定されてたら setPosition メソッドを呼び出すってゆーのは scaleImage メソッドの時とおんなじだからわかるんだけど、 pos 属性が指定されてない時に setPos メソッドを呼び出してるのってなんでなの?
setPosition メソッド、 scaleImage メソッドについては §9.9 参照。
 setPos メソッドについては §3.1 参照。
さっき lefttop の値を計算したでしょ。
うん。
でもまだ計算しただけで実際にその位置にレイヤを動かしたわけじゃないから、ここで setPos メソッドでレイヤを移動させて、 正しい位置に画像が表示されるようにしてるんだ。
ちなみに pos 属性が指定されてたらそっちの方を優先するから、 lefttop の値は無視して setPosition メソッドで位置を指定するの。
あー、なるほどね。
それじゃ最後は rotateImage メソッドね。
とりあえずスクリプトはこんな感じなんだけど…

rotateImage メソッド>

function rotateImage(layer, elm)
{
    var tmpLayer = new Layer(kag, layer); // 一時レイヤを作ります
    tmpLayer.assignImages(layer); // 一時レイヤに操作対象のレイヤの画像をコピーします

    // アフィン変換のパラメータを計算します ※詳細は割愛します
    var angle = +elm.angle * Math.PI / 180;
    var xscale = (elm.xscale !== void ? +elm.xscale : (elm.scale !== void ? +elm.scale : 100)) / 100;
    var yscale = (elm.yscale !== void ? +elm.yscale : (elm.scale !== void ? +elm.scale : 100)) / 100;
    var sin = Math.sin(angle), cos = Math.cos(angle);
    var xw = xscale * tmpLayer.imageWidth, xh = xscale * tmpLayer.imageHeight;
    var yw = yscale * tmpLayer.imageWidth, yh = yscale * tmpLayer.imageHeight;
    var ofsx, ofsy;
    if(sin >= 0.0)
    {
        if(cos >= 0.0)
        {
            ofsx = yh * sin;
            ofsy = 0.0;
        }
        else
        {
            ofsx = -xw * cos + yh * sin;
            ofsy = -yh * cos;
        }
    }
    else
    {
        if(cos < 0.0)
        {
            ofsx = -xw * cos;
            ofsy = -xw * sin - yh * cos;
        }
        else
        {
            ofsx = 0.0;
            ofsy = -xw * sin;
        }
    }

    var width = int (xw * Math.abs(cos) + yh * Math.abs(sin)); // 変換後の幅を計算します
    var height = int (xw * Math.abs(sin) + yh * Math.abs(cos)); // 変換後の高さを計算します
    width = 1 if width < 1; // 幅は必ず 1 ピクセル以上になるようにします
    height = 1 if height < 1; // 高さも必ず 1 ピクセル以上になるようにします

    layer.setImageSize(width, height); // 操作対象のレイヤのサイズを変更します
    layer.setSizeToImageSize(); // 操作対象のレイヤのサイズを画像のサイズに合わせます

    // 操作対象のレイヤの中性色を一時的に透明色(0x00000000)に設定します
    var neutralColor = layer.neutralColor;
    layer.neutralColor = 0x00000000;

    // 一時レイヤの画像を回転・拡大・縮小して操作対象のレイヤにコピーします
    layer.affineCopy(tmpLayer, 0, 0, tmpLayer.imageWidth, tmpLayer.imageHeight, true, xscale * cos, xscale * sin, -yscale * sin, yscale * cos, ofsx, ofsy, stFastLinear, true);

    // 操作対象のレイヤの中性色を元に戻します
    layer.neutralColor = neutralColor;

    invalidate tmpLayer; // 一時レイヤはもう必要ないので無効化します

    // pos 属性が指定されている場合は操作対象のレイヤの位置を調整します
    setPosition(layer, elm.pos) if elm.pos !== void;
}

えっと、これって前に出てきたすごいフクザツなスクリプトに似てるよね…
rotate メソッドのことだね。
まぁ rotateImage メソッドは rotate メソッドをベースにして作ってるからね。
確か rotate メソッドって詳しい説明しなかったんだよね?
ん〜、説明しようとすると色々タイヘンだからねー。
ってワケで、rotateImage メソッドも詳しい説明は省略させてもらうね。
コレ全部省略しちゃうの?
特に新しいメソッドとかも使ってないからね。
まぁ興味があったらどんな処理してるか調べてみてね。
う〜ん、それはちょっと遠慮しときたいかも…
えっと、とりあえず angle 属性で指定した角度だけ画像を回転させるってことでいーんだよね?
うん。あと scale, xscale, yscale 属性で画像を回転させつつ拡大/縮小もできるよ。
りょーかいっ。
さて、それじゃこれで画像読み込み機能拡張プラグインも(一応)できたことだし、 実際に使ってみよっか。
なんかその「一応」ってゆーのが気になるんだけど…?
まぁまぁとりあえず実行してみよ?
う、うん。そーだね。
じゃスクリプトはここに置いとくから、 ちょっと実行してみて。
ちなみに first.ks はこんな感じね。

first.ks の中身>

; 画像読み込み機能拡張プラグインのスクリプトを読み込みます
[call storage="eximage.ks"]

; メッセージレイヤを非表示にします
[position layer=message0 page=fore visible=false]

; 画像を2倍に拡大して表示します
[eximage layer=0 page=fore storage="krkr" left=256 top=176 scale=200 visible=true]

これって scale 属性が 200 になってるから、 画像を縦横に 200%(2倍)に拡大してるってことだよね?
ん、そうそう。
それじゃ実行してみるね。

<実行結果>

これって2倍になってるよね…?
ん、ちゃんと縦横2倍に拡大されてるよ。
あと回転とか変形とかぼかすのとかも試してみてね。
うん、わかった。
これでやっと完成ってことだよね!
…だと良かったんだけどね〜。
えっ、それってどーゆーコト!?
実は、このプラグインには重大な欠陥があるんだよねー。
え、重大な欠陥って…?
さっきの first.ks をこのスクリプトで置き換えてもっかい実行してみて。

<セーブ・ロードを行うスクリプト(first.ks の中身)>

; 画像読み込み機能拡張プラグインのスクリプトを読み込みます
[call storage="eximage.ks"]

; 画像を2倍に拡大して表示します
[eximage layer=0 page=fore storage="krkr" left=256 top=176 scale=200 visible=true]

*label|

データを保存します。[p][cm]

[save place=0]

データを読み込みます。[p][cm]

[load place=0]

save タグと load タグがあるってことは、 save タグのとこで一旦セーブして、 そのデータを load タグのとこで読み込んでるってことだよね?
ん、そう。
じゃ実行してみて。
う、うん…。

<実行結果(save タグを実行した直後の状態)>

別に何も問題なさそーだけど…?
データをセーブするとこまではね。
もうちょっと進めてみて。
うん。

<実行結果(load タグを実行した直後の状態)>

あれ、画像がちっちゃくなっちゃった!?
正確には小さくなったんじゃなくて元の大きさになったんだけどね。
え、なんで??
画像を拡大したのってプラグインの scaleImage メソッドでしょ。
うん、そーだね。
実はこんなふうにプラグインで勝手に前景レイヤとか背景レイヤとかメッセージレイヤの画像を操作すると、 その後 KAG システムに面倒見てもらえなくなっちゃうんだ。
えっと、それってどーいうコト?
つまりね、KAG システムは前景レイヤに画像を読み込んだってとこまでは知ってるんだけど(画像を読み込むのには KAG の image タグを使ってるからね)、 その後プラグインで画像を拡大したってことは知らないから、 セーブデータを読み込んだ時に、レイヤに画像を読み込むとこまではやってくれるんだけど、 拡大まではしてくれないんだ。
あ、そーなんだ…
じゃあもしかして eximage マクロを使う時はセーブしちゃダメってこと?
まぁセーブ・ロードしなかったら今のままでも使えるんだけど、 さすがにそれじゃ実用的じゃないでしょ。
だよねぇ…
じゃあどーするの?
KAG システムにやってもらえないんだったら、 プラグインでやればいいよね!
…ってコトで、セーブデータを読み込む時に、プラグインの方で画像を拡大するの。
あ、そんなコトできるんだ?
ん、結構タイヘンだけどね。
えっ…
ってワケだから、次回からセーブ・ロードに対応できるようにプラグインを改良してくね。
それじゃ、また次回!


前へ | TOP | 次へ