Skip to content

Repository files navigation

hexcraft

hexcraft - perceptually uniform OKLCh hue spectrum

The complete color library for Python. Parse, convert, manipulate, mix, measure, and visualize color across 11 color spaces - with zero required dependencies and a single fluent Color API.

PyPIPyPI - Python VersionPyPI - Licenseimage

fromhexcraftimportColorc=Color("oklch(0.7 0.15 250)")
c.hex# '#5e91d8'c.contrast(Color("white")) # 2.83c.lighter(5) # [Color, Color, Color, Color, Color]c.material_palette() # {0..100: Color} Material You tonal scalec.simulate("deuteranopia") # color as seen by red-green color-blind viewers

Table of contents


Installation

pip install hexcraft # core, zero dependencies
pip install 'hexcraft[numpy]'# vectorized array ops + image utilities
pip install 'hexcraft[science]'# full color-science integration (reserved)
pip install 'hexcraft[dev]'# tests, ruff, mypy

Requires Python 3.10 or newer.


Quick start

fromhexcraftimportColor# Parse anything CSS Color 4 supportsc=Color("oklch(0.7 0.15 250)")
# Read in any spacec.hex# '#5e91d8'c.rgb# (94, 145, 216)c.hsl# (213.16, 0.59, 0.61)c.lab# (58.5, -3.5, -38.7)c.oklch# (0.7, 0.15, 250.0)# Manipulate (immutable - every method returns a new Color)c.lighten(0.1)
c.rotate(60)
c.with_alpha(0.5)
# Mix in any spacec.mix(Color("red")) # OKLab by defaultc.mix(Color("red"), 0.3, space="oklch") # shortest-hue interpolation# Accessibilityc.contrast(Color("white")) # WCAG ratioc.accessible_against(Color("white")) # auto-shift to meet 4.5:1# Generate palettesc.tints(5) # 5 steps to whitec.material_palette() # 13-stop Material You scalec.tailwind() # 11-stop Tailwind 50–950c.triadic() # 3 colors 120° apart# CSS outputc.css("hex") # '#5e91d8'c.css("oklch") # 'oklch(0.7 0.15 250)'

Examples

hexcraft showcase: a perceptual OKLCh hue spectrum, a Tailwind 50-950 scale, and the viridis, magma and turbo colormaps

Twelve runnable scripts in examples/ cover the full feature surface, with ANSI 24-bit color output so the gradients, palettes, and accessibility checks are visible in any modern terminal:

python examples/01_quickstart.py
#FileDemonstrates
0101_quickstart.pyParse, convert, manipulate, contrast, mix - the 60-second tour
0202_parsing_anything.pyEvery CSS Color 4 syntax, hex variant, unit, and named color
0303_perceptual_vs_naive_mixing.pySame gradient interpolated in 5 spaces - sRGB muddies, OKLab doesn't
0404_palette_harmonies.pyEvery classic harmony plus tints / shades / tones / monochromatic
0505_design_system.pyMaterial You + Tailwind 50-950 scales from one brand color
0606_accessibility_audit.pyWCAG / APCA pass-fail with auto-suggested fixes
0707_color_blindness.pySimulate + daltonize a palette per CVD type
0808_perceptual_colormaps.pyAll 11 bundled colormaps sampled side-by-side
0909_color_temperature.pyKelvin temperatures from candle flame to deep sky
1010_brand_color_match.pyclosest_from for normalizing input to a brand palette
1111_image_palette.py(numpy)Extract dominant colors from an image via OKLab k-means
1212_theme_generator.pyFull light + dark theme tokens with WCAG-checked text pairings

See examples/README.md for the same table with a viewing-tips section.


Parsing

See it: 02_parsing_anything.py

Color(value) accepts every CSS Color 4 syntax, all 148 CSS named colors, hex with optional alpha, and existing Color objects:

