自由線の描画(1)・点での描画

自由線の描画
まずは、ペイントソフトの基本として、
マウスの左ボタンドラッグで、キャンバスに自由線を描画する」プログラムを作りたいと思います。

左ボタンが押されたら描画を開始し、ボタンが離されたら描画を終了します。
ボタンが押されている間は、ポインタの位置に点を描画します。

実際に線に見えるように描画するためには、点ではなく直線で描画する必要がありますが、それは次でやります。
スクリーンショット


マウスを素早く動かすと点々…となり、このままでは線には見えません。
ソースコード
002_linepoint.c
#include "sptk.h"

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

void winhandle(SPTK_EVENT *ev)
{
    switch(ev->type)
    {
        case SPTK_EVENT_BTTDOWN:
            if(ev->mouse.btt == SPTK_MOUSEBTT_LEFT)
                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())
            {
                sptk_image_setpixel(sptk_window_get_image(), ev->mouse.x, ev->mouse.y, PIXCOL);
                sptk_update(NULL, ev->mouse.x, ev->mouse.y, 1, 1, 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_BTTDOWN 時
マウスのボタンが押された時に来るイベントです。

左ボタン (SPTK_MOUSEBTT_LEFT) が押されていたら、描画を開始します。
ここでは、sptk_grab() でマウスをグラブしています。
グラブについて
マウスカーソルが、プログラムのウィンドウの領域外に出ている場合、カーソルに関するイベントは、そのウィンドウに来なくなります。

この状態では、カーソル座標が取得できない上、ウィンドウ外でボタンが離された場合にそのイベントを受け取ることもできないため、正しく処理させることができません。
そこで必要なのが、マウスの「グラブ (キャプチャ)」です。

マウスをグラブすると、グラブ中は、特定のウィンドウに常にマウスイベントを送るようにすることができます。
これで、カーソルがウィンドウ外に出た場合でも座標を取得でき、ウィンドウ外でボタンが離されても、そのイベントを取得できます。

ただし、マウスイベントが常に一つのウィンドウに送られるということは、「他のウィンドウにはマウスイベントが送られない=他のウィンドウが操作できなくなる」ということなので、グラブは一時的なものでなければなりません。
グラブが必要なくなった時は、すぐにグラブを解除して通常の状態に戻しましょう。

SPTK では、sptk_grab() でマウスをグラブでき、sptk_ungrab() でグラブを解除できます。

void sptk_grab(SPTK_WIDGET *widget);
void sptk_ungrab(void);

widget: グラブするウィジェットを指定します。NULL で、メインウィンドウ自体をグラブします。
SPTK_EVENT_BTTUP 時
マウスボタンが離された時

ボタンが離されたら描画を終えます。
ここでは、sptk_ungrab() でグラブを解除します。
SPTK_EVENT_MOUSEMOVE 時
マウスカーソルが移動した時

グラブされている間 (左ボタンが押されている間)、カーソルの位置に点を描画して、その後、画面を更新します。
sptk_image_setpixel() でイメージ上に点を描画し、sptk_update() で、イメージの内容をウィンドウに反映させるために更新要求を行います。
描画について
SPTK では、メインウィンドウが作成されると同時に、ウィンドウのサイズと同じサイズのイメージ (SPTK_IMAGE) が作成されます。
このイメージに描画されている内容が、そのままウィンドウの内容として表示されます。
そのため、ウィンドウの内容を変更したい場合は、まずそのイメージに描画して、その後、イメージの内容をウィンドウに反映させる必要があります。

イメージへの描画
ウィンドウが内部で保持しているイメージは、sptk_window_get_image() で取得できます。

SPTK_IMAGE *sptk_window_get_image(void);

SPTK_IMAGE への描画には、sptk_image_*() 関数を使ってください。
色は、「0xRRGGBB」の値で指定します。

なお、SPTK_IMAGE のイメージのフォーマットは、OS やディスプレイのカラーモードなど、環境によって異なるので、直接イメージのバッファを操作することは避けてください。

//点を打つ
void sptk_image_setpixel(SPTK_IMAGE *img,int x,int y,uint32_t col);

//指定色ですべて塗りつぶす
void sptk_image_clear(SPTK_IMAGE *img,uint32_t col);

ウィンドウの更新要求
イメージに描画するだけでは、ウィンドウに表示される内容は変化しません。
イメージの内容をウィンドウに反映させるためには、ウィンドウの更新要求を行う必要があります。

void sptk_update(SPTK_WIDGET *wg,int x,int y,int w,int h,int time);

wg位置の基準とするウィジェット。NULL でメインウィンドウ自体。
x,y,w,h更新範囲
timeミリセカンド単位の時間。
この時間後には必ず更新させる。
0 ですぐに更新させる。負の値で範囲の追加のみ行う。

この関数で更新範囲を指定すると、更新するタイミングが来た時に、イメージの内容がウィンドウに転送されます。
更新のタイミングについて
ウィンドウの更新処理は、sptk_update() を実行した時点で、すぐに行われるわけではありません。
(time に 0 を指定した場合は除く)
実際には、更新する範囲を内部で記録しておき、他に処理するイベントがなくなった時点で、更新が行われます。

イベントが溜まっている間はそちらの処理を優先して、余裕がある時にまとめて更新させることで、描画処理を最適化することができます。
そのため、通常のプログラムでは、更新のタイミングについて気にする必要はありません。
Windows API でも、ウィンドウ更新に関しては同じような仕組みになっています。

しかし、ペイントソフトなどでは、更新のタイミングが重要になる場合があります。
例えば、重い処理を行っている場合は、イベントが常に溜まっている状態になり、その結果、なかなか更新が行われないという事態になることがあります。

ペイントソフトの場合は、キャンバスのスクロールなど、キャンバス全体を更新する必要がある場合は、そういった状態になるので、スクロール中はカクカクとした表示になってしまい、「処理が重い?」と思うことがありますが、実際には処理の負荷には余裕があるけれど、更新が行われていないだけ、ということがあります。

SPTK の更新タイミング
ここでは、ペイントソフトを作るという前提なので、SPTK ライブラリ自体に、更新タイミングを指定する機能を付けました。

sptk_update() 時に time で時間が指定されると、タイマーを起動させて、指定時間後には確実に更新が行われるようにしています。

時間の単位はミリセカンドです (1秒 = 1000 ms)。
time に 0 を指定すると、タイマーは使わずに、すぐに更新を行います。
time に負の値を指定すると、タイマーは起動させず、通常の更新タイミングに任せます。

なお、すでに更新用のタイマーが起動している状態で更新範囲が追加された場合、タイマーは現状のまま継続させます。
追加された範囲は、次の更新タイミングの時に、同時に転送されます。