A modern, header-only C++ library for color manipulation and conversion between different color spaces. Supports compile-time operations, perceptually uniform color spaces, and comprehensive color operations.
- Header-only: No build required, just include and use
- Multiple color spaces: RGB, HSV, HSL, HWB, CMYK, Linear RGB, CIELAB, CIELCH, OkLab, OkLCH, CIE XYZ, Display P3
- Perceptually uniform: Built-in support for CIELAB and OkLab for perceptual operations
- Color operations: Blending, interpolation, palette generation, accessibility checking, color temperature
- I/O support: CSS Color 4 parsing (hex, rgb, hsl, oklab, oklch, display-p3, lab, lch, hwb, named colors)
- Serialization: JSON and MessagePack adapters for network/config integration
- Binary IO: Read/write DaVinci Resolve .cube LUT files (1D and 3D)
- ANSI terminal: Colored swatches, palettes, gradients, WCAG contrast previews for debugging
- Type-safe literals: User-defined literals for RGB, HSL, HSV, HWB, CMYK, OkLab, and OkLCH
- Template-based: Zero-cost abstractions with compile-time validation
- C++17+: Works with any C++17 compatible compiler
| Color Space | Description |
|---|---|
| RGB | Standard red-green-blue (8-bit and float) |
| HSV | Hue-Saturation-Value (cylindrical) |
| HSL | Hue-Saturation-Lightness (cylindrical) |
| CMYK | Cyan-Magenta-Yellow-Key (printing) |
| Linear RGB | Gamma-corrected RGB (no sRGB transfer) |
| CIELAB | CIE L*a*b* (perceptual uniformity, D65) |
| CIELCH | Cylindrical form of CIELAB |
| OkLab | Modern perceptually uniform space (Björn Ottosson) |
| OkLCH | Cylindrical form of OkLab |
| CIE XYZ | Device-independent reference space (D65) |
| Display P3 | DCI-P3 primaries with D65 white point (wide gamut) |
- Algorithm reference (reStructuredText): docs/reference/index.rst — background, conventions, and links to specs/papers for conversions, ΔE, blending, gamut, accessibility (WCAG 2 + APCA), vision simulation, CSS parsing, and related topics. Start from docs/index.rst for how this relates to other doc outputs.
- Changelog: CHANGELOG.md — user-facing release history and version notes.
- API reference (Doxygen): Configure CMake with
-DCOLORCPP_BUILD_DOCS=ON, build targetdoc, and open the generated HTML (output directory is set in doxygen/Doxygen.in). - Sphinx HTML (optional): Install Sphinx (
pip install -r requirements-docs.txt), then runsphinx-build -b html docs docs/_build/html, or configure with-DCOLORCPP_BUILD_SPHINX=ONand build thesphinxCMake target.
CMake (recommended)
find_package(colorcpp REQUIRED)
target_link_libraries(your_target PRIVATE colorcpp::colorcpp)Header-only: Copy include/colorcpp/ to your project
Canonical public namespaces:
colorcpp::corefor color types, constants, and stream I/Ocolorcpp::operations::{conversion, blend, interpolate, palette, random, compare}colorcpp::algorithms::{accessibility, color_temperature, delta_e, gamut, gradient, harmony, vision}colorcpp::io::{css, literals, serialization, binary_io, ansi}
When you include colorcpp/colorcpp.hpp, core, operations, and algorithms
are also re-exported into colorcpp for convenience, so colorcpp::conversion-style code remains valid. The docs and
examples below use the canonical nested namespaces; colorcpp::io stays under colorcpp::io.
#include <colorcpp/colorcpp.hpp>
using namespace colorcpp::core;
using namespace colorcpp::operations::blend;
using namespace colorcpp::operations::conversion;
// Create colors using type aliases
constexpr auto red = rgba8_t{255, 0, 0, 255};
constexpr auto blue = rgbaf_t{0.0f, 0.0f, 1.0f, 1.0f};
// Convert between color spaces
auto hsv_red = color_cast<hsv_float_t>(red);
auto lab_red = color_cast<cielab_t>(red);
// Color operations
auto blended = blend(red, blue, blend_mode::multiply);using namespace colorcpp::io::literals;
// RGB/Hex literals
auto coral = 0xFF6347_rgb; // -> rgba8_t{255, 99, 71, 255}
auto with_alpha = 0xFF634780_rgba; // -> rgba8_t{255, 99, 71, 128}
auto argb = 0x80FF6347_argb; // -> rgba8_t{255, 99, 71, 128} (AARRGGBB)
auto from_hex = "#FF6347"_hex; // -> rgba8_t
// HSL / HSV literals
auto mint = 160'070'080_hsl; // -> hsl_float_t{160.0f, 0.70f, 0.80f}
auto sky = 210'080'090_hsv; // -> hsv_float_t{210.0f, 0.80f, 0.90f}
// CMYK / Oklab / OkLCH literals
auto teal = 50'030'000'020_cmyk; // -> cmyk8_t{50, 30, 0, 20}
auto neutral = 050'050'050_oklab; // -> oklab_t{0.50f, 0.0f, 0.0f}
auto vivid = 050'100'120_oklch; // -> oklch_t{0.50f, 0.40f, 120.0f}Include colorcpp/io/css.hpp or the umbrella colorcpp/colorcpp.hpp. Parsing returns std::nullopt on failure (no exceptions).
#include <colorcpp/colorcpp.hpp>
auto c = colorcpp::io::css::parse_css_color_rgba8("rgb(255 99 71 / 50%)");
// or any registered color type:
auto hsl = colorcpp::io::css::parse_css_color<colorcpp::core::hsla_float_t>("hsl(120, 100%, 50%)");
colorcpp::io::css::parse_css_color_context css_context;
css_context.dark_theme = true;
css_context.current_color = colorcpp::core::rgbaf_t{1.0f, 0.0f, 0.0f, 0.5f};
css_context.canvas_text = colorcpp::core::rgbaf_t{1.0f, 1.0f, 1.0f, 1.0f};
auto resolved = colorcpp::io::css::parse_css_color_rgba8("light-dark(currentColor, CanvasText)", css_context);
css_context.variable_resolver = [](std::string_view name) -> std::optional<colorcpp::core::rgbaf_t> {
if (name == "--theme-primary") return colorcpp::core::rgbaf_t{0.9f, 0.2f, 0.4f, 1.0f};
return std::nullopt;
};
css_context.numeric_variable_resolver = [](std::string_view name) -> std::optional<float> {
if (name == "--opacity") return 0.75f;
return std::nullopt;
};
auto relative = colorcpp::io::css::parse_css_color_rgbaf(
"rgb(from var(--theme-primary) r g b / calc(var(--opacity) * 0.75))", css_context);Supported (CSS Color Module Level 4 plus selected Level 5 helpers):
- Hex:
#rgb,#rgba,#rrggbb,#rrggbbaa rgb()/rgba(): legacy commas, modern spaces, slash alpha, percentageshsl()/hsla(): hue withdeg/grad/rad/turn, percentage saturation/lightnesshwb()oklab(L a b): L as number/percentage, a/b as numbers/percentagesoklch(L C H): L as number/percentage, C as number/percentage, H as hue anglelab(L a b): CIE L*a*b* with D65 white pointlch(L C H): CIE L*C*h* cylindrical formhwb(H W B): Hue-Whiteness-Blacknesscolor(...):srgb,srgb-linear,display-p3,display-p3-linear,a98-rgb,prophoto-rgb,rec2020,xyz,xyz-d50,xyz-d65- Relative colors:
rgb(from …)andcolor(from …)with full expression support:- Arithmetic operations
+ - * / - Parenthesized expressions and
calc()wrapper syntax - Nested
var(--token)references anywhere inside expressions - Delayed AST evaluation with
parse_css_color_ast()/evaluate() - Separate
variable_resolver(color values) andnumeric_variable_resolver(scalar values)
- Arithmetic operations
color-mix(): omitted method defaults tooklab; item-list mixes support one or more colors; progress-form mixes supportcolor-mix(25% in srgb, red, blue); interpolation spaces includesrgb,srgb-linear,display-p3,display-p3-linear,lab,lch,oklab,oklch, andxyzdevice-cmyk(...): CMYK device colors, including slash alpha- Named colors and keywords: all 140+ CSS named colors plus
transparent— case-insensitive
Context-aware support: currentColor, CSS system colors, light-dark(), and var(--token)-backed relative colors are available through the overloads that accept parse_css_color_context.
Still pending: relative syntaxes beyond rgb(from …) / color(from …) and the remaining context-sensitive CSS color features that depend on authoring-time style state.
using namespace colorcpp::core;
using namespace colorcpp::operations::conversion;
// Convert to any color space
auto lab = color_cast<cielab_t>(rgb_color);
auto oklch = color_cast<oklch_t>(rgb_color);
auto xyz = color_cast<xyz_t>(rgb_color);
// sRGB ↔ Linear RGB (gamma handling)
auto linear = color_cast<linear_rgbf_t>(srgb_color);
auto gamma = color_cast<rgba8_t>(linear_color);
// CIELAB ↔ CIELCH (cartesian ↔ polar)
auto lch = color_cast<cielch_t>(lab_color);
auto cartesian = color_cast<cielab_t>(lch_color);
// OkLab ↔ OkLCH
auto ok = color_cast<oklab_t>(lch_color);
auto polar = color_cast<oklch_t>(oklab_color);
// Cross-space conversions (graph-routed)
auto lab_to_ok = color_cast<oklab_t>(lab_color); // uses the lowest-cost registered route via XYZCurrent note: the shipped blend() implementation currently evaluates through rgbaf_t and should not be read as
proof of a linear-sRGB compositing contract.
using namespace colorcpp::operations::blend;
// Basic blend modes
auto normal = blend(red /* dst */, blue /* src */);
auto multiply = blend(red /* dst */, blue /* src */, blend_mode::multiply);
auto screen = blend(red /* dst */, blue /* src */, blend_mode::screen);
auto overlay = blend(red /* dst */, blue /* src */, blend_mode::overlay);
// All modes: normal, multiply, screen, overlay, darken, lighten,
// addition, subtraction, difference, exclusion, hard_light,
// soft_light, color_dodge, color_burn, divide
// With opacity control
auto semi_transparent = blend(red /* dst */, blue /* src */, blend_mode::multiply, 0.5f);
// Non-separable blends (hue, saturation, color, luminosity)
auto hue_blend = blend(red /* dst */, blue /* src */, blend_mode::hue);using namespace colorcpp::operations::palette;
// Generate color scales
auto linear_palette = linear_scale(red, blue, 5);
auto visual_palette = visual_scale(red, blue, 5); // HSL interpolation
auto perceptual_palette = perceptual_scale(red, blue, 5); // OkLab interpolation
// Color harmony palettes backed by algorithms::harmony rules
auto complementary = schemes::complementary(base_color);
auto analogous = schemes::analogous(base_color);
auto triadic = schemes::triadic(base_color);
auto split_complementary = schemes::split_complementary(base_color);
auto tetradic = schemes::tetradic(base_color);
auto square = schemes::square(base_color);
auto monochromatic = schemes::monochromatic(base_color);
// Warm / cool / neutral families
auto warm = families::warm(base_color);
auto cool = families::cool(base_color);
auto neutral = families::neutral(base_color);
// Access palette colors
for (const auto& color : perceptual_palette) {
// ...
}
auto first = perceptual_palette[0];schemes, families, and scale builders are structural palette APIs; material_* and theme are heuristic design helpers.
Choose interpolation helpers by semantic family first: RGB-style, hue-aware cylindrical, perceptual, or path/spline.
using namespace colorcpp::operations::interpolate;
// Linear interpolation
auto mid = lerp(red, blue, 0.5f);
// HSL interpolation (visually smoother)
auto smooth = lerp_hsl(red, blue, 0.5f);
// Perceptual interpolation via OkLab
auto perceptual = lerp_oklab(red, blue, 0.5f);
// Shape-preserving cubic interpolation with neighbor anchors
auto smooth_curve = lerp_monotonic_spline(prev, red, blue, next, 0.5f);
// Multi-stop path interpolation
auto path = lerp_path(std::vector{red, 0x00FF00_rgb, blue}, 0.35f);using namespace colorcpp::algorithms::accessibility;
// WCAG 2.x contrast ratio (do not compare numerically to APCA Lc)
auto ratio = contrast_ratio(foreground, background);
bool passes_aa = is_wcag_compliant(foreground, background, wcag_level::aa, text_size::normal);
bool passes_aaa = is_wcag_compliant(foreground, background, wcag_level::aaa, text_size::normal);
// APCA Lc (SAPC / Silver draft style; text vs background order matters; thresholds are not WCAG 2)
float lc = apca_contrast(foreground, background);
bool strong_enough = apca_meets_min_abs_lc(foreground, background, 60.0f);
// Pick black or white text for a background
auto recommended_text = contrast_text_color(background);using namespace colorcpp::algorithms::vision;
// Simulate color blindness
auto protanopia = simulate_protanopia(color);
auto deuteranopia = simulate_deuteranopia(color);
auto tritanopia = simulate_tritanopia(color);
// Check if colors remain distinguishable under common CVD models
bool distinguishable = is_accessible_for_deuteranopia(c1, c2);
bool robust = is_accessible_for_all_cvd(c1, c2);using namespace colorcpp::algorithms::gamut;
// Check if color is within sRGB gamut
bool in_gamut = is_in_srgb_gamut(color);
// Clip to sRGB gamut
auto clipped = gamut_clip(color);
// Inspect mapping metadata
float distance = gamut_distance(color);
auto info = gamut_clip_with_info(color);using namespace colorcpp::algorithms::delta_e;
// Different Delta E metrics
auto de00 = delta_e_2000(color1, color2); // CIEDE2000 (best for human perception)
auto de94 = delta_e_94(color1, color2); // CIE94
auto de76 = delta_e_76(color1, color2); // CIE76 (simple Euclidean)
// Threshold comparisons
bool imperceptible = delta_e_2000(c1, c2) < 1.0f;
bool acceptable = delta_e_2000(c1, c2) < 3.0f;using namespace colorcpp::core;
using namespace colorcpp::operations;
using namespace colorcpp::operations::random;
// Random colors in different spaces
auto random_rgb = random_color<rgbf_t>();
auto random_hsl = random_color<hsl_float_t>();
auto random_lab = random_color<cielab_t>();
// Constrained random colors
auto accessible_fg = random_contrast_color<rgbf_t>(rgbf_t{0.08f, 0.10f, 0.14f}, 4.5f);
auto mid_luminance = random_luminance_color<rgbf_t>(0.35f, 0.65f);
// random_contrast_color is best-effort within its internal attempt budget; it is not an unconditional guarantee.
// With seed for reproducibility
auto seeded = random_color<rgbf_t>(seed);
rgb8_generator generator(seed);
auto sample = generator.next();
// Reuse generator families directly
contrast_rgbf_generator contrast_gen(seed);
auto contrast_sample = contrast_gen.next(rgbf_t{1.0f, 1.0f, 1.0f});
luminance_rgbf_generator luminance_gen(seed);
auto luminance_sample = luminance_gen.next();using namespace colorcpp::core;
using namespace colorcpp::core::io;
// Output formatting
std::cout << rgba_color << "\n"; // "255 99 71 255"
std::cout << std::hex << rgba_color; // "#ff6347ff"
// Parse from strings
auto rgb = parse<rgb8_t>("255 99 71");
auto hsl = parse<hsl_float_t>("hsl(16, 100, 63.9)");
auto cmyk = parse<cmyk8_t>("cmyk(0, 61, 72, 0)");
// Round-trip: output → re-parse
std::ostringstream oss;
oss << color;
auto recovered = parse<decltype(color)>(oss.str());using namespace colorcpp::io::css::named_literal;
// CSS named color → rgba8_t
auto coral = "coral"_color; // → rgba8_t{255, 127, 80, 255}
auto red = "red"_color; // → rgba8_t{255, 0, 0, 255}
auto steelblue = "steelblue"_color; // → rgba8_t{70, 130, 180, 255}
// Case-insensitive
auto gold = "GOLD"_color; // → rgba8_t{255, 215, 0, 255}
// Lookup by name
auto c = get_named_color("coral"); // → std::optional<rgba8_t>
bool valid = is_named_color("red"); // → true#include <colorcpp/io/serialization.hpp>
using namespace colorcpp::core;
using namespace colorcpp::io::serialization;
// Specialize json_adapter for your JSON library (e.g. nlohmann::json)
// Then use to_json / from_json for any color type:
auto j = to_json<nlohmann::json>(coral_color);
auto recovered = from_json<nlohmann::json, rgba8_t>(j);
// Named format defaults to generic keys such as ch0/ch1/ch2/ch3
serialization_options opts;
opts.format = serialization_format::named;
auto j_generic = to_json<nlohmann::json>(coral, opts);
// Or provide your own channel names explicitly
std::string names[] = {"red", "green", "blue", "alpha"};
auto j_named = to_json<nlohmann::json>(coral, names, opts);
// MessagePack support currently uses msgpack_packer/msgpack_unpacker
// helper traits plus pack_color / unpack_color style functions.#include <colorcpp/io/binary_io.hpp>
using namespace colorcpp::io::binary_io;
// Read a DaVinci Resolve .cube LUT file
auto lut = cube::read_3d("grade.cube");
// Apply LUT to a color
auto graded = apply(*lut, 0.5f, 0.3f, 0.8f);
// Write a LUT file
cube::write("output.cube", *lut, "My Grade");#include <colorcpp/io/ansi.hpp>
using namespace colorcpp::io::ansi;
using namespace colorcpp::io::css::named_literal;
// Print colored swatch with info
auto c = "coral"_color;
print_color(std::cout, c, "coral");
// Output: ██████ coral #ff7f50ff RGB(255,127,80)
// Print palette
print_palette(std::cout, colors);
// Print gradient
print_gradient(std::cout, "red"_color, "blue"_color);
// Print WCAG contrast check
print_contrast(std::cout, "white"_color, "navy"_color);
// Output: Aa #fff on #000080 contrast: 14.4:1 AAA
// Notes:
// - ANSI output is a terminal preview from rgba8 conversion.
// - Alpha is shown numerically but is not rendered as terminal transparency.
// - Verbose HSL values are derived from converted RGB.#include <colorcpp/algorithms/color_temperature.hpp>
using namespace colorcpp::algorithms::color_temperature;
// Kelvin → sRGB (1000–40000K)
auto warm = kelvin_to_rgba8(2700.0f); // Warm white
auto daylight = kelvin_to_rgba8(6500.0f); // D65 white
auto cool = kelvin_to_rgba8(10000.0f); // Overcast sky
// Float version
auto linear = kelvin_to_linear_rgb(5500.0f);
auto srgb = kelvin_to_rgb(5500.0f);.
├── include/colorcpp/ # Public header-only library
│ ├── colorcpp.hpp # Umbrella header
│ ├── core/ # Color types, constants, stream I/O
│ ├── operations/ # Conversion, blend, compare, interpolate, palette, random
│ ├── algorithms/ # Accessibility, delta_e, gamut, gradient, harmony, vision, color_temperature
│ ├── io/ # CSS parsing, literals, serialization, binary IO, ANSI helpers
│ └── detail/ # Internal shared helpers (for example SIMD support)
├── examples/ # Small runnable examples for major features
├── tests/ # GoogleTest suites
│ ├── core/ # Core color space types and primitives
│ ├── operations/ # Conversion, blend, compare, interpolate, palette, random
│ ├── algorithms/ # Delta E, gamut, gradient, harmony, accessibility, vision, etc.
│ ├── io/ # CSS, literals, serialization, ANSI, binary IO
│ └── test_api_contract.cpp # Umbrella header / namespace contract checks
├── benchmarks/ # Optional Google Benchmark microbenchmarks
├── docs/ # Narrative Sphinx docs and reference notes
│ └── reference/ # Topic-oriented reference pages
├── cmake/ # Package config and pkg-config templates
├── doxygen/ # Doxygen template/config input
├── .github/workflows/ # CI and release workflows
├── CMakeLists.txt # Main build configuration
├── Dockerfile # Containerized build / verification image
└── requirements-docs.txt # Python dependencies for Sphinx docs
The public API is centered around include/colorcpp/, while examples/, tests/, and docs/
show how the library is expected to be consumed, validated, and documented.
- C++17 or later
- Any C++17-compatible compiler (GCC 7+, Clang 5+, MSVC 2017+)
- Optional: C++20 for enhanced template features
See the examples/ directory for complete working examples:
io_example.cpp- Parsing and formatting colorscast_example.cpp- Color space conversionsinterpolate_example.cpp- Color interpolationpalette_example.cpp- Palette generationrandom_example.cpp- Random color generationaccessibility_example.cpp- WCAG 2 and APCA helpersgamut_example.cpp- Gamut mapping and clippingdelta_e_example.cpp- Color difference metricsvision_example.cpp- Vision deficiency simulationserialization_example.cpp- JSON and MessagePack adapters
Optional microbenchmarks use Google Benchmark, fetched with CMake FetchContent when enabled:
cmake -B build -DCOLORCPP_BUILD_BENCHMARKS=ON -DCOLORCPP_ENABLE_SIMD=ON
cmake --build build
./build/benchmarks/benchmark_conversion
# or: cmake --build build --target run_all_benchmarksSources under benchmarks/ are split by area and each builds into its own executable, such as
benchmark_conversion, benchmark_blend, benchmark_interpolate, benchmark_delta_e, benchmark_io, and
benchmark_gamut_palette.
COLORCPP_ENABLE_SIMD=ON is currently an opt-in fast path for selected operations. Confirmed accelerated paths cover
selected separable blend() modes. delta_e_ok() also has an experimental SIMD-backed implementation; unsupported
targets and all other modes keep the scalar implementation.
HTML API documentation is generated from headers with Doxygen. Configure with COLORCPP_BUILD_DOCS=ON, build the doc target, then open build/doc_doxygen/html/index.html in a browser:
cmake -B build -DCOLORCPP_BUILD_DOCS=ON
cmake --build build --target docRequires doxygen on your PATH. The Doxygen config template lives in doxygen/Doxygen.in (EXTRACT_ALL is off so only documented symbols are listed; see WARN_IF_UNDOCUMENTED in that file).
Docker — configure, build examples, and run unit tests inside Ubuntu 24.04:
docker build -t colorcpp:verify .
# or
docker compose build verifyInteractive shell with the repo bind-mounted at /src:
docker compose run --rm devGitHub Actions (.github/workflows/ci.yml): on push/PR to main, master, or channel, runs a GCC and Clang matrix (Ninja + CMake) with tests and examples enabled, plus an install + consumer smoke test and a Docker build of the verify stage.
Publishing (.github/workflows/docker-publish.yml): pushing a tag v* builds the same verify image and pushes it to GHCR as ghcr.io/<owner>/<repo> (useful as a reproducible toolchain snapshot, not a runtime dependency for consumers).
MIT License - see LICENSE file for details.
Merlot.Qi - merlotqi
- CIE color space definitions per ISO 11664-4
- OkLab by Björn Ottosson
- sRGB gamma per IEC 61966-2-1
- W3C blend mode specifications