Color("#ff0000")
Color("#f00") # 3-digitColor("#f008") # 4-digit (with alpha)Color("#ff000080") # 8-digit (with alpha)Color("rgb(255, 0, 0)")
Color("rgb(255 0 0)") # space-separated CSS Color 4Color("rgb(100%, 0%, 0%)")
Color("rgba(255, 0, 0, 0.5)")
Color("rgb(255 0 0 / 50%)")
Color("hsl(0, 100%, 50%)")
Color("hsl(120deg 100% 50%)")
Color("hsl(0.333turn 100% 50%)")
Color("hsla(0, 100%, 50%, 0.5)")
Color("hwb(0 0% 0%)")
Color("lab(54 81 70)")
Color("lch(54 100 41)")
Color("oklab(0.628 0.225 0.126)")
Color("oklch(0.628 0.258 29.234)")
Color("color(srgb 1 0 0)")
Color("color(srgb-linear 1 0 0)")
Color("color(display-p3 1 0 0)")
Color("color(xyz 0.412 0.213 0.019)")
Color("rebeccapurple") # any of 148 CSS named colorsColor("transparent") # also valid; alpha = 0Color(other_color) # copy

Hue accepts deg (default), rad, turn, or grad units. none is treated as 0. Errors raise hexcraft.ColorParseError.

fromhexcraftimportparse, ColorParseErrortry:
c=Color("not-a-color")
exceptColorParseErrorase:
print(e) # could not parse color: 'not-a-color'# Direct parser if you need the intermediate representation:parse("oklch(0.7 0.15 250)") # Parsed(space='oklch', components=(0.7, 0.15, 250.0), alpha=1.0)

Color spaces

hexcraft works canonically in linear sRGB and converts to any space on demand. Out-of-gamut and HDR values pass through; gamut mapping is applied at output.

SpacePropertyConstructor
sRGB (gamma)c.rgb, c.srgb, c.hexColor.from_rgb8(255, 0, 0)
Linear sRGBc.linear_rgbColor.from_linear_rgb(1, 0, 0)
HSLc.hslColor.from_hsl(0, 1, 0.5)
HSVc.hsvColor.from_hsv(0, 1, 1)
HWBc.hwbColor.from_hwb(0, 0, 0)
CIE XYZ (D65)c.xyzColor.from_xyz(0.412, 0.213, 0.019)
CIE Labc.labColor.from_lab(54, 81, 70)
CIE LChc.lchColor.from_lch(54, 107, 41)
OKLabc.oklabColor.from_oklab(0.63, 0.22, 0.13)
OKLChc.oklchColor.from_oklch(0.63, 0.26, 29)
Display-P3c.p3Color.from_p3(1, 0, 0)
CMYK (naive)c.cmykColor.from_cmyk(0, 1, 1, 0)
Kelvin (CCT)c.kelvinColor.from_kelvin(2700)

hsl/hsv/hwb use degrees for hue; lab/lch/oklab/oklch use the CIE/OK natural units. Hue is in [0, 360). CMYK is naive algebraic - see CMYK for caveats.


Reading components

c=Color("#3498db")
c.alpha# 1.0c.rgb# (52, 152, 219)c.rgba# (52, 152, 219, 1.0)c.srgb# (0.204, 0.596, 0.859) - gamma-encoded, clampedc.srgb_unclamped# same but allows out-of-gamutc.linear_rgb# (0.034, 0.318, 0.708)c.hsl# (204.1, 0.70, 0.53)c.hsv# (204.1, 0.76, 0.86)c.hwb# (204.1, 0.20, 0.14)c.xyz# (0.222, 0.247, 0.700)c.lab# (56.9, -2.4, -39.0)c.lch# (56.9, 39.0, 266.5)c.oklab# (0.65, -0.06, -0.12)c.oklch# (0.65, 0.13, 242.7)c.p3# (0.252, 0.589, 0.851)c.cmyk# (0.76, 0.31, 0.0, 0.14)c.kelvin# ~10500 (or None for non-blackbody chromaticities)c.luminance# 0.281 - WCAG relative luminancec.name# 'dodgerblue' - closest CSS named colorc.in_gamut() # True/False - within sRGB

linear_rgb and srgb_unclamped may contain values outside [0, 1] to faithfully represent wide-gamut and HDR colors. Use c.to_gamut() to fold back into sRGB.


Manipulation

Every method is immutable and returns a new Color. Operations work in OKLCh by default for perceptual uniformity.

