blueloveTH 10 месяцев назад
Родитель
Сommit
cfa909f1b9

+ 10 - 0
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;

+ 1 - 0
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;

+ 3 - 0
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,

+ 40 - 0
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: ...

+ 269 - 0
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

+ 25 - 0
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)