colourpicker

Simple Colour Picker written in C
git clone anongit@rnpnr.xyz:colourpicker.git
Log | Files | Refs | Feed | Submodules | README | LICENSE

Commit: 272a24c00d46f0006276ee2b8c15e55785c1e875
Parent: 4c2545a25a1029b986dc7165db81ddff0ec516aa
Author: Randy Palamar
Date:   Sat,  8 Jun 2024 17:47:46 -0600

first attempt at converting to HSV

Raylib has a problem where Gradients seemingly can only be drawn
in RGB space so HSV slide looks like garbage. This needs to fixed
later.

Diffstat:
Mcolourpicker.c | 223+++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------------
Mmain.c | 3+++
Mutil.c | 12++++++++----
3 files changed, 166 insertions(+), 72 deletions(-)

diff --git a/colourpicker.c b/colourpicker.c @@ -1,7 +1,13 @@ /* See LICENSE for copyright details */ +#include <stdio.h> #include <raylib.h> #include "util.c" +static const char *mode_labels[CPM_LAST][4] = { + [CPM_RGB] = { "R:", "G:", "B:", "A:" }, + [CPM_HSV] = { "H:", "S:", "V:", "A:" }, +}; + static v2 center_text_in_rect(Rect r, const char *text, Font font, f32 fontsize) { @@ -37,111 +43,197 @@ cut_rect_right(Rect r, f32 fraction) return r; } -static f32 -do_slider(Rect r, char *label, f32 current, Font font) +static v4 +rgb_to_hsv(v4 rgb) +{ + Vector3 hsv = ColorToHSV(ColorFromNormalized(rgb.rv)); + return (v4){ .x = hsv.x / 360, .y = hsv.y, .z = hsv.z, .w = rgb.a }; +} + +static v4 +hsv_to_rgb(v4 hsv) { - Rect lr = cut_rect_left(r, 0.1); + Color rgba = ColorFromHSV(hsv.x * 360, hsv.y, hsv.z); + rgba.a = hsv.a * 255; + return (v4){ .rv = ColorNormalize(rgba) }; +} + +static void +draw_slider_rect(Rect r, v4 colour, enum colour_picker_mode mode, i32 idx, Color bg) +{ + Color sel, left, right; + if (mode != CPM_HSV) { + v4 cl = colour; + v4 cr = colour; + cl.E[idx] = 0; + cr.E[idx] = 1; + left = ColorFromNormalized(cl.rv); + right = ColorFromNormalized(cr.rv); + sel = ColorFromNormalized(colour.rv); + } else { + v4 tmp = colour; + switch (idx) { + case 0: /* H */ + left = ColorFromHSV(0, colour.y, colour.z); + right = ColorFromHSV(360, colour.y, colour.z); + break; + case 1: /* S */ + left = ColorFromHSV(colour.x * 360, 0, colour.z); + right = ColorFromHSV(colour.x * 360, 1, colour.z); + break; + case 2: /* V */ + left = ColorFromHSV(colour.x * 360, colour.y, 0); + right = ColorFromHSV(colour.x * 360, colour.y, 1); + break; + case 3: + tmp.a = 0; + left = ColorFromNormalized(hsv_to_rgb(tmp).rv); + tmp.a = 1; + right = ColorFromNormalized(hsv_to_rgb(tmp).rv); + break; + } + if (idx != 3) { + left.a = colour.a; + right.a = colour.a; + } + sel = ColorFromNormalized(hsv_to_rgb(colour).rv); + } + + f32 current = colour.E[idx]; + Rect srl = cut_rect_left(r, current); + Rect srr = cut_rect_right(r, current); + DrawRectangleGradientEx(srl.rr, left, left, sel, sel); + DrawRectangleGradientEx(srr.rr, sel, sel, right, right); + DrawRectangleRoundedLines(r.rr, 1, 0, 12, bg); +} + +static void +do_slider(ColourPickerCtx *ctx, Rect r, i32 label_idx) +{ + Rect lr = cut_rect_left(r, 0.08); Rect vr = cut_rect_right(r, 0.85); Rect sr = cut_rect_middle(r, 0.1, 0.85); sr.size.h *= 0.75; sr.pos.y += r.size.h * 0.125; - v2 fpos = center_text_in_rect(lr, label, font, 48); - DrawTextEx(font, label, fpos.rv, 48, 0, LIGHTGRAY); + const char *label = mode_labels[ctx->mode][label_idx]; + v2 fpos = center_text_in_rect(lr, label, ctx->font, 48); + DrawTextEx(ctx->font, label, fpos.rv, 48, 0, ctx->fg); + f32 current = ctx->colour.E[label_idx]; v2 mouse = { .rv = GetMousePosition() }; if (CheckCollisionPointRec(mouse.rv, sr.rr)) { f32 wheel = GetMouseWheelMove(); if (IsMouseButtonDown(MOUSE_BUTTON_LEFT)) current = (mouse.x - sr.pos.x) / sr.size.w; - current += 4 * wheel / sr.size.w; + current += wheel / 255; CLAMP(current, 0.0, 1.0); + ctx->colour.E[label_idx] = current; } - DrawRectangleRounded(sr.rr, 1.0, 0, PURPLE); - v2 start = { - .x = sr.pos.x + current * sr.size.w, - .y = sr.pos.y + sr.size.h, - }; - v2 end = start; - end.y -= sr.size.h; - DrawLineEx(start.rv, end.rv, 8, BLACK); + draw_slider_rect(sr, ctx->colour, ctx->mode, label_idx, ctx->bg); - const char *value = TextFormat("%0.02f", current); - fpos = center_text_in_rect(vr, value, font, 36); - DrawTextEx(font, value, fpos.rv, 36, 0, LIGHTGRAY); + { + f32 half_tri_w = 8; + f32 tri_h = 12; + v2 t_mid = { + .x = sr.pos.x + current * sr.size.w, + .y = sr.pos.y, + }; + v2 t_left = { + .x = t_mid.x - half_tri_w, + .y = t_mid.y - tri_h, + }; + v2 t_right = { + .x = t_mid.x + half_tri_w, + .y = t_mid.y - tri_h, + }; + DrawTriangle(t_right.rv, t_left.rv, t_mid.rv, ctx->fg); - return current; + t_mid.y += sr.size.h; + t_left.y += sr.size.h + 2 * tri_h; + t_right.y += sr.size.h + 2 * tri_h; + DrawTriangle(t_mid.rv, t_left.rv, t_right.rv, ctx->fg); + } + + const char *value = TextFormat("%0.02f", current); + fpos = center_text_in_rect(vr, value, ctx->font, 36); + DrawTextEx(ctx->font, value, fpos.rv, 36, 0, ctx->fg); } -static enum colour_picker_mode -do_status_bar(Rect r, enum colour_picker_mode mode, Color colour, Font font) +static void +do_status_bar(ColourPickerCtx *ctx, Rect r) { - Rect hr = cut_rect_left(r, 0.5); - Rect sr = cut_rect_right(r, 0.7); - - const char *hex = TextFormat("%02x%02x%02x%02x", - colour.r, colour.b, colour.g, - colour.a); - v2 fpos = center_text_in_rect(hr, hex, font, 48); - DrawTextEx(font, hex, fpos.rv, 48, 0, LIGHTGRAY); + Rect hex_r = cut_rect_left(r, 0.5); + Rect mode_r = cut_rect_right(r, 0.7); v2 mouse = { .rv = GetMousePosition() }; - if (CheckCollisionPointRec(mouse.rv, hr.rr)) { - if (IsMouseButtonDown(MOUSE_BUTTON_LEFT)) - SetClipboardText(hex); - } - - if (CheckCollisionPointRec(mouse.rv, sr.rr)) { + if (CheckCollisionPointRec(mouse.rv, mode_r.rr)) { if (IsMouseButtonPressed(MOUSE_BUTTON_LEFT)) { - if (mode == HSV) - mode = RGB; - else - mode++; + if (ctx->mode == CPM_HSV) { + ctx->mode = CPM_RGB; + ctx->colour = hsv_to_rgb(ctx->colour); + } else { + ctx->mode++; + ctx->colour = rgb_to_hsv(ctx->colour); + } } if (IsMouseButtonPressed(MOUSE_BUTTON_RIGHT)) { - if (mode == RGB) - mode = HSV; - else - mode--; + if (ctx->mode == CPM_RGB) { + ctx->mode = CPM_HSV; + ctx->colour = rgb_to_hsv(ctx->colour); + } else { + ctx->mode--; + ctx->colour = hsv_to_rgb(ctx->colour); + } } } - char *mtext; - switch (mode) { - case RGB: mtext = "RGB"; break; - case HSV: mtext = "HSV"; break; + i32 hex_collides = CheckCollisionPointRec(mouse.rv, hex_r.rr); + if (hex_collides && IsMouseButtonPressed(MOUSE_BUTTON_RIGHT)) { + const char *new = TextToLower(GetClipboardText()); + u32 r, g, b, a; + sscanf(new, "%02x%02x%02x%02x", &r, &g, &b, &a); + ctx->colour.rv = ColorNormalize((Color){ .r = r, .g = g, .b = b, .a = a }); } - fpos = center_text_in_rect(sr, mtext, font, 48); - DrawTextEx(font, mtext, fpos.rv, 48, 0, LIGHTGRAY); - return mode; + Color hc = ColorFromNormalized(ctx->colour.rv); + const char *hex = TextFormat("%02x%02x%02x%02x", hc.r, hc.g, hc.b, hc.a); + v2 fpos = center_text_in_rect(hex_r, hex, ctx->font, 48); + DrawTextEx(ctx->font, hex, fpos.rv, 48, 0, ctx->fg); + + if (hex_collides && IsMouseButtonPressed(MOUSE_BUTTON_LEFT)) + SetClipboardText(hex); + + char *mtext; + switch (ctx->mode) { + case CPM_RGB: mtext = "RGB"; break; + case CPM_HSV: mtext = "HSV"; break; + case CPM_LAST: ASSERT(0); break; + } + fpos = center_text_in_rect(mode_r, mtext, ctx->font, 48); + DrawTextEx(ctx->font, mtext, fpos.rv, 48, 0, ctx->fg); } void do_colour_picker(ColourPickerCtx *ctx) { - ClearBackground(BLACK); + ClearBackground(ctx->bg); DrawFPS(20, 20); uv2 ws = ctx->window_size; - Color colour = ColorFromNormalized(ctx->colour.rv); { + v4 vcolour = ctx->mode == CPM_HSV ? hsv_to_rgb(ctx->colour) : ctx->colour; + Color colour = ColorFromNormalized(vcolour.rv); v2 cc = { .x = (f32)ws.w / 2, .y = (f32)ws.h / 4 }; DrawCircleSector(cc.rv, 0.6 * cc.x, 0, 360, 69, RED); DrawCircleSector(cc.rv, 0.58 * cc.x, 0, 360, 69, colour); } { - v2 start = { .x = 0, .y = (f32)ws.h / 2 }; - v2 end = { .x = ws.h, .y = (f32)ws.h / 2 }; - DrawLineEx(start.rv, end.rv, 8, BLUE); - } - - { Rect subregion = { .pos = { .x = 0.05 * (f32)ws.w, .y = (f32)ws.h / 2, }, .size = { .w = (f32)ws.w * 0.9, .h = (f32)ws.h / 2 * 0.9 }, @@ -149,21 +241,16 @@ do_colour_picker(ColourPickerCtx *ctx) Rect sb = subregion; sb.size.h *= 0.25; - ctx->mode = do_status_bar(sb, ctx->mode, colour, ctx->font); + + do_status_bar(ctx, sb); Rect r = subregion; r.pos.y += 0.25 * ((f32)ws.h / 2); r.size.h *= 0.15; - ctx->colour.r = do_slider(r, "R:", ctx->colour.r, ctx->font); - - r.pos.y += r.size.h + 0.03 * ((f32)ws.h / 2); - ctx->colour.g = do_slider(r, "G:", ctx->colour.g, ctx->font); - - r.pos.y += r.size.h + 0.03 * ((f32)ws.h / 2); - ctx->colour.b = do_slider(r, "B:", ctx->colour.b, ctx->font); - - r.pos.y += r.size.h + 0.03 * ((f32)ws.h / 2); - ctx->colour.a = do_slider(r, "A:", ctx->colour.a, ctx->font); + for (i32 i = 0; i < 4; i++) { + do_slider(ctx, r, i); + r.pos.y += r.size.h + 0.03 * ((f32)ws.h / 2); + } } } diff --git a/main.c b/main.c @@ -47,6 +47,9 @@ main(void) { ColourPickerCtx ctx = {0}; ctx.window_size = (uv2){.w = 720, .h = 960}; + ctx.bg = (Color){ .r = 0x26, .g = 0x1e, .b = 0x22, .a = 0xff }; + ctx.fg = (Color){ .r = 0xea, .g = 0xe1, .b = 0xb4, .a = 0xff }; + ctx.colour.a = 1.0; SetConfigFlags(FLAG_VSYNC_HINT); InitWindow(ctx.window_size.w, ctx.window_size.h, "Colour Picker"); diff --git a/util.c b/util.c @@ -36,14 +36,18 @@ typedef union { } Rect; enum colour_picker_mode { - RGB, - HSV, + CPM_RGB, + CPM_HSV, + CPM_LAST }; typedef struct { - uv2 window_size; - Font font; v4 colour; + Font font; + uv2 window_size; + Color bg; + Color fg; + enum colour_picker_mode mode; } ColourPickerCtx;