Python colorsys: RGB, HSV, and HLS

Published on: August 9, 2026
Reading time: 5 minutes
Color wheel representing RGB, HSV, and HLS conversions with Python colorsys

User interfaces, charts, imaging utilities, and design systems often need to transform a color between different representations. Python colorsys provides bidirectional conversions between RGB and HSV, HLS, and YIQ using only the standard library.

The module is small, but its scale matters: most components are floating-point values between zero and one. It also does not manage ICC profiles, gamma, Lab spaces, alpha channels, or perceptual color differences. This guide explains normalization, palette generation, hue rotation, saturation and brightness changes, validation, testing, and professional color-management limits.

It complements our guides to Decimal, fractions, statistics, filecmp, and mimetypes.

Available color models

The module converts RGB to three coordinate systems:

  • HSV: hue, saturation, and value;
  • HLS: hue, lightness, and saturation;
  • YIQ: luminance plus two chrominance components.

Inverse functions convert each model back to RGB.

Normalized component scale

RGB, HSV, and HLS components normally range from zero to one. This differs from common 8-bit RGB, whose components range from zero to 255.

def rgb_255_to_unit(r, g, b):
    return r / 255, g / 255, b / 255


def rgb_unit_to_255(r, g, b):
    return tuple(round(c * 255) for c in (r, g, b))

Normalize values at the input boundary and convert back to integers only at the output boundary.

RGB to HSV

import colorsys

rgb = rgb_255_to_unit(51, 102, 102)
h, s, v = colorsys.rgb_to_hsv(*rgb)
print(h, s, v)

Hue represents a position on a color circle, saturation represents color intensity, and value is based on the largest RGB component.

HSV to RGB

r, g, b = colorsys.hsv_to_rgb(h, s, v)
print(rgb_unit_to_255(r, g, b))

A round trip can produce tiny differences because of floating-point representation and later rounding to 8-bit integers.

Hue is circular

Hue wraps around: zero and one represent the same direction. Use modulo arithmetic for rotations.

new_h = (h + 30 / 360) % 1.0

Adding 30 degrees means adding 30/360 in the normalized scale.

Generating a hue palette

def palette(start_h, count):
    colors = []
    for index in range(count):
        h = (start_h + index / count) % 1.0
        colors.append(colorsys.hsv_to_rgb(h, 0.7, 0.9))
    return colors

This distributes hues evenly, but equal angular spacing does not guarantee equal perceived difference, text contrast, or accessibility.

Changing saturation

def change_saturation(rgb, factor):
    h, s, v = colorsys.rgb_to_hsv(*rgb)
    s = min(1.0, max(0.0, s * factor))
    return colorsys.hsv_to_rgb(h, s, v)

A factor of zero produces gray. Larger factors increase chroma until the normalized limit. Validate whether clipping is acceptable for the application.

Changing value

def change_value(rgb, delta):
    h, s, v = colorsys.rgb_to_hsv(*rgb)
    v = min(1.0, max(0.0, v + delta))
    return colorsys.hsv_to_rgb(h, s, v)

HSV value is not perceptual luminance. Increasing it does not create the same perceived brightness change for every color.

RGB to HLS

h, l, s = colorsys.rgb_to_hls(*rgb)

Notice the order: the function returns H, L, S. Many web tools use the acronym HSL and show hue, saturation, lightness. Mixing the component order creates incorrect colors.

Changing lightness

def lighten_hls(rgb, amount):
    h, l, s = colorsys.rgb_to_hls(*rgb)
    l = min(1.0, l + amount)
    return colorsys.hls_to_rgb(h, l, s)

HLS can be convenient for light and dark variants, but it is not perceptually uniform either.

HSV versus HLS

HSV uses value while HLS uses lightness. The models reorganize RGB differently. A saturation of 100 percent in HLS does not have exactly the same visual meaning as 100 percent in HSV.

Choose according to the operation: HSV is common for pickers and intensity controls, while HLS may feel more intuitive for light and dark variants.

The YIQ model

YIQ was used in NTSC television. Y represents approximate luminance, while I and Q carry chrominance information.

y, i, q = colorsys.rgb_to_yiq(*rgb)
r, g, b = colorsys.yiq_to_rgb(y, i, q)

Y stays between zero and one, but I and Q can be positive or negative. Do not apply a zero-to-one clamp to those two components.

Hexadecimal colors

def hex_to_rgb(color):
    value = color.lstrip("#")
    if len(value) != 6:
        raise ValueError("use #RRGGBB")
    return tuple(int(value[i:i+2], 16) / 255 for i in (0, 2, 4))


def rgb_to_hex(rgb):
    r, g, b = rgb_unit_to_255(*rgb)
    return f"#{r:02X}{g:02X}{b:02X}"

Validate the accepted format and decide explicitly whether abbreviated notation or alpha components are allowed.

Safe clamping

def clamp(value, minimum=0.0, maximum=1.0):
    return min(maximum, max(minimum, value))

Clamping is appropriate after controlled arithmetic. It should not silently hide malformed API input when an exception would be more useful.

Floating-point precision

Values such as 0.30000000000000004 are normal in binary floating-point. Compare with tolerance.

import math

assert math.isclose(restored, original, abs_tol=1e-9)

Keep floats throughout the pipeline and round once when producing 8-bit output.

Achromatic colors

When saturation is zero, hue has no visible meaning. The conversion still returns a numeric value, commonly zero.

A color picker that remembers user intent may need to store the previous hue separately so it reappears when saturation increases.

Alpha is not supported

