From da7830795d72a8e63eeeac8ad21a6807221d1953 Mon Sep 17 00:00:00 2001 From: Matiiss <83066658+Matiiss@users.noreply.github.com> Date: Sun, 3 Aug 2025 23:49:29 +0300 Subject: [PATCH 1/5] implement the __bytes__ method and add a test case --- src_c/color.c | 15 +++++++++++++++ test/color_test.py | 11 +++++++++++ 2 files changed, 26 insertions(+) diff --git a/src_c/color.c b/src_c/color.c index 694a7853d9..f157956c46 100644 --- a/src_c/color.c +++ b/src_c/color.c @@ -181,6 +181,9 @@ _color_int(pgColorObject *); static PyObject * _color_float(pgColorObject *); +static PyObject * +_color_bytes(pgColorObject *); + /* Sequence protocol methods */ static Py_ssize_t _color_length(pgColorObject *); @@ -256,6 +259,8 @@ static PyMethodDef _color_methods[] = { {"premul_alpha", (PyCFunction)_premul_alpha, METH_NOARGS, DOC_COLOR_PREMULALPHA}, {"update", (PyCFunction)_color_update, METH_FASTCALL, DOC_COLOR_UPDATE}, + {"__bytes__", (PyCFunction)_color_bytes, METH_NOARGS, + "Get a byte representation of the color"}, {NULL, NULL, 0, NULL}}; /** @@ -1753,6 +1758,16 @@ _color_float(pgColorObject *color) return PyFloat_FromDouble((double)tmp); } +/** + * bytes(color) + */ +static PyObject * +_color_bytes(pgColorObject *color) +{ + return PyBytes_FromFormat("%c%c%c%c", color->data[0], color->data[1], + color->data[2], color->data[3]); +} + /* Sequence protocol methods */ /** diff --git a/test/color_test.py b/test/color_test.py index addb2d6591..5662ef0a43 100644 --- a/test/color_test.py +++ b/test/color_test.py @@ -773,6 +773,17 @@ def test_long(self): self.assertEqual(c.a, 146) self.assertEqual(int(c), int(0x33727592)) + def test_bytes(self): + c = pygame.Color(0x00012345) + self.assertEqual(c.r, 0x00) + self.assertEqual(c.g, 0x01) + self.assertEqual(c.b, 0x23) + self.assertEqual(c.a, 0x45) + + as_bytes = bytes(c) + self.assertEqual(as_bytes, bytes([0x00, 0x01, 0x23, 0x45])) + self.assertEqual(len(as_bytes), 4) + def test_from_cmy(self): cmy = pygame.Color.from_cmy(0.5, 0.5, 0.5) cmy_tuple = pygame.Color.from_cmy((0.5, 0.5, 0.5)) From e0bf78a1e2a2eae6862b772b81c75286f38773c4 Mon Sep 17 00:00:00 2001 From: Matiiss <83066658+Matiiss@users.noreply.github.com> Date: Sun, 3 Aug 2025 23:54:34 +0300 Subject: [PATCH 2/5] add Color.__bytes__ to the stubs --- buildconfig/stubs/pygame/color.pyi | 1 + 1 file changed, 1 insertion(+) diff --git a/buildconfig/stubs/pygame/color.pyi b/buildconfig/stubs/pygame/color.pyi index 10980f347c..fc2fb489fb 100644 --- a/buildconfig/stubs/pygame/color.pyi +++ b/buildconfig/stubs/pygame/color.pyi @@ -42,6 +42,7 @@ class Color(Collection[int]): def __mod__(self, other: Color) -> Color: ... def __int__(self) -> int: ... def __float__(self) -> float: ... + def __bytes__(self) -> bytes: ... def __len__(self) -> int: ... def __index__(self) -> int: ... def __invert__(self) -> Color: ... From a6acbe6b6b8ac4641b8208b34c668a5aec9be385 Mon Sep 17 00:00:00 2001 From: Matiiss <83066658+Matiiss@users.noreply.github.com> Date: Thu, 7 Aug 2025 02:15:19 +0300 Subject: [PATCH 3/5] Update src_c/color.c Co-authored-by: Ankith --- src_c/color.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src_c/color.c b/src_c/color.c index f157956c46..7c86ac7253 100644 --- a/src_c/color.c +++ b/src_c/color.c @@ -1764,8 +1764,7 @@ _color_float(pgColorObject *color) static PyObject * _color_bytes(pgColorObject *color) { - return PyBytes_FromFormat("%c%c%c%c", color->data[0], color->data[1], - color->data[2], color->data[3]); + return PyBytes_FromStringAndSize((char *)color->data, 4); } /* Sequence protocol methods */ From ae96a239c01952ac635969db35b83809240de13f Mon Sep 17 00:00:00 2001 From: Matiiss <83066658+Matiiss@users.noreply.github.com> Date: Thu, 7 Aug 2025 02:33:40 +0300 Subject: [PATCH 4/5] add comment clarifying why the magic dunder is defined as part of methods (as opposed to through a special struct slot) --- src_c/color.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src_c/color.c b/src_c/color.c index 7c86ac7253..83908ebc90 100644 --- a/src_c/color.c +++ b/src_c/color.c @@ -259,8 +259,17 @@ static PyMethodDef _color_methods[] = { {"premul_alpha", (PyCFunction)_premul_alpha, METH_NOARGS, DOC_COLOR_PREMULALPHA}, {"update", (PyCFunction)_color_update, METH_FASTCALL, DOC_COLOR_UPDATE}, + + /** + * While object.__bytes__(self) is listed in the Data Model reference (see: + * https://docs.python.org/3/reference/datamodel.html#object.__bytes__) it + * does not appear to have a PyTypeObject struct analog (see: + * https://docs.python.org/3/c-api/typeobj.html), so we declare it for the + * type as any other custom method. + */ {"__bytes__", (PyCFunction)_color_bytes, METH_NOARGS, "Get a byte representation of the color"}, + {NULL, NULL, 0, NULL}}; /** From 68e8f05c64d6b7b4cde5534ca2aee329b57d9728 Mon Sep 17 00:00:00 2001 From: Matiiss <83066658+Matiiss@users.noreply.github.com> Date: Thu, 7 Aug 2025 02:53:09 +0300 Subject: [PATCH 5/5] add a versionchanged note --- docs/reST/ref/color.rst | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/reST/ref/color.rst b/docs/reST/ref/color.rst index 05effd8888..114559ebba 100644 --- a/docs/reST/ref/color.rst +++ b/docs/reST/ref/color.rst @@ -92,6 +92,11 @@ :returns: a newly created :class:`Color` object :rtype: Color + .. versionchanged:: 2.5.6 + ``bytes(Color(...))`` (assuming `bytes `_ is + the built-in type) now returns a ``bytes`` object (of length 4) with the RGBA values of the color, + as opposed to :class:`Color` being interpreted as an integer (think ``int(Color(...))``) causing it + to return a ``bytes`` object filled with 0s the length of said integer. .. versionchangedold:: 2.0.0 Support for tuples, lists, and :class:`Color` objects when creating :class:`Color` objects.