Hex.pm Hex Docs CI License

A comprehensive Elixir library for representing, converting, and analysing color, with no runtime dependencies.

Color covers the CIE color spaces, the modern perceptual spaces (Oklab, JzAzBz, ICtCp, IPT, CAM16-UCS), the standard RGB working spaces (sRGB, Adobe RGB, Display P3, Rec. 709, Rec. 2020, ProPhoto, …), the non-linear UI spaces (HSL, HSV, HSLuv, HPLuv), CMYK, YCbCr (BT.601 / 709 / 2020) and the spectral pipeline (CIE 1931 and 1964 standard observers, illuminants, reflectance integration). Conversions are based on the canonical formulas published by Bruce Lindbloom.

Beyond conversions, the library provides chromatic adaptation (Bradford, von Kries, CAT02, …), four ΔE color-difference metrics (CIE76, CIE94, CIEDE2000, CMC l:c), WCAG and APCA contrast, gamut checking and CSS Color 4 perceptual gamut mapping, color mixing and gradient generation, harmonies, color temperature, blend modes, CSS Color 4 / 5 parsing and serialisation, an ICC matrix-profile reader, and a ~COLOR sigil.

Features

  • 20+ color space structs including Color.SRGB, Color.AdobeRGB, Color.RGB (linear, in any of 24 named working spaces), Color.Lab, Color.LCHab, Color.Luv, Color.LCHuv, Color.XYZ, Color.XyY, Color.Oklab, Color.Oklch, Color.HSLuv, Color.HPLuv, Color.HSL, Color.HSV, Color.CMYK, Color.YCbCr, Color.JzAzBz, Color.ICtCp, Color.IPT, Color.CAM16UCS.

  • Top-level conversion API. Color.new/2 and Color.convert/2,3,4 accept structs, hex strings, CSS named colors, atoms, or bare lists of numbers (with strict per-space validation) and convert between any pair of supported spaces. Color.convert_many/2,3,4 is the batch equivalent. Alpha is preserved across every path.

  • Chromatic adaptation with six methods (:bradford, :xyz_scaling, :von_kries, :sharp, :cmccat2000, :cat02). Color.convert/3,4 auto-adapts the source illuminant when the target requires a fixed reference white.

  • ICC rendering intents wired into Color.convert/3,4: :relative_colorimetric (default), :absolute_colorimetric, :perceptual, :saturation. Optional black-point compensation via bpc: true.

  • ICC matrix-profile reader (Color.ICC.Profile) for ICC v2 / v4 RGB→XYZ profiles, with curv LUT and para parametric tone response curves. Loads profiles like sRGB IEC61966-2.1.icc, Display P3.icc, AdobeRGB1998.icc, and most camera and scanner profiles.

  • Color difference (ΔE). CIE76, CIE94, CIEDE2000 (verified against the Sharma 2005 test data), and CMC l:c.

  • Contrast. WCAG 2.x relative luminance and contrast ratio, APCA W3 0.1.9 (L_c), and pick_contrasting/2 for accessibility helpers.

  • Mixing and gradients. Color.Mix.mix/4 interpolates in any supported space (default Oklab) with CSS Color 4 hue-interpolation modes (:shorter, :longer, :increasing, :decreasing). Color.Mix.gradient/4 produces evenly spaced gradients.

  • Gamut checking and mapping. Color.Gamut.in_gamut?/2 and Color.Gamut.to_gamut/3 with the CSS Color 4 Oklch binary-search algorithm or simple RGB clip.

  • Color harmonies. Complementary, analogous, triadic, tetradic, and split-complementary in any cylindrical space (default Oklch).

  • Color temperature. CCT ↔ chromaticity, Planckian locus and CIE daylight locus.

  • CSS Color Module Level 4 / 5. Full parser and serialiser for hex, named colors, rgb()/rgba(), hsl()/hsla(), hwb(), lab(), lch(), oklab(), oklch(), color(srgb|display-p3|rec2020|…), device-cmyk(), color-mix(), relative color syntax, none keyword, and calc() expressions.

  • ~COLOR sigil for compile-time color literals in any supported space.

  • Spectral pipeline. Color.Spectral and Color.Spectral.Tables provide the CIE 1931 2° and CIE 1964 10° standard observer CMFs, the D65 / D50 / A / E illuminant SPDs, emissive and reflective integration to XYZ, and a metamerism helper.

  • Blend modes. All 16 CSS Compositing Level 1 modes (:multiply, :screen, :overlay, :darken, :lighten, :color_dodge, :color_burn, :hard_light, :soft_light, :difference, :exclusion, :hue, :saturation, :color, :luminosity, :normal).

  • Transfer functions. sRGB, gamma 2.2 / 1.8, L*, BT.709, BT.2020, PQ (SMPTE ST 2084), HLG, Adobe RGB γ.

  • Pre-multiplied alpha helpers. Color.premultiply/1 and Color.unpremultiply/1 for callers that need to round-trip pre-multiplied pixel data through the conversion pipeline.

  • Typed errors. Every fallible function returns {:ok, color} or {:error, %SomeError{...}} where the exception struct carries the offending value, the space, the reason, and any other relevant fields. See Color.InvalidColorError, Color.InvalidComponentError, Color.UnknownColorSpaceError, Color.UnknownColorNameError, Color.UnknownWorkingSpaceError, Color.InvalidHexError, Color.ParseError, Color.UnknownBlendModeError, Color.UnknownGamutMethodError, Color.MissingWorkingSpaceError, Color.UnsupportedTargetError, Color.ICC.ParseError.

