First attempt at nearest color finding.

Include debug CLI.
This commit is contained in:
Bill Thiede 2020-07-22 19:27:03 -07:00
parent 9fdb42850c
commit e7ac178f7f
4 changed files with 213 additions and 3 deletions

10
Cargo.lock generated
View File

@ -187,8 +187,6 @@ dependencies = [
[[package]]
name = "image"
version = "0.23.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5b0553fec6407d63fe2975b794dfb099f3f790bdc958823851af37b26404ab4"
dependencies = [
"bytemuck",
"byteorder",
@ -325,6 +323,7 @@ dependencies = [
"structopt",
"svg",
"thiserror",
"x11colors",
]
[[package]]
@ -569,3 +568,10 @@ name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "x11colors"
version = "0.1.0"
dependencies = [
"lazy_static",
]

View File

@ -7,12 +7,15 @@ edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
image = "0.23.6"
#image = "0.23.6"
image = {path="../../github.com/wathiede/image"}
log = "0.4.8"
svg = "0.8.0"
structopt = "0.3.15"
thiserror = "1.0.20"
anyhow = "1.0.31"
#x11colors = {git="https://git.z.xinu.tv/wathiede/x11colors"}
x11colors = {path="../../xinu.tv/x11colors"}
[dev-dependencies]
lazy_static = "1.4.0"

134
src/bin/debugger.rs Normal file
View File

@ -0,0 +1,134 @@
use std::cmp::Ordering;
use std::collections::HashMap;
use std::io::Write;
use std::path::PathBuf;
use anyhow::Result;
use image::imageops::colorops::{index_colors, ColorMap};
use image::{DynamicImage, GenericImage, GenericImageView, ImageBuffer, Rgba};
use structopt::StructOpt;
use x11colors::COLORS;
use perler::NearestColor;
#[derive(Debug, StructOpt)]
enum Command {
Nearest {
/// Input image file.
input: PathBuf,
/// Output of processed image.
output: PathBuf,
},
Generate {
/// Output of processed image.
output: PathBuf,
},
}
/// Convert image to SVG in the style of perler instructions.
#[derive(StructOpt, Debug)]
#[structopt(name = "perler")]
struct Opt {
#[structopt(subcommand)]
cmd: Command,
}
fn print_stats(img: &DynamicImage) {
let mut h = HashMap::new();
for (_x, _y, p) in img.pixels() {
let c = h.entry(p).or_insert(0);
*c += 1;
}
let mut lines = HashMap::<u8, Vec<Rgba<u8>>>::new();
for col in h.keys() {
lines.entry(col.0[0]).or_insert(vec![]).push(*col);
}
let mut grp: Vec<_> = lines.keys().cloned().collect();
let lines = lines;
grp.sort();
println!("({}) Colors:", h.len());
for g in grp {
let mut keys: Vec<_> = lines[&g].iter().cloned().collect();
keys.sort_by(|l, r| {
let [l_r, l_g, l_b, _] = l.0;
let [r_r, r_g, r_b, _] = r.0;
match l_r.cmp(&r_r) {
Ordering::Less => return Ordering::Less,
Ordering::Equal => match l_g.cmp(&r_g) {
Ordering::Less => return Ordering::Less,
Ordering::Equal => return l_b.cmp(&r_b),
Ordering::Greater => return Ordering::Greater,
},
Ordering::Greater => return Ordering::Greater,
}
});
let mut out = Vec::new();
for col in keys {
let cnt = h[&col];
write!(
out,
" #{:02x}{:02x}{:02x}: {}",
col.0[0], col.0[1], col.0[2], cnt
)
.unwrap();
}
println!("{}", std::str::from_utf8(&out).unwrap());
}
}
fn generate(output: PathBuf) -> Result<()> {
let w = 256;
let h = 300;
let mut img = DynamicImage::new_rgba8(w, h);
for y in 0..50 {
for x in 0..w {
img.put_pixel(x, y, [x as u8, 0, 0, 255].into());
img.put_pixel(x, 50 + y, [x as u8, x as u8, 0, 255].into());
img.put_pixel(x, 100 + y, [0, x as u8, 0, 255].into());
img.put_pixel(x, 150 + y, [0, x as u8, x as u8, 255].into());
img.put_pixel(x, 200 + y, [0, 0, x as u8, 255].into());
img.put_pixel(x, 250 + y, [x as u8, 0, x as u8, 255].into());
}
}
img.save(output)?;
Ok(())
}
fn nearest(input: PathBuf, output: PathBuf) -> Result<()> {
let img = image::open(input)?;
let (w, h) = &img.dimensions();
println!("Before:");
print_stats(&img);
let mut img2 = img.to_rgba();
//let cmap = NeuQuant::new(1, 256, &raw);
let mut palette: Vec<_> = COLORS
.iter()
.cloned()
.map(|[r, g, b]| [r, g, b, 255].into())
.collect();
palette.sort_by_key(|Rgba([r, g, b, a])| r << 24 | g << 16 | b << 8 | a);
let cmap = NearestColor::new(palette);
let pal = index_colors(&mut img2, &cmap);
let out_buf = ImageBuffer::from_fn(*w, *h, |x, y| -> Rgba<u8> {
let p = pal.get_pixel(x, y);
cmap.lookup(p.0[0] as usize)
.expect(&format!("({}) indexed color out-of-range", p.0[0]))
.into()
});
let out = DynamicImage::ImageRgba8(out_buf);
println!("After:");
print_stats(&out);
out.save(output)?;
Ok(())
}
fn main() -> Result<()> {
let opt = Opt::from_args();
match opt.cmd {
Command::Nearest { input, output } => nearest(input, output),
Command::Generate { output } => generate(output),
}
}

View File

@ -1,3 +1,7 @@
use std::collections::HashMap;
use image::imageops::colorops::ColorMap;
use image::Rgba;
use image::{DynamicImage, GenericImage, GenericImageView};
use log::debug;
use svg::node::element::Group;
@ -17,6 +21,55 @@ pub enum PerlerError {
},
}
#[derive(Default, Debug)]
pub struct NearestColor {
palette: Vec<Rgba<u8>>,
color_cache: HashMap<Rgba<u8>, usize>,
}
impl NearestColor {
pub fn new(palette: Vec<Rgba<u8>>) -> NearestColor {
NearestColor {
palette,
color_cache: HashMap::new(),
}
}
}
impl ColorMap for NearestColor {
type Color = Rgba<u8>;
fn index_of(&self, color: &Self::Color) -> usize {
// TODO(wathiede): cache hits.
let mut min_idx = usize::MAX;
let mut min_dist = i32::MAX;
for (idx, rgb) in self.palette.iter().enumerate() {
let [r1, g1, b1, a1] = color.0;
let [r1, g1, b1, a1] = [r1 as i32, g1 as i32, b1 as i32, a1 as i32];
let [r2, g2, b2, a2] = rgb.0;
let [r2, g2, b2, a2] = [r2 as i32, g2 as i32, b2 as i32, a2 as i32];
let dist = (r2 - r1) * (r2 - r1)
+ (g2 - g1) * (g2 - g1)
+ (b2 - b1) * (b2 - b1)
+ (a2 - a1) * (a2 - a1);
//dbg!((dist, min_dist, idx, min_idx));
if dist < min_dist {
min_dist = dist;
min_idx = idx;
}
}
min_idx
}
fn map_color(&self, color: &mut Self::Color) {
*color = self.palette[self.index_of(color)];
}
fn lookup(&self, idx: usize) -> Option<Self::Color> {
self.palette.get(idx).cloned()
}
}
pub fn svgify(input: &DynamicImage) -> Result<impl Node, PerlerError> {
let dim = input.dimensions();
let (w, h) = (dim.0 as usize, dim.1 as usize);
@ -274,4 +327,18 @@ mod tests {
compare_images(want, got);
}
}
#[test]
fn test_color_map() {
let palette = vec![[0, 0, 0, 255], [255, 255, 255, 255], [255, 0, 0, 255]]
.into_iter()
.map(|rgb| rgb.into())
.collect();
let cmap = NearestColor::new(palette);
let got = cmap.index_of(&[255, 10, 10, 255].into());
assert_eq!(got, 2);
let got = cmap.lookup(2);
assert_eq!(got, Some([255, 0, 0, 255].into()));
}
}