ブラシ画像回転
前回は、ブラシ画像を使って点を描画しましたが、今回はさらに、ブラシ画像を回転させて点を描画します。
角度を固定して、任意の回転角度で回転させることもできますが、今は自由線を描画しているので、線の進行方向に向けて回転できれば、面白い線を描画することができます。
角度を固定して、任意の回転角度で回転させることもできますが、今は自由線を描画しているので、線の進行方向に向けて回転できれば、面白い線を描画することができます。
スクリーンショット&ソースファイル
ソースファイル: 029_aaline5.c
ブラシ画像: brush2.bmp
点の描画部分
まず、点の描画部分は、前回とほとんど変わっていません。
ブラシ画像の回転角度の指定として、関数の引数に rd が追加されています。
ここには、画像の回転角度をラジアン単位で指定します。
なお、描画先の範囲を計算する際に、半径に 1.44 を掛けていますが、これは、回転によってはみ出る部分も正しく描画できるように、少し大きめのサイズにして範囲を求めています。
今回に関しては、これをしなくても正しく描画はできますが、ブラシ画像の四隅まで色がある形状の場合、回転0の場合の範囲で描画すると、描画範囲が足らず、四隅が欠けた状態になってしまいます。
これを回避するために、とりあえず半径に適当に 1.44 を掛けて、これくらいの大きさなら回転後のイメージが全て入るだろう、という範囲に広げています。
次に、指定された角度の sin, cos 値を取得しておきます。
ここでは、逆回転を行ってブラシ画像の座標を計算するのですから、角度は符号反転させます。
そして、座標変換部分で、拡大縮小を適用する前に、回転計算を行って、回転後の座標から、回転前の座標を得ます。
xx, yy が回転後の座標ですから、これに逆回転を行って、xx2, yy2 に回転前の座標を入れます。
sin, cos 値には逆回転用の値が入っているので、そのまま回転計算を行います。
あとは、ほぼ前回と同じです。
void drawpoint(double x,double y,double radius,double rd) { int nx1,ny1,nx2,ny2,ix,iy,jx,jy,val,bx,by; double xx,yy,xx2,yy2,scale,brushhalf,dsin,dcos; SPTK_PIX_RGBA col,*pbrush; /* 描画先の範囲 */ xx = radius * 1.44; nx1 = (int)floor(x - xx); ny1 = (int)floor(y - xx); nx2 = (int)floor(x + xx); ny2 = (int)floor(y + xx); /* 描画先ループ */ brushhalf = brushimg->w / 2.0; scale = brushhalf / radius; dcos = cos(-rd); dsin = sin(-rd); for(iy = ny1; iy <= ny2; iy++) { for(ix = nx1; ix <= nx2; ix++) { /* オーバーサンプリング */ val = 0; for(jy = 0; jy < 8; jy++) { yy = iy - y + jy * (1.0 / 8); for(jx = 0; jx < 8; jx++) { xx = ix - x + jx * (1.0 / 8); xx2 = xx * dcos - yy * dsin; yy2 = xx * dsin + yy * dcos; bx = (int)(xx2 * scale + brushhalf); by = (int)(yy2 * scale + brushhalf); if(bx >= 0 && bx < brushimg->w && by >= 0 && by < brushimg->h) { pbrush = brushimg->pixbuf + by * brushimg->w + bx; val += 255 - pbrush->r; } } } val >>= 6; /* 結果 */ col = drawcol; col.a = drawcol.a * val / 255; sptk_image32_setpixel_rgba(layerimg, ix, iy, &col); } } }
ブラシ画像の回転角度の指定として、関数の引数に rd が追加されています。
ここには、画像の回転角度をラジアン単位で指定します。
なお、描画先の範囲を計算する際に、半径に 1.44 を掛けていますが、これは、回転によってはみ出る部分も正しく描画できるように、少し大きめのサイズにして範囲を求めています。
今回に関しては、これをしなくても正しく描画はできますが、ブラシ画像の四隅まで色がある形状の場合、回転0の場合の範囲で描画すると、描画範囲が足らず、四隅が欠けた状態になってしまいます。
これを回避するために、とりあえず半径に適当に 1.44 を掛けて、これくらいの大きさなら回転後のイメージが全て入るだろう、という範囲に広げています。
次に、指定された角度の sin, cos 値を取得しておきます。
dcos = cos(-rd); dsin = sin(-rd);
ここでは、逆回転を行ってブラシ画像の座標を計算するのですから、角度は符号反転させます。
そして、座標変換部分で、拡大縮小を適用する前に、回転計算を行って、回転後の座標から、回転前の座標を得ます。
xx, yy が回転後の座標ですから、これに逆回転を行って、xx2, yy2 に回転前の座標を入れます。
xx2 = xx * dcos - yy * dsin; yy2 = xx * dsin + yy * dcos; bx = (int)(xx2 * scale + brushhalf); by = (int)(yy2 * scale + brushhalf);
sin, cos 値には逆回転用の値が入っているので、そのまま回転計算を行います。
あとは、ほぼ前回と同じです。
進行方向の角度
では、進行方向の角度を求めるため、直線描画時に、ブラシ画像の回転角度をラジアン単位で求めましょう。
今回は線の進行方向の角度を求めたいのですが、この場合は、「アークタンジェント」の関数を使うと、簡単に求めることができます。
C 言語では、atan()、atan2() の関数を使います。
アークタンジェントについての詳しい説明はしませんが、とりあえず、原点を (0, 0) とした場合に、(x, y) の位置の角度をラジアン単位で求める関数だと思っておけば OK です。
直線描画時は、(x1,y1)-(x2,y2) 間で線を引くのですから、(x1, y1) を原点 (0, 0) とすれば、進行方向の位置は、(x2-x1, y2-y1) となり、この位置の角度を求めれば、進行方向の角度を得ることができます。
今回は、atan2() を使います。
第一引数に y 位置、第二引数に x 位置を指定します。
x, y の指定順序が逆なので、注意してください。
これで、ラジアン単位の角度が求められますから、あとはこの値を点描画関数に渡すだけです。
ちなみに、3時の方向が0度です。
今回は線の進行方向の角度を求めたいのですが、この場合は、「アークタンジェント」の関数を使うと、簡単に求めることができます。
C 言語では、atan()、atan2() の関数を使います。
#include <math.h> double atan(double x); double atan2(double y,double x);
アークタンジェントについての詳しい説明はしませんが、とりあえず、原点を (0, 0) とした場合に、(x, y) の位置の角度をラジアン単位で求める関数だと思っておけば OK です。
直線描画時は、(x1,y1)-(x2,y2) 間で線を引くのですから、(x1, y1) を原点 (0, 0) とすれば、進行方向の位置は、(x2-x1, y2-y1) となり、この位置の角度を求めれば、進行方向の角度を得ることができます。
rd = atan2(y2 - y1, x2 - x1);
今回は、atan2() を使います。
第一引数に y 位置、第二引数に x 位置を指定します。
x, y の指定順序が逆なので、注意してください。
これで、ラジアン単位の角度が求められますから、あとはこの値を点描画関数に渡すだけです。
ちなみに、3時の方向が0度です。
最適化
ついでに、処理を最適化して高速化したものも記述しておきます。
ちょっとややこしくなっていますが、基本的にはイメージの回転を最適化した時と同じやり方です。
ループの開始時のブラシ画像座標を計算しておき、それに変化値分を加算していく形で求めています。
今回は、回転後に拡大縮小も行っているので、変化分の値には、それも考慮する必要があります。
浮動小数点を固定小数点数に代えれば、さらに高速化できるでしょう。
ただし、その場合は、整数部分の値の範囲に注意しておく必要があります。
ブラシ画像のサイズが大きい場合は、それだけ整数部分のビット幅を取っておかなければなりません。
void drawpoint_fast(double x,double y,double radius,double rd) { int nx1,ny1,nx2,ny2,ix,iy,jx,jy,val,bx,by; double xx,yy,scale,brushhalf,dsin,dcos,dsin_j,dcos_j; double dbx,dby,dbx_j,dby_j,dbx_j2,dby_j2; SPTK_PIX_RGBA col,*pbrush; /* 描画先の範囲 */ xx = radius * 1.44; nx1 = (int)floor(x - xx); ny1 = (int)floor(y - xx); nx2 = (int)floor(x + xx); ny2 = (int)floor(y + xx); /* 各値 */ brushhalf = brushimg->w / 2.0; scale = brushhalf / radius; dcos = cos(-rd); dsin = sin(-rd); xx = nx1 - x; yy = ny1 - y; dbx = (xx * dcos - yy * dsin) * scale + brushhalf; dby = (xx * dsin + yy * dcos) * scale + brushhalf; dcos = dcos * scale; dsin = dsin * scale; dcos_j = dcos / 8; dsin_j = dsin / 8; /* 描画先ループ */ for(iy = ny1; iy <= ny2; iy++) { xx = dbx; yy = dby; for(ix = nx1; ix <= nx2; ix++) { /* オーバーサンプリング */ val = 0; dbx_j = dbx; dby_j = dby; for(jy = 0; jy < 8; jy++) { dbx_j2 = dbx_j; dby_j2 = dby_j; for(jx = 0; jx < 8; jx++) { bx = (int)dbx_j; by = (int)dby_j; if(bx >= 0 && bx < brushimg->w && by >= 0 && by < brushimg->h) { pbrush = brushimg->pixbuf + by * brushimg->w + bx; val += 255 - pbrush->r; } dbx_j += dcos_j; dby_j += dsin_j; } dbx_j = dbx_j2 - dsin_j; dby_j = dby_j2 + dcos_j; } val >>= 6; /* 結果 */ col = drawcol; col.a = drawcol.a * val / 255; sptk_image32_setpixel_rgba(layerimg, ix, iy, &col); dbx += dcos; dby += dsin; } dbx = xx - dsin; dby = yy + dcos; } }
ちょっとややこしくなっていますが、基本的にはイメージの回転を最適化した時と同じやり方です。
ループの開始時のブラシ画像座標を計算しておき、それに変化値分を加算していく形で求めています。
今回は、回転後に拡大縮小も行っているので、変化分の値には、それも考慮する必要があります。
浮動小数点を固定小数点数に代えれば、さらに高速化できるでしょう。
ただし、その場合は、整数部分の値の範囲に注意しておく必要があります。
ブラシ画像のサイズが大きい場合は、それだけ整数部分のビット幅を取っておかなければなりません。