tuple(bytes.fromhex(color.lstrip('#'))) turns '#ff5733' into (255, 87, 51). bytes.fromhex reads each pair of hex digits as one byte, and iterating over a bytes object gives integers. Reading each pair with int(…, 16) gets the same result:
color = '#ff5733'
print(tuple(bytes.fromhex(color.lstrip('#'))))
print(tuple(int(color[i:i + 2], 16) for i in (1, 3, 5)))
(255, 87, 51)
(255, 87, 51)
The tool below shows the same rgb(255 87 51). The variable is color rather than hex so that it doesn’t hide the built-in hex().
Short codes and alpha
CSS also allows 3 digits, where each one doubles (#f53 is #ff5533), and 4 or 8 digits, where the last one or two are alpha. Neither one-liner handles a short code:
bytes.fromhex('f53') ValueError: fromhex() arg must contain an even number of hexadecimal digits
int('#f53'[5:7], 16) ValueError: invalid literal for int() with base 16: ''
Expand it first. This needs Python 3.9 for removeprefix:
def hex_to_rgb(value):
h = value.removeprefix('#')
if len(h) in (3, 4):
h = ''.join(c * 2 for c in h)
if len(h) not in (6, 8):
raise ValueError(f'not a hex color: {value!r}')
return tuple(bytes.fromhex(h))
print(hex_to_rgb('#ff5733'), hex_to_rgb('#f53'), hex_to_rgb('#ff573380'))
(255, 87, 51) (255, 85, 51) (255, 87, 51, 128)
The alpha comes back as 0 to 255; CSS writes it from 0 to 1, so the tool reads #ff573380 as rgb(255 87 51 / 0.502), 128 divided by 255. A stray letter fails loudly, which is what you want: '#ff5g33' raises non-hexadecimal number found in fromhex() arg at position 3.
Going back is a format spec: with r, g, b = 255, 87, 51, f'#{r:02x}{g:02x}{b:02x}' is '#ff5733' and f'rgb({r} {g} {b})' is 'rgb(255 87 51)'.
Pillow and matplotlib
If either is installed already, it parses every CSS hex form, and names too:
from PIL import ImageColor
ImageColor.getrgb('#ff5733') # (255, 87, 51)
ImageColor.getrgb('#f53') # (255, 85, 51)
ImageColor.getrgb('#ff573380') # (255, 87, 51, 128)
ImageColor.getrgb('tomato') # (255, 99, 71)
import matplotlib.colors as mcolors
mcolors.to_rgb('#ff5733') # (1.0, 0.3411764705882353, 0.2)
mcolors.to_rgba('#ff573380') # (1.0, 0.3411764705882353, 0.2, 0.5019607843137255)
matplotlib works in floats from 0 to 1, so tuple(round(c * 255) for c in mcolors.to_rgb('#ff5733')) gets back to (255, 87, 51). Both insist on the #: Pillow raises ValueError: unknown color specifier: 'ff5733' and matplotlib Invalid RGBA argument: 'ff5733'. That’s Pillow 12.3.0 and matplotlib 3.11.2 on Python 3.14.