color_tools.conversions

Color space conversion functions.

Handles conversions between: - sRGB (0-255) ↔ XYZ ↔ LAB ↔ LCH - sRGB ↔ HSL (various formats) - Gamma correction (sRGB companding)

All conversions use D65 illuminant and proper color science math.

Example

>>> from color_tools import rgb_to_lab, lab_to_lch, rgb_to_hsl
>>>
>>> # Convert vibrant orange from web color
>>> orange_rgb = (255, 128, 0)
>>>
>>> # To LAB for perceptual analysis
>>> lab = rgb_to_lab(orange_rgb)
>>> print(f"LAB: L={lab[0]:.1f} a={lab[1]:.1f} b={lab[2]:.1f}")
LAB: L=67.8 a=43.4 b=78.0
>>>
>>> # To LCH for hue/chroma work
>>> lch = lab_to_lch(lab)
>>> print(f"LCH: L={lch[0]:.1f} C={lch[1]:.1f} H={lch[2]:.1f}°")
LCH: L=67.8 C=88.6 H=60.9°
>>>
>>> # To HSL for web design
>>> hsl = rgb_to_hsl(orange_rgb)
>>> print(f"HSL: {hsl[0]:.0f}° {hsl[1]:.0f}% {hsl[2]:.0f}%")
HSL: 30° 100% 50%
color_tools.conversions.hex_to_rgb(hex_code)[source]

Converts a hex color string to an RGB tuple.

Parameters:

hex_code (str) – Hex color string in the format “#RGB”, “RGB”, “#RRGGBB” or “RRGGBB”. 3-character codes are expanded (e.g., “#24c” -> “#2244cc”).

Returns:

Tuple of (R, G, B) where each component is 0-255. None if the hex code is invalid.

Return type:

rgb

color_tools.conversions.rgb_to_hex(rgb)[source]

Converts an RGB tuple to a hex color string.

Parameters:

rgb (Tuple[int, int, int]) – Tuple of (R, G, B) where each component is 0-255.

Return type:

str

Returns:

Hex color string in the format “#RRGGBB”.

color_tools.conversions.rgb_to_xyz(rgb)[source]

Convert sRGB (0-255) to CIE XYZ using D65 illuminant.

XYZ is a device-independent color space that represents how the human eye responds to light. It’s the bridge between RGB and LAB.

Return type:

Tuple[float, float, float]

Parameters:

rgb (Tuple[int, int, int])

color_tools.conversions.xyz_to_lab(xyz)[source]

Convert CIE XYZ to L*a*b* using D65 illuminant.

LAB is perceptually uniform - equal distances in LAB space correspond to roughly equal perceived color differences. Perfect for color matching!

  • L*: Lightness (0=black, 100=white)

  • a*: Green←→Red axis

  • b*: Blue←→Yellow axis

Return type:

Tuple[float, float, float]

Parameters:

xyz (Tuple[float, float, float])

color_tools.conversions.rgb_to_lab(rgb)[source]

Convert sRGB (0-255) to CIE L*a*b*.

This is the main conversion you’ll use for color matching! Goes RGB → XYZ → LAB in one shot.

Return type:

Tuple[float, float, float]

Parameters:

rgb (Tuple[int, int, int])

color_tools.conversions.lab_to_xyz(lab)[source]

Convert CIE L*a*b* to XYZ using D65 illuminant.

Reverses the LAB → XYZ transformation.

Return type:

Tuple[float, float, float]

Parameters:

lab (Tuple[float, float, float])

color_tools.conversions.xyz_to_rgb(xyz, clamp=True)[source]

Convert CIE XYZ to sRGB (0-255).

Parameters:
  • xyz (Tuple[float, float, float]) – XYZ color tuple

  • clamp (bool) – If True, clamp out-of-gamut values to 0-255. If False, may return values outside valid range (useful for gamut checking).

Return type:

Tuple[int, int, int]

Returns:

RGB tuple (0-255)

color_tools.conversions.lab_to_rgb(lab, clamp=True)[source]

Convert CIE L*a*b* to sRGB (0-255).

Parameters:
  • lab (Tuple[float, float, float]) – L*a*b* color tuple

  • clamp (bool) – If True, clamp out-of-gamut colors to valid RGB range

Return type:

Tuple[int, int, int]

Returns:

RGB tuple (0-255)

color_tools.conversions.lab_to_lch(lab)[source]

Convert L*a*b* to L*C*h° (cylindrical LAB).