c=Color("#3498db")
c.lighten(0.1) # +0.1 in OKLab Lc.darken(0.1)
c.saturate(0.2) # +chroma in OKLChc.desaturate(0.2)
c.rotate(60) # rotate hue by 60°c.complement() # rotate(180)c.grayscale() # collapse chroma to 0c.invert() # 1 - rgb in linear sRGBc.with_alpha(0.5)

Equality and hashing use linear-sRGB tolerance (≈ 1e-6) and are well-behaved across round-trips:

Color("red") ==Color("#ff0000") ==Color("rgb(255, 0, 0)") # True
{Color("red"), Color("#ff0000")} # set of size 1

Mixing and blending

The same red-to-blue gradient interpolated in sRGB, OKLab and OKLCh; sRGB darkens and muddies through the middle while OKLab and OKLCh hold lightness and chroma

See it: 03_perceptual_vs_naive_mixing.py

mix interpolates two colors in any space; the default is OKLab for smooth, hue-faithful results. Hue spaces (hsl, hsv, hwb, lch, oklch) automatically take the shortest-arc path.

fromhexcraftimportColor, mix, blendmix(Color("red"), Color("blue")) # OKLab midpointmix(Color("red"), Color("blue"), 0.3) # 30% toward bluemix(Color("red"), Color("blue"), 0.5, space="oklch") # different perceptual pathmix(Color("red"), Color("blue"), 0.5, space="srgb") # naive sRGB lerp# Method form (always defaults to OKLab):Color("red").mix(Color("blue"), 0.5)

blend does Porter-Duff "over" composite in linear sRGB, the gamma-correct way to alpha-composite:

fg=Color("blue").with_alpha(0.5)
bg=Color("red")
blend(bg, fg) # red showing through 50% blue

Palettes and harmonies

Complementary, analogous, triadic, tetradic and split-complementary harmonies generated from #3498db

See it: 04_palette_harmonies.py

Module-level functions return a list of Color. Equivalent methods on Color return the same lists:

fromhexcraftimport (
complementary, analogous, triadic, tetradic, split_complementary,
monochromatic, shades, tints, tones, scale, stops,
)
c=Color("#3498db")
complementary(c) # [c, c.rotate(180)]triadic(c) # 3 colors 120° aparttetradic(c) # 4 colors 90° apartsplit_complementary(c) # base + 2 colors flanking the complementanalogous(c, count=5, spread=20) # N colors spaced by 20° hueshades(c, count=5) # toward blacktints(c, count=5) # toward whitetones(c, count=5) # toward equal-lightness graymonochromatic(c, count=7) # full lightness ramp at fixed hue/chroma# Method-style (return type-stable lists):c.tints(10)
c.lighter(10) # alias for .tints()c.darker(10) # alias for .shades()c.triadic()
c.analogous(count=5)

Two-color and multi-stop scales:

scale(Color("red"), Color("blue"), steps=10, space="oklab")
stops([Color("red"), Color("green"), Color("blue")], steps=20, space="oklab")

steps includes both endpoints. space accepts any mixing space.


Tonal scales (Material You, Tailwind)

Material You 0-100 tonal palette and Tailwind 50-950 scale generated from #3498db, each stop labelled

See it: 05_design_system.py, 12_theme_generator.py

c=Color("#3498db")
c.material_palette()
# {0: '#000000', 10: '#00040a', 20: '#00182a', 30: '#00314f', 40: '#004c77',# 50: '#0069a1', 60: '#1d87c9', 70: '#45a7eb', 80: '#7bc6ff', 90: '#c0e3ff',# 95: '#e0f1ff', 99: '#f9fcff', 100: '#ffffff'}c.tailwind()
# {50: '#edf7ff', 100: '#d4ebff', 200: '#9fd5ff', 300: '#65b3ed', 400: '#308ecc',# 500: '#0069a1', 600: '#00527f', 700: '#003b5e', 800: '#002943', 900: '#00182a',# 950: '#000a16'}

Both implementations build on OKLCh - they preserve the source hue, hold chroma roughly constant for Material, and ramp chroma for Tailwind so the extreme stops stay legible. Out-of-gamut points are folded back into sRGB via OKLCh chroma reduction.

