sevenevesai/color-merge

Threshold-based perceptual color merging for images. Specify a Delta E distance — palette size derives from content.

Rust

0

8 commits

updated May 5, 2026

See the code

README

color-merge

github crates.io

Merge 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.


Install

[dependencies]
color-merge = "0.1"

Usage

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));

Algorithm

  1. Collect unique opaque colors with pixel counts
  2. Sort by frequency descending
  3. Convert to CIE L*a*b*
  4. Greedy first-fit clustering against threshold
  5. Compute representative per cluster
  6. Rewrite pixels in-place

Complexity: O(unique_colors × clusters).


Configuration

Threshold

RangeEffect
1.0–3.0Nearly-identical shades only
3.0–5.0Standard pixel art cleanup
5.0–10.0Aggressive palette reduction

Metrics

  • DeltaE76 (default) — Euclidean distance in L*a*b*. Fast.
  • DeltaE2000 — CIEDE2000. Perceptually uniform. ~3× slower.

Representative

  • WeightedCentroid (default) — pixel-count-weighted average in L*a*b*.
  • MostFrequent — most-used original color in the cluster.

Prior art

The threshold-based pattern has been independently implemented multiple times without a shared library:


Notes

  • Idempotent (second pass with same threshold is a no-op)
  • Pixels with alpha == 0 are skipped
  • #![forbid(unsafe_code)]
  • Single dependency: image

License

Licensed under either of Apache License, Version 2.0 or MIT license at your option.
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in this crate by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.

Contributors

sevenevesai

8 commits

sevenevesai/color-merge

Threshold-based perceptual color merging for images. Specify a Delta E distance — palette size derives from content.

Rust

0

8 commits

updated May 5, 2026

See the code

README

color-merge

github crates.io

Merge 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.


Install

[dependencies]
color-merge = "0.1"

Usage

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));

Algorithm

  1. Collect unique opaque colors with pixel counts
  2. Sort by frequency descending
  3. Convert to CIE L*a*b*
  4. Greedy first-fit clustering against threshold
  5. Compute representative per cluster
  6. Rewrite pixels in-place

Complexity: O(unique_colors × clusters).


Configuration

Threshold

RangeEffect
1.0–3.0Nearly-identical shades only
3.0–5.0Standard pixel art cleanup
5.0–10.0Aggressive palette reduction

Metrics

  • DeltaE76 (default) — Euclidean distance in L*a*b*. Fast.
  • DeltaE2000 — CIEDE2000. Perceptually uniform. ~3× slower.

Representative

  • WeightedCentroid (default) — pixel-count-weighted average in L*a*b*.
  • MostFrequent — most-used original color in the cluster.

Prior art

The threshold-based pattern has been independently implemented multiple times without a shared library:


Notes

  • Idempotent (second pass with same threshold is a no-op)
  • Pixels with alpha == 0 are skipped
  • #![forbid(unsafe_code)]
  • Single dependency: image

License

Licensed under either of Apache License, Version 2.0 or MIT license at your option.
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in this crate by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.

Contributors

sevenevesai

8 commits

Languages

Rust

100.0%