Skip to content

Add Color.__bytes__ #3547

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions buildconfig/stubs/pygame/color.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -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: ...
Expand Down
5 changes: 5 additions & 0 deletions docs/reST/ref/color.rst
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,11 @@
:returns: a newly created :class:`Color` object
:rtype: Color

.. versionchanged:: 2.5.6
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I still think versionadded is more appropriate because versionchanged indicates that we changed behavior on our end, instead of overriding default python behavior by adding a new method.

``bytes(Color(...))`` (assuming `bytes <https://docs.python.org/3/library/stdtypes.html#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.
Expand Down
23 changes: 23 additions & 0 deletions src_c/color.c
Original file line number Diff line number Diff line change
Expand Up @@ -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 *);
Expand Down Expand Up @@ -256,6 +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.
*/
Comment on lines +263 to +269
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure this giant comment is strictly necessary in the middle of this large array definition

{"__bytes__", (PyCFunction)_color_bytes, METH_NOARGS,
"Get a byte representation of the color"},

{NULL, NULL, 0, NULL}};

/**
Expand Down Expand Up @@ -1753,6 +1767,15 @@ _color_float(pgColorObject *color)
return PyFloat_FromDouble((double)tmp);
}

/**
* bytes(color)
*/
static PyObject *
_color_bytes(pgColorObject *color)
{
return PyBytes_FromStringAndSize((char *)color->data, 4);
}

/* Sequence protocol methods */

/**
Expand Down
11 changes: 11 additions & 0 deletions test/color_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
Loading