fromhexcraftimport (
material_tonal_palette, tailwind_scale,
MATERIAL_TONES, TAILWIND_STOPS,
)
material_tonal_palette(c) # equivalent to c.material_palette()tailwind_scale(c) # equivalent to c.tailwind()MATERIAL_TONES# (0, 10, 20, ..., 100)TAILWIND_STOPS# (50, 100, 200, ..., 950)

Perceptual colormaps

All 11 bundled colormaps grouped into sequential, diverging and qualitative, each interpolated in OKLab

See it: 08_perceptual_colormaps.py

Eleven named colormaps are bundled, each interpolated in OKLab so that any sample size stays perceptually uniform.

NameTypeNotes
viridissequentialPurple → yellow, default for matplotlib
magmasequentialBlack → cream, warm
plasmasequentialPurple → yellow, more saturated than viridis
infernosequentialBlack → cream, hotter than magma
cividissequentialColor-blind friendly dark blue → yellow
turborainbowBlue → red, replaces jet
rdbudivergingColorBrewer Red–Blue
brbgdivergingColorBrewer Brown–Bluegreen
spectraldivergingColorBrewer Spectral
tab10qualitativematplotlib's Tableau 10
set1qualitativeColorBrewer Set1

Each map is callable and exposes a .colors(n) discrete sampler:

fromhexcraftimportviridis, magma, rdbu, tab10, colormapviridis(0.5) # Color at midpointviridis.colors(8) # 8 evenly spaced Colorsmagma(0.0).hex# '#000004' (deep black)magma(1.0).hex# '#fcfdbf' (cream)rdbu(0.0) # red endrdbu(0.5) # neutral midpointrdbu(1.0) # blue endtab10.colors() # all 10 categorical colorstab10.colors(15) # extends with interpolation if you ask for morecolormap("viridis")(0.5) # lookup by string

Maps are also grouped:

fromhexcraftimportSEQUENTIAL_MAPS, DIVERGING_MAPS, QUALITATIVE_MAPS, ALL_MAPSlist(SEQUENTIAL_MAPS) # ['viridis', 'magma', ..., 'turbo']ALL_MAPS["RdBu"](0.7)

Accessibility (WCAG, APCA)

WCAG contrast samples with ratio, AA and AAA pass/fail badges, plus accessible_against() nudging a failing pair until it passes

See it: 06_accessibility_audit.py

fromhexcraftimport (
Color, wcag_ratio, passes_wcag, apca_lc,
find_accessible_pair, best_text_color,
)
wcag_ratio(Color("white"), Color("black")) # 21.0wcag_ratio(Color("#777"), Color("#fff")) # 4.48passes_wcag(Color("#777"), Color("#fff")) # False (AA, normal text)passes_wcag(Color("#777"), Color("#fff"), large=True) # True (AA, large text)passes_wcag(Color("#777"), Color("#fff"), level="AAA") # Falseapca_lc(Color("black"), Color("white")) # 106.04 (dark on light → positive)apca_lc(Color("white"), Color("black")) # -107.88 (light on dark → negative)apca_lc(Color("#888"), Color("white")) # 63.06

find_accessible_pair walks lightness in OKLCh until a target ratio is met:

fg=Color("#888888")
bg=Color("white")
fixed=find_accessible_pair(fg, bg, ratio=4.5)
# fixed = Color('#777777') - minimal nudge, still close to original# Method formColor("#888").accessible_against(Color("white"), ratio=4.5)
# Specify directionfind_accessible_pair(fg, bg, ratio=7.0, direction="darken")

For a quick "black or white?" decision:

best_text_color(Color("#3498db")) # Color('#000000')best_text_color(Color("#222")) # Color('#ffffff')

WCAG ratios are symmetric, in [1, 21]. APCA Lc is signed (matching the APCA-W3 polarity convention): positive for dark text on light backgrounds, negative for light on dark. Magnitude is what you compare against the published readability tables (e.g. Lc 60 for body text).


Color difference (ΔE)

fromhexcraftimportdelta_edelta_e(a, b, method="76") # CIE76 - Euclidean in Lab, fastestdelta_e(a, b, method="94") # CIE94 - graphic-arts weightingdelta_e(a, b, method="2000") # CIEDE2000 - current CIE recommendation (default)delta_e(a, b, method="cmc") # CMC(l:c) with l=2 c=1, textile standarddelta_e(a, b, method="ok") # Euclidean in OKLab, modern alternative# Method formColor("red").delta_e(Color("orangered"))

