アンチエイリアス付きの自由線描画(5)・ブラシ画像回転

ブラシ画像回転
前回は、ブラシ画像を使って点を描画しましたが、今回はさらに、ブラシ画像を回転させて点を描画します。

角度を固定して、任意の回転角度で回転させることもできますが、今は自由線を描画しているので、線の進行方向に向けて回転できれば、面白い線を描画することができます。
スクリーンショット&ソースファイル


ソースファイル: 029_aaline5.c
ブラシ画像: brush2.bmp
点の描画部分
まず、点の描画部分は、前回とほとんど変わっていません。

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() の関数を使います。

#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;
    }
}

ちょっとややこしくなっていますが、基本的にはイメージの回転を最適化した時と同じやり方です。
ループの開始時のブラシ画像座標を計算しておき、それに変化値分を加算していく形で求めています。

今回は、回転後に拡大縮小も行っているので、変化分の値には、それも考慮する必要があります。

浮動小数点を固定小数点数に代えれば、さらに高速化できるでしょう。
ただし、その場合は、整数部分の値の範囲に注意しておく必要があります。
ブラシ画像のサイズが大きい場合は、それだけ整数部分のビット幅を取っておかなければなりません。