イメージの回転(2)・高速化

回転処理の高速化
前回はイメージの回転処理を行いましたが、ループ内で sin, cos 関数を使っていたので、このままでは速度的に実用的ではありません。
今回は、回転処理の高速化を行います。
変化分を求める
まず、キャンバス描画の高速化を行った時のことを思い出してください。

あの時は、ループの値が +1 された時の座標の変化値を求めて、ループ時の初期値にそれを加算していくことで、ソース画像の座標を求めていました。
ここでも、同じことが行えます。

まずは、元のコードを見てみます。

for(iy = 0; iy < HEIGHT; iy++)
{
    for(ix = 0; ix < WIDTH; ix++)
    {
        x = ix - WIDTH / 2;
        y = iy - HEIGHT / 2;
        
        sx = (int)(x * cos(rd) - y * sin(rd) + srcimg->w / 2);
        sy = (int)(x * sin(rd) + y * cos(rd) + srcimg->h / 2);

ix が変化した時、つまり x が変化した時の変化値を求めようとするのであれば、計算式内で x が使われている部分に注目すれば良いです。

回転の計算式において、x が 1 増加した場合、sx, sy の変化値は、sxcos(rd)sysin(rd) です。

また、今回は ix, iy のループそれぞれで X, Y 座標の変化値が異なるので、「ix が変化した時の X, Y 変化値」と、「iy が変化した時の X, Y 変化値」の各4つの変化値が必要になります。

iy が変化した時の sx, sy の変化値は、sx-sin(rd)sycos(rd) です。

ix 変化時sx : cos(rd)
sy : sin(rd)
iy 変化時sx : -sin(rd)
sy : cos(rd)

実際の変化値の値としては、cos(rd)sin(rd) の2つがあれば問題なさそうです。
実際のコード
実際に、初期値に変化分を加算する形で高速化したのが、以下のコードです。

void draw_rotation()
{
    int ix,iy;
    double rd,dcos,dsin,dsx,dsy,xx,yy;
    SPTK_PIX_RGBA *psrc;
    
    rd = angle * PI / 180.0;
    
    dcos = cos(rd);
    dsin = sin(rd);
    
    /* ループの初期値 */
    
    xx = 0 - WIDTH / 2;
    yy = 0 - HEIGHT / 2;
    
    dsx = xx * dcos - yy * dsin + srcimg->w / 2;
    dsy = xx * dsin + yy * dcos + srcimg->h / 2;
    
    for(iy = 0; iy < HEIGHT; iy++)
    {
        xx = dsx;
        yy = dsy;
    
        for(ix = 0; ix < WIDTH; ix++)
        {
            psrc = sptk_image32_getptbuf(srcimg, (int)dsx, (int)dsy);
            
            if(psrc)
                sptk_image_setpixel(winimg, ix, iy, SPTK_RGB(psrc->r, psrc->g, psrc->b));
            else
                sptk_image_setpixel(winimg, ix, iy, 0xcccccc);
            
            dsx += dcos;
            dsy += dsin;
        }
        
        dsx = xx - dsin;
        dsy = yy + dcos;
    }
}

dsx, dsy が、現在のループにおけるソース画像の座標です。

まず、最初に sin, cos 値を求めておきます。

次に、ループの初期時のソース画像座標を求めます。
ループの初期時ということは、ix, iy がそれぞれ 0 の時の値なので、計算式に 0 を当てはめてソース画像座標を計算します。

そして、ループ内で dsx, dsy に変化分を加算していけばいいのですが、まず、ix のループ内では、「dsx += dcos; dsy += dsin;」で、普通に変化分を加算しています。

しかし、iy の変化時というのは、X が変化せずに Y のみが +1 された場合を想定していますから、ix のループ終了後に dsx, dsy にそのまま Y の変化分を加算したのでは、X 方向で進んだ分が余計に加算されている状態となります。

なので、ix のループ開始前に、現在の座標を保存しておいて、ループ終了後にそれを戻してやる必要があります。
ここでは、xx, yy に座標を保存しておいて、ループ終了後に戻し、それに Y の変化分を加算しています。

dsx, dsy にはすでに回転前のソース画像の座標が入っているので、後はそれを整数に変換して、実際の座標値とすれば OK です。

これで、ループ内では加算しか使われなくなりました。
固定小数点演算を使う
では、上記のコードを元に、浮動小数点を固定小数点数に代えてさらに高速化してみましょう。

void draw_rotation()
{
    int ix,iy,fcos,fsin,fsx,fsy,xx,yy;
    double rd;
    SPTK_PIX_RGBA *psrc;
    
    rd = angle * PI / 180.0;
    
    fcos = (int)(cos(rd) * (1<<18));
    fsin = (int)(sin(rd) * (1<<18));
    
    xx = 0 - WIDTH / 2;
    yy = 0 - HEIGHT / 2;
    
    fsx = xx * fcos - yy * fsin + (srcimg->w << (18 - 1));
    fsy = xx * fsin + yy * fcos + (srcimg->h << (18 - 1));
    
    for(iy = 0; iy < HEIGHT; iy++)
    {
        xx = fsx;
        yy = fsy;
    
        for(ix = 0; ix < WIDTH; ix++)
        {
            psrc = sptk_image32_getptbuf(srcimg, fsx >> 18, fsy >> 18);
            
            if(psrc)
                sptk_image_setpixel(winimg, ix, iy, SPTK_RGB(psrc->r, psrc->g, psrc->b));
            else
                sptk_image_setpixel(winimg, ix, iy, 0xcccccc);
            
            fsx += fcos;
            fsy += fsin;
        }
        
        fsx = xx - fsin;
        fsy = yy + fcos;
    }
}

fcos, fsin, fsx, fsy は 小数:18bit の固定小数点数です。

fsx, fsy の初期値を求める計算では、普通の整数 xx, yy に固定小数点数である fcos, fsin を掛けているので、これで値は固定小数点数になります。

ソース画像の中心位置は普通の整数なので、<< 18 で固定小数点数に変換しますが、
(srcimg->w / 2 << 18)」ということは「(srcimg->w >> 1 << 18)」ということなので、
ここでは「(srcimg->w >> (18 - 1))」でまとめています。