Rough thresholds for CIEDE2000: <1 imperceptible, 1–2 perceptible to a trained eye, >5 clearly different.


Gamut mapping

When a color exceeds sRGB (e.g., oklch(0.7 0.4 30) is more saturated than sRGB can show), to_gamut() reduces its OKLCh chroma until it fits, preserving lightness and hue (CSS Color 4 algorithm):

wide=Color.from_oklch(0.7, 0.4, 30)
wide.in_gamut() # Falsemapped=wide.to_gamut()
mapped.in_gamut() # Truemapped.hex# '#ff6551'# Cheaper alternative: per-channel clip in linear sRGBfromhexcraftimportclipclip(wide).hex

Color blindness simulation and daltonization

A six-color palette shown as seen with normal vision and simulated protanopia, deuteranopia and tritanopia

See it: 07_color_blindness.py

fromhexcraftimportColor, simulate, daltonizered=Color("red")
simulate(red, "protanopia") # red as protans (red-blind) see itsimulate(red, "deuteranopia") # red as deutans (green-blind) see itsimulate(red, "tritanopia") # red as tritans (blue-blind) see itsimulate(red, "deuteranopia", severity=0.5) # interpolate to anomalous trichromacydaltonize(red, "deuteranopia") # adjust the color so deutans can distinguish it# Method formsred.simulate("deuteranopia")
red.daltonize("deuteranopia")

Simulation uses Machado/Oliveira/Fernandes (2009) physiologically-based matrices; severity interpolates between full vision (0.0) and complete dichromacy (1.0). Daltonization uses Fidaner-style error redistribution onto channels the viewer can still perceive.

# Concrete example: red and green look similar to deutans → daltonize for a UIbutton_a=Color("#10b981") # success greenbutton_b=Color("#ef4444") # error redbutton_a.daltonize("deuteranopia") # shifted green so it remains distinguishablebutton_b.daltonize("deuteranopia")

Color temperature (Kelvin)

Blackbody color-temperature strip from 1500K to 12000K produced by Color.from_kelvin, with candle, tungsten, daylight, D65 and sky markers

See it: 09_color_temperature.py

fromhexcraftimportColorColor.from_kelvin(2700).hex# '#ffa757' warm tungstenColor.from_kelvin(5500).hex# '#ffedde' daylightColor.from_kelvin(6500).hex# '#fffefa' D65 (sRGB white point)Color.from_kelvin(9000).hex# '#d2dfff' cool bluec=Color("#fffefa")
c.kelvin# ~6300 K (returns None outside ~2000–25000 K)

Forward direction uses Tanner Helland's piecewise approximation. Inverse (CCT from a color) uses McCamy's cubic in CIE xy chromaticity. Both are visualization-grade, not photometric.


Closest match in a palette

See it: 10_brand_color_match.py

fromhexcraftimportColor, closest_from, closest_n_frombrand= [
Color("#3b82f6"), # blueColor("#10b981"), # greenColor("#ef4444"), # redColor("#f59e0b"), # amber
]
closest_from(Color("#ee5544"), brand)
# Color('#ef4444') - best perceptual match by CIEDE2000closest_n_from(Color("#ee5544"), brand, n=2)
# [Color('#ef4444'), Color('#f59e0b')] - sorted by ΔE# Use a different metric for image-scale workclosest_from(target, brand, method="ok")

The default metric is "2000" (CIEDE2000). Use "ok" for fast batch matching and "cmc" for textile/print contexts.


Display-P3 and chromatic adaptation

red_p3=Color("color(display-p3 1 0 0)")
red_p3.hex# '#ff0000' (out-of-gamut clipped to sRGB red)red_p3.p3# (1.0, 0.0, 0.0) - round-trips through P3c=Color("#3498db")
c.p3# gamma-encoded P3 in [0, 1]Color.from_p3(0.252, 0.589, 0.851)

For converting XYZ between white points (e.g., D65 ↔ D50 for ICC v4 work):

