イメージの回転
今回は、イメージの回転について説明していきます。
回転の基本
原点が (0, 0) である場合、点 (x, y) を角度 θ で回転した後の点 (x1, y1) は、以下の計算で求められます。
この式は、回転の基本となりますから、覚えておいてください。
x1 = x * cosθ - y * sinθ y1 = x * sinθ + y * cosθ
この式は、回転の基本となりますから、覚えておいてください。
C 言語での回転
C 言語で実際に回転の計算をする場合は、以下のようになります。
PI は、円周率です。
コンパイラが gcc の場合は、<math.h> 内でマクロ M_PI に円周率が定義されているので、それを使っても良いです。
まず、C 言語の cos()、sin() 関数は、ラジアン単位で角度を指定しなければいけないので、
円の一周を 360 度とした場合、「角度 × PI / 180」で、角度をラジアン単位に変換できます。
また、ここでは角度を符号反転していますが、これは回転方向を「反時計回り」にするためです。
数学で習った円の場合は、3時の方向を 0 度として、反時計回りに回転していましたが、ここでは、正の方向は「時計回り」になります。
これはなぜかというと、Y の座標方向が異なるからです。
数学で描く座標系では、Y は「上が正、下が負の方向」ですが、画像処理におけるイメージでは、「上が負、下が正の方向」になるので、Y が逆方向になります。
なので、角度が同じでも、表示上では回転方向が逆になるというわけです。
画像処理では、基本的に Y は上から下への方向で扱いますから、cos, sin などの値は、そのままでは「時計回り」になると思ってください。
角度を符号反転すれば反時計回りになるので、ちょっとした差ではありますが…。
後は、基本の計算式と同じです。
#include <math.h> #define PI 3.141592 double x1,y1,rd; rd = -angle * PI / 180.0; x1 = x * cos(rd) - y * sin(rd); y1 = y * sin(rd) + y * cos(rd);
PI は、円周率です。
コンパイラが gcc の場合は、<math.h> 内でマクロ M_PI に円周率が定義されているので、それを使っても良いです。
まず、C 言語の cos()、sin() 関数は、ラジアン単位で角度を指定しなければいけないので、
円の一周を 360 度とした場合、「角度 × PI / 180」で、角度をラジアン単位に変換できます。
また、ここでは角度を符号反転していますが、これは回転方向を「反時計回り」にするためです。
数学で習った円の場合は、3時の方向を 0 度として、反時計回りに回転していましたが、ここでは、正の方向は「時計回り」になります。
これはなぜかというと、Y の座標方向が異なるからです。
数学で描く座標系では、Y は「上が正、下が負の方向」ですが、画像処理におけるイメージでは、「上が負、下が正の方向」になるので、Y が逆方向になります。
なので、角度が同じでも、表示上では回転方向が逆になるというわけです。
画像処理では、基本的に Y は上から下への方向で扱いますから、cos, sin などの値は、そのままでは「時計回り」になると思ってください。
角度を符号反転すれば反時計回りになるので、ちょっとした差ではありますが…。
後は、基本の計算式と同じです。
スクリーンショット
では、イメージを回転表示するプログラムを作ります。
左クリックで、15 度ずつ回転します。
左クリックで、15 度ずつ回転します。
ソースコード
使用画像: bitmap2.bmp
※ Linux では、libm のリンクが必要なので、「-lm」のオプションを追加する必要があります。
022_rotate.c
※ Linux では、libm のリンクが必要なので、「-lm」のオプションを追加する必要があります。
cc ... -lX11 -lXext -lm
022_rotate.c
#include <math.h> #include "sptk.h" #define WIDTH 300 #define HEIGHT 300 #define PI 3.141592 SPTK_IMAGE *winimg; SPTK_IMAGE32 *srcimg; int angle = 15; void draw_rotation() { int ix,iy,sx,sy; double x,y,rd; SPTK_PIX_RGBA *psrc; rd = angle * PI / 180.0; 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); psrc = sptk_image32_getptbuf(srcimg, sx, sy); if(psrc) sptk_image_setpixel(winimg, ix, iy, SPTK_RGB(psrc->r, psrc->g, psrc->b)); else sptk_image_setpixel(winimg, ix, iy, 0xcccccc); } } } void winhandle(SPTK_EVENT *ev) { switch(ev->type) { case SPTK_EVENT_BTTDOWN: angle = (angle + 15) % 360; draw_rotation(); sptk_update(NULL, 0, 0, WIDTH, HEIGHT, 0); break; case SPTK_EVENT_WINDOW_CLOSE: sptk_quit(); break; } } int main() { sptk_init("test", WIDTH, HEIGHT); sptk_window_set_handle(winhandle); winimg = sptk_window_get_image(); srcimg = sptk_image32_load_bitmap("bitmap2.bmp"); if(!srcimg) sptk_errexit("cannot load bitmap file"); draw_rotation(); sptk_run(); sptk_image32_free(srcimg); return 0; }
解説
"bitmap2.bmp" の画像を読み込み、それを元にウィンドウイメージ上に回転させます。
回転の中心は、それぞれのイメージの中心とします。
回転の中心は、それぞれのイメージの中心とします。
回転計算
まずは、ソース画像を基準として考え、ソース画像上の任意の点を、任意の角度で回転させる計算式を考えてみます。
とりあえず、回転は反時計回りとします。
これで、回転後のウィンドウイメージの座標が求められます。
しかし、以前もやったように、画像処理においては、処理後の位置から処理前の位置を逆算して求めていくというのが基本でした。
ここでは、回転後のウィンドウイメージの点から、回転前のソース画像の座標を求める必要があるので、上記の手順を最後から逆方向に実行していけば、ソース画像の座標を求めることが出来ます。
逆方向に実行すると、以下のような手順になります。
なお、逆回転は、角度を符号反転させることで実現できます。
とりあえず、回転は反時計回りとします。
- ソース画像の中心を原点 (0, 0) にするため、座標からサイズの半分を引く
- 任意の角度で回転させる
- 現在の座標は原点が (0, 0) なので、これをウィンドウイメージの中央に置くために、ウィンドウイメージのサイズの半分を足す
これで、回転後のウィンドウイメージの座標が求められます。
しかし、以前もやったように、画像処理においては、処理後の位置から処理前の位置を逆算して求めていくというのが基本でした。
ここでは、回転後のウィンドウイメージの点から、回転前のソース画像の座標を求める必要があるので、上記の手順を最後から逆方向に実行していけば、ソース画像の座標を求めることが出来ます。
逆方向に実行すると、以下のような手順になります。
- ウィンドウイメージの座標から、ウィンドウサイズの半分を引く
- 任意の角度で逆回転させる
- ソース画像のサイズの半分を足す
なお、逆回転は、角度を符号反転させることで実現できます。
実際のコード
以下が、実際に回転イメージを描画するコードです。
まず、回転角度 angle (0〜360) を、ラジアン単位にします。
ここでは、回転計算時には逆回転を行う必要があるので、角度を符号反転させる必要があるのですが、ちょっと前に戻って、C 言語での実際の回転計算のところを思い出してください。
反時計回りにするためには、角度を符号反転する必要がありました。
ここでは、実際に表示されるイメージは反時計回りで回転することにしているので、その逆回転を行うということは、-angle を符号反転して -(-angle)、ということは、そのまま angle にすればいいということです。
あとは、先ほど求めた計算手順の通りに実行しています。
イメージ画像の座標が範囲外の場合 (psrc == NULL) は、背景色を描画しています。
void draw_rotation() { int ix,iy,sx,sy; double x,y,rd; SPTK_PIX_RGBA *psrc; rd = angle * PI / 180.0; 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); psrc = sptk_image32_getptbuf(srcimg, sx, sy); if(psrc) sptk_image_setpixel(winimg, ix, iy, SPTK_RGB(psrc->r, psrc->g, psrc->b)); else sptk_image_setpixel(winimg, ix, iy, 0xcccccc); } } }
まず、回転角度 angle (0〜360) を、ラジアン単位にします。
ここでは、回転計算時には逆回転を行う必要があるので、角度を符号反転させる必要があるのですが、ちょっと前に戻って、C 言語での実際の回転計算のところを思い出してください。
反時計回りにするためには、角度を符号反転する必要がありました。
ここでは、実際に表示されるイメージは反時計回りで回転することにしているので、その逆回転を行うということは、-angle を符号反転して -(-angle)、ということは、そのまま angle にすればいいということです。
あとは、先ほど求めた計算手順の通りに実行しています。
イメージ画像の座標が範囲外の場合 (psrc == NULL) は、背景色を描画しています。
まとめ
今回は、求めたソース画像の座標の1点をそのまま描画先にセットしました。
つまりこれは、キャンバス描画の時にやった「ニアレストネイバー」と同じです。
ニアレストネイバーやバイリニアは、拡大縮小だけではなく、回転時にも使えます。
要するに、これらは、ソース画像のどの範囲の要素を、どのような計算で取得するかで違ってきます。
「ニアレストネイバー」は、1点の色のみを得ます。
「バイリニア」は、周囲4点の色から得ます。
「バイキュービック」は、周囲16点の色から得ます。
つまりこれは、キャンバス描画の時にやった「ニアレストネイバー」と同じです。
ニアレストネイバーやバイリニアは、拡大縮小だけではなく、回転時にも使えます。
要するに、これらは、ソース画像のどの範囲の要素を、どのような計算で取得するかで違ってきます。
「ニアレストネイバー」は、1点の色のみを得ます。
「バイリニア」は、周囲4点の色から得ます。
「バイキュービック」は、周囲16点の色から得ます。