A comprehensive Go library for perceptually uniform color manipulation with full support for modern wide-gamut color spaces and professional LOG formats for cinema cameras.
Most color libraries treat RGB as the only color space, causing three fundamental problems:
| Problem | Standard Libraries | This Library |
|---|---|---|
| Perceptual Operations | Lightening blue → cyan | Lightening blue → lighter blue ✅ |
| Gradients | Muddy midpoints, uneven steps | Perceptually smooth ✅ |
| Wide Gamut | Force through sRGB, lose vibrancy | Preserve Display P3/Rec.2020 ✅ |
| Color Science | Limited or incorrect | Industry-standard algorithms ✅ |
// The difference:
// ❌ Standard RGB manipulation
lighter := RGB{blue.R + 0.2, blue.G + 0.2, blue.B + 0.2} // Looks cyan-ish
// ✅ This library (perceptually uniform)
lighter := color.Lighten(blue, 0.2) // Actually looks lighter blue!Operations work in OKLCH space where "20% lighter" actually looks 20% lighter to human eyes.
blue := color.RGB(0, 0, 1)
// All operations are perceptually uniform:
lighter := color.Lighten(blue, 0.2) // Looks evenly lighter
darker := color.Darken(blue, 0.2) // Looks evenly darker
vivid := color.Saturate(blue, 0.3) // Looks more saturated
muted := color.Desaturate(blue, 0.3) // Looks less saturatedGenerate gradients that actually look smooth, not muddy.
red := color.RGB(1, 0, 0)
blue := color.RGB(0, 0, 1)
// Smooth, perceptually uniform gradient
gradient := color.Gradient(red, blue, 20)Full support for modern display color spaces:
- Display P3 (26% more colors than sRGB) - iPhone X+, iPad Pro, Mac displays
- DCI-P3 - Digital cinema
- Adobe RGB (44% more colors) - Professional photography
- Rec.2020 (73% more colors) - UHDTV, HDR
- ProPhoto RGB (189% more colors) - RAW photo editing
// Create in Display P3 (wider gamut)
p3Red, _ := color.ConvertFromRGBSpace(1, 0, 0, 1, "display-p3")
// Manipulate in perceptual space
lighter := color.Lighten(p3Red, 0.2)
// Convert back to Display P3 (preserves vibrancy!)
result, _ := color.ConvertToRGBSpace(lighter, "display-p3")Industry-standard color difference metrics:
color1, _ := color.ParseColor("#FF6B6B")
color2, _ := color.ParseColor("#FF6D6C")
// How different do they look to humans?
diff := color.DeltaE2000(color1, color2) // Industry standard
if diff < 1.0 {
fmt.Println("Imperceptible difference")
} else if diff < 2.0 {
fmt.Println("Small difference")
} else {
fmt.Println("Noticeable difference")
}When converting from wide to narrow gamuts, choose your mapping strategy:
// Vivid Display P3 color (out of sRGB gamut)
vividColor := NewSpaceColor(DisplayP3Space, []float64{1.1, 0.3, 0.2}, 1.0)
// Choose your mapping strategy:
clipped := MapToGamut(vividColor, GamutClip) // Fast
lightness := MapToGamut(vividColor, GamutPreserveLightness) // Keep brightness ⭐
chroma := MapToGamut(vividColor, GamutPreserveChroma) // Keep saturation
best := MapToGamut(vividColor, GamutProject) // Best qualityProfessional LOG color spaces for cinema camera workflows with HDR support:
// Load LOG footage from cinema camera
slog3 := color.NewSpaceColor(color.SLog3Space,
[]float64{0.41, 0.39, 0.35}, 1.0)
// Process in HDR workflow
hdr := slog3.ConvertTo(color.Rec2020Space) // HDR mastering
web := slog3.ConvertTo(color.SRGBSpace) // Web delivery
// Supported: Canon C-Log, Sony S-Log3, Panasonic V-Log,
// Arri LogC, Red Log3G10, Blackmagic Filmgo get github.com/SCKelemen/colorpackage main
import (
"fmt"
"github.com/SCKelemen/color"
)
func main() {
// Parse any CSS color format
blue, _ := color.ParseColor("#0000FF")
// Perceptually uniform operations
lighter := color.Lighten(blue, 0.2)
darker := color.Darken(blue, 0.2)
vivid := color.Saturate(blue, 0.3)
// Generate smooth gradient
red := color.RGB(1, 0, 0)
gradient := color.Gradient(red, blue, 10)
// Convert to hex
for _, c := range gradient {
fmt.Println(color.RGBToHex(c))
}
}- sRGB - Standard web colors
- sRGB-linear - Linear RGB for correct blending
- Display P3 - Modern Apple devices, HDR displays
- DCI-P3 - Digital cinema
- Adobe RGB 1998 - Professional photography
- ProPhoto RGB - RAW photo editing (widest gamut)
- Rec.2020 - UHDTV, future displays
- Rec.709 - HDTV
Professional logarithmic color spaces for cinema camera workflows with HDR support:
- Canon C-Log - Cinema EOS cameras (C300, C500, etc.) with Cinema Gamut
- Sony S-Log3 - Sony cinema cameras (FX6, FX9, Venice, etc.) with S-Gamut3
- Panasonic V-Log - Panasonic cameras (GH5, S1H, EVA1, etc.) with V-Gamut
- Arri LogC - Arri cameras (Alexa, Amira) with Arri Wide Gamut
- Red Log3G10 - Red cameras (Komodo, V-Raptor) with RedWideGamutRGB
- Blackmagic Film - Blackmagic cameras (Pocket, URSA) with wide gamut
// Load S-Log3 footage from Sony camera
slog3 := color.NewSpaceColor(color.SLog3Space,
[]float64{0.41, 0.39, 0.35}, 1.0)
// Convert to Rec.2020 for HDR delivery
hdr := slog3.ConvertTo(color.Rec2020Space)
// Convert to sRGB for web
web := slog3.ConvertTo(color.SRGBSpace)- OKLCH ⭐ - Modern, recommended (cylindrical)
- OKLAB - Modern, recommended (rectangular)
- CIELAB - Industry standard (rectangular)
- CIELCH - Industry standard (cylindrical)
- CIELUV - For emissive displays
- LCHuv - CIELUV cylindrical
- HSL - Hue, Saturation, Lightness
- HSV/HSB - Hue, Saturation, Value
- HWB - Hue, Whiteness, Blackness (CSS Level 4)
- XYZ - CIE 1931 (conversion hub)
// Create colors
red := color.RGB(1, 0, 0)
oklch := color.NewOKLCH(0.7, 0.2, 30, 1.0)
// Parse CSS colors
parsed, _ := color.ParseColor("#FF0000")
parsed, _ := color.ParseColor("rgb(255, 0, 0)")
parsed, _ := color.ParseColor("hsl(0, 100%, 50%)")
parsed, _ := color.ParseColor("oklch(0.7 0.2 30)")
parsed, _ := color.ParseColor("color(display-p3 1 0 0)")Notes:
color(...)RGB-space channels must be non-negative and use either normalized0..1or byte-style0..255input.color.NewSpaceColornormalizes channel count to the target space (extra channels ignored, missing channels zero-filled).
// Perceptually uniform
lighter := color.Lighten(c, 0.2)
darker := color.Darken(c, 0.2)
vivid := color.Saturate(c, 0.3)
muted := color.Desaturate(c, 0.3)
// Hue operations
shifted := color.AdjustHue(c, 60)
complement := color.Complement(c)
// Mixing
mixed := color.MixOKLCH(c1, c2, 0.5)
// Other
inverted := color.Invert(c)
gray := color.Grayscale(c)// Simple gradient (OKLCH, perceptually uniform)
gradient := color.Gradient(start, end, 20)
// Choose interpolation space
gradient := color.GradientInSpace(start, end, 20, color.GradientOKLCH)
// Multi-stop gradient
stops := []color.GradientStop{
{Color: red, Position: 0.0},
{Color: yellow, Position: 0.5},
{Color: blue, Position: 1.0},
}
gradient := color.GradientMultiStop(stops, 30, color.GradientOKLCH)
// With easing
gradient := color.GradientWithEasing(start, end, 20,
color.GradientOKLCH, color.EaseInOutCubic)// Modern, fast
diff := color.DeltaEOK(c1, c2)
// Industry standard (slower, most accurate)
diff := color.DeltaE2000(c1, c2)
// Classic formula
diff := color.DeltaE76(c1, c2)// Convert to wide-gamut space
displayP3, _ := color.ConvertToRGBSpace(c, "display-p3")
// Create from wide-gamut values
p3Color, _ := color.ConvertFromRGBSpace(1.0, 0.5, 0.0, 1.0, "display-p3")
// Check if in gamut
if color.InGamut(c) {
fmt.Println("Fits in sRGB")
}
// Map to gamut with strategy
mapped := color.MapToGamut(c, color.GamutPreserveLightness)// Create color in specific space
p3Color := color.NewSpaceColor(
color.DisplayP3Space,
[]float64{1.0, 0.5, 0.0}, // R, G, B
1.0, // alpha
)
// Convert between spaces (preserves gamut)
rec2020Color := p3Color.ConvertTo(color.Rec2020Space)
// Get metadata
metadata := color.Metadata(color.DisplayP3Space)
fmt.Printf("Gamut: %.2f× sRGB\n", metadata.GamutVolumeRelativeToSRGB)- Design Systems - Generate perceptually uniform color palettes
- Data Visualization - Smooth, accurate heatmaps and gradients
- Photo Editing - Professional wide-gamut workflows
- UI Frameworks - Consistent hover/active/disabled states
- Color Tools - Pickers, analyzers, converters
- Brand Guidelines - Color consistency checking
- Accessibility - Perceptual contrast calculations
- Static hex colors - Overkill if you just need
#FF0000 - Per-pixel GPU operations - Use shaders instead
- Simple RGB-only needs - Standard library may suffice
- WHEN_TO_USE.md - Decision trees and use cases
- COLOR_PRIMER.md - Comprehensive color theory guide
- GRADIENTS.md - Gradient examples and techniques
- COLOR_SPACE_ARCHITECTURE.md - Technical design
- API Reference - Full function docs
brand := color.NewOKLCH(0.55, 0.15, 230, 1.0) // Blue
palette := map[string]string{
"50": color.RGBToHex(color.Lighten(brand, 0.45)),
"100": color.RGBToHex(color.Lighten(brand, 0.35)),
"200": color.RGBToHex(color.Lighten(brand, 0.25)),
"300": color.RGBToHex(color.Lighten(brand, 0.15)),
"400": color.RGBToHex(color.Lighten(brand, 0.05)),
"500": color.RGBToHex(brand), // Base
"600": color.RGBToHex(color.Darken(brand, 0.05)),
"700": color.RGBToHex(color.Darken(brand, 0.15)),
"800": color.RGBToHex(color.Darken(brand, 0.25)),
"900": color.RGBToHex(color.Darken(brand, 0.35)),
}// RAW photo in ProPhoto RGB (widest gamut)
raw, _ := color.ConvertFromRGBSpace(0.9, 0.2, 0.1, 1.0, "prophoto-rgb")
// Edit in perceptual space
edited := color.Saturate(raw, 0.15)
edited = color.Lighten(edited, 0.05)
// Export for Display P3 (modern screens)
p3, _ := color.ConvertToRGBSpace(edited, "display-p3")
// Or map to sRGB with quality gamut mapping
srgb := color.MapToGamut(edited, color.GamutProject)func checkContrast(fg, bg Color) {
diff := color.DeltaE2000(fg, bg)
if diff < 30 {
fmt.Println("⚠️ Poor contrast - may fail accessibility")
} else if diff < 50 {
fmt.Println("✅ Acceptable contrast")
} else {
fmt.Println("✅✅ Excellent contrast")
}
}Fast enough for:
- UI operations (< 1000 colors at 60fps) ✅
- Palette generation ✅
- Real-time color pickers ✅
- Batch processing (< 100k colors) ✅
Optimize when:
- Processing millions of colors per second
- Per-pixel image operations (batch convert instead)
- GPU shader operations (implement there instead)
| Feature | This Library | image/color |
CSS |
|---|---|---|---|
| Color spaces | 15+ | 1 (RGB) | ~10 |
| Perceptually uniform | ✅ | ❌ | |
| Wide gamut | ✅ | ❌ | ✅ |
| Gradients | ✅ Smooth | ❌ | Browser only |
| Color difference | ✅ ΔE2000 | ❌ | ❌ |
| Gamut mapping | ✅ 4 strategies | ❌ | |
| Programmatic | ✅ | ✅ | ❌ |
Q: Do I need to understand color science?
A: No! Just use color.Gradient() and color.Lighten() - they do the right thing automatically.
Q: Is this faster than RGB operations? A: Slightly slower (conversions needed), but usually imperceptible. The visual quality improvement is worth it.
Q: Can I use this in the browser? A: Not directly (Go library), but you can compile to WASM or generate colors server-side.
Q: Does this work with the standard library?
A: Yes! Converts to/from image/color.Color interface.
Q: Why not just use CSS color functions? A: CSS only works in browsers. This works anywhere Go runs - CLIs, backends, image processing, etc.
📖 Documentation Hub - Start here for organized documentation
- 📘 API Reference - Complete API documentation
- 🎨 Color Space List - All supported color spaces
- 🚀 Quickstart Guide - Get started in 5 minutes
- Gradient Generation - Perceptually uniform gradients
- LOG Workflows - Cinema camera color spaces
- Lipgloss Integration - Terminal UI styling
- Color Primer - Color science fundamentals
- Why Use This Library - Decision guide
- Visual Comparisons - See the difference
Contributions welcome! Areas where help would be appreciated:
- Additional color spaces (e.g., CMYK, HSLuv)
- Performance optimizations
- More examples and documentation
- Visualization tools
MIT License - see LICENSE file for details.
If you use this library in academic work:
@software{color_go,
author = {Kelemen, Samuel},
title = {Color: Professional Color Manipulation for Go},
year = {2024},
url = {https://github.com/SCKelemen/color}
}- Björn Ottosson - OKLAB and OKLCH color spaces
- CIE - LAB, LUV, XYZ color spaces
- W3C - CSS Color specifications
- Go community - Feedback and contributions
Made with 🎨 by developers who care about color science

