回転処理の高速化
前回はイメージの回転処理を行いましたが、ループ内で sin, cos 関数を使っていたので、このままでは速度的に実用的ではありません。
今回は、回転処理の高速化を行います。
今回は、回転処理の高速化を行います。
変化分を求める
まず、キャンバス描画の高速化を行った時のことを思い出してください。
あの時は、ループの値が +1 された時の座標の変化値を求めて、ループ時の初期値にそれを加算していくことで、ソース画像の座標を求めていました。
ここでも、同じことが行えます。
まずは、元のコードを見てみます。
ix が変化した時、つまり x が変化した時の変化値を求めようとするのであれば、計算式内で x が使われている部分に注目すれば良いです。
回転の計算式において、x が 1 増加した場合、sx, sy の変化値は、sx が cos(rd)、sy が sin(rd) です。
また、今回は ix, iy のループそれぞれで X, Y 座標の変化値が異なるので、「ix が変化した時の X, Y 変化値」と、「iy が変化した時の X, Y 変化値」の各4つの変化値が必要になります。
iy が変化した時の sx, sy の変化値は、sx が -sin(rd)、sy が cos(rd) です。
実際の変化値の値としては、cos(rd) と sin(rd) の2つがあれば問題なさそうです。
あの時は、ループの値が +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 の変化値は、sx が cos(rd)、sy が sin(rd) です。
また、今回は ix, iy のループそれぞれで X, Y 座標の変化値が異なるので、「ix が変化した時の X, Y 変化値」と、「iy が変化した時の X, Y 変化値」の各4つの変化値が必要になります。
iy が変化した時の sx, sy の変化値は、sx が -sin(rd)、sy が cos(rd) です。
ix 変化時 | sx : cos(rd) sy : sin(rd) |
---|---|
iy 変化時 | sx : -sin(rd) sy : cos(rd) |
実際の変化値の値としては、cos(rd) と sin(rd) の2つがあれば問題なさそうです。
実際のコード
実際に、初期値に変化分を加算する形で高速化したのが、以下のコードです。
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; 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 です。
これで、ループ内では加算しか使われなくなりました。
固定小数点演算を使う
では、上記のコードを元に、浮動小数点を固定小数点数に代えてさらに高速化してみましょう。
fcos, fsin, fsx, fsy は 小数:18bit の固定小数点数です。
fsx, fsy の初期値を求める計算では、普通の整数 xx, yy に固定小数点数である fcos, fsin を掛けているので、これで値は固定小数点数になります。
ソース画像の中心位置は普通の整数なので、<< 18 で固定小数点数に変換しますが、
「(srcimg->w / 2 << 18)」ということは「(srcimg->w >> 1 << 18)」ということなので、
ここでは「(srcimg->w >> (18 - 1))」でまとめています。
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))」でまとめています。