fromhexcraftimportadapt, D50, D65fromhexcraftimportD55, D75, A# also availableadapt((0.5, 0.6, 0.7), D65, D50) # Bradford (default)adapt((0.5, 0.6, 0.7), D65, D50, method="cat16")
adapt((0.5, 0.6, 0.7), D65, D50, method="xyz") # simplest, von Kries XYZ scaling

CMYK

Color("red").cmyk# (0.0, 1.0, 1.0, 0.0)Color.from_cmyk(0.5, 0.2, 0.0, 0.1)

This is the algebraic CMYK that browsers and design tools display when no profile is attached. It is not color-accurate for press output - real print workflows go through ICC profiles (SWOP, FOGRA, GRACoL, etc.). Use it for quick previews and color picker UI only.


Numpy arrays and images

See it: 11_image_palette.py

hexcraft.arrays (requires numpy) provides vectorized conversions for image-scale work:

importnumpyasnpfromhexcraft.arraysimport (
srgb_decode, srgb_encode,
linear_rgb_to_xyz, xyz_to_linear_rgb,
linear_rgb_to_oklab, oklab_to_linear_rgb,
srgb_to_oklab, oklab_to_srgb,
relative_luminance, wcag_ratio, delta_e_ok,
)
img=np.random.rand(1024, 1024, 3) # gamma sRGB imageok=srgb_to_oklab(img) # (H, W, 3) in OKLabluma=relative_luminance(img) # (H, W) WCAG relative luminanceratios=wcag_ratio(img, np.array([1, 1, 1])) # contrast against whitediffs=delta_e_ok(img, np.array([1, 0, 0])) # per-pixel OKLab ΔE vs red

hexcraft.image provides higher-level image utilities:

importnumpyasnpfromhexcraft.imageimportdominant_colors, average_colorimg=np.array(...).astype(np.uint8) # or float in [0, 1]; (H, W, 3) or (H, W, 4)# k-means clustering in OKLab space (perceptually meaningful)top5=dominant_colors(img, n=5)
# [Color('#3a8fd2'), Color('#f4ecd0'), Color('#221d18'), ...]# ordered by cluster size (most-common first)# Median-cut alternative (faster, less perceptual)top5=dominant_colors(img, n=5, method="median_cut")
# For very large images, dominant_colors subsamples to 50_000 pixels by default;# pass sample=None to use every pixel, or sample=N to control:dominant_colors(img, n=5, sample=10_000, seed=42)
# Gamma-correct meanaverage_color(img)

Command-line interface

Installing hexcraft registers a hexcraft console script. Output uses ANSI 24-bit color blocks when stdout is a TTY.

$ hexcraft inspect "#3498db"
#3498db
hex #3498db
rgb rgb(52, 152, 219)
hsl hsl(204.1 69.9% 53.1%)
lab lab(60.2 -6.1 -42.2)
oklch oklch(0.653 0.135 242.7)
cmyk 76.3% 30.6% 0.0% 14.1%
luma 0.2830
kelvin ~10500 K
name dodgerblue
in sRGB True
$ hexcraft convert red --to oklch
oklch(0.628 0.258 29.23)
$ hexcraft palette "#3498db" --type material
0 #000000
10 #00040a
20 #00182a
...
100 #ffffff
$ hexcraft palette red --type triadic
0 #ff0000
1 #00ae00
2 #4f6fff
$ hexcraft contrast "#888" white
fg #888888
bg #ffffff
WCAG ratio 3.54:1
AA FAIL (large: PASS)
AAA FAIL (large: FAIL)
APCA Lc -63.1
→ try fg #777777 for AA
$ hexcraft closest "#ee5544" "#3b82f6" "#10b981" "#ef4444" "#f59e0b"
target #ee5544
match #ef4444

Subcommands:

CommandPurpose
inspectShow all common representations + closest CSS name + CCT
convertRender a color as a CSS string in any space
paletteEmit a palette of any type to stdout
contrastWCAG ratio + AA/AAA pass + APCA Lc + suggested fix on fail
closestFind the closest entry in a palette by ΔE

Use hexcraft <command> --help for full options.


API reference

hexcraft.Color

Constructors