LCH is more intuitive than LAB for certain operations: - L* (Lightness): Same as LAB, 0-100 - C* (Chroma): Color intensity/saturation, sqrt(a² + b²) - h° (Hue): Hue angle in degrees, 0-360

Return type:

Tuple[float, float, float]

Returns:

(L*, C*, h°) tuple

Parameters:

lab (Tuple[float, float, float])

color_tools.conversions.lch_to_lab(lch)[source]

Convert L*C*h° to L*a*b*.

Parameters:

lch (Tuple[float, float, float]) – (L*, C*, h°) tuple

Return type:

Tuple[float, float, float]

Returns:

(L*, a*, b*) tuple

color_tools.conversions.lch_to_rgb(lch, clamp=True)[source]

Convert L*C*h° directly to sRGB (0-255).

Return type:

Tuple[int, int, int]

Parameters:
color_tools.conversions.rgb_to_lch(rgb)[source]

Convert sRGB (0-255) directly to L*C*h°.

Return type:

Tuple[float, float, float]

Parameters:

rgb (Tuple[int, int, int])

color_tools.conversions.rgb_to_hsl(rgb)[source]

Convert RGB (0-255) to HSL (H: 0-360, S: 0-100, L: 0-100).

This is the standard HSL representation: - H: Hue in degrees (0° = red, 120° = green, 240° = blue) - S: Saturation as percentage (0% = gray, 100% = pure color) - L: Lightness as percentage (0% = black, 50% = pure color, 100% = white)

Return type:

Tuple[float, float, float]

Parameters:

rgb (Tuple[int, int, int])

color_tools.conversions.rgb_to_winhsl240(rgb)[source]

Convert RGB (0-255) to winHSL240 — the Windows OS variant.

Return type:

Tuple[int, int, int]

Parameters:

rgb (Tuple[int, int, int])

Used by Windows applications including Paint, WordPad, and Win32 GDI APIs. Components are scaled to:

  • H: 0–239 (240 = 0° again, so the valid range stops at 239)

  • S: 0–240

  • L: 0–240

This is subtly different from the range you might expect: hue stops at 239, not 240, because 240 wraps back to 0° (red) and would be a duplicate.

color_tools.conversions.rgb_to_winhsl255(rgb)[source]

Convert RGB (0-255) to winHSL255 — the Microsoft Office variant.

Return type:

Tuple[int, int, int]

Parameters:

rgb (Tuple[int, int, int])

Used by Microsoft Office applications (Word, Excel, PowerPoint colour pickers). Components are scaled to:

  • H: 0–254 (255 = 0° again, so the valid range stops at 254)

  • S: 0–255

  • L: 0–255

Like winHSL240, the hue ceiling is one less than the scale factor because the maximum would alias back to 0° (red).

color_tools.conversions.rgb_to_winhsl(rgb)

Convert RGB (0-255) to winHSL240 — the Windows OS variant.

Return type:

Tuple[int, int, int]

Parameters:

rgb (Tuple[int, int, int])

Used by Windows applications including Paint, WordPad, and Win32 GDI APIs. Components are scaled to:

  • H: 0–239 (240 = 0° again, so the valid range stops at 239)

  • S: 0–240

  • L: 0–240

This is subtly different from the range you might expect: hue stops at 239, not 240, because 240 wraps back to 0° (red) and would be a duplicate.

color_tools.conversions.hsl_to_rgb(hsl)[source]

Convert HSL (H: 0-360, S: 0-100, L: 0-100) to RGB (0-255).

This is the inverse of rgb_to_hsl(), converting from the standard HSL representation back to 8-bit RGB values.

Parameters:

hsl (Tuple[float, float, float]) – HSL tuple (Hue 0-360°, Saturation 0-100%, Lightness 0-100%)

Return type:

Tuple[int, int, int]

Returns:

RGB tuple (0-255 for each component)

color_tools.conversions.rgb_to_cmy(rgb)[source]

Convert RGB (0-255) to CMY (C: 0-100, M: 0-100, Y: 0-100).

CMY is the simple subtractive complement of RGB. Each channel is the percentage of ink needed to absorb the corresponding RGB primary:

  • C = 100 × (1 - R/255) (cyan absorbs red)

  • M = 100 × (1 - G/255) (magenta absorbs green)

  • Y = 100 × (1 - B/255) (yellow absorbs blue)

Unlike CMYK, CMY has no black channel; pure black requires C=M=Y=100. CMY is useful for simple subtractive-model analysis and as the intermediate step when computing CMYK.