colorsys works with three components. Preserve alpha outside the conversion.

r, g, b, a = rgba
h, s, v = colorsys.rgb_to_hsv(r, g, b)
# keep a unchanged

Gamma and RGB spaces

Typical display values are encoded in sRGB with a transfer curve. Colorsys applies formulas directly to the numbers supplied. It does not linearize sRGB or convert ICC profiles.

For physical blending, lighting, print production, and scientific imaging, use a library that handles gamma and color management.

Contrast and accessibility

Different HSV hues do not guarantee adequate text contrast. Calculate contrast using accessibility formulas and test color-vision deficiencies, light themes, and dark themes.

Do not communicate state through hue alone. Combine color with text, icons, shape, or patterns.

Complementary color

def complementary(rgb):
    h, s, v = colorsys.rgb_to_hsv(*rgb)
    return colorsys.hsv_to_rgb((h + 0.5) % 1.0, s, v)

The mathematically opposite hue may not be the best design choice. Treat it as a starting point and validate contrast.

Analogous colors

def analogous(rgb, degrees=30):
    h, s, v = colorsys.rgb_to_hsv(*rgb)
    offset = degrees / 360
    return [
        colorsys.hsv_to_rgb((h - offset) % 1, s, v),
        rgb,
        colorsys.hsv_to_rgb((h + offset) % 1, s, v),
    ]

Processing many colors

The module performs scalar conversions. Python loops can be slow for millions of pixels. Vectorized array or imaging libraries are better for full images.

For themes, palettes, configuration, and small graphics tools, the standard-library simplicity is valuable.

Round-trip testing

def test_hsv_round_trip():
    original = (0.2, 0.4, 0.7)
    hsv = colorsys.rgb_to_hsv(*original)
    restored = colorsys.hsv_to_rgb(*hsv)
    for a, b in zip(original, restored):
        assert math.isclose(a, b, abs_tol=1e-9)

Test black, white, grays, primary colors, boundary values, and values close to zero and one.

Input validation

def validate_rgb(rgb):
    if len(rgb) != 3:
        raise ValueError("RGB requires three components")
    if not all(0.0 <= c <= 1.0 for c in rgb):
        raise ValueError("component outside range")

Reject NaN and infinity with math.isfinite(), because those values can propagate through an entire palette.

Common mistakes

  • Passing 0-to-255 components without normalization.
  • Confusing HLS component order with an HSL API.
  • Clamping YIQ I and Q to zero through one.
  • Rounding at every stage.
  • Discarding alpha accidentally.
  • Using HSV as a perceptual brightness model.
  • Ignoring profiles and gamma in professional workflows.
  • Relying on hue alone for accessibility.

Best practices

  • Normalize at application boundaries.
  • Keep floating-point values during calculations.
  • Wrap hue with modulo.
  • Validate finiteness and ranges.
  • Preserve alpha separately.
  • Measure contrast independently.
  • Use vectorized libraries for large images.
  • Document the model and scale of every function.

Conclusion

Python colorsys provides straightforward conversions between RGB, HSV, HLS, and YIQ. It is ideal for palettes, themes, small graphical tools, and configuration transformations.

Use it with a clear understanding of its limits: values are normalized, the models are not perceptually uniform, and there is no profile, gamma, or alpha management. Consult the official colorsys documentation and the W3C contrast guidance for more robust interface design.

Share:

Facebook
WhatsApp
Twitter
LinkedIn

Article content

    Related articles

    Configuration icon representing plist files with Python plistlib
    Libraries and Modules
    Foto de perfil de Leandro Hirt da Academify

    Python plistlib: Apple Plist Files

    Learn Python plistlib to read and write XML and binary plist files, validate data, handle dates, bytes, and UIDs safely.

    Ler mais

    Tempo de leitura: 6 minutos
    08/08/2026
    Digital padlock representing host credentials with Python netrc
    Libraries and Modules
    Foto de perfil de Leandro Hirt da Academify

    Python netrc: Credentials by Host

    Learn Python netrc to read credentials by host, validate permissions, handle parse errors, and integrate network clients safely.

    Ler mais

    Tempo de leitura: 6 minutos
    08/08/2026
    Digital message representing quoted-printable encoding with Python quopri
    Libraries and Modules
    Foto de perfil de Leandro Hirt da Academify

    Python quopri: Quoted-Printable

    Learn Python quopri to encode and decode quoted-printable data in email, files, and MIME integrations safely.

    Ler mais

    Tempo de leitura: 6 minutos
    08/08/2026
    Digital file icon representing MIME types with Python mimetypes
    Libraries and Modules
    Foto de perfil de Leandro Hirt da Academify

    Python mimetypes: MIME Types

    Learn Python mimetypes to identify MIME types, extensions, and encodings safely in uploads, downloads, email, and web APIs.

    Ler mais

    Tempo de leitura: 6 minutos
    08/08/2026
    Binary search and sorted lists with Python bisect
    Libraries and Modules
    Foto de perfil de Leandro Hirt da Academify

    Python bisect: Search Sorted Lists

    Learn Python bisect for binary search, ordered insertion, duplicate ranges, thresholds, and safe sorted-list design.

    Ler mais

    Tempo de leitura: 4 minutos
    08/08/2026
    Code and packaged files with Python importlib.resources
    Libraries and Modules
    Foto de perfil de Leandro Hirt da Academify

    Python importlib.resources: Practical Guide

    Learn Python importlib.resources to access packaged files safely across wheels, installed applications, and custom import loaders.

    Ler mais

    Tempo de leitura: 5 minutos
    07/08/2026