自由線の描画(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 という意味で、小数点第1位を四捨五入しています。
その後、>>16 で整数部分を取り出しています。

点を一つ描画したら、次の描画位置へ移動します。
x は進行方向に +1、y は fpos += finc で finc の値を加算します。

x 位置が直線の終点 (x1 == x2) に来たら、終了します。