Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

10 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Chromoxide

A constraint‑driven palette optimizer for Rust.

Chromoxide solves for optimal color palettes given image evidence, slot‑wise hard domains, and pairwise constraints. It uses continuous optimization in Oklab/OkLCh color space with multi‑start L‑BFGS.

Note: This project is in early development. APIs may change.

Crates

  • chromoxide – Core optimization engine, domain definitions, and solver.
  • chromoxide‑image – Image preprocessing, saliency detection, sampling, and support extraction.
  • chrox – CLI palette generation tool built on top of chromoxide and chromoxide-image.

Cap estimation vs. enforcement

Cap construction and cap enforcement are separate:

  • chromoxide-image::CapEstimator decides how a cap surface is built from image evidence (MaxObserved or Statistical). The default builds a statistical conditional cap and a same-lightness global chroma profile from all prepared pixels, not from the exported 24 samples.
  • CapPolicy decides how a slot enforces the already-built cap (Ignore, HardIntersect, SoftPenalty, or AdaptiveSoftPenalty).

HardIntersect never lowers a slot's user chroma.min; problems whose required minimum exceeds the cap over the whole slot domain are rejected during validation.

SoftPenalty remains strict: it uses evidence at the queried (L, h). AdaptiveSoftPenalty blends that conditional cap with the global profile using the original, pre-smoothing support confidence. Supported hues stay conditional; an unsupported semantic hue inherits only the image's chroma style at the same lightness. The fallback therefore remains tied to source evidence and the slot's existing user chroma interval.

Usage

Add to your Cargo.toml (replace the git URL with your own):

[dependencies]
chromoxide = { git = "https://github.com/werdxz/chromoxide" }
chromoxide-image = { git = "https://github.com/werdxz/chromoxide" }

Install the CLI with:

cargo install --git https://github.com/werdxz/chromoxide chrox

Basic example using pre‑computed samples:

use chromoxide::*;

let samples = vec![
    WeightedSample::new(Oklch { l: 0.35, c: 0.12, h: 0.2 }.to_oklab(), 2.0, 0.5),
    WeightedSample::new(Oklch { l: 0.75, c: 0.10, h: 2.8 }.to_oklab(), 2.0, 0.8),
];

let slots = vec![
    SlotSpec {
        name: "a".into(),
        domain: SlotDomain {
            lightness: Interval { min: 0.2, max: 0.9 },
            chroma: Interval { min: 0.0, max: 0.2 },
            hue: HueDomain::Any,
            cap_policy: CapPolicy::Ignore,
            chroma_epsilon: 0.02,
        },
    },
    SlotSpec {
        name: "b".into(),
        domain: SlotDomain {
            lightness: Interval { min: 0.2, max: 0.9 },
            chroma: Interval { min: 0.0, max: 0.2 },
            hue: HueDomain::Any,
            cap_policy: CapPolicy::Ignore,
            chroma_epsilon: 0.02,
        },
    },
];

let problem = PaletteProblem {
    slots,
    samples,
    image_cap: None,
    terms: vec![WeightedTerm {
        weight: 3.0,
        name: Some("cover".into()),
        term: Term::Cover(CoverTerm {
            slots: vec![0, 1],
            tau: 0.02,
            delta: 0.03,
        }),
    }],
    config: SolveConfig::default(),
};

let solution = solve(&problem)?;

For a full image‑based pipeline, see the examples in chromoxide‑image.

For CLI-driven palette generation and template rendering, see crates/chrox/README.md.

Reproducibility

The chrox CLI defaults to content-derived deterministic runs: it hashes the image bytes plus the image and global solve configuration, then derives domain-separated RNG seeds for image sampling and each palette solve. Use --seed <U64> to select an explicit deterministic master seed, or --randomize to generate and print a fresh master seed for exploration.

The core library keeps solve as a random convenience API and solve_with_rng as the caller-controlled RNG API. Use solve_with_seed for the stable deterministic contract; every local start gets an independent ChaCha stream.

Determinism is versioned with the algorithm. Bitwise-identical OkLCh values are not promised across different algorithm versions; within one algorithm version, the goal is stable final hex output for identical inputs.

Examples

Run the workspace examples with:

cargo run --example neutral_ladder --release
cargo run --example basic_pipeline --release

Documentation

Build local documentation:

cargo doc --workspace --open

For project terminology, see VOCABULARY.md. For pipeline and parameter explanations, see ALGORITHMS.md.

License

This project is licensed under the MIT License – see the LICENSE file for details.

Releases

Packages

Contributors

Languages