自由線の描画(2)・ブレゼンハム

ブレゼンハムアルゴリズム
前回では、カーソルの位置に点を描画するだけだったので、描画結果は直線には見えませんでした。
これを繋がった直線にするためには、点と点の間を、直線で描画する必要があります。

直線を描画するアルゴリズムには、有名なものでは「ブレゼンハム」のアルゴリズムがあります。
(参考) wikipedia:ブレゼンハムのアルゴリズム

他にも固定小数点演算を使った方法もありますが、これは後述します。
まずは、ブレゼンハムアルゴリズムを使って直線描画を実装し、ちゃんとした自由線を描画させます。
スクリーンショット
ソースコード
003_freeline.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,sx,sy,a,a1,e;
        
    if(x1 <= x2)
        dx = x2 - x1, sx = 1;
    else
        dx = x1 - x2, sx = -1;

    if(y1 <= y2)
        dy = y2 - y1, sy = 1;
    else
        dy = y1 - y2, sy = -1;
    
    if(dx >= dy)
    {
        a  = 2 * dy;
        a1 = a - 2 * dx;
        e  = a - dx;
    
        while(1)
        {
            sptk_image_setpixel(img, x1, y1, col);
            
            if(x1 == x2) break;
            
            if(e >= 0) y1 += sy, e += a1;
            else e += a;
            
            x1 += sx;
        }
    }
    else
    {
        a  = 2 * dx;
        a1 = a - 2 * dy;
        e  = a - dy;
        
        while(1)
        {
            sptk_image_setpixel(img, x1, y1, col);
            
            if(y1 == y2) break;
            
            if(e >= 0) x1 += sx, e += a1;
            else e += a;
            
            y1 += sy;
        }
    }
}

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;
}
解説
drawline() 関数が、ブレゼンハムによる直線描画の処理です。
動作
直線を描画するためには、始点と終点の2点が必要です。

まず、ボタンが押された時は、その位置が直線の「始点」になるので、変数にその位置を記録しておきます。
次に、カーソルが移動された時は、「始点」はボタンが押された時の位置、「終点」は現在位置となるので、[押し時に記録しておいた位置〜現在位置] までを直線で引きます。
次に引く直線では、「始点」は現在の位置になるので、次の始点として現在位置を記録しておきます。
以降は、[記録しておいた位置〜現在位置] までを直線で引きます。

これを繰り返せば、直線での自由線が描画できます。

ここでは、始点を記録しておくための変数として lastx,lasty が用意されています。
この変数に常に始点位置を記録しておきます。
更新
今回は更新に sptk_update_rect() を使っています。
点 (x1,y1) と 点 (x2,y2) からなる矩形範囲を更新させます。
この関数は、値の小さい方が左上、値の大きい方が右下の位置になるように処理されるので、左上、右下を意識する必要はありません。