Hex to RGB in Python

tuple(bytes.fromhex(color.lstrip('#')))

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.

Try it in the Color Converter & Contrast Checker

Every format

FormatValueCopy
HEX#ff5733
RGBrgb(255 87 51)
HSLhsl(10.6 100% 60%)
OKLCHoklch(68.04% 0.21 33.69)

Contrast

The quick brown fox jumps over the lazy dog.

3.15:1

  • FailAA, normal text needs 4.5:1
  • PassAA, large text needs 3:1
  • FailAAA, normal text needs 7:1
  • FailAAA, large text needs 4.5:1

Runs in your browser. Nothing you enter is sent anywhere.