Skip to content

Commit 32b0cb3

Browse files
damusssMatiiss
andauthored
Add Color.hex (#3379)
* Add Color.hex * Add DEL_ATTR_NOT_SUPPORTED_CHECK * Fix docs and tests * Formatting This little maneuver took 15 hours! on my Android --------- Co-authored-by: Matiiss <[email protected]>
1 parent be3d05a commit 32b0cb3

File tree

5 files changed

+93
-1
lines changed

5 files changed

+93
-1
lines changed

buildconfig/stubs/pygame/color.pyi

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ class Color(Collection[int]):
1818
hsla: tuple[float, float, float, float]
1919
i1i2i3: tuple[float, float, float]
2020
normalized: tuple[float, float, float, float]
21+
hex: str
2122
__hash__: ClassVar[None] # type: ignore[assignment]
2223
@property
2324
def __array_struct__(self) -> Any: ...

docs/reST/ref/color.rst

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,23 @@
210210

211211
.. ## Color.normalized ##
212212
213+
.. attribute:: hex
214+
215+
| :sl:`Gets or sets the stringified hexadecimal representation of the Color.`
216+
| :sg:`hex -> str`
217+
218+
The stringified hexadecimal representation of the Color. The hexadecimal string
219+
is formatted as ``"#rrggbbaa"`` where rr, gg, bb, and aa are two digit hex numbers
220+
in the range from 0x00 to 0xff.
221+
222+
Setting this property means changing the color channels in place. Both lowercase
223+
and uppercase letters are allowed, the alpha can be omitted (defaults to 0xff) and
224+
the string can start with either ``#`` or ``0x``.
225+
226+
.. versionadded:: 2.5.4
227+
228+
.. ## Color.hex ##
229+
213230
.. classmethod:: from_cmy
214231

215232
| :sl:`Returns a Color object from a CMY representation`

src_c/color.c

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,10 @@ _color_get_normalized(pgColorObject *, void *);
156156
static int
157157
_color_set_normalized(pgColorObject *, PyObject *, void *);
158158
static PyObject *
159+
_color_get_hex(pgColorObject *, void *);
160+
static int
161+
_color_set_hex(pgColorObject *, PyObject *, void *);
162+
static PyObject *
159163
_color_get_arraystruct(pgColorObject *, void *);
160164

161165
/* Number protocol methods */
@@ -269,6 +273,8 @@ static PyGetSetDef _color_getsets[] = {
269273
NULL},
270274
{"normalized", (getter)_color_get_normalized,
271275
(setter)_color_set_normalized, DOC_COLOR_NORMALIZED, NULL},
276+
{"hex", (getter)_color_get_hex, (setter)_color_set_hex, DOC_COLOR_HEX,
277+
NULL},
272278
{"__array_struct__", (getter)_color_get_arraystruct, NULL,
273279
"array structure interface, read only", NULL},
274280
{NULL, NULL, NULL, NULL, NULL}};
@@ -1543,6 +1549,36 @@ _color_set_normalized(pgColorObject *color, PyObject *value, void *closure)
15431549
return 0;
15441550
}
15451551

