use exoquant::ditherer::FloydSteinberg;
use exoquant::optimizer::{KMeans, Optimizer};
use exoquant::{Color, Histogram, Quantizer, Remapper, SimpleColorSpace};
use image::{ImageBuffer, Rgb, RgbImage};
use itertools::Itertools;
use num::integer::Roots;
pub mod cga;
pub mod ega;
#[macro_export]
macro_rules! value_iter {
() => {
std::iter::empty()
};
($v: expr, $( $rest: expr ), +) => {
std::iter::once($v).chain(
value_iter!($($rest),*)
)
};
($v: expr) => {
std::iter::once($v)
};
}
fn color_diff(c1: Color, c2: Color) -> u64 {
if cfg!(feature = "l1") {
color_diff_l1(c1, c2)
} else {
color_diff_l2(c1, c2)
}
}
fn image_diff(a: &[Color], b: &[Color]) -> u64 {
assert_eq!(a.len(), b.len());
Iterator::zip(a.iter(), b.iter())
.map(|(a, b)| color_diff(*a, *b))
.sum()
}
fn color_diff_l1(c1: Color, c2: Color) -> u64 {
let Color {
r: r1,
g: g1,
b: b1,
..
} = c1;
let Color {
r: r2,
g: g2,
b: b2,
..
} = c2;
let (r1, r2) = (i64::from(r1), i64::from(r2));
let (g1, g2) = (i64::from(g1), i64::from(g2));
let (b1, b2) = (i64::from(b1), i64::from(b2));
(r1 - r2).abs() as u64 + (g1 - g2).abs() as u64 + (b1 - b2).abs() as u64
}
fn color_diff_l2(c1: Color, c2: Color) -> u64 {
let Color {
r: r1,
g: g1,
b: b1,
..
} = c1;
let Color {
r: r2,
g: g2,
b: b2,
..
} = c2;
let (r1, r2) = (i64::from(r1), i64::from(r2));
let (g1, g2) = (i64::from(g1), i64::from(g2));
let (b1, b2) = (i64::from(b1), i64::from(b2));
let dr = (r1 - r2) as u64;
let dg = (g1 - g2) as u64;
let db = (b1 - b2) as u64;
(dr * dr + dg * dg + db * db).sqrt()
}
fn color_median(colors: &[Color]) -> Color {
let mut colors_r = colors.iter().map(|c| c.r).collect_vec();
let mut colors_g = colors.iter().map(|c| c.g).collect_vec();
let mut colors_b = colors.iter().map(|c| c.b).collect_vec();
colors_r.sort_unstable();
colors_g.sort_unstable();
colors_b.sort_unstable();
let r = colors_r[colors_r.len() / 2];
let g = colors_r[colors_g.len() / 2];
let b = colors_r[colors_b.len() / 2];
Color { r, g, b, a: 255 }
}
pub trait ColorDepth {
fn convert_image_with_loss(
&self,
image: &RgbImage,
num_colors: Option<u32>,
) -> (Vec<Color>, u64);
fn convert_image(&self, image: &RgbImage, num_colors: Option<u32>) -> Vec<Color> {
self.convert_image_with_loss(image, num_colors).0
}
fn loss(&self, image: &RgbImage, num_colors: Option<u32>) -> u64 {
self.convert_image_with_loss(image, num_colors).1
}
}
impl<'a, T: ColorDepth> ColorDepth for &'a T {
fn convert_image_with_loss(
&self,
image: &RgbImage,
num_colors: Option<u32>,
) -> (Vec<Color>, u64) {
(**self).convert_image_with_loss(image, num_colors)
}
fn convert_image(&self, image: &RgbImage, num_colors: Option<u32>) -> Vec<Color> {
(**self).convert_image(image, num_colors)
}
fn loss(&self, image: &RgbImage, num_colors: Option<u32>) -> u64 {
(**self).loss(image, num_colors)
}
}
pub trait ColorMapper {
fn convert_color(&self, c: Color) -> Color;
}
impl<'a, T: ColorMapper> ColorMapper for &'a T {
fn convert_color(&self, c: Color) -> Color {
(**self).convert_color(c)
}
}
impl ColorMapper for fn(Color) -> Color {
fn convert_color(&self, c: Color) -> Color {
self(c)
}
}
#[derive(Debug, Default, Copy, Clone)]
pub struct MappingColorDepth<M>(M);
impl<M> ColorMapper for MappingColorDepth<M>
where
M: ColorMapper,
{
fn convert_color(&self, c: Color) -> Color {
self.0.convert_color(c)
}
}
impl<M> ColorDepth for MappingColorDepth<M>
where
M: ColorMapper,
{
fn convert_image_with_loss(
&self,
image: &RgbImage,
num_colors: Option<u32>,
) -> (Vec<Color>, u64) {
let original = image
.pixels()
.map(|&p| {
let Rgb([r, g, b]) = p;
Color { r, g, b, a: 255 }
})
.collect_vec();
let pixels = image
.pixels()
.map(|&p| {
let Rgb([r, g, b]) = p;
self.0.convert_color(Color { r, g, b, a: 255 })
})
.collect_vec();
let converted_pixels = if let Some(num_colors) = num_colors {
let mut palette = build_palette(&pixels, num_colors);
for c in &mut palette {
*c = self.convert_color(*c);
}
let colorspace = SimpleColorSpace::default();
let ditherer = FloydSteinberg::new();
let remapper = Remapper::new(&palette, &colorspace, &ditherer);
let indexed_data = remapper.remap(&pixels, image.width() as usize);
indexed_data
.into_iter()
.map(|i| palette[i as usize])
.collect_vec()
} else {
pixels
};
let loss = image_diff(&original, &converted_pixels);
(converted_pixels, loss)
}
}
#[derive(Debug, Default, Copy, Clone)]
pub struct TrueColor24BitMapper;
impl ColorMapper for TrueColor24BitMapper {
fn convert_color(&self, pixel: Color) -> Color {
pixel
}
}
pub type TrueColor24Bit = MappingColorDepth<TrueColor24BitMapper>;
impl TrueColor24Bit {
pub fn new() -> Self {
MappingColorDepth::default()
}
}
#[derive(Debug, Default, Copy, Clone)]
pub struct Vga18BitMapper;
impl ColorMapper for Vga18BitMapper {
fn convert_color(&self, pixel: Color) -> Color {
let Color { r, g, b, a } = pixel;
Color {
r: (r & !0x03) | r >> 6,
g: (g & !0x03) | g >> 6,
b: (b & !0x03) | b >> 6,
a,
}
}
}
#[derive(Debug, Default, Copy, Clone)]
pub struct Vga16BitMapper;
impl ColorMapper for Vga16BitMapper {
fn convert_color(&self, pixel: Color) -> Color {
let Color { r, g, b, a } = pixel;
Color {
r: (r & !0x07) | r >> 5,
g: (g & !0x03) | g >> 6,
b: (b & !0x07) | b >> 5,
a,
}
}
}
pub type Vga18Bit = MappingColorDepth<Vga18BitMapper>;
impl Vga18Bit {
pub fn new() -> Self {
MappingColorDepth::default()
}
}
pub type Vga16Bit = MappingColorDepth<Vga16BitMapper>;
impl Vga16Bit {
pub fn new() -> Self {
MappingColorDepth::default()
}
}
#[derive(Debug, Copy, Clone)]
pub struct FixedPalette<T>(T);
impl<T> FixedPalette<T>
where
T: AsRef<[[u8; 3]]>,
{
fn convert_color(&self, pixel: Color) -> Color {
let Color {
r: sr,
g: sg,
b: sb,
a: _,
} = pixel;
let (sr, sg, sb) = (i32::from(sr), i32::from(sg), i32::from(sb));
let [r, g, b] = *self
.0
.as_ref()
.iter()
.min_by_key(|[pr, pg, pb]| {
let (pr, pg, pb) = (i32::from(*pr), i32::from(*pg), i32::from(*pb));
let rd = sr - pr;
let rg = sg - pg;
let rb = sb - pb;
rd * rd + rg * rg + rb * rb
})
.unwrap();
Color { r, g, b, a: 255 }
}
}
impl<T> ColorDepth for FixedPalette<T>
where
T: AsRef<[[u8; 3]]>,
{
fn convert_image_with_loss(
&self,
image: &RgbImage,
num_colors: Option<u32>,
) -> (Vec<Color>, u64) {
let original = image
.pixels()
.map(|&p| {
let Rgb([r, g, b]) = p;
Color { r, g, b, a: 255 }
})
.collect_vec();
let converted_pixels = if let Some(num_colors) = num_colors {
let mut palette = build_palette(&original, num_colors);
for c in &mut palette {
*c = self.convert_color(*c);
}
let colorspace = SimpleColorSpace::default();
let ditherer = FloydSteinberg::new();
let remapper = Remapper::new(&palette, &colorspace, &ditherer);
let indexed_data = remapper.remap(&original, image.width() as usize);
indexed_data
.into_iter()
.map(|i| palette[i as usize])
.collect_vec()
} else {
original.clone()
};
let loss = image_diff(&original, &converted_pixels);
(converted_pixels, loss)
}
}
fn build_palette(pixels: &[Color], num_colors: u32) -> Vec<Color> {
let mut histogram = Histogram::new();
histogram.extend(pixels.iter().cloned());
let colorspace = SimpleColorSpace::default();
let optimizer = KMeans;
let mut quantizer = Quantizer::new(&histogram, &colorspace);
while quantizer.num_colors() < num_colors as usize {
quantizer.step();
if quantizer.num_colors() % 256 == 0 {
quantizer = quantizer.optimize(&optimizer, 16);
}
}
let palette = quantizer.colors(&colorspace);
optimizer.optimize_palette(&colorspace, &palette, &histogram, 8)
}
#[derive(Debug, Copy, Clone)]
pub struct BackForePalette<B, F>(B, F);
impl<B, F> BackForePalette<B, F>
where
B: AsRef<[[u8; 3]]>,
F: AsRef<[[u8; 3]]>,
{
fn convert_color<T>(pixel: Color, palette: T) -> Color
where
T: AsRef<[[u8; 3]]>,
{
let Color {
r: sr,
g: sg,
b: sb,
a: _,
} = pixel;
let (sr, sg, sb) = (i32::from(sr), i32::from(sg), i32::from(sb));
let [r, g, b] = *palette
.as_ref()
.iter()
.min_by_key(|[pr, pg, pb]| {
let (pr, pg, pb) = (i32::from(*pr), i32::from(*pg), i32::from(*pb));
let rd = sr - pr;
let rg = sg - pg;
let rb = sb - pb;
rd * rd + rg * rg + rb * rb
})
.unwrap();
Color { r, g, b, a: 255 }
}
fn convert_color_back(&self, pixel: Color) -> Color {
BackForePalette::<B, F>::convert_color(pixel, &self.0)
}
fn background_color(&self, image: &RgbImage) -> Color {
let original = image
.pixels()
.map(|&p| {
let Rgb([r, g, b]) = p;
Color { r, g, b, a: 255 }
})
.collect_vec();
color_median(&original)
}
}
impl<B, F> ColorDepth for BackForePalette<B, F>
where
B: AsRef<[[u8; 3]]>,
F: AsRef<[[u8; 3]]>,
{
fn convert_image_with_loss(
&self,
image: &RgbImage,
num_colors: Option<u32>,
) -> (Vec<Color>, u64) {
let bkg_color = self.background_color(image);
let bkg_color = self.convert_color_back(bkg_color);
let mut fixed = self.1.as_ref().to_vec();
fixed.push([bkg_color.r, bkg_color.g, bkg_color.b]);
let fixed = FixedPalette(fixed);
let original = image
.pixels()
.map(|&p| {
let Rgb([r, g, b]) = p;
Color { r, g, b, a: 255 }
})
.collect_vec();
let converted_pixels = if let Some(num_colors) = num_colors {
let mut palette = build_palette(&original, num_colors);
for c in &mut palette {
*c = fixed.convert_color(*c);
}
let colorspace = SimpleColorSpace::default();
let ditherer = FloydSteinberg::new();
let remapper = Remapper::new(&palette, &colorspace, &ditherer);
let indexed_data = remapper.remap(&original, image.width() as usize);
indexed_data
.into_iter()
.map(|i| palette[i as usize])
.collect_vec()
} else {
original.clone()
};
let loss = image_diff(&original, &converted_pixels);
(converted_pixels, loss)
}
}
#[derive(Debug, Copy, Clone)]
pub struct BestPalette<C>(C);
impl<C, P> ColorDepth for BestPalette<C>
where
C: std::ops::Deref<Target = [P]>,
P: ColorDepth,
{
fn convert_image_with_loss(
&self,
image: &RgbImage,
num_colors: Option<u32>,
) -> (Vec<Color>, u64) {
self.0
.iter()
.map(|cd| cd.convert_image_with_loss(image, num_colors))
.min_by_key(|(_pixels, loss)| *loss)
.unwrap()
}
}
pub fn colors_to_image<I>(width: u32, height: u32, pixels: I) -> RgbImage
where
I: IntoIterator<Item = Color>,
{
let pixels = pixels
.into_iter()
.flat_map(|Color { r, g, b, .. }| value_iter![r, g, b])
.collect_vec();
ImageBuffer::from_raw(width, height, pixels).expect("there should be enough pixels")
}