MethodNotes
Color(value)string / Color / (r,g,b) or (r,g,b,a) tuple
Color.parse(s)same as Color(s); raises ColorParseError
Color.from_rgb(r, g, b, a=1)gamma-encoded sRGB, 0–1 floats (wide-gamut allowed)
Color.from_rgb8(r, g, b, a=1)gamma-encoded sRGB, 0–255 integers
Color.from_hex(value)
Color.from_linear_rgb(r, g, b, a=1)
Color.from_hsl(h, s, l, a=1)h in degrees, s/l in [0, 1]
Color.from_hsv(h, s, v, a=1)
Color.from_hwb(h, w, b, a=1)
Color.from_lab(L, a, b, alpha=1)L [0, 100], a/b roughly [-128, 128]
Color.from_lch(L, c, h, a=1)
Color.from_oklab(L, a, b, alpha=1)L [0, 1], a/b roughly [-0.4, 0.4]
Color.from_oklch(L, c, h, a=1)
Color.from_xyz(x, y, z, a=1)D65 reference white
Color.from_p3(r, g, b, a=1, gamma=True)
Color.from_cmyk(c, m, y, k, a=1)naive (no profile)
Color.from_kelvin(temperature, a=1)Helland approximation

Properties

alpha, rgb, rgba, hex, srgb, srgb_unclamped, linear_rgb, hsl, hsv, hwb, xyz, lab, lch, oklab, oklch, p3, p3_unclamped, cmyk, kelvin, luminance, name.

Methods

MethodReturns
with_alpha(a)Color
lighten(amount) / darken(amount)Color
saturate(amount) / desaturate(amount)Color
rotate(degrees)Color
complement()Color
grayscale() / invert()Color
mix(other, t=0.5, space="oklab")Color
contrast(other, method="wcag")float
delta_e(other, method="2000")float
simulate(kind, severity=1.0)Color
daltonize(kind)Color
accessible_against(other, ratio=4.5)Color | None
to_gamut(space="srgb")Color
in_gamut(space="srgb")bool
tints(count=5) / lighter(count=5)list[Color]
shades(count=5) / darker(count=5)list[Color]
tones(count=5)list[Color]
monochromatic(count=5)list[Color]
analogous(count=3, spread=30)list[Color]
complementary(), triadic(), tetradic(), split_complementary(spread=30)list[Color]
material_palette()dict[int, Color]
tailwind()dict[int, Color]
css(fmt="hex")str - "hex", "rgb", "hsl", "hwb", "lab", "lch", "oklab", "oklch"

Free functions

fromhexcraftimport (
parse, ColorParseError, # parsingmix, blend, # mixingwcag_ratio, passes_wcag, apca_lc, # contrastfind_accessible_pair, best_text_color, # accessibilitydelta_e, closest_from, closest_n_from, # differencemap_to_gamut, clip, # gamutsimulate, daltonize, # CVDmaterial_tonal_palette, tailwind_scale, # tonal palettescomplementary, analogous, triadic, tetradic, # harmoniessplit_complementary, square,
monochromatic, shades, tints, tones,
scale, stops, # gradientskelvin_to_rgb, rgb_to_kelvin, # temperatureadapt, D65, D50, D55, D75, A, # chromatic adaptationviridis, magma, plasma, inferno, cividis, # colormapsturbo, rdbu, brbg, spectral, tab10, set1,
colormap, ALL_MAPS,
SEQUENTIAL_MAPS, DIVERGING_MAPS, QUALITATIVE_MAPS,
)

Submodules

ModulePurpose
hexcraft.spacesLow-level pure-function space conversions
hexcraft.arraysNumpy-vectorized conversions and metrics (numpy required)
hexcraft.imageDominant color extraction, average color (numpy required)
hexcraft.cliConsole-script entry point
hexcraft.adaptChromatic adaptation, white-point constants
hexcraft.colormapsColormap objects
hexcraft.temperatureKelvin / CCT helpers
hexcraft.tonalMaterial You and Tailwind tonal generators
hexcraft.cvdColor vision deficiency simulation and daltonization
hexcraft.accessibilityHigh-level helpers built on contrast metrics

License

MIT.

Releases

Packages

Contributors

Languages