diff --git a/include/pocketpy/linalg.h b/include/pocketpy/linalg.h index 38e37228..09a9dacb 100644 --- a/include/pocketpy/linalg.h +++ b/include/pocketpy/linalg.h @@ -33,3 +33,13 @@ typedef union c11_mat3x3 { float m[3][3]; float data[9]; } c11_mat3x3; + +typedef union c11_color32 { + struct { + unsigned char r; + unsigned char g; + unsigned char b; + unsigned char a; + }; + unsigned char data[4]; +} c11_color32; diff --git a/include/pocketpy/objects/base.h b/include/pocketpy/objects/base.h index d7dbbc43..f3432aef 100644 --- a/include/pocketpy/objects/base.h +++ b/include/pocketpy/objects/base.h @@ -19,6 +19,7 @@ typedef struct py_TValue { PyObject* _obj; c11_vec2 _vec2; c11_vec2i _vec2i; + c11_color32 _color32; void* _ptr; }; } py_TValue; diff --git a/include/pocketpy/pocketpy.h b/include/pocketpy/pocketpy.h index a44faa41..92f4637d 100644 --- a/include/pocketpy/pocketpy.h +++ b/include/pocketpy/pocketpy.h @@ -672,12 +672,14 @@ void py_newvec2(py_OutRef out, c11_vec2); void py_newvec3(py_OutRef out, c11_vec3); void py_newvec2i(py_OutRef out, c11_vec2i); void py_newvec3i(py_OutRef out, c11_vec3i); +void py_newcolor32(py_OutRef out, c11_color32); c11_mat3x3* py_newmat3x3(py_OutRef out); c11_vec2 py_tovec2(py_Ref self); c11_vec3 py_tovec3(py_Ref self); c11_vec2i py_tovec2i(py_Ref self); c11_vec3i py_tovec3i(py_Ref self); c11_mat3x3* py_tomat3x3(py_Ref self); +c11_color32 py_tocolor32(py_Ref self); /************* Others *************/ @@ -765,6 +767,7 @@ enum py_PredefinedType { tp_vec2i, tp_vec3i, tp_mat3x3, + tp_color32, /* array2d */ tp_array2d_like, tp_array2d_like_iterator, diff --git a/include/typings/linalg.pyi b/include/typings/linalg.pyi index 936a9b04..06477cc8 100644 --- a/include/typings/linalg.pyi +++ b/include/typings/linalg.pyi @@ -180,4 +180,44 @@ class vec3(_vecF['vec3']): def __init__(self, xyz: vec3i) -> None: ... +# Color32 +class color32: + def __new__(cls, r: int, g: int, b: int, a: int) -> 'color32': ... + def __eq__(self, other: object) -> bool: ... + def __ne__(self, other: object) -> bool: ... + def __repr__(self) -> str: ... + @property + def r(self) -> int: ... + @property + def g(self) -> int: ... + @property + def b(self) -> int: ... + @property + def a(self) -> int: ... + + def with_r(self, r: int) -> 'color32': ... + def with_g(self, g: int) -> 'color32': ... + def with_b(self, b: int) -> 'color32': ... + def with_a(self, a: int) -> 'color32': ... + + @staticmethod + def from_hex(hex: str) -> 'color32': ... + @staticmethod + def from_vec3(vec: vec3) -> 'color32': ... + @staticmethod + def from_vec3i(vec: vec3i) -> 'color32': ... + + def to_hex(self) -> str: ... + def to_vec3(self) -> vec3: ... + def to_vec3i(self) -> vec3i: ... + + def ansi_fg(self, text: str) -> str: ... + def ansi_bg(self, text: str) -> str: ... + + @staticmethod + def alpha_blend(src: color32, dst: color32) -> color32: ... + + +def rgb(r: int, g: int, b: int) -> color32: ... +def rgba(r: int, g: int, b: int, a: float) -> color32: ... diff --git a/src/modules/linalg.c b/src/modules/linalg.c index 188bc779..4a79dfbb 100644 --- a/src/modules/linalg.c +++ b/src/modules/linalg.c @@ -1,3 +1,4 @@ +#include "pocketpy/linalg.h" #include "pocketpy/pocketpy.h" #include "pocketpy/common/sstream.h" @@ -836,6 +837,246 @@ static bool vec3__with_xy(int argc, py_Ref argv) { return true; } +/* Color32 */ +void py_newcolor32(py_OutRef out, c11_color32 color) { + out->type = tp_color32; + out->is_ptr = false; + out->_color32 = color; +} + +c11_color32 py_tocolor32(py_Ref obj) { + assert(obj->type == tp_color32); + return obj->_color32; +} + +static bool color32__new__(int argc, py_Ref argv) { + PY_CHECK_ARGC(5); + c11_color32 color; + for(int i = 1; i < 5; i++) { + PY_CHECK_ARG_TYPE(i, tp_int); + py_i64 val = py_toint(&argv[i]); + if(val < 0 || val > 255) return ValueError("color32 values must be between 0 and 255"); + color.data[i - 1] = (unsigned char)val; + } + py_newcolor32(py_retval(), color); + return true; +} + +#define DEFINE_COLOR32_FIELD(name) \ + static bool color32__##name(int argc, py_Ref argv) { \ + PY_CHECK_ARGC(1); \ + c11_color32 color = py_tocolor32(argv); \ + py_newint(py_retval(), color.name); \ + return true; \ + } \ + static bool color32_with_##name(int argc, py_Ref argv) { \ + PY_CHECK_ARGC(2); \ + c11_color32 color = py_tocolor32(argv); \ + PY_CHECK_ARG_TYPE(1, tp_int); \ + py_i64 val = py_toint(&argv[1]); \ + if(val < 0 || val > 255) { \ + return ValueError("color32 values must be between 0 and 255"); \ + } \ + color.name = (unsigned char)val; \ + py_newcolor32(py_retval(), color); \ + return true; \ + } + +DEFINE_COLOR32_FIELD(r) +DEFINE_COLOR32_FIELD(g) +DEFINE_COLOR32_FIELD(b) +DEFINE_COLOR32_FIELD(a) + +#undef DEFINE_COLOR32_FIELD + +static bool color32_from_hex_STATIC(int argc, py_Ref argv) { + PY_CHECK_ARGC(1); + PY_CHECK_ARG_TYPE(0, tp_str); + c11_sv hex = py_tosv(argv); + c11_color32 color; + int res; + if(hex.size == 7) { + res = scanf(hex.data, "#%2hhx%2hhx%2hhx", &color.r, &color.g, &color.b); + if(res != 3) return ValueError("invalid hex color format"); + color.a = 255; + } else { + res = sscanf(hex.data, "#%2hhx%2hhx%2hhx%2hhx", &color.r, &color.g, &color.b, &color.a); + if(res != 4) return ValueError("invalid hex color format"); + } + py_newcolor32(py_retval(), color); + return true; +} + +static bool color32_from_vec3_STATIC(int argc, py_Ref argv) { + PY_CHECK_ARGC(1); + PY_CHECK_ARG_TYPE(0, tp_vec3); + c11_vec3 v = py_tovec3(argv); + c11_color32 color; + color.r = (unsigned char)(v.x * 255); + color.g = (unsigned char)(v.y * 255); + color.b = (unsigned char)(v.z * 255); + color.a = 255; + py_newcolor32(py_retval(), color); + return true; +} + +static bool color32_from_vec3i_STATIC(int argc, py_Ref argv) { + PY_CHECK_ARGC(1); + PY_CHECK_ARG_TYPE(0, tp_vec3i); + c11_vec3i v = py_tovec3i(argv); + c11_color32 color; + color.r = (unsigned char)v.x; + color.g = (unsigned char)v.y; + color.b = (unsigned char)v.z; + color.a = 255; + py_newcolor32(py_retval(), color); + return true; +} + +static bool color32_to_hex(int argc, py_Ref argv) { + PY_CHECK_ARGC(1); + c11_color32 color = py_tocolor32(argv); + char buf[16]; + int size; + if(color.a == 255) { + size = snprintf(buf, sizeof(buf), "#%02x%02x%02x", color.r, color.g, color.b); + } else { + size = snprintf(buf, sizeof(buf), "#%02x%02x%02x%02x", color.r, color.g, color.b, color.a); + } + py_newstrv(py_retval(), (c11_sv){buf, size}); + return true; +} + +static void c11_color32_premult(c11_color32* color) { + if(color->a == 255) return; + float alpha = color->a / 255.0f; + color->r = (unsigned char)(color->r * alpha); + color->g = (unsigned char)(color->g * alpha); + color->b = (unsigned char)(color->b * alpha); +} + +static bool color32_to_vec3(int argc, py_Ref argv) { + PY_CHECK_ARGC(1); + c11_color32 color = py_tocolor32(argv); + c11_color32_premult(&color); + c11_vec3 v; + v.x = (float)color.r / 255; + v.y = (float)color.g / 255; + v.z = (float)color.b / 255; + py_newvec3(py_retval(), v); + return true; +} + +static bool color32_to_vec3i(int argc, py_Ref argv) { + PY_CHECK_ARGC(1); + c11_color32 color = py_tocolor32(argv); + c11_color32_premult(&color); + c11_vec3i v; + v.x = (int)color.r; + v.y = (int)color.g; + v.z = (int)color.b; + py_newvec3i(py_retval(), v); + return true; +} + +static bool color32__eq__(int argc, py_Ref argv) { + PY_CHECK_ARGC(2); + if(argv[1].type != tp_color32) { + py_newnotimplemented(py_retval()); + return true; + } + c11_color32 lhs = py_tocolor32(&argv[0]); + c11_color32 rhs = py_tocolor32(&argv[1]); + bool eq = memcmp(&lhs, &rhs, sizeof(c11_color32)) == 0; + py_newbool(py_retval(), eq); + return true; +} + +static bool color32__ne__(int argc, py_Ref argv) { + PY_CHECK_ARGC(2); + if(argv[1].type != tp_color32) { + py_newnotimplemented(py_retval()); + return true; + } + c11_color32 lhs = py_tocolor32(&argv[0]); + c11_color32 rhs = py_tocolor32(&argv[1]); + bool eq = memcmp(&lhs, &rhs, sizeof(c11_color32)) != 0; + py_newbool(py_retval(), eq); + return true; +} + +static bool color32__repr__(int argc, py_Ref argv) { + PY_CHECK_ARGC(1); + c11_color32 color = py_tocolor32(argv); + char buf[64]; + int size = snprintf(buf, 64, "color32(%d, %d, %d, %d)", color.r, color.g, color.b, color.a); + py_newstrv(py_retval(), (c11_sv){buf, size}); + return true; +} + +static bool color32_ansi_fg(int argc, py_Ref argv) { + PY_CHECK_ARGC(1); + c11_color32 color = py_tocolor32(argv); + c11_color32_premult(&color); + char buf[32]; + int size = snprintf(buf, sizeof(buf), "\033[38;2;%d;%d;%dm", color.r, color.g, color.b); + py_newstrv(py_retval(), (c11_sv){buf, size}); + return true; +} + +static bool color32_ansi_bg(int argc, py_Ref argv) { + PY_CHECK_ARGC(1); + c11_color32 color = py_tocolor32(argv); + c11_color32_premult(&color); + char buf[32]; + int size = snprintf(buf, sizeof(buf), "\033[48;2;%d;%d;%dm", color.r, color.g, color.b); + py_newstrv(py_retval(), (c11_sv){buf, size}); + return true; +} + +static bool linalg_rgb(int argc, py_Ref argv) { + PY_CHECK_ARGC(3); + PY_CHECK_ARG_TYPE(0, tp_int); + PY_CHECK_ARG_TYPE(1, tp_int); + PY_CHECK_ARG_TYPE(2, tp_int); + c11_color32 color; + color.r = (unsigned char)py_toint(&argv[0]); + color.g = (unsigned char)py_toint(&argv[1]); + color.b = (unsigned char)py_toint(&argv[2]); + color.a = 255; + py_newcolor32(py_retval(), color); + return true; +} + +static bool linalg_rgba(int argc, py_Ref argv) { + PY_CHECK_ARGC(4); + PY_CHECK_ARG_TYPE(0, tp_int); + PY_CHECK_ARG_TYPE(1, tp_int); + PY_CHECK_ARG_TYPE(2, tp_int); + PY_CHECK_ARG_TYPE(3, tp_float); + c11_color32 color; + color.r = (unsigned char)py_toint(&argv[0]); + color.g = (unsigned char)py_toint(&argv[1]); + color.b = (unsigned char)py_toint(&argv[2]); + color.a = (unsigned char)(py_tofloat(&argv[3]) * 255); + py_newcolor32(py_retval(), color); + return true; +} + +static bool color32_alpha_blend_STATIC(int argc, py_Ref argv) { + PY_CHECK_ARGC(2); + c11_color32 src = py_tocolor32(&argv[0]); + c11_color32 dst = py_tocolor32(&argv[1]); + float alpha = src.a / 255.0f; + c11_color32 res; + res.r = (unsigned char)(src.r * alpha + dst.r * (1 - alpha)); + res.g = (unsigned char)(src.g * alpha + dst.g * (1 - alpha)); + res.b = (unsigned char)(src.b * alpha + dst.b * (1 - alpha)); + res.a = (unsigned char)(src.a * alpha + dst.a * (1 - alpha)); + py_newcolor32(py_retval(), res); + return true; +} + void pk__add_module_linalg() { py_Ref mod = py_newmodule("linalg"); @@ -844,18 +1085,21 @@ void pk__add_module_linalg() { py_Type vec2i = pk_newtype("vec2i", tp_object, mod, NULL, false, true); py_Type vec3i = pk_newtype("vec3i", tp_object, mod, NULL, false, true); py_Type mat3x3 = pk_newtype("mat3x3", tp_object, mod, NULL, false, true); + py_Type color32 = pk_newtype("color32", tp_object, mod, NULL, false, true); py_setdict(mod, py_name("vec2"), py_tpobject(vec2)); py_setdict(mod, py_name("vec3"), py_tpobject(vec3)); py_setdict(mod, py_name("vec2i"), py_tpobject(vec2i)); py_setdict(mod, py_name("vec3i"), py_tpobject(vec3i)); py_setdict(mod, py_name("mat3x3"), py_tpobject(mat3x3)); + py_setdict(mod, py_name("color32"), py_tpobject(color32)); assert(vec2 == tp_vec2); assert(vec3 == tp_vec3); assert(vec2i == tp_vec2i); assert(vec3i == tp_vec3i); assert(mat3x3 == tp_mat3x3); + assert(color32 == tp_color32); /* vec2 */ py_bindmagic(vec2, __new__, vec2__new__); @@ -997,6 +1241,31 @@ void pk__add_module_linalg() { (c11_vec3){ {1, 1, 1} }); + + /* color32 */ + py_bindmagic(color32, __new__, color32__new__); + py_bindmagic(color32, __repr__, color32__repr__); + py_bindmagic(color32, __eq__, color32__eq__); + py_bindmagic(color32, __ne__, color32__ne__); + py_bindproperty(color32, "r", color32__r, NULL); + py_bindproperty(color32, "g", color32__g, NULL); + py_bindproperty(color32, "b", color32__b, NULL); + py_bindproperty(color32, "a", color32__a, NULL); + py_bindmethod(color32, "with_r", color32_with_r); + py_bindmethod(color32, "with_g", color32_with_g); + py_bindmethod(color32, "with_b", color32_with_b); + py_bindmethod(color32, "with_a", color32_with_a); + py_bindstaticmethod(color32, "from_hex", color32_from_hex_STATIC); + py_bindstaticmethod(color32, "from_vec3", color32_from_vec3_STATIC); + py_bindstaticmethod(color32, "from_vec3i", color32_from_vec3i_STATIC); + py_bindmethod(color32, "to_hex", color32_to_hex); + py_bindmethod(color32, "to_vec3", color32_to_vec3); + py_bindmethod(color32, "to_vec3i", color32_to_vec3i); + py_bindmethod(color32, "ansi_fg", color32_ansi_fg); + py_bindmethod(color32, "ansi_bg", color32_ansi_bg); + py_bindfunc(mod, "rgb", linalg_rgb); + py_bindfunc(mod, "rgba", linalg_rgba); + py_bindstaticmethod(color32, "alpha_blend", color32_alpha_blend_STATIC); } #undef DEFINE_VEC_FIELD diff --git a/tests/80_color32.py b/tests/80_color32.py new file mode 100644 index 00000000..1c51d75b --- /dev/null +++ b/tests/80_color32.py @@ -0,0 +1,25 @@ +from linalg import color32, rgb, rgba + +a = color32(100, 200, 255, 120) +assert a.r == 100 +assert a.g == 200 +assert a.b == 255 +assert a.a == 120 +assert a.with_r(255).r == 255 +assert a.with_g(255).g == 255 +assert a.with_b(255).b == 255 +assert a.with_a(255).a == 255 and a.with_a(255).g == a.g + +assert a.to_hex() == '#64c8ff78' +assert color32.from_hex('#64c8ff78') == a + +assert rgb(100, 200, 255) != a +assert rgba(100, 200, 255, 120 / 255) == a + +b = color32(75, 150, 200, 200) +assert a == a and b == b +assert a != b + +assert repr(b) == 'color32(75, 150, 200, 200)' + +# assert color32.alpha_blend(a, b) == color32(86, 173, 225, 162)