自由線の太さを変更

自由線の太さを変更
今までは太さが 1px の線しか描画できなかったので、
今回は太さを変えて自由線を描画できるようにします。
スクリーンショット


指定できる太さは 1px 〜 4px まで。
キーボードの '1''4' (テンキーは除く) を押すと、現在の太さを変更できます。
ソースコード
008_freeline3.c
#include "sptk.h"

#define WIDTH  300
#define HEIGHT 300
#define PIXCOL 0x0000ff

int lastx,lasty;
int drawsize = 1;

unsigned char bit_1px[1] = {0x80};
unsigned char bit_2px[2] = {0xc0,0xc0};
unsigned char bit_3px[3] = {0x40,0xe0,0x40};
unsigned char bit_4px[4] = {0x60,0xf0,0xf0,0x60};

unsigned char *bit_array[4] = {bit_1px, bit_2px, bit_3px, bit_4px};

void drawpoint(SPTK_IMAGE *img,int x,int y,uint32_t col,int size)
{
    int ix,iy,f;
    unsigned char *p;
    
    x -= size / 2;
    y -= size / 2;
    
    p = bit_array[size - 1];
    
    for(iy = 0; iy < size; iy++, p++)
    {
        f = 0x80;

        for(ix = 0; ix < size; ix++, f >>= 1)
        {
            if(*p & f)
                sptk_image_setpixel(img, x + ix, y + iy, col);
        }
    }
}

void drawline(SPTK_IMAGE *img,int x1,int y1,int x2,int y2,uint32_t col,int size)
{
    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)
        {
            drawpoint(img, x1, y1, col, size);
            
            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)
        {
            drawpoint(img, x1, y1, col, size);
            
            if(y1 == y2) break;
            
            if(e >= 0) x1 += sx, e += a1;
            else e += a;
            
            y1 += sy;
        }
    }
}

void winhandle(SPTK_EVENT *ev)
{
    SPTK_RECT rc;
    int half;

    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, drawsize);
                
                /* 更新 */
                
                sptk_rect_swap(&rc);
                
                half = drawsize / 2;
                
                rc.x1 -= half;
                rc.y1 -= half;
                rc.x2 += drawsize - half - 1;
                rc.y2 += drawsize - half - 1;
                
                sptk_update_rect(NULL, &rc, 0);
                
                lastx = ev->mouse.x;
                lasty = ev->mouse.y;
            }
            break;

        case SPTK_EVENT_WINDOW_KEYDOWN:
            if(ev->key.code >= '1' && ev->key.code <= '4')
                drawsize = ev->key.code - '0';
            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;
}
解説
キー押しイベント
ウィンドウ上でキーが押されると、SPTK_EVENT_WINDOW_KEYDOWN イベントが発生します。

取得できるキーは、アルファベットか、数字 (テンキーは除く) のキーのみです。

SPTK_EVENT 構造体の key.code にキーの ASCII 文字コードが入っています。
アルファベットの場合は、大文字の文字コードが入ります。

なお、このイベントはメインウィンドウのイベントハンドラでしか発生しません。
(ウィジェットのハンドラでは起こらない)

今回は、'1'〜'4' が押された時に、drawsize に描画する太さをセットします。
drawpoint()
直線描画のアルゴリズム自体はブレゼンハムそのままですが、点を打つべきところが、drawpoint() 関数に置き換わっています。
今回は、点を打つかわりに、その位置に指定サイズの円を描画することで、線の太さを変えています。

drawpoint() は、x,y を中心として、指定されたサイズの円を描画します。
円のイメージは、あらかじめビットイメージで定義してあるので、それを使って描画します。

最初に
x -= size / 2;
y -= size / 2;
としているのは、

x,y が円の中心位置であるのに対して、実際の描画では円の左上位置から描画しているため、x,y を円の左上位置に修正する必要があるためです。
size / 2 で、円の半径分移動します。

あとは、定義してある円のイメージのビットが ON の部分に点を打っていきます。
更新範囲
気をつけなければいけないのは、sptk_update_rect() による更新時です。

太さが 1px の場合は、そのまま直線の範囲で指定すれば問題なかったのですが、今回は線の太さが変わっているので、太さによって拡張された部分を追加して範囲を指定する必要があります。

まずは、sptk_rect_swap() で、(x1,y1) - (x2,y2) がそれぞれ左上、右下になるように値を入れ替えます。

(x1,y1) の左上位置は、drawpoint() 内で円の半径分左に描画部分が拡張しているので、drawsize / 2 分を左に広げます。
(x2,y2) の右下位置は、円の中心から右側部分のサイズを、右に広げます。
実際の実装
今回はすでに用意された 1 〜 4px までの太さしか指定できませんでしたが、実際にペイントソフトで実装する場合には、任意のサイズの円のビットイメージを作成できるようにすれば、自由な太さで描画することができます。

なお、今回の方法は主にドット絵など、ドットでの描画を目的とした方法です。
アンチエイリアスな線を描画する場合にはまた別のやり方があるので、それは今後説明していきます。