1552+
static PyObject *
1553+
_color_get_hex(pgColorObject *color, void *closure)
1554+
{
1555+
return PyUnicode_FromFormat("#%02x%02x%02x%02x", color->data[0],
1556+
color->data[1], color->data[2],
1557+
color->data[3]);
1558+
}
1559+
1560+
static int
1561+
_color_set_hex(pgColorObject *color, PyObject *value, void *closure)
1562+
{
1563+
DEL_ATTR_NOT_SUPPORTED_CHECK("hex", value);
1564+
1565+
if (!PyUnicode_Check(value)) {
1566+
PyErr_SetString(PyExc_TypeError, "hex color must be a string");
1567+
return -1;
1568+
}
1569+
1570+
switch (_hexcolor(value, color->data)) {
1571+
case TRISTATE_FAIL:
1572+
PyErr_SetString(PyExc_ValueError, "invalid hex string");
1573+
return -1;
1574+
case TRISTATE_ERROR:
1575+
return -1; /* forward python error */
1576+
default:
1577+
return 0;
1578+
}
1579+
return 0;
1580+
}
1581+
15461582
static PyObject *
15471583
_color_get_arraystruct(pgColorObject *color, void *closure)
15481584
{

src_c/doc/color_doc.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#define DOC_COLOR_HSLA "hsla -> tuple\nGets or sets the HSLA representation of the Color."
1010
#define DOC_COLOR_I1I2I3 "i1i2i3 -> tuple\nGets or sets the I1I2I3 representation of the Color."
1111
#define DOC_COLOR_NORMALIZED "normalized -> tuple\nGets or sets the normalized representation of the Color."
12+
#define DOC_COLOR_HEX "hex -> str\nGets or sets the stringified hexadecimal representation of the Color."
1213
#define DOC_COLOR_FROMCMY "from_cmy(object, /) -> Color\nfrom_cmy(c, m, y, /) -> Color\nReturns a Color object from a CMY representation"
1314
#define DOC_COLOR_FROMHSVA "from_hsva(object, /) -> Color\nfrom_hsva(h, s, v, a, /) -> Color\nReturns a Color object from an HSVA representation"
1415
#define DOC_COLOR_FROMHSLA "from_hsla(object, /) -> Color\nfrom_hsla(h, s, l, a, /) -> Color\nReturns a Color object from an HSLA representation"

test/color_test.py

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -950,6 +950,37 @@ def test_normalize__all_elements_within_limits(self):
950950
self.assertTrue(0 <= b <= 1)
951951
self.assertTrue(0 <= a <= 1)
952952

953+
def test_hex_property(self):
954+
color = pygame.Color(255, 0, 255, 0)
955+
hex = color.hex
956+
self.assertEqual(hex, "#ff00ff00")
957+
958+
for c in rgba_combos_Color_generator():
959+
col_hex = c.hex
960+
self.assertIsInstance(col_hex, str)
961+
self.assertEqual(len(col_hex), 9)
962+
self.assertEqual(col_hex[0], "#")
963+
for char in col_hex:
964+
self.assertIn(char, "#0123456789abcdef")
965+
self.assertEqual(c, pygame.Color(col_hex))
966+
967+
with self.assertRaises(TypeError):
968+
color.hex = 0xFFFFFFFF
969+
with self.assertRaises(AttributeError):
970+
del color.hex
971+
972+
for value in ["FFFFFFFF", "#FFzzFF00", "0x FFFFFF", "#FF"]:
973+
for v in [value, value.lower()]:
974+
with self.assertRaises(ValueError):
975+
color.hex = v
976+
977+
for value in ["#FFFFFFFF", "#FFFFFF", "0xFFFFFFFF", "0xFFFFFF"]:
978+
for v in [value, value.lower()]:
979+
color.hex = v
980+
self.assertEqual(
981+
(color.r, color.g, color.b, color.a), (255, 255, 255, 255)
982+
)
983+
953984
def test_issue_284(self):
954985
"""PyColor OverflowError on HSVA with hue value of 360
955986
@@ -1007,6 +1038,9 @@ def test_i1i2i3__sanity_testing_converted_should_not_raise(self):
10071038
def test_normalized__sanity_testing_converted_should_not_raise(self):
10081039
self.colorspaces_converted_should_not_raise("normalized")
10091040

1041+
def test_hex__sanity_testing_converted_should_not_raise(self):
1042+
self.colorspaces_converted_should_not_raise("hex")
1043+
10101044
################################################################################
10111045

10121046
def colorspaces_converted_should_equate_bar_rounding(self, prop):
@@ -1021,7 +1055,7 @@ def colorspaces_converted_should_equate_bar_rounding(self, prop):
10211055
self.assertTrue(abs(other.b - c.b) <= 1)
10221056
self.assertTrue(abs(other.g - c.g) <= 1)
10231057
# CMY and I1I2I3 do not care about the alpha
1024-
if not prop in ("cmy", "i1i2i3"):
1058+
if prop not in ("cmy", "i1i2i3"):
10251059
self.assertTrue(abs(other.a - c.a) <= 1)
10261060

10271061
except ValueError:
@@ -1042,6 +1076,9 @@ def test_i1i2i3__sanity_testing_converted_should_equate_bar_rounding(self):
10421076
def test_normalized__sanity_testing_converted_should_equate_bar_rounding(self):
10431077
self.colorspaces_converted_should_equate_bar_rounding("normalized")
10441078

1079+
def test_hex__sanity_testing_converted_should_equate_bar_rounding(self):
1080+
self.colorspaces_converted_should_equate_bar_rounding("hex")
1081+
10451082
def test_colorspaces_deprecated_large_sequence(self):
10461083
c = pygame.Color("black")
10471084
for space in ("hsla", "hsva", "i1i2i3", "cmy", "normalized"):

0 commit comments

Comments
 (0)