colourpicker

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

Commit: 330a74720ecc865ef988c42763d9f62ed7e10991
Parent: 99991ca2edf0d7e96ff989a366b58f7c13544ea4
Author: Randy Palamar
Date:   Wed,  7 Aug 2024 22:15:24 -0600

move slider mode lerping to shader

Diffstat:
Mbuild.sh | 2+-
Mcolourpicker.c | 174+++++++++++++++++++++++++++++++------------------------------------------------
Mconfig.def.h | 2+-
Dhsv_lerp.glsl | 86-------------------------------------------------------------------------------
Aslider_lerp.glsl | 132+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mutil.c | 7+++----
6 files changed, 205 insertions(+), 198 deletions(-)

diff --git a/build.sh b/build.sh @@ -30,7 +30,7 @@ fi [ ! -s "config.h" ] && cp config.def.h config.h -if [ ! -e "external/include/shader_inc.h" ] || [ "hsv_lerp.glsl" -nt "external/include/shader_inc.h" ]; then +if [ ! -e "external/include/shader_inc.h" ] || [ "slider_lerp.glsl" -nt "external/include/shader_inc.h" ]; then ${cc} $cflags -o gen_incs gen_incs.c $ldflags ./gen_incs mv lora_sb*.h external/include/ diff --git a/colourpicker.c b/colourpicker.c @@ -214,38 +214,6 @@ step_colour_mode(ColourPickerCtx *ctx, i32 inc) } static void -fill_hsv_texture(RenderTexture texture, v4 hsv, Color bg) -{ - f32 line_length = (f32)texture.texture.height / 3; - v2 vtop = {0}; - v2 vbot = { .y = vtop.y + line_length }; - v2 sbot = { .y = vbot.y + line_length }; - v2 hbot = { .y = sbot.y + line_length }; - - v4 h = hsv, s = hsv, v = hsv; - h.x = 0; - s.y = 0; - v.z = 0; - - f32 inc = 1.0 / texture.texture.width; - BeginTextureMode(texture); - ClearBackground(bg); - for (u32 i = 0; i < texture.texture.width; i++) { - DrawLineV(sbot.rv, hbot.rv, colour_from_normalized(hsv_to_rgb(h))); - DrawLineV(vbot.rv, sbot.rv, colour_from_normalized(hsv_to_rgb(s))); - DrawLineV(vtop.rv, vbot.rv, colour_from_normalized(hsv_to_rgb(v))); - hbot.x += 1.0; - sbot.x += 1.0; - vtop.x += 1.0; - vbot.x += 1.0; - h.x += inc; - s.y += inc; - v.z += inc; - } - EndTextureMode(); -} - -static void get_slider_subrects(Rect r, Rect *label, Rect *slider, Rect *value) { if (label) *label = cut_rect_left(r, 0.08); @@ -500,42 +468,6 @@ do_slider(ColourPickerCtx *ctx, Rect r, i32 label_idx, v2 relative_origin) ctx->held_idx = -1; f32 current = ctx->colour.E[label_idx]; - Rect srl = cut_rect_left(sr, current); - Rect srr = cut_rect_right(sr, current); - - /* TODO: this should probably be a switch */ - if (ctx->colour_mode == CM_RGB) { - Color sel, left, right; - v4 cl = ctx->colour; - v4 cr = ctx->colour; - cl.E[label_idx] = 0; - cr.E[label_idx] = 1; - left = colour_from_normalized(cl); - right = colour_from_normalized(cr); - sel = colour_from_normalized(ctx->colour); - DrawRectangleGradientEx(srl.rr, left, left, sel, sel); - DrawRectangleGradientEx(srr.rr, sel, sel, right, right); - } else { - if (label_idx == 3) { /* Alpha */ - Color sel = colour_from_normalized(hsv_to_rgb(ctx->colour)); - Color left = sel; - Color right = sel; - left.a = 0; - right.a = 255; - DrawRectangleGradientEx(srl.rr, left, left, sel, sel); - DrawRectangleGradientEx(srr.rr, sel, sel, right, right); - } else { - Rect tr = {0}; - tr.pos.y += ctx->hsv_texture.texture.height / 3 * label_idx; - tr.size.h = sr.size.h; - tr.size.w = ctx->hsv_texture.texture.width; - DrawTexturePro(ctx->hsv_texture.texture, tr.rr, sr.rr, (Vector2){0}, 0, WHITE); - } - } - - DrawRectangleRoundedLinesEx(sr.rr, SLIDER_ROUNDNESS, 0, 4.85 * SLIDER_BORDER_WIDTH, ctx->bg); - DrawRectangleRoundedLinesEx(sr.rr, SLIDER_ROUNDNESS, 0, SLIDER_BORDER_WIDTH, - SLIDER_BORDER_COLOUR); { f32 scale_delta = (SLIDER_SCALE_TARGET - 1.0) * SLIDER_SCALE_SPEED * ctx->dt; @@ -811,36 +743,56 @@ do_slider_mode(ColourPickerCtx *ctx, v2 relative_origin) }; Rect sb = tr; Rect ss = tr; + sb.size.h *= 0.1; ss.size.h *= 0.15; - - if (ctx->flags & CPF_REFILL_TEXTURE) { - Rect sr; - get_slider_subrects(ss, 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, get_formatted_colour(ctx, CM_HSV), ctx->bg); - ctx->flags &= ~CPF_REFILL_TEXTURE; - } + ss.pos.y += 1.2 * sb.size.h; BeginTextureMode(ctx->slider_texture); ClearBackground(ctx->bg); - sb.size.h *= 0.1; do_status_bar(ctx, sb, relative_origin); - ss.pos.y += 1.2 * sb.size.h; - f32 y_pad = 0.525 * ss.size.h; + Rect sr; + get_slider_subrects(ss, 0, &sr, 0); + f32 r_bound = sr.pos.x + sr.size.w; + f32 y_step = 1.525 * ss.size.h; for (i32 i = 0; i < 4; i++) { do_slider(ctx, ss, i, relative_origin); - ss.pos.y += ss.size.h + y_pad; + ss.pos.y += y_step; } + BeginShaderMode(ctx->picker_shader); + { + f32 start_y = sr.pos.y - 0.5 * ss.size.h; + f32 end_y = start_y + sr.size.h; + f32 regions[] = { + sr.pos.x, start_y + 3 * y_step, r_bound, end_y + 3 * y_step, + sr.pos.x, start_y + 2 * y_step, r_bound, end_y + 2 * y_step, + sr.pos.x, start_y + 1 * y_step, r_bound, end_y + 1 * y_step, + sr.pos.x, start_y + 0 * y_step, r_bound, end_y + 0 * y_step + }; + + f32 radius = SLIDER_BORDER_RADIUS; + f32 border_thick = SLIDER_BORDER_WIDTH; + i32 cm_mode = ctx->colour_mode; + i32 mode = ctx->mode; + + SetShaderValue(ctx->picker_shader, ctx->mode_id, &mode, SHADER_UNIFORM_INT); + SetShaderValue(ctx->picker_shader, ctx->radius_id, &radius, SHADER_UNIFORM_FLOAT); + SetShaderValue(ctx->picker_shader, ctx->border_thick_id, &border_thick, + SHADER_UNIFORM_FLOAT); + SetShaderValue(ctx->picker_shader, ctx->colour_mode_id, &cm_mode, + SHADER_UNIFORM_INT); + SetShaderValue(ctx->picker_shader, ctx->size_id, tr.size.E, SHADER_UNIFORM_VEC2); + SetShaderValue(ctx->picker_shader, ctx->colours_id, ctx->colour.E, + SHADER_UNIFORM_VEC4); + SetShaderValueV(ctx->picker_shader, ctx->regions_id, regions, + SHADER_UNIFORM_VEC4, 4); + DrawRectangleRec(tr.rr, BLACK); + } + EndShaderMode(); + EndTextureMode(); } @@ -895,20 +847,6 @@ do_vertical_slider(ColourPickerCtx *ctx, v2 test_pos, Rect r, i32 idx, static void do_picker_mode(ColourPickerCtx *ctx, v2 relative_origin) { - if (!IsShaderReady(ctx->picker_shader)) { -#ifdef _DEBUG - ctx->picker_shader = LoadShader(0, HSV_LERP_SHADER_NAME); -#else - ctx->picker_shader = LoadShaderFromMemory(0, g_hsv_shader_text); -#endif - ctx->colour_mode_id = GetShaderLocation(ctx->picker_shader, "u_colour_mode"); - ctx->colours_id = GetShaderLocation(ctx->picker_shader, "u_colours"); - ctx->regions_id = GetShaderLocation(ctx->picker_shader, "u_regions"); - ctx->size_id = GetShaderLocation(ctx->picker_shader, "u_size"); - ctx->radius_id = GetShaderLocation(ctx->picker_shader, "u_radius"); - ctx->border_thick_id = GetShaderLocation(ctx->picker_shader, "u_border_thick"); - } - v4 colour = get_formatted_colour(ctx, CM_HSV); colour.x = ctx->pms.base_hue + ctx->pms.fractional_hue; @@ -928,6 +866,7 @@ do_picker_mode(ColourPickerCtx *ctx, v2 relative_origin) test_pos.y -= relative_origin.y; BeginTextureMode(ctx->picker_texture); + ClearBackground(ctx->bg); v4 hsv[3] = {colour, colour, colour}; hsv[1].x = 0; @@ -952,7 +891,6 @@ do_picker_mode(ColourPickerCtx *ctx, v2 relative_origin) colour = do_vertical_slider(ctx, test_pos, hs2, PM_MIDDLE, hsv[2], hsv[1], colour); ctx->pms.fractional_hue = colour.x - ctx->pms.base_hue; - ClearBackground(ctx->bg); BeginShaderMode(ctx->picker_shader); { f32 regions[] = { @@ -963,7 +901,9 @@ do_picker_mode(ColourPickerCtx *ctx, v2 relative_origin) f32 radius = SLIDER_BORDER_RADIUS; f32 border_thick = SLIDER_BORDER_WIDTH; i32 cm_mode = CM_HSV; + i32 mode = ctx->mode; + SetShaderValue(ctx->picker_shader, ctx->mode_id, &mode, SHADER_UNIFORM_INT); SetShaderValue(ctx->picker_shader, ctx->radius_id, &radius, SHADER_UNIFORM_FLOAT); SetShaderValue(ctx->picker_shader, ctx->border_thick_id, &border_thick, SHADER_UNIFORM_FLOAT); @@ -1045,9 +985,8 @@ DEBUG_EXPORT void do_colour_picker(ColourPickerCtx *ctx, f32 dt, Vector2 window_pos, Vector2 mouse_pos) { if (IsWindowResized()) { - ctx->window_size.h = GetScreenHeight(); - ctx->window_size.w = ctx->window_size.h / WINDOW_ASPECT_RATIO; - ctx->flags |= CPF_REFILL_TEXTURE; + ctx->window_size.h = GetScreenHeight(); + ctx->window_size.w = ctx->window_size.h / WINDOW_ASPECT_RATIO; SetWindowSize(ctx->window_size.w, ctx->window_size.h); UnloadTexture(ctx->font.texture); @@ -1056,6 +995,21 @@ do_colour_picker(ColourPickerCtx *ctx, f32 dt, Vector2 window_pos, Vector2 mouse ctx->font_size = ctx->font.baseSize; } + if (!IsShaderReady(ctx->picker_shader)) { +#ifdef _DEBUG + ctx->picker_shader = LoadShader(0, HSV_LERP_SHADER_NAME); +#else + ctx->picker_shader = LoadShaderFromMemory(0, g_hsv_shader_text); +#endif + ctx->mode_id = GetShaderLocation(ctx->picker_shader, "u_mode"); + ctx->colour_mode_id = GetShaderLocation(ctx->picker_shader, "u_colour_mode"); + ctx->colours_id = GetShaderLocation(ctx->picker_shader, "u_colours"); + ctx->regions_id = GetShaderLocation(ctx->picker_shader, "u_regions"); + ctx->size_id = GetShaderLocation(ctx->picker_shader, "u_size"); + ctx->radius_id = GetShaderLocation(ctx->picker_shader, "u_radius"); + ctx->border_thick_id = GetShaderLocation(ctx->picker_shader, "u_border_thick"); + } + ctx->dt = dt; ctx->mouse_pos.rv = mouse_pos; ctx->window_pos.rv = window_pos; @@ -1084,8 +1038,12 @@ do_colour_picker(ColourPickerCtx *ctx, f32 dt, Vector2 window_pos, Vector2 mouse i32 h = ma.size.h; UnloadRenderTexture(ctx->picker_texture); ctx->picker_texture = LoadRenderTexture(w, h); - if (ctx->mode != CPM_PICKER) + if (ctx->mode != CPM_PICKER) { + i32 mode = ctx->mode; + ctx->mode = CPM_PICKER; do_picker_mode(ctx, ma.pos); + ctx->mode = mode; + } } if (ctx->slider_texture.texture.width != (i32)(ma.size.w)) { @@ -1093,8 +1051,12 @@ do_colour_picker(ColourPickerCtx *ctx, f32 dt, Vector2 window_pos, Vector2 mouse i32 h = ma.size.h; UnloadRenderTexture(ctx->slider_texture); ctx->slider_texture = LoadRenderTexture(w, h); - if (ctx->mode != CPM_SLIDERS) + if (ctx->mode != CPM_SLIDERS) { + i32 mode = ctx->mode; + ctx->mode = CPM_SLIDERS; do_slider_mode(ctx, ma.pos); + ctx->mode = mode; + } } } diff --git a/config.def.h b/config.def.h @@ -1,6 +1,6 @@ /* NOTE: This is used by gen_incs for generating font_inc.h and shader_inc.h */ #define FONT_SIZE 40u -#define HSV_LERP_SHADER_NAME "hsv_lerp.glsl" +#define HSV_LERP_SHADER_NAME "slider_lerp.glsl" /* NOTE: Values below here are just used for initializing the ctx in main. * They are not needed if you are are embedding into another application. */ diff --git a/hsv_lerp.glsl b/hsv_lerp.glsl @@ -1,86 +0,0 @@ -/* See LICENSE for copyright details */ -#version 330 - -in vec2 fragTexCoord; -in vec4 fragColor; - -out vec4 out_colour; - -#define CM_RGB 0 -#define CM_HSV 1 - -uniform int u_colour_mode; -uniform float u_radius; -uniform float u_border_thick; -uniform vec4 u_colours[3]; -uniform vec4 u_regions[4]; -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; -} - -bool in_rounded_region(vec2 pos, vec4 min_max, float radius) -{ - vec2 min_xy = min_max.xy; - vec2 max_xy = min_max.zw; - vec2 ulp = vec2(min_xy.x + radius, max_xy.y - radius); - vec2 urp = vec2(max_xy.x - radius, max_xy.y - radius); - vec2 blp = vec2(min_xy.x + radius, min_xy.y + radius); - vec2 brp = vec2(max_xy.x - radius, min_xy.y + radius); - - bool ulr = length(ulp - pos) < radius; - bool urr = length(urp - pos) < radius; - bool blr = length(blp - pos) < radius; - bool brr = length(brp - pos) < radius; - bool rect = (pos.x > min_xy.x) && (pos.x < max_xy.x) && - (pos.y < max_xy.y - radius) && (pos.y > min_xy.y + radius); - bool upr = (pos.x > ulp.x) && (pos.x < urp.x) && (pos.y < max_xy.y) && (pos.y > min_xy.y); - - return rect || ulr || urr || blr || brr || upr; -} - -void main() -{ - vec2 pos = gl_FragCoord.xy; - - vec4 colour = vec4(0); - switch (u_colour_mode) { - case CM_RGB: - out_colour = vec4(0); - break; - case CM_HSV: - vec4 shift = vec4(vec2(u_border_thick), -vec2(u_border_thick)); - - if (in_rounded_region(pos, u_regions[0], u_radius + u_border_thick)) { - colour.w = 1; - if (in_rounded_region(pos, u_regions[0] + shift, u_radius)) { - float scale = u_regions[0].w - u_regions[0].y; - colour = mix(vec4(1, u_colours[0].yzw), vec4(0, u_colours[0].yzw), - (pos.y - u_regions[0].y) / scale); - } - } else if (in_rounded_region(pos, u_regions[1], u_radius + u_border_thick)) { - colour.w = 1; - if (in_rounded_region(pos, u_regions[1] + shift, u_radius)) { - float scale = u_regions[1].w - u_regions[1].y; - colour = mix(u_colours[2], u_colours[1], - (pos.y - u_regions[1].y) / scale); - } - } else if (in_rounded_region(pos, u_regions[2], u_radius + u_border_thick)) { - colour.w = 1; - if (in_rounded_region(pos, u_regions[2] + shift, u_radius)) { - vec2 scale = u_regions[2].zw - u_regions[2].xy; - vec2 t = (pos - u_regions[2].xy) / scale; - colour = vec4(u_colours[0].x, t.x, t.y, 1); - } - } - colour.x *= 360; - out_colour = vec4(hsv2rgb(colour.xyz), colour.w); - break; - } -} diff --git a/slider_lerp.glsl b/slider_lerp.glsl @@ -0,0 +1,132 @@ +/* See LICENSE for copyright details */ +#version 330 + +/* TODO: this shader should be instanced!! */ + +in vec2 fragTexCoord; +in vec4 fragColor; + +out vec4 out_colour; + +#define CPM_PICKER 0 +#define CPM_SLIDERS 1 + +#define CM_RGB 0 +#define CM_HSV 1 + +uniform int u_mode; +uniform int u_colour_mode; +uniform float u_radius; +uniform float u_border_thick; +uniform vec4 u_colours[3]; +uniform vec4 u_regions[4]; +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; +} + +bool in_rounded_region(vec2 pos, vec4 min_max, float radius) +{ + vec2 min_xy = min_max.xy; + vec2 max_xy = min_max.zw; + vec2 ulp = vec2(min_xy.x + radius, max_xy.y - radius); + vec2 urp = vec2(max_xy.x - radius, max_xy.y - radius); + vec2 blp = vec2(min_xy.x + radius, min_xy.y + radius); + vec2 brp = vec2(max_xy.x - radius, min_xy.y + radius); + + bool ulr = length(ulp - pos) < radius; + bool urr = length(urp - pos) < radius; + bool blr = length(blp - pos) < radius; + bool brr = length(brp - pos) < radius; + bool rect = (pos.x > min_xy.x) && (pos.x < max_xy.x) && + (pos.y < max_xy.y - radius) && (pos.y > min_xy.y + radius); + bool upr = (pos.x > ulp.x) && (pos.x < urp.x) && (pos.y < max_xy.y) && (pos.y > min_xy.y); + + return rect || ulr || urr || blr || brr || upr; +} + +vec4 picker_mode(vec2 pos) +{ + vec4 result = vec4(0); + vec4 shift = vec4(vec2(u_border_thick), -vec2(u_border_thick)); + + if (in_rounded_region(pos, u_regions[0], u_radius + u_border_thick)) { + result.w = 1; + if (in_rounded_region(pos, u_regions[0] + shift, u_radius)) { + float scale = u_regions[0].w - u_regions[0].y; + result = mix(vec4(1, u_colours[0].yzw), vec4(0, u_colours[0].yzw), + (pos.y - u_regions[0].y) / scale); + } + } else if (in_rounded_region(pos, u_regions[1], u_radius + u_border_thick)) { + result.w = 1; + if (in_rounded_region(pos, u_regions[1] + shift, u_radius)) { + float scale = u_regions[1].w - u_regions[1].y; + result = mix(u_colours[2], u_colours[1], + (pos.y - u_regions[1].y) / scale); + } + } else if (in_rounded_region(pos, u_regions[2], u_radius + u_border_thick)) { + result.w = 1; + if (in_rounded_region(pos, u_regions[2] + shift, u_radius)) { + vec2 scale = u_regions[2].zw - u_regions[2].xy; + vec2 t = (pos - u_regions[2].xy) / scale; + result = vec4(u_colours[0].x, t.x, t.y, 1); + } + } + + result.x *= 360; + return vec4(hsv2rgb(result.xyz), result.w); +} + +bool slider_fill(vec2 pos, int idx, out vec4 colour) +{ + bool result = false; + vec4 shift = vec4(vec2(u_border_thick), -vec2(u_border_thick)); + colour = vec4(0); + if (in_rounded_region(pos, u_regions[idx], u_radius + u_border_thick)) { + colour.w = 1; + if (in_rounded_region(pos, u_regions[idx] + shift, u_radius)) { + float scale = u_regions[idx].z - u_regions[idx].x; + vec4 c1 = u_colours[0]; + vec4 c2 = u_colours[0]; + c1[idx] = 0; + c2[idx] = 1; + colour = mix(c1, c2, (pos.x - u_regions[idx].x) / scale); + } + result = true; + } + return result; +} + +vec4 slider_mode(vec2 pos) +{ + vec4 result = vec4(0); + vec4 fill_out; + + if (slider_fill(pos, 0, fill_out)) result = fill_out; + else if (slider_fill(pos, 1, fill_out)) result = fill_out; + else if (slider_fill(pos, 2, fill_out)) result = fill_out; + else if (slider_fill(pos, 3, fill_out)) result = fill_out; + + switch (u_colour_mode) { + case CM_RGB: break; + case CM_HSV: result.x *= 360; result = vec4(hsv2rgb(result.xyz), result.w); break; + } + + return result; +} + +void main() +{ + vec2 pos = gl_FragCoord.xy; + out_colour = vec4(0); + switch (u_mode) { + case CPM_PICKER: out_colour = picker_mode(pos); break; + case CPM_SLIDERS: out_colour = slider_mode(pos); break; + } +} diff --git a/util.c b/util.c @@ -64,8 +64,8 @@ enum colour_mode { }; enum colour_picker_mode { - CPM_PICKER, - CPM_SLIDERS, + CPM_PICKER = 0, + CPM_SLIDERS = 1, CPM_LAST }; @@ -181,9 +181,8 @@ typedef struct { Shader picker_shader; RenderTexture slider_texture; RenderTexture picker_texture; - RenderTexture hsv_texture; - i32 colour_mode_id, colours_id; + i32 mode_id, colour_mode_id, colours_id; i32 regions_id, size_id; i32 radius_id, border_thick_id;