Supported Elixir and OTP Releases

Color is supported on Elixir 1.17+ and OTP 26+.

Quick Start

Add :color to your mix.exs dependencies:

def deps do
  [
    {:color, "~> 0.1.0"}
  ]
end

Then run mix deps.get.

Building colors

# From a hex string or CSS named color:
{:ok, red}    = Color.new("#ff0000")
{:ok, purple} = Color.new("rebeccapurple")
{:ok, also}   = Color.new(:misty_rose)

# From a list of unit-range floats (assumed sRGB):
{:ok, srgb}   = Color.new([1.0, 0.5, 0.0])

# From a list of 0..255 integers (assumed sRGB):
{:ok, srgb}   = Color.new([255, 128, 0])

# In any supported space:
{:ok, lab}    = Color.new([53.24, 80.09, 67.20], :lab)
{:ok, oklch}  = Color.new([0.7, 0.2, 30.0], :oklch)
{:ok, cmyk}   = Color.new([0.0, 0.5, 1.0, 0.0], :cmyk)

Converting

{:ok, lab}     = Color.convert("#ff0000", Color.Lab)
{:ok, oklch}   = Color.convert([1.0, 0.0, 0.0], Color.Oklch)
{:ok, cmyk}    = Color.convert(:rebecca_purple, Color.CMYK)
{:ok, p3}      = Color.convert(red, Color.RGB, :P3_D65)

# Wide-gamut Display P3 colors gamut-mapped into sRGB
# using the CSS Color 4 Oklch perceptual algorithm:
{:ok, mapped}  = Color.convert(p3, Color.SRGB, intent: :perceptual)

# Batch convert a list:
{:ok, labs}    = Color.convert_many(["red", "green", "blue"], Color.Lab)

Difference and contrast

Color.Distance.delta_e_2000(red, purple)        # CIEDE2000
Color.Contrast.wcag_ratio("white", "#777")      # 4.48
Color.Contrast.wcag_level("black", "white")     # :aaa
Color.Contrast.apca("black", "white")           # 106.04

Mixing, gradients, harmonies

{:ok, mid}    = Color.Mix.mix("red", "blue", 0.5, in: Color.Oklch)
{:ok, ramp}   = Color.Mix.gradient("black", "white", 8)
{:ok, [_a, _b]}    = Color.Harmony.complementary("red")
{:ok, [_a, _b, _c]} = Color.Harmony.triadic("red")

CSS Color 4 / 5

{:ok, c} = Color.CSS.parse("oklch(70% 0.15 180 / 50%)")
Color.CSS.to_css(c)
# => "oklch(70% 0.15 180 / 0.5)"

{:ok, c} = Color.CSS.parse("color-mix(in oklch, red 30%, blue)")
{:ok, c} = Color.CSS.parse("rgb(from oklch(0.7 0.15 180) calc(r * 0.9) g b)")

~COLOR Sigil

import Color.Sigil

~COLOR[#ff0000]            # Color.SRGB
~COLOR[rebeccapurple]      # Color.SRGB via CSS name
~COLOR[1.0, 0.5, 0.0]r     # unit sRGB
~COLOR[255, 128, 0]b       # 0..255 sRGB
~COLOR[53.24, 80.09, 67.2]l   # Color.Lab
~COLOR[0.63, 0.22, 0.13]o     # Color.Oklab

Spectral

d65 = Color.Spectral.illuminant(:D65)
{:ok, white_xyz} = Color.Spectral.to_xyz(d65)
# => Color.XYZ at D65 white point

# Reflectance under an illuminant:
sample = %Color.Spectral{wavelengths: ws, values: rs}
{:ok, xyz} = Color.Spectral.reflectance_to_xyz(sample, :D65)

# Detect a metamer pair:
{:ok, delta_e} = Color.Spectral.metamerism(sample_a, sample_b, :D65, :A)

ICC profiles

{:ok, profile} = Color.ICC.Profile.load("/path/to/Display P3.icc")
profile.description
# => "Display P3"

{x, y, z} = Color.ICC.Profile.to_xyz(profile, {1.0, 0.0, 0.0})
# => XYZ in PCS (D50, 2°)

{r, g, b} = Color.ICC.Profile.from_xyz(profile, {0.9642, 1.0, 0.8249})
# => encoded RGB in the profile's colour space

Documentation

See the module documentation on HexDocs.

Contributing

A mix format pre-commit hook is committed under .githooks/. Enable it once per clone with:

git config core.hooksPath .githooks

The hook formats every staged .ex / .exs file with mix format and re-stages the result, so commits never include unformatted code.

License

Apache 2.0. See LICENSE.md for the full text.