Parameters:

rgb (Tuple[int, int, int]) – RGB tuple (0-255 for each component)

Return type:

Tuple[float, float, float]

Returns:

CMY tuple (Cyan 0-100%, Magenta 0-100%, Yellow 0-100%)

Example

>>> rgb_to_cmy((255, 0, 0))       # pure red
(0.0, 100.0, 100.0)
>>> rgb_to_cmy((0, 0, 0))         # black
(100.0, 100.0, 100.0)
>>> rgb_to_cmy((255, 255, 255))   # white
(0.0, 0.0, 0.0)
color_tools.conversions.cmy_to_rgb(cmy)[source]

Convert CMY (C: 0-100, M: 0-100, Y: 0-100) to RGB (0-255).

Inverts the CMY model: each RGB channel is the fraction of light not absorbed by the corresponding ink:

  • R = 255 × (1 - C/100)

  • G = 255 × (1 - M/100)

  • B = 255 × (1 - Y/100)

Parameters:

cmy (Tuple[float, float, float]) – CMY tuple (Cyan 0-100%, Magenta 0-100%, Yellow 0-100%)

Return type:

Tuple[int, int, int]

Returns:

RGB tuple (0-255 for each component)

Example

>>> cmy_to_rgb((0.0, 100.0, 100.0))   # cyan=0, full magenta+yellow → red
(255, 0, 0)
>>> cmy_to_rgb((100.0, 100.0, 100.0)) # full ink → black
(0, 0, 0)
>>> cmy_to_rgb((0.0, 0.0, 0.0))       # no ink → white
(255, 255, 255)
color_tools.conversions.rgb_to_cmyk(rgb)[source]

Convert RGB (0-255) to CMYK (C: 0-100, M: 0-100, Y: 0-100, K: 0-100).

CMYK is the standard four-channel subtractive color model used in print. The black (K) channel is extracted from the CMY values so that dark colors use less ink overall and produce a richer, more accurate black:

  • K = 100 × (1 - max(R, G, B) / 255)

  • C = 100 × (1 - R/255 - K/100) / (1 - K/100) [0 if K = 100]

  • M = 100 × (1 - G/255 - K/100) / (1 - K/100)

  • Y = 100 × (1 - B/255 - K/100) / (1 - K/100)

Pure black (0, 0, 0) maps to C=0, M=0, Y=0, K=100 rather than C=100, M=100, Y=100, K=0, which is the key advantage over plain CMY.

Parameters:

rgb (Tuple[int, int, int]) – RGB tuple (0-255 for each component)

Return type:

Tuple[float, float, float, float]

Returns:

CMYK tuple (Cyan 0-100%, Magenta 0-100%, Yellow 0-100%, Key/Black 0-100%)

Example

>>> rgb_to_cmyk((255, 0, 0))       # pure red
(0.0, 100.0, 100.0, 0.0)
>>> rgb_to_cmyk((0, 0, 0))         # black → K=100, C=M=Y=0
(0.0, 0.0, 0.0, 100.0)
>>> rgb_to_cmyk((255, 255, 255))   # white → all zero
(0.0, 0.0, 0.0, 0.0)
>>> rgb_to_cmyk((128, 64, 192))
(33.3333, 66.6667, 0.0, 24.7059)
color_tools.conversions.cmyk_to_rgb(cmyk)[source]

Convert CMYK (C: 0-100, M: 0-100, Y: 0-100, K: 0-100) to RGB (0-255).

Inverts the CMYK model. The K channel reduces the effective lightness before the CMY channels are applied:

  • R = 255 × (1 - C/100) × (1 - K/100)

  • G = 255 × (1 - M/100) × (1 - K/100)

  • B = 255 × (1 - Y/100) × (1 - K/100)

Parameters:

cmyk (Tuple[float, float, float, float]) – CMYK tuple (Cyan 0-100%, Magenta 0-100%, Yellow 0-100%, Key/Black 0-100%)

Return type:

Tuple[int, int, int]

Returns:

RGB tuple (0-255 for each component)

Example

>>> cmyk_to_rgb((0.0, 100.0, 100.0, 0.0))   # red
(255, 0, 0)
>>> cmyk_to_rgb((0.0, 0.0, 0.0, 100.0))     # black via K
(0, 0, 0)
>>> cmyk_to_rgb((0.0, 0.0, 0.0, 0.0))       # white
(255, 255, 255)