HSVカラーマップから色選択

HSV カラーマップから色選択
前回では、RGB バーを付けて色選択ができるようにしましたが、ペイントソフトでは、HSV や HLS などの色空間でカラーマップを用意して、そこから選択できるようにすると便利です。

今回は、よく使われる HSV 色空間のカラーマップを作って、色を選択できるようにします。

HSV は、H (色相)、S (彩度)、V (明度) から成ります。
スクリーンショット


左の色相部分をドラッグで、現在の色相を変更。
右の SV 部分をドラッグで、現在の色を下の色プレビュー上に表示。
ソースコード
009_hsvmap.c
#include "sptk.h"

#define HSVMAP_SIZE 150
#define HSVMAP_H_W  15
#define WIDTH   (10 + HSVMAP_H_W + 10 + HSVMAP_SIZE + 10)
#define HEIGHT  (10 + HSVMAP_SIZE + 10 + 50 + 10)

#define DRAGF_H  1
#define DRAGF_SV 2

SPTK_IMAGE *image;
int hsv_h = 0,    /* 現在のH: 0-359 */
    hsv_h_y = 0,  /* H のカーソル y 位置 */
    dragflag = 0;
SPTK_POINT hsv_sv_pos;

/** HSV -> RGB
 * h:0-359 s,v:0.0-1.0 */

uint32_t hsv_to_rgb(int h,double s,double v)
{
    double r,g,b,c1,c2,c3,t;
    int rr,gg,bb;

    if(s == 0)
        r = g = b = v;
    else
    {
        t  = ((h * 6) % 360) / 360.0;
        c1 = v * (1 - s);
        c2 = v * (1 - s * t);
        c3 = v * (1 - s * (1 - t));

        switch(h / 60)
        {
            case 0: r = v;  g = c3; b = c1; break;
            case 1: r = c2; g = v;  b = c1; break;
            case 2: r = c1; g = v;  b = c3; break;
            case 3: r = c1; g = c2; b = v;  break;
            case 4: r = c3; g = c1; b = v;  break;
            case 5: r = v;  g = c1; b = c2; break;
        }
    }
    
    rr = (int)(r * 255 + 0.5);
    gg = (int)(g * 255 + 0.5);
    bb = (int)(b * 255 + 0.5);

    return (rr << 16) | (gg << 8) | bb;
}

/** H バーの初期描画 */

void draw_hsv_h()
{
    int i;
    uint32_t col;
    
    for(i = 0; i < HSVMAP_SIZE; i++)
    {
        col = hsv_to_rgb(i * 359 / (HSVMAP_SIZE - 1), 1, 1);
                
        sptk_image_hline(image, 10, 10 + i, HSVMAP_H_W, col); 
    }
    
    /* XOR カーソル */
    sptk_image_hline(image, 10, 10, HSVMAP_H_W, SPTK_COL_XOR);
}

/** SV 部分の描画 */

void draw_hsv_sv()
{
    int ix,iy;
    uint32_t col;
    
    for(iy = 0; iy < HSVMAP_SIZE; iy++)
    {
        for(ix = 0; ix < HSVMAP_SIZE; ix++)
        {
            col = hsv_to_rgb(hsv_h,
                    (double)ix / (HSVMAP_SIZE - 1),
                    1 - (double)iy / (HSVMAP_SIZE - 1));
            
            sptk_image_setpixel(image, 10 + HSVMAP_H_W + 10 + ix, 10 + iy, col);
        }
    }
}

/** SV カーソル描画 */

void draw_sv_cursor()
{
    int x,y;
    
    x = 10 + HSVMAP_H_W + 10 + hsv_sv_pos.x - 3;
    y = 10 + hsv_sv_pos.y - 3;

    sptk_image_box(image, x, y, 7, 7, SPTK_COL_XOR);
    sptk_update(NULL, x, y, 7, 7, 5);
}

void winhandle(SPTK_EVENT *ev)
{
    uint32_t col;
    int x,y;

    switch(ev->type)
    {
        case SPTK_EVENT_BTTDOWN:
            if(ev->mouse.btt == SPTK_MOUSEBTT_LEFT && dragflag == 0)
            {
                if(ev->mouse.x >= 10 && ev->mouse.x < 10 + HSVMAP_H_W &&
                    ev->mouse.y >= 10 && ev->mouse.y < 10 + HSVMAP_SIZE)
                {
                    /* H 部分 */
                    
                    dragflag = DRAGF_H;
                    sptk_grab(NULL);
                }
                else if(ev->mouse.x >= 10 + HSVMAP_H_W + 10 &&
                    ev->mouse.x < 10 + HSVMAP_H_W + 10 + HSVMAP_SIZE &&
                    ev->mouse.y >= 10 && ev->mouse.y < 10 + HSVMAP_SIZE)
                {
                    /* SV 部分 */
                    
                    dragflag = DRAGF_SV;
                    hsv_sv_pos.x = ev->mouse.x - (10 + HSVMAP_H_W + 10);
                    hsv_sv_pos.y = ev->mouse.y - 10;
                    
                    draw_sv_cursor();
                    
                    sptk_grab(NULL);
                }
            }
            break;
        case SPTK_EVENT_BTTUP:
            if(ev->mouse.btt == SPTK_MOUSEBTT_LEFT && dragflag)
            {
                if(dragflag == DRAGF_SV)
                    draw_sv_cursor();
            
                dragflag = 0;
                sptk_ungrab();
            }
            break;
        case SPTK_EVENT_MOUSEMOVE:
            if(dragflag == DRAGF_H)
            {
                /*---- H 部分 ----*/
                
                /* H の XOR バーを移動 */
                
                sptk_image_hline(image, 10, 10 + hsv_h_y, HSVMAP_H_W, SPTK_COL_XOR);
                
                hsv_h_y = ev->mouse.y - 10;
                
                if(hsv_h_y < 0) hsv_h_y = 0;
                else if(hsv_h_y >= HSVMAP_SIZE) hsv_h_y = HSVMAP_SIZE - 1;
                
                hsv_h = hsv_h_y * 359 / (HSVMAP_SIZE - 1);
                
                sptk_image_hline(image, 10, 10 + hsv_h_y, HSVMAP_H_W, SPTK_COL_XOR);
                sptk_update(NULL, 10, 10, HSVMAP_H_W, HSVMAP_SIZE, 5);
                
                /* SV を再描画 */
                
                draw_hsv_sv();
                sptk_update(NULL, 10 + HSVMAP_H_W + 10, 10, HSVMAP_SIZE, HSVMAP_SIZE, 5);
            }
            else if(dragflag == DRAGF_SV)
            {
                /*---- SV 部分 ----*/
                
                x = ev->mouse.x - (10 + HSVMAP_H_W + 10);
                y = ev->mouse.y - 10;
                
                if(x < 0) x = 0; else if(x >= HSVMAP_SIZE) x = HSVMAP_SIZE - 1;
                if(y < 0) y = 0; else if(y >= HSVMAP_SIZE) y = HSVMAP_SIZE - 1;
                
                /* XOR カーソル移動 */
                
                draw_sv_cursor();
                
                hsv_sv_pos.x = x;
                hsv_sv_pos.y = y;
                
                draw_sv_cursor();

                /* 色プレビューを更新 */
                
                col = hsv_to_rgb(hsv_h,
                    (double)x / (HSVMAP_SIZE - 1),
                    1 - (double)y / (HSVMAP_SIZE - 1));
                
                sptk_image_fillbox(image, 11, 10 + HSVMAP_SIZE + 11, 48, 48, col);
                sptk_update(NULL, 11, 10 + HSVMAP_SIZE + 11, 48, 48, 5);
            }
            break;
    
        case SPTK_EVENT_WINDOW_CLOSE:
            sptk_quit();
            break;
    }
}

int main()
{
    sptk_init("test", WIDTH, HEIGHT);
    sptk_window_set_handle(winhandle);
    
    image = sptk_window_get_image();
    
    draw_hsv_h();
    draw_hsv_sv();
    
    sptk_image_box(image, 10, 10 + HSVMAP_SIZE + 10, 50, 50, 0);
    
    sptk_run();

    return 0;
}
解説
HSV -> RGB
HSV の色を RGB 値に変換する関数として、hsv_to_rgb() を用意してあります。
H は 0〜359、S,V は 0.0〜1.0 の値で指定します。
H バー部分
左側の色相を選択する部分では、縦に H の値を変化させ、SV は最大値、の色をサンプルとしています。
現在選択されている色相の位置には、XOR で横線のカーソルを描画しています。
SV マップ部分
右側の色一覧は、現在の色相に、x 方向は彩度、y 方向は明度を変化させたものです。
この部分で左ドラッグすると、カーソル位置の色を取得し、下の色プレビューに現在色を表示します。
他の形
今回は、作りやすいように四角形の形で HSV マップを作りましたが、他によく見る形としては、H を円状にしたものがあります。
それらは今やるにはちょっと難しいので、また後で説明します。