固定小数点演算を使った直線描画
今回は、「固定小数点演算」を使って直線を描画し、自由線を描画します。
ブレゼンハムとはアルゴリズムが異なりますが、描画結果としては大して差はありません。
固定小数点数を使う場合は、整数ビット部分が x, y 座標の値となるので、大きな画像を扱う場合には、整数部分のビット数に注意しておく必要があります。
例えば、「整数:16bit、小数:16bit」の場合、座標の値は -32768 ~ 32767 までの範囲しか表現できません。
ブレゼンハムとはアルゴリズムが異なりますが、描画結果としては大して差はありません。
固定小数点数を使う場合は、整数ビット部分が x, y 座標の値となるので、大きな画像を扱う場合には、整数部分のビット数に注意しておく必要があります。
例えば、「整数:16bit、小数:16bit」の場合、座標の値は -32768 ~ 32767 までの範囲しか表現できません。
ソースコード
005_freeline2.c
#include "sptk.h" #define WIDTH 300 #define HEIGHT 300 #define PIXCOL 0x0000ff int lastx,lasty; void drawline(SPTK_IMAGE *img,int x1,int y1,int x2,int y2,uint32_t col) { int dx,dy,inc,fpos,finc; dx = (x1 < x2)? x2 - x1: x1 - x2; dy = (y1 < y2)? y2 - y1: y1 - y2; if(dx > dy) { fpos = y1 << 16; finc = ((y2 - y1) << 16) / (x2 - x1); if(x1 <= x2) inc = 1; else inc = -1, finc = -finc; while(1) { sptk_image_setpixel(img, x1, (fpos + (1<<15)) >> 16, col); if(x1 == x2) break; x1 += inc; fpos += finc; } } else { fpos = x1 << 16; finc = ((x2 - x1) << 16) / (y2 - y1); if(y1 <= y2) inc = 1; else inc = -1, finc = -finc; while(1) { sptk_image_setpixel(img, (fpos + (1<<15)) >> 16, y1, col); if(y1 == y2) break; y1 += inc; fpos += finc; } } } void winhandle(SPTK_EVENT *ev) { SPTK_RECT rc; switch(ev->type) { case SPTK_EVENT_BTTDOWN: if(ev->mouse.btt == SPTK_MOUSEBTT_LEFT) { lastx = ev->mouse.x; lasty = ev->mouse.y; sptk_grab(NULL); } break; case SPTK_EVENT_BTTUP: if(ev->mouse.btt == SPTK_MOUSEBTT_LEFT && sptk_isgrab()) sptk_ungrab(); break; case SPTK_EVENT_MOUSEMOVE: if(sptk_isgrab()) { rc.x1 = lastx; rc.y1 = lasty; rc.x2 = ev->mouse.x; rc.y2 = ev->mouse.y; drawline(sptk_window_get_image(), rc.x1, rc.y1, rc.x2, rc.y2, PIXCOL); sptk_update_rect(NULL, &rc, 0); lastx = ev->mouse.x; lasty = ev->mouse.y; } break; case SPTK_EVENT_WINDOW_CLOSE: sptk_quit(); break; } } int main() { sptk_init("test", WIDTH, HEIGHT); sptk_window_set_handle(winhandle); sptk_run(); return 0; }
解説
ここでは、小数部分が 16bit の固定小数点数で計算しています。
まず、直線が横長か縦長かどうかで、処理を2通りに分けます。
横長の線の場合 (dx > dy)、x 方向には +1/-1 ずつ点を移動していき、y 位置を固定小数点数で計算していきます。
縦長の線の場合、y 方向には +1/-1 ずつ点を移動していき、x 位置を固定小数点数で計算します。
まず、直線が横長か縦長かどうかで、処理を2通りに分けます。
横長の線の場合 (dx > dy)、x 方向には +1/-1 ずつ点を移動していき、y 位置を固定小数点数で計算していきます。
縦長の線の場合、y 方向には +1/-1 ずつ点を移動していき、x 位置を固定小数点数で計算します。
横長の線の場合
fpos が、固定小数点数で表現した現在の y 位置です。
始点の y 位置は y1 なので、fpos = y1 << 16 で y1 を左にシフトして固定小数点数にします。
finc は、「x が 1px 移動するごとに y 位置 (fpos) がどれだけ移動するか」という値です。
ここでは、finc = ((y2 - y1) << 16) / (x2 - x1) となります。
y2 - y1 を << 16 で固定小数点数にしてから、x2 - x1 で割ることで、finc も固定小数点数となります。
実際に点を描画する場合は、fpos の固定小数点数から整数部分を取り出して、y 位置とします。
ここでは、(fpos + (1<<15)) >> 16 となっています。
+(1<<15) は、+0.5 という意味で、小数点以下を四捨五入しています。
その後、>>16 で整数部分を取り出しています。
点を一つ描画したら、次の描画位置へ移動します。
x は進行方向に +1、y は fpos += finc で finc の値を加算します。
x 位置が直線の終点 (x1 == x2) に来たら、終了します。
始点の y 位置は y1 なので、fpos = y1 << 16 で y1 を左にシフトして固定小数点数にします。
finc は、「x が 1px 移動するごとに y 位置 (fpos) がどれだけ移動するか」という値です。
ここでは、finc = ((y2 - y1) << 16) / (x2 - x1) となります。
y2 - y1 を << 16 で固定小数点数にしてから、x2 - x1 で割ることで、finc も固定小数点数となります。
実際に点を描画する場合は、fpos の固定小数点数から整数部分を取り出して、y 位置とします。
ここでは、(fpos + (1<<15)) >> 16 となっています。
+(1<<15) は、+0.5 という意味で、小数点以下を四捨五入しています。
その後、>>16 で整数部分を取り出しています。
点を一つ描画したら、次の描画位置へ移動します。
x は進行方向に +1、y は fpos += finc で finc の値を加算します。
x 位置が直線の終点 (x1 == x2) に来たら、終了します。