Added test for rgb->hsv->rgb, and fixed function

main
noah metz 2024-11-17 16:13:37 -07:00
parent 01285dc197
commit 8a30340202
6 changed files with 208 additions and 100 deletions

2
.gitignore vendored

@ -34,3 +34,5 @@ roleplay
*.spv
.DS_Store
client/test/test_*

@ -2,8 +2,12 @@ ROOT_DIR:=$(shell dirname $(realpath $(firstword $(MAKEFILE_LIST))))
CFLAGS = -I $(ROOT_DIR)/include -I/usr/local/include -O0 -g -Wall -Wextra
LDFLAGS = -lfreetype -lz -lglfw -lvulkan -ldl -Xlinker -rpath -Xlinker /opt/homebrew/lib
SOURCES = src/main.c src/draw.c src/ui.c src/gpu.c src/hex.c lib/spng.c lib/vma.cpp
OBJECTS = $(addsuffix .o, $(basename $(SOURCES)))
SOURCES = src/draw.c src/ui.c src/gpu.c src/hex.c src/hsv.h lib/spng.c lib/vma.cpp
APP_SOURCES = src/main.c $(SOURCES)
TEST_SOURCES = test/hsv.c $(SOURCES)
APP_OBJECTS = $(addsuffix .o, $(basename $(APP_SOURCES)))
TEST_OBJECTS = $(addsuffix .o, $(basename $(TEST_SOURCES)))
VERT_SPV = $(addsuffix .vert.spv, $(basename $(wildcard shader/*.vert)))
FRAG_SPV = $(addsuffix .frag.spv, $(basename $(wildcard shader/*.frag)))
COMP_SPV = $(addsuffix .comp.spv, $(basename $(wildcard shader/*.comp)))
@ -29,9 +33,17 @@ CXX ?= clang++
.PHONY: all
all: roleplay $(SPV_FILES)
roleplay: $(OBJECTS)
roleplay: $(APP_OBJECTS)
$(CXX) $(CFLAGS) $(LDFLAGS) -o $@ $^
test/test_hsv: $(TEST_OBJECTS)
$(CXX) $(CFLAGS) $(LDFLAGS) -o $@ $^
.PHONY: test
test: test/test_hsv
./test/test_hsv
%.o: %.cpp
$(CXX) $(CFLAGS) -std=c++17 -Wno-nullability-completeness -Wno-unused-parameter -Wno-missing-field-initializers -Wno-unused-private-field -Wno-unused-variable -c -o $@ $<
@ -42,7 +54,8 @@ roleplay: $(OBJECTS)
clean:
rm -f $(SPV_FILES)
rm -f $(OBJECTS)
rm -f $(APP_OBJECTS)
rm -f $(TEST_OBJECTS)
rm -f roleplay
rm -rf $(EXTRA_DEBUG_REQUIREMENTS)

@ -0,0 +1,9 @@
#ifndef HSV_H
#define HSV_H
#include "cglm/types.h"
void hsv_to_rgb(double hsv[3], vec3 rgb);
void rgb_to_hsv(vec3 rgb, double hsv[3]);
#endif

@ -0,0 +1,75 @@
#include "hsv.h"
#include "math.h"
#define max(a, b) ((a > b) ? a : b)
#define min(a, b) ((a < b) ? a : b)
float floatmod(float a, float b) {
return a - b*floor(a/b);
}
void rgb_to_hsv(vec3 rgb, double hsv[3]) {
if(rgb[0] == 0 && rgb[1] == 0 && rgb[2] == 0) {
hsv[0] = 0;
hsv[1] = 0;
hsv[2] = 0;
return;
}
hsv[2] = max(rgb[0], max(rgb[1], rgb[2]));
float M = min(rgb[0], min(rgb[1], rgb[2]));
float C = hsv[2] - M;
hsv[1] = C / hsv[2];
float X;
if(hsv[2] == rgb[0] && M == rgb[2]) {
X = rgb[1] - M;
hsv[0] = (X/C + 0)/6;
} else if(hsv[2] == rgb[1] && M == rgb[2]) {
X = rgb[0] - M;
hsv[0] = (2 - X/C)/6;
} else if(hsv[2] == rgb[1] && M == rgb[0]) {
X = rgb[2] - M;
hsv[0] = (X/C + 2)/6;
} else if(hsv[2] == rgb[2] && M == rgb[0]) {
X = rgb[1] - M;
hsv[0] = (4 - X/C)/6;
} else if(hsv[2] == rgb[2] && M == rgb[1]) {
X = rgb[0] - M;
hsv[0] = (X/C + 4)/6;
} else if(hsv[2] == rgb[0] && M == rgb[1]) {
X = rgb[2] - M;
hsv[0] = (6 - X/C)/6;
}
}
void hsv_to_rgb(double hsv[3], vec3 rgb) {
float C = hsv[1] * hsv[2];
float H = hsv[0]*6;
float X = C * (1 - fabs(floatmod(H, 2) - 1));
vec4 temp = {0, 0, 0, 0};
if(0 <= H && H <= 1) {
temp[0] = C;
temp[1] = X;
} else if(1 <= H && H <= 2) {
temp[0] = X;
temp[1] = C;
} else if(2 <= H && H <= 3) {
temp[1] = C;
temp[2] = X;
} else if(3 <= H && H <= 4) {
temp[1] = X;
temp[2] = C;
} else if(4 <= H && H <= 5) {
temp[0] = X;
temp[2] = C;
} else if(5 <= H && H <= 6) {
temp[0] = C;
temp[2] = X;
}
float m = hsv[2] - C;
rgb[0] = temp[0] + m;
rgb[1] = temp[1] + m;
rgb[2] = temp[2] + m;
}

@ -6,6 +6,7 @@
#include "gpu.h"
#include "draw.h"
#include "hex.h"
#include "hsv.h"
#include "arpa/inet.h"
#include "vk_mem_alloc.h"
@ -93,55 +94,21 @@ uint32_t add_hex_region(ClientContext* context) {
#define COLOR_PICK_CONTAINER_ID 0x03
typedef struct ColorUIDataStruct {
vec3 current;
double current_hsv[3];
vec4 current;
vec3 saved[12];
char string[8];
char string[10];
int string_len;
} ColorUIData;
float floatmod(float a, float b) {
return a - b*floor(a/b);
}
void hsv_to_rgb(vec3 hsv, vec3 rgb) {
float C = hsv[1] * hsv[2];
float H = hsv[0]*6;
float X = C * (1 - fabs(floatmod(H, 2) - 1));
vec4 temp = {0, 0, 0, 0};
if(0 <= H && H <= 1) {
temp[0] = C;
temp[1] = X;
} else if(1 <= H && H <= 2) {
temp[0] = X;
temp[1] = C;
} else if(2 <= H && H <= 3) {
temp[1] = C;
temp[2] = X;
} else if(3 <= H && H <= 4) {
temp[1] = X;
temp[2] = C;
} else if(4 <= H && H <= 5) {
temp[0] = X;
temp[2] = C;
} else if(5 <= H && H <= 6) {
temp[0] = C;
temp[2] = X;
}
float m = hsv[2] - C;
rgb[0] = temp[0] + m;
rgb[1] = temp[1] + m;
rgb[2] = temp[2] + m;
}
void rgb_string_set(UIContext* ui, RenderContext* gpu, ColorUIData* data, vec3 hsv) {
vec3 rgb;
hsv_to_rgb(hsv, rgb);
snprintf(data->string, 8, "#%02X%02X%02X",
(uint)(rgb[0]*255),
(uint)(rgb[1]*255),
(uint)(rgb[2]*255));
data->string_len = 6;
void set_rgb_from_hsv(UIContext* ui, RenderContext* gpu, ColorUIData* data, double hsv[3]) {
hsv_to_rgb(hsv, data->current);
snprintf(data->string, 10, "#%02X%02X%02X%02X",
(uint)(data->current[0]*255),
(uint)(data->current[1]*255),
(uint)(data->current[2]*255),
(uint)(data->current[3]*255));
data->string_len = 8;
update_ui_string(data->string, COLOR_PICK_CONTAINER_ID, 0, 0, ui, gpu);
}
@ -152,8 +119,8 @@ void sv_square_pick(UIContext* ui, RenderContext* gpu, ColorUIData* data, float
if(v < 0) v = 0;
if(v > 1) v = 1;
data->current[1] = s;
data->current[2] = 1-v;
data->current_hsv[1] = s;
data->current_hsv[2] = 1-v;
Container* container = context_container(COLOR_PICK_CONTAINER_ID, ui);
Layer* layer = &container->layers[0];
@ -199,7 +166,7 @@ void sv_square_pick(UIContext* ui, RenderContext* gpu, ColorUIData* data, float
4*sizeof(vec4),
gpu);
rgb_string_set(ui, gpu, data, select->color[0]);
set_rgb_from_hsv(ui, gpu, data, data->current_hsv);
}
void sv_square_button_callback(void* data, UIContext* ui, RenderContext* gpu, float x, float y, int button, int action, int mods) {
@ -226,7 +193,7 @@ void hue_bar_set(UIContext* ui, RenderContext* gpu, ColorUIData* data, float y)
if(y < 0) y = 0;
if(y > 1) y = 1;
data->current[0] = y;
data->current_hsv[0] = y;
Container* container = context_container(COLOR_PICK_CONTAINER_ID, ui);
Layer* layer = &container->layers[0];
@ -267,7 +234,7 @@ void hue_bar_set(UIContext* ui, RenderContext* gpu, ColorUIData* data, float y)
1*sizeof(float),
gpu);
rgb_string_set(ui, gpu, data, sv_select->color[0]);
set_rgb_from_hsv(ui, gpu, data, data->current_hsv);
}
void hue_bar_scroll_callback(void* data, UIContext* ui, RenderContext* gpu, double x, double y) {
@ -330,57 +297,22 @@ void hex_string_text_callback(void* ptr, UIContext* ui, RenderContext* gpu, unsi
return;
}
if(data->string_len < 6) {
if(data->string_len < 8) {
data->string_len += 1;
data->string[data->string_len] = codepoint;
update_ui_string(data->string, COLOR_PICK_CONTAINER_ID, 0, 0, ui, gpu);
}
}
void rgb_to_hsv(vec3 rgb, vec3 hsv) {
hsv[2] = max(rgb[0], max(rgb[1], rgb[2]));
float M = min(rgb[0], min(rgb[1], rgb[2]));
float C = hsv[2] - M;
hsv[1] = C / hsv[2];
float X;
if(hsv[2] == rgb[0] && M == rgb[2]) {
X = rgb[1] - M;
hsv[0] = (X/C + 0)/6;
// H = [0, 1]
} else if(hsv[2] == rgb[1] && M == rgb[2]) {
X = 1 - rgb[0] - M;
hsv[0] = (X/C + 1)/6;
// H = [1, 2]
} else if(hsv[2] == rgb[1] && M == rgb[0]) {
X = rgb[2] - M;
hsv[0] = (X/C + 2)/6;
// H = [2, 3]
} else if(hsv[2] == rgb[2] && M == rgb[0]) {
X = 1 - rgb[1] - M;
hsv[0] = (X/C + 3)/6;
// H = [3, 4]
} else if(hsv[2] == rgb[2] && M == rgb[1]) {
X = rgb[0] - M;
hsv[0] = (X/C + 4)/6;
// H = [4, 5]
} else if(hsv[2] == rgb[0] && M == rgb[1]) {
X = 1 - rgb[2] - M;
hsv[0] = (X/C + 5)/6;
// H = [5, 6]
}
}
bool hex_string_key_callback(void* ptr, UIContext* ui, RenderContext* gpu, int key, int action, int mods) {
(void)mods;
ColorUIData* data = ptr;
char tmp[3];
vec3 rgb;
if(action == GLFW_PRESS) {
switch(key) {
case GLFW_KEY_ESCAPE:
rgb_string_set(ui, gpu, data, data->current);
set_rgb_from_hsv(ui, gpu, data, data->current_hsv);
clear_active_element(ui, gpu);
break;
case GLFW_KEY_ENTER:
@ -388,22 +320,26 @@ bool hex_string_key_callback(void* ptr, UIContext* ui, RenderContext* gpu, int k
tmp[0] = data->string[1];
tmp[1] = data->string[2];
tmp[2] = 0;
rgb[0] = strtol(tmp, NULL, 16)/255.0;
data->current[0] = strtol(tmp, NULL, 16)/255.0;
tmp[0] = data->string[3];
tmp[1] = data->string[4];
tmp[2] = 0;
rgb[1] = strtol(tmp, NULL, 16)/255.0;
data->current[1] = strtol(tmp, NULL, 16)/255.0;
tmp[0] = data->string[5];
tmp[1] = data->string[6];
tmp[2] = 0;
rgb[2] = strtol(tmp, NULL, 16)/255.0;
data->current[2] = strtol(tmp, NULL, 16)/255.0;
rgb_to_hsv(rgb, data->current);
tmp[0] = data->string[7];
tmp[1] = data->string[8];
tmp[2] = 0;
data->current[3] = strtol(tmp, NULL, 16)/255.0;
hue_bar_set(ui, gpu, data, data->current[0]);
sv_square_pick(ui, gpu, data, data->current[1], 1-data->current[2]);
rgb_to_hsv(data->current, data->current_hsv);
hue_bar_set(ui, gpu, data, data->current_hsv[0]);
sv_square_pick(ui, gpu, data, data->current_hsv[1], 1-data->current_hsv[2]);
clear_active_element(ui, gpu);
break;
@ -443,7 +379,7 @@ VkResult color_ui(ClientContext* context) {
.color = {1, 1, 1, 1},
.size = 16,
.offset = 0,
.length = 7,
.length = 9,
.font = 0,
},
};
@ -565,7 +501,7 @@ VkResult color_ui(ClientContext* context) {
};
ColorUIData* data = malloc(sizeof(ColorUIData));
data->string_len = 6;
data->string_len = 8;
data->string[0] = '#';
data->string[1] = '0';
data->string[2] = '0';
@ -573,10 +509,13 @@ VkResult color_ui(ClientContext* context) {
data->string[4] = '0';
data->string[5] = '0';
data->string[6] = '0';
data->string[7] = '\0';
data->string[7] = '0';
data->string[8] = '0';
data->string[9] = '\0';
data->current[0] = 0;
data->current[1] = 0;
data->current[2] = 0;
data->current[3] = 0;
UICallbacks callbacks[] = {
{

@ -0,0 +1,70 @@
#include "hsv.h"
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#define FAIL -1
#define OK 0
int test_conv(int r, int g, int b, double tolerance) {
vec3 rgb = {r/255.0, g/255.0, b/255.0};
double hsv[3];
vec3 out;
int test[3];
rgb_to_hsv(rgb, hsv);
hsv_to_rgb(hsv, out);
test[0] = out[0]*255.0;
test[1] = out[1]*255.0;
test[2] = out[2]*255.0;
if(fabs(rgb[0] - out[0]) > tolerance || fabs(rgb[1] - out[1]) > tolerance || fabs(rgb[2] - out[2]) > tolerance) {
fprintf(stderr, "FAIL:\t#%02X%02X%02X -> #%02X%02X%02X\nIN:\t(%.12f %.12f %.12f)\nOUT:\t(%.12f %.12f %.12f)\nDIF:\t(%.12f %.12f %.12f)\n",
r, g, b,
test[0], test[1], test[2],
rgb[0], rgb[1], rgb[2],
out[0], out[1], out[2],
rgb[0]-out[0], rgb[1]-out[1], rgb[2]-out[2]);
return FAIL;
} else {
return OK;
}
}
int main() {
for(int r = 0; r < 256; r++) {
for(int g = 0; g < 256; g++) {
for(int b = 0; b < 256; b++) {
if(test_conv(r, g, b, 0.001) != OK) {
return -1;
}
}
}
}
}
// To RGB steps:
// 1. C = V * S <- maps to 3 in inv
// 2. H' = H * 6 <- maps to 7 in inv
// 3. X = C * (1 - abs(H' - 2*floor(H'/2) - 1))
// 4. M = V - C <- maps to 4 in inv
// 5. R = V, G = X + M, B = M
// To HSV steps:
// 1. V = max(R, G, B) <- maps to 5,R in inv
// 2. M = min(R, G, B) <- maps to 5,B in inv
// 3. S = C / V <- maps to 1 in inv
// 4. C = V - M <- maps to 4 in inv
// 5. X = G - M
// 6. H' = X/C
// 7. H = H'/6 <- maps to 2 in inv
//
// So out of the two algos, the only step that doest match exactly so far is
// RGB: X = C * (1 - abs(H' - 2*floor(H'/2) - 1))
// HSV: X = G - M, H' = X/C
//
// After floor():
// RGB: X = C * (1 - abs(H' - 1))
//
// After abs() assuming H' = [0, 1]:
// RGB: X = C * (1 - (1 - H'))
// RGB: X = C * H'