Threshold-based perceptual color merging for images. Specify a Delta E distance — palette size derives from content.
See the codeMerge colors in an RGBA image by perceptual distance threshold. You specify how different two colors must be to stay separate; the palette size follows from the image content.
Existing quantizers (imagequant, kmeans_colors, quantette, exoquant,
color_quant) all take a target color count. This crate takes a Delta E
threshold instead.
[dependencies]
color-merge = "0.1"
use color_merge::{merge, Config};
use image::open;
let mut img = open("sprite.png").unwrap().to_rgba8();
let result = merge(&mut img, &Config::default());
println!("{} colors → {}", result.colors_before, result.colors_after);
img.save("output.png").unwrap();
use color_merge::{Config, Metric, Representative};
let config = Config::new(5.0)
.with_metric(Metric::DeltaE2000)
.with_representative(Representative::MostFrequent);
Raw buffer API (no image types):
use color_merge::{merge_raw, Config};
let result = merge_raw(&mut rgba_bytes, width, height, &Config::new(3.0));
Complexity: O(unique_colors × clusters).
| Range | Effect |
|---|---|
| 1.0–3.0 | Nearly-identical shades only |
| 3.0–5.0 | Standard pixel art cleanup |
| 5.0–10.0 | Aggressive palette reduction |
DeltaE76 (default) — Euclidean distance in L*a*b*. Fast.DeltaE2000 — CIEDE2000. Perceptually uniform. ~3× slower.WeightedCentroid (default) — pixel-count-weighted average in L*a*b*.MostFrequent — most-used original color in the cluster.The threshold-based pattern has been independently implemented multiple times without a shared library:
#![forbid(unsafe_code)]image8 commits
Rust
100.0%
Threshold-based perceptual color merging for images. Specify a Delta E distance — palette size derives from content.
See the codeMerge colors in an RGBA image by perceptual distance threshold. You specify how different two colors must be to stay separate; the palette size follows from the image content.
Existing quantizers (imagequant, kmeans_colors, quantette, exoquant,
color_quant) all take a target color count. This crate takes a Delta E
threshold instead.
[dependencies]
color-merge = "0.1"
use color_merge::{merge, Config};
use image::open;
let mut img = open("sprite.png").unwrap().to_rgba8();
let result = merge(&mut img, &Config::default());
println!("{} colors → {}", result.colors_before, result.colors_after);
img.save("output.png").unwrap();
use color_merge::{Config, Metric, Representative};
let config = Config::new(5.0)
.with_metric(Metric::DeltaE2000)
.with_representative(Representative::MostFrequent);
Raw buffer API (no image types):
use color_merge::{merge_raw, Config};
let result = merge_raw(&mut rgba_bytes, width, height, &Config::new(3.0));
Complexity: O(unique_colors × clusters).
| Range | Effect |
|---|---|
| 1.0–3.0 | Nearly-identical shades only |
| 3.0–5.0 | Standard pixel art cleanup |
| 5.0–10.0 | Aggressive palette reduction |
DeltaE76 (default) — Euclidean distance in L*a*b*. Fast.DeltaE2000 — CIEDE2000. Perceptually uniform. ~3× slower.WeightedCentroid (default) — pixel-count-weighted average in L*a*b*.MostFrequent — most-used original color in the cluster.The threshold-based pattern has been independently implemented multiple times without a shared library:
#![forbid(unsafe_code)]image8 commits
Rust
100.0%