colourpicker

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

Commit: 0dcf930485520ceb00687aef4cff0c8ad2432572
Parent: c6034595d0d6cd7c0987dec0599606621f830528
Author: Randy Palamar
Date:   Sat, 27 Jul 2024 12:52:57 -0600

initial version of alternate mode

Diffstat:
Mcolourpicker.c | 318+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------
Mmain.c | 13++++++-------
Apicker_shader.glsl | 42++++++++++++++++++++++++++++++++++++++++++
Mutil.c | 17+++++++++++++++--
4 files changed, 302 insertions(+), 88 deletions(-)

diff --git a/colourpicker.c b/colourpicker.c @@ -4,9 +4,9 @@ #include "util.c" -static const char *mode_labels[CPM_LAST][4] = { - [CPM_RGB] = { "R", "G", "B", "A" }, - [CPM_HSV] = { "H", "S", "V", "A" }, +static const char *mode_labels[CM_LAST][4] = { + [CM_RGB] = { "R", "G", "B", "A" }, + [CM_HSV] = { "H", "S", "V", "A" }, }; static f32 @@ -150,7 +150,7 @@ do_slider(ColourPickerCtx *ctx, Rect r, i32 label_idx) Rect lr, sr, vr; get_slider_subrects(r, &lr, &sr, &vr); - const char *label = mode_labels[ctx->mode][label_idx]; + const char *label = mode_labels[ctx->colour_mode][label_idx]; v2 fpos = center_align_text_in_rect(lr, label, ctx->font, ctx->font_size); DrawTextEx(ctx->font, label, fpos.rv, ctx->font_size, 0, ctx->fg); @@ -167,8 +167,8 @@ do_slider(ColourPickerCtx *ctx, Rect r, i32 label_idx) current += wheel / 255; CLAMP01(current); ctx->colour.E[held_idx] = current; - switch (ctx->mode) { - case CPM_HSV: ctx->flags |= CPF_REFILL_TEXTURE; break; + switch (ctx->colour_mode) { + case CM_HSV: ctx->flags |= CPF_REFILL_TEXTURE; break; default: break; } } @@ -180,7 +180,7 @@ do_slider(ColourPickerCtx *ctx, Rect r, i32 label_idx) Rect srl = cut_rect_left(sr, current); Rect srr = cut_rect_right(sr, current); - if (ctx->mode == CPM_RGB) { + if (ctx->colour_mode == CM_RGB) { Color sel, left, right; v4 cl = ctx->colour; v4 cr = ctx->colour; @@ -261,29 +261,29 @@ do_status_bar(ColourPickerCtx *ctx, Rect r) b32 mode_collides = CheckCollisionPointRec(ctx->mouse_pos.rv, mode_r.rr); if (mode_collides) { if (IsMouseButtonPressed(MOUSE_BUTTON_LEFT)) { - switch (ctx->mode++) { - case CPM_HSV: - ctx->mode = CPM_RGB; + switch (ctx->colour_mode++) { + case CM_HSV: + ctx->colour_mode = CM_RGB; ctx->colour = hsv_to_rgb(ctx->colour); break; - case CPM_RGB: + case CM_RGB: ctx->flags |= CPF_REFILL_TEXTURE; ctx->colour = rgb_to_hsv(ctx->colour); break; - case CPM_LAST: ASSERT(0); break; + case CM_LAST: ASSERT(0); break; } } if (IsMouseButtonPressed(MOUSE_BUTTON_RIGHT)) { - switch (ctx->mode--) { - case CPM_HSV: + switch (ctx->colour_mode--) { + case CM_HSV: ctx->colour = hsv_to_rgb(ctx->colour); break; - case CPM_RGB: - ctx->mode = CPM_HSV; + case CM_RGB: + ctx->colour_mode = CM_HSV; ctx->colour = rgb_to_hsv(ctx->colour); ctx->flags |= CPF_REFILL_TEXTURE; break; - case CPM_LAST: ASSERT(0); break; + case CM_LAST: ASSERT(0); break; } } } @@ -299,17 +299,17 @@ do_status_bar(ColourPickerCtx *ctx, Rect r) 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 }); - switch (ctx->mode) { - case CPM_HSV: ctx->colour = rgb_to_hsv(ctx->colour); break; + switch (ctx->colour_mode) { + case CM_HSV: ctx->colour = rgb_to_hsv(ctx->colour); break; default: break; } } Color hc; - switch (ctx->mode) { - case CPM_HSV: hc = colour_from_normalized(hsv_to_rgb(ctx->colour)); break; - case CPM_RGB: hc = colour_from_normalized(ctx->colour); break; - case CPM_LAST: ASSERT(0); break; + switch (ctx->colour_mode) { + case CM_HSV: hc = colour_from_normalized(hsv_to_rgb(ctx->colour)); break; + case CM_RGB: hc = colour_from_normalized(ctx->colour); break; + case CM_LAST: ASSERT(0); break; } const char *hex = TextFormat("%02x%02x%02x%02x", hc.r, hc.g, hc.b, hc.a); @@ -339,10 +339,10 @@ do_status_bar(ColourPickerCtx *ctx, Rect r) DrawTextEx(ctx->font, hex, fpos.rv, ctx->font_size, 0, colour_from_normalized(hex_colour)); char *mtext; - switch (ctx->mode) { - case CPM_RGB: mtext = "RGB"; break; - case CPM_HSV: mtext = "HSV"; break; - case CPM_LAST: ASSERT(0); break; + switch (ctx->colour_mode) { + case CM_RGB: mtext = "RGB"; break; + case CM_HSV: mtext = "HSV"; break; + case CM_LAST: ASSERT(0); break; } fpos = right_align_text_in_rect(mode_r, mtext, ctx->font, ctx->font_size); DrawTextEx(ctx->font, mtext, fpos.rv, ctx->font_size, 0, colour_from_normalized(mode_colour)); @@ -360,15 +360,15 @@ do_colour_stack_item(ColourPickerCtx *ctx, Rect r, i32 item_idx, b32 fade) b32 stack_collides = CheckCollisionPointRec(ctx->mouse_pos.rv, r.rr); if (stack_collides && IsMouseButtonPressed(MOUSE_BUTTON_LEFT)) { - switch (ctx->mode) { - case CPM_HSV: + switch (ctx->colour_mode) { + case CM_HSV: ctx->colour = rgb_to_hsv(colour); ctx->flags |= CPF_REFILL_TEXTURE; break; - case CPM_RGB: + case CM_RGB: ctx->colour = colour; break; - case CPM_LAST: + case CM_LAST: ASSERT(0); break; } @@ -466,8 +466,8 @@ do_colour_stack(ColourPickerCtx *ctx, Rect sa) if (push_collides && IsMouseButtonPressed(MOUSE_BUTTON_LEFT)) { css->fade_param -= 1e-6; v4 colour = ctx->colour; - switch (ctx->mode) { - case CPM_HSV: colour = hsv_to_rgb(colour); break; + switch (ctx->colour_mode) { + case CM_HSV: colour = hsv_to_rgb(colour); break; default: break; } @@ -483,10 +483,10 @@ static void do_colour_selector(ColourPickerCtx *ctx, Rect r) { Color colour = {0}, pcolour = colour_from_normalized(ctx->previous_colour); - switch (ctx->mode) { - case CPM_HSV: colour = colour_from_normalized(hsv_to_rgb(ctx->colour)); break; - case CPM_RGB: colour = colour_from_normalized(ctx->colour); break; - case CPM_LAST: ASSERT(0); break; + switch (ctx->colour_mode) { + case CM_HSV: colour = colour_from_normalized(hsv_to_rgb(ctx->colour)); break; + case CM_RGB: colour = colour_from_normalized(ctx->colour); break; + case CM_LAST: ASSERT(0); break; } Rect cs[2] = {cut_rect_left(r, 0.5), cut_rect_right(r, 0.5)}; @@ -514,8 +514,8 @@ do_colour_selector(ColourPickerCtx *ctx, Rect r) v2 fpos = center_align_text_in_rect(cs[i], labels[i], ctx->font, ctx->font_size); v2 pos = fpos; - pos.x += 2; - pos.y += 2.5; + pos.x += 1.75; + pos.y += 2; DrawTextEx(ctx->font, labels[i], pos.rv, ctx->font_size, 0, Fade(BLACK, 0.8)); DrawTextEx(ctx->font, labels[i], fpos.rv, ctx->font_size, 0, colour_from_normalized(colour)); @@ -529,25 +529,184 @@ do_colour_selector(ColourPickerCtx *ctx, Rect r) DrawLineEx(start.rv, end.rv, 3, Fade(BLACK, 0.8)); if (pressed_idx == 0) { - switch (ctx->mode) { - case CPM_RGB: + switch (ctx->colour_mode) { + case CM_RGB: ctx->colour = ctx->previous_colour; break; - case CPM_HSV: + case CM_HSV: ctx->colour = rgb_to_hsv(ctx->previous_colour); ctx->flags |= CPF_REFILL_TEXTURE; break; - case CPM_LAST: ASSERT(0); break; + case CM_LAST: ASSERT(0); break; } } else if (pressed_idx == 1) { - switch (ctx->mode) { - case CPM_RGB: ctx->previous_colour = ctx->colour; break; - case CPM_HSV: ctx->previous_colour = hsv_to_rgb(ctx->colour); break; - case CPM_LAST: ASSERT(0); break; + switch (ctx->colour_mode) { + case CM_RGB: ctx->previous_colour = ctx->colour; break; + case CM_HSV: ctx->previous_colour = hsv_to_rgb(ctx->colour); break; + case CM_LAST: ASSERT(0); break; } } } +static void +do_slider_mode(ColourPickerCtx *ctx, Rect r) +{ + Rect sb = r; + + r.size.h *= 0.15; + f32 y_pad = 0.525 * r.size.h; + + sb.size.h *= 0.1; + r.pos.y += 1.2 * sb.size.h; + do_status_bar(ctx, sb); + + if (ctx->flags & CPF_REFILL_TEXTURE) { + Rect sr; + get_slider_subrects(r, 0, &sr, 0); + if (ctx->hsv_texture.texture.width != (i32)(sr.size.w + 0.5)) { + i32 w = sr.size.w + 0.5; + i32 h = sr.size.h * 3; + h += 3 - (h % 3); + UnloadRenderTexture(ctx->hsv_texture); + ctx->hsv_texture = LoadRenderTexture(w, h); + } + fill_hsv_texture(ctx->hsv_texture, ctx->colour); + ctx->flags &= ~CPF_REFILL_TEXTURE; + } + + for (i32 i = 0; i < 4; i++) { + do_slider(ctx, r, i); + r.pos.y += r.size.h + y_pad; + } +} + +static void +do_picker_mode(ColourPickerCtx *ctx, Rect r) +{ + if (ctx->picker_texture.texture.width != (i32)(r.size.w)) { + i32 w = r.size.w; + i32 h = r.size.h; + UnloadRenderTexture(ctx->picker_texture); + ctx->picker_texture = LoadRenderTexture(w, h); + } + + if (!IsShaderReady(ctx->picker_shader)) { + /* TODO: LoadShaderFromMemory */ + ctx->picker_shader = LoadShader(0, "./picker_shader.glsl"); + ctx->mode_id = GetShaderLocation(ctx->picker_shader, "u_mode"); + ctx->hsv_id = GetShaderLocation(ctx->picker_shader, "u_hsv"); + ctx->size_id = GetShaderLocation(ctx->picker_shader, "u_size"); + ctx->offset_id = GetShaderLocation(ctx->picker_shader, "u_offset"); + } + + v4 colour = {0}; + switch (ctx->colour_mode) { + case CM_RGB: colour = rgb_to_hsv(ctx->colour); break; + case CM_HSV: colour = ctx->colour; break; + case CM_LAST: ASSERT(0); break; + } + + Rect tr = { + .size = { + .w = ctx->picker_texture.texture.width, + .h = ctx->picker_texture.texture.height + } + }; + + Rect hs1 = cut_rect_left(tr, 0.2); + Rect hs2 = cut_rect_middle(tr, 0.2, 0.4); + Rect sv = cut_rect_right(tr, 0.4); + + BeginTextureMode(ctx->picker_texture); + + ClearBackground(ctx->bg); + + v4 hsv[2] = {colour, colour}; + Rect shs; + i32 mode; + BeginShaderMode(ctx->picker_shader); + /* NOTE: Full H Slider */ + shs = hs1; + shs.pos.x += 0.25 * shs.size.x; + shs.size.x *= 0.5; + shs.pos.y += 0.01 * shs.size.y; + shs.size.y *= 0.98; + + hsv[0].x = 0; + hsv[1].x = 1; + mode = 0; + SetShaderValue(ctx->picker_shader, ctx->mode_id, &mode, SHADER_UNIFORM_INT); + SetShaderValue(ctx->picker_shader, ctx->size_id, shs.size.E, SHADER_UNIFORM_VEC2); + SetShaderValueV(ctx->picker_shader, ctx->hsv_id, (f32 *)hsv, SHADER_UNIFORM_VEC4, 2); + DrawRectangleRec(shs.rr, BLACK); + EndShaderMode(); + DrawRectangleRoundedLinesEx(shs.rr, STACK_ROUNDNESS, 0, 12, ctx->bg); + DrawRectangleRoundedLinesEx(shs.rr, STACK_ROUNDNESS, 0, 3, Fade(BLACK, 0.8)); + + v2 start = {.x = shs.pos.x, .y = shs.pos.y + (colour.x * shs.size.h)}; + v2 end = start; + end.x += shs.size.w; + DrawLineEx(start.rv, end.rv, 3, BLACK); + + BeginShaderMode(ctx->picker_shader); + /* NOTE: Zoomed H Slider */ + shs = hs2; + shs.pos.x += 0.25 * shs.size.x; + shs.size.x *= 0.5; + shs.pos.y += 0.01 * shs.size.y; + shs.size.y *= 0.98; + + f32 fraction = 0.1; + if (colour.x - 0.5 * fraction < 0) { + hsv[0].x = 0; + hsv[1].x = fraction; + } else if (colour.x + 0.5 * fraction > 1) { + hsv[0].x = 1 - fraction; + hsv[1].x = 1; + } else { + hsv[0].x = colour.x - 0.5 * fraction; + hsv[1].x = colour.x + 0.5 * fraction; + } + + mode = 0; + SetShaderValue(ctx->picker_shader, ctx->mode_id, &mode, SHADER_UNIFORM_INT); + SetShaderValue(ctx->picker_shader, ctx->size_id, shs.size.E, SHADER_UNIFORM_VEC2); + SetShaderValueV(ctx->picker_shader, ctx->hsv_id, (f32 *)hsv, SHADER_UNIFORM_VEC4, 2); + DrawRectangleRec(shs.rr, BLACK); + EndShaderMode(); + DrawRectangleRoundedLinesEx(shs.rr, STACK_ROUNDNESS, 0, 12, ctx->bg); + DrawRectangleRoundedLinesEx(shs.rr, STACK_ROUNDNESS, 0, 3, Fade(BLACK, 0.8)); + + f32 scale = (colour.x - hsv[0].x) / (hsv[1].x - hsv[0].x); + start = (v2){.x = shs.pos.x, .y = shs.pos.y + (scale * shs.size.h)}; + end = start; + end.x += shs.size.w; + DrawLineEx(start.rv, end.rv, 3, BLACK); + + BeginShaderMode(ctx->picker_shader); + /* NOTE: S-V Plane */ + sv.pos.y += 3; + sv.pos.x += 3; + sv.size.y -= 6; + sv.size.x -= 6; + v2 offset = {.x = sv.pos.x}; + mode = 1; + SetShaderValue(ctx->picker_shader, ctx->mode_id, &mode, SHADER_UNIFORM_INT); + SetShaderValue(ctx->picker_shader, ctx->size_id, sv.size.E, SHADER_UNIFORM_VEC2); + SetShaderValue(ctx->picker_shader, ctx->offset_id, offset.E, SHADER_UNIFORM_VEC2); + SetShaderValueV(ctx->picker_shader, ctx->hsv_id, colour.E, SHADER_UNIFORM_VEC4, 1); + DrawRectangleRec(sv.rr, BLACK); + EndShaderMode(); + + DrawRectangleLinesEx(sv.rr, 3, BLACK); + v2 cpos = {.x = sv.pos.x + colour.y * sv.size.w, .y = sv.pos.y + colour.z * sv.size.h}; + DrawCircleV(cpos.rv, 6, BLACK); + + EndTextureMode(); + + DrawTextureV(ctx->picker_texture.texture, r.pos.rv, WHITE); +} + DEBUG_EXPORT void do_colour_picker(ColourPickerCtx *ctx) { @@ -572,44 +731,45 @@ do_colour_picker(ColourPickerCtx *ctx) .size = { .w = (f32)ws.w * 0.9, .h = (f32)ws.h * 0.9 / 2 }, }; - { - Rect ca = cut_rect_left(upper, 0.8); - Rect sa = cut_rect_right(upper, 0.8); - Rect sb = ca; - - ca.size.h *= 0.15; - f32 y_pad = 0.525 * ca.size.h; - - sb.size.h *= 0.1; - ca.pos.y += 1.2 * sb.size.h; - do_status_bar(ctx, sb); - - do_colour_stack(ctx, sa); - - if (ctx->flags & CPF_REFILL_TEXTURE) { - Rect sr; - get_slider_subrects(ca, 0, &sr, 0); - if (ctx->hsv_texture.texture.width != (i32)(sr.size.w + 0.5)) { - i32 w = sr.size.w + 0.5; - i32 h = sr.size.h * 3; - h += 3 - (h % 3); - UnloadRenderTexture(ctx->hsv_texture); - ctx->hsv_texture = LoadRenderTexture(w, h); - } - fill_hsv_texture(ctx->hsv_texture, ctx->colour); - ctx->flags &= ~CPF_REFILL_TEXTURE; - } - for (i32 i = 0; i < 4; i++) { - do_slider(ctx, ca, i); - ca.pos.y += ca.size.h + y_pad; - } + Rect ma = cut_rect_left(upper, 0.8); + Rect sa = cut_rect_right(upper, 0.8); + do_colour_stack(ctx, sa); + + switch (ctx->mode) { + case CPM_SLIDERS: do_slider_mode(ctx, ma); break; + case CPM_PICKER: do_picker_mode(ctx, ma); break; + case CPM_LAST: ASSERT(0); break; } { Rect cb = lower; cb.size.h *= 0.25; - cb.pos.y += 0.02 * lower.size.h; + cb.pos.y += 0.04 * lower.size.h; do_colour_selector(ctx, cb); + + cb.pos.y += 0.075 * lower.size.h + cb.size.h; + + f32 mode_x_pad = 0.04 * lower.size.w; + + Rect mb = cb; + mb.size.w *= (1.0 / (CPM_LAST + 1) - 0.1); + mb.size.w -= 0.5 * mode_x_pad; + mb.size.h = mb.size.w; + + f32 offset = lower.size.w - CPM_LAST * (mb.size.w + 0.5 * mode_x_pad); + mb.pos.x += 0.5 * offset; + + for (u32 i = 0; i < CPM_LAST; i++) { + DrawRectangleLinesEx(mb.rr, 3, RED); + + if (CheckCollisionPointRec(ctx->mouse_pos.rv, mb.rr)) { + if (IsMouseButtonPressed(MOUSE_BUTTON_LEFT)) { + ctx->mode = i; + } + } + + mb.pos.x += mb.size.w + mode_x_pad; + } } } diff --git a/main.c b/main.c @@ -113,8 +113,9 @@ main(i32 argc, char *argv[]) ColourPickerCtx ctx = { .window_size = { .w = 720, .h = 960 }, - .mode = CPM_HSV, - .flags = CPF_REFILL_TEXTURE, + .mode = CPM_PICKER, + .colour_mode = CM_HSV, + .flags = CPF_REFILL_TEXTURE, .colour = { .r = 0.53, .g = 0.82, .b = 0.59, .a = 1.0 }, @@ -178,8 +179,6 @@ main(i32 argc, char *argv[]) ctx.font_size = 40; ctx.font = LoadFontEx("assets/Lora-SemiBold.ttf", ctx.font_size, 0, 0); - ctx.hsv_texture = LoadRenderTexture(360, 360); - while(!WindowShouldClose()) { do_debug(); @@ -190,9 +189,9 @@ main(i32 argc, char *argv[]) v4 rgba = {0}; switch (ctx.mode) { - case CPM_RGB: rgba = ctx.colour; break; - case CPM_HSV: rgba = hsv_to_rgb(ctx.colour); break; - default: ASSERT(0); break; + case CM_RGB: rgba = ctx.colour; break; + case CM_HSV: rgba = hsv_to_rgb(ctx.colour); break; + default: ASSERT(0); break; } u32 packed_rgba = pack_rl_colour(colour_from_normalized(rgba)); diff --git a/picker_shader.glsl b/picker_shader.glsl @@ -0,0 +1,42 @@ +/* See LICENSE for copyright details */ +#version 330 + +in vec2 fragTexCoord; +in vec4 fragColor; + +out vec4 out_colour; + +#define MODE_H 0 +#define MODE_SV 1 + +uniform int u_mode; +uniform vec4 u_hsv[2]; +uniform vec2 u_offset; +uniform vec2 u_size; + +/* input: h [0,360] | s,v [0, 1] * + * output: rgb [0,1] */ +vec3 hsv2rgb(vec3 hsv) +{ + vec3 k = mod(vec3(5, 3, 1) + hsv.x / 60, 6); + k = max(min(min(k, 4 - k), 1), 0); + return hsv.z - hsv.z * hsv.y * k; +} + +void main() +{ + vec2 pos = (gl_FragCoord.xy - u_offset) / u_size; + pos.y = 1 - pos.y; + + vec4 out_hsv; + switch (u_mode) { + case MODE_H: + out_hsv = mix(u_hsv[0], u_hsv[1], pos.y); + break; + case MODE_SV: + out_hsv = vec4(u_hsv[0].x, pos.x, pos.y, 1); + break; + } + out_hsv.x *= 360; + out_colour = vec4(hsv2rgb(out_hsv.xyz), 1); +} diff --git a/util.c b/util.c @@ -47,9 +47,15 @@ typedef union { Rectangle rr; } Rect; +enum colour_mode { + CM_RGB, + CM_HSV, + CM_LAST +}; + enum colour_picker_mode { - CPM_RGB, - CPM_HSV, + CPM_PICKER, + CPM_SLIDERS, CPM_LAST }; @@ -91,9 +97,16 @@ typedef struct { f32 dt; + RenderTexture slider_texture; + RenderTexture picker_texture; RenderTexture hsv_texture; + Shader picker_shader; + i32 mode_id, hsv_id; + i32 offset_id, size_id; + u32 flags; + enum colour_mode colour_mode; enum colour_picker_mode mode; } ColourPickerCtx;