自由線の描画(3)・固定小数点演算

固定小数点演算を使った直線描画
今回は、「固定小数点演算」を使って直線を描画し、自由線を描画します。
ブレゼンハムとはアルゴリズムが異なりますが、描画結果としては大して差はありません。

固定小数点数を使う場合は、整数ビット部分が 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 位置を固定小数点数で計算します。
横長の線の場合
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) に来たら、終了します。