fix for more image types
| -rw-r--r-- | Cargo.lock | 7 | ||||
| -rw-r--r-- | Cargo.toml | 1 | ||||
| -rw-r--r-- | blue.f32 | bin | 12582912 -> 0 bytes | |||
| -rw-r--r-- | blue_1.f32 | bin | 0 -> 4194304 bytes | |||
| -rw-r--r-- | src/diffusion.rs | 18 | ||||
| -rw-r--r-- | src/diffusion/riemerasma.rs | 7 | ||||
| -rw-r--r-- | src/diffusion/sierra.rs | 10 | ||||
| -rw-r--r-- | src/dumb.rs | 47 | ||||
| -rw-r--r-- | src/kd.rs | 219 | ||||
| -rw-r--r-- | src/lib.rs | 22 | ||||
| -rw-r--r-- | src/main.rs | 116 | ||||
| -rw-r--r-- | src/ordered.rs | 200 |
12 files changed, 238 insertions, 409 deletions
@@ -106,12 +106,6 @@ dependencies = [ ] [[package]] -name = "fux_kdtree" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "216bf8256834a87394bf0364f82a92444ee8a00c757630a02a2bf278456f74e7" - -[[package]] name = "getrandom" version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -234,7 +228,6 @@ dependencies = [ "car", "exoquant", "fimg", - "fux_kdtree", "mattr 0.0.3", "rand", ] @@ -11,7 +11,6 @@ fimg = { version = "0.4.43", git = "https://github.com/bend-n/fimg", default-fea "save", "scale", ] } -fux_kdtree = "0.2.0" mattr = "0.0.3" rand = "0.8.5" diff --git a/blue.f32 b/blue.f32 Binary files differdeleted file mode 100644 index f92fc3a..0000000 --- a/blue.f32 +++ /dev/null diff --git a/blue_1.f32 b/blue_1.f32 Binary files differnew file mode 100644 index 0000000..c518540 --- /dev/null +++ b/blue_1.f32 diff --git a/src/diffusion.rs b/src/diffusion.rs index 494fa7e..257cfce 100644 --- a/src/diffusion.rs +++ b/src/diffusion.rs @@ -5,11 +5,13 @@ use super::*; mod riemerasma; pub mod sierra; pub use riemerasma::*; -pub fn atkinson(image: Image<&[f32], 4>, palette: &[[f32; 4]]) -> Image<Box<[f32]>, 4> { - let kd = map(palette); +pub fn atkinson<const N: usize>( + image: Image<&[f32], N>, + palette: &[[f32; N]], +) -> Image<Box<[f32]>, N> { let mut image = Image::build(image.width(), image.height()).buf(image.buffer().to_vec().into_boxed_slice()); - let eighth = [1. / 8.; 4]; + let eighth = [1. / 8.; N]; for (x, y) in image.serpent() { unsafe { /* @@ -18,10 +20,10 @@ pub fn atkinson(image: Image<&[f32], 4>, palette: &[[f32; 4]]) -> Image<Box<[f32 1 */ let p = image.pixel(x, y); - let new = palette[kd.find_nearest(p) as usize]; + let new = palette.best(p); *image.pixel_mut(x, y) = new; let error = p.asub(new); - let f = |x: [f32; 4]| x.aadd(error.amul(eighth)); + let f = |x: [f32; N]| x.aadd(error.amul(eighth)); image.replace(x + 1, y, f); image.replace(x + 2, y, f); @@ -39,14 +41,13 @@ pub fn jarvis<const FAC: u8>( image: Image<&[f32], 4>, palette: &[[f32; 4]], ) -> Image<Box<[f32]>, 4> { - let kd = map(palette); let mut image = Image::build(image.width(), image.height()).buf(image.buffer().to_vec().into_boxed_slice()); for (x, y) in image.serpent() { #[rustfmt::skip] unsafe { let p = image.pixel(x, y); - let new = palette[kd.find_nearest(p) as usize]; + let new = palette.best(p); *image.pixel_mut(x, y) = new; let error = p.asub(new); let f = |f| { @@ -79,13 +80,12 @@ pub fn floyd_steinberg<const FAC: u8>( image: Image<&[f32], 4>, palette: &[[f32; 4]], ) -> Image<Box<[f32]>, 4> { - let kd = map(palette); let mut image = Image::build(image.width(), image.height()).buf(image.buffer().to_vec().into_boxed_slice()); for (x, y) in image.serpent() { unsafe { let p = image.pixel(x, y); - let new = palette[kd.find_nearest(p) as usize]; + let new = palette.best(p); *image.pixel_mut(x, y) = new; let error = p.asub(new); let f = |f| { diff --git a/src/diffusion/riemerasma.rs b/src/diffusion/riemerasma.rs index 1d640c3..279acf5 100644 --- a/src/diffusion/riemerasma.rs +++ b/src/diffusion/riemerasma.rs @@ -41,7 +41,6 @@ impl<T, const N: usize> Ring<T, N> { } pub fn riemerasma(image: Image<&[f32], 4>, palette: &[[f32; 4]]) -> Image<Box<[f32]>, 4> { - let kd = map(palette); let mut image = Image::build(image.width(), image.height()).buf(image.buffer().to_vec().into_boxed_slice()); #[rustfmt::skip] @@ -63,7 +62,7 @@ pub fn riemerasma(image: Image<&[f32], 4>, palette: &[[f32; 4]]) -> Image<Box<[f hl( level, UP, - &mut (&mut (0, 0), &mut errors, &kd, palette, &mut image), + &mut (&mut (0, 0), &mut errors, &(), palette, &mut image), ); fn hl( level: u32, @@ -71,7 +70,7 @@ pub fn riemerasma(image: Image<&[f32], 4>, palette: &[[f32; 4]]) -> Image<Box<[f p: &mut ( &mut (i32, i32), &mut Ring<[f32; 4], 16>, - &KD, + &(), &[[f32; 4]], &mut Image<Box<[f32]>, 4>, ), @@ -95,7 +94,7 @@ pub fn riemerasma(image: Image<&[f32], 4>, palette: &[[f32; 4]]) -> Image<Box<[f // visualization.set_pixel(x, y, [stage]); // stage += 1; let px = p.4.pixel(x, y).aadd(error); - let np = p.3[p.2.find_nearest(px) as usize]; + let np = p.3.best(px); p.1.pop_front_push_back(px.asub(np)); *p.4.pixel_mut(x, y) = np; } diff --git a/src/diffusion/sierra.rs b/src/diffusion/sierra.rs index c3f3a3a..4ba814e 100644 --- a/src/diffusion/sierra.rs +++ b/src/diffusion/sierra.rs @@ -1,17 +1,15 @@ use super::*; - pub fn sierra<const FAC: u8>( image: Image<&[f32], 4>, palette: &[[f32; 4]], ) -> Image<Box<[f32]>, 4> { - let kd = map(palette); let mut image = Image::build(image.width(), image.height()).buf(image.buffer().to_vec().into_boxed_slice()); for (x, y) in image.serpent() { #[rustfmt::skip] unsafe { let p = image.pixel(x, y); - let new = palette[kd.find_nearest(p) as usize]; + let new = palette.closest(p).1; *image.pixel_mut(x, y) = new; let error = p.asub(new); let f = |f| { @@ -43,14 +41,13 @@ pub fn sierra_two<const FAC: u8>( image: Image<&[f32], 4>, palette: &[[f32; 4]], ) -> Image<Box<[f32]>, 4> { - let kd = map(palette); let mut image = Image::build(image.width(), image.height()).buf(image.buffer().to_vec().into_boxed_slice()); for (x, y) in image.serpent() { #[rustfmt::skip] unsafe { let p = image.pixel(x, y); - let new = palette[kd.find_nearest(p) as usize]; + let new = palette.best(p); *image.pixel_mut(x, y) = new; let error = p.asub(new); let f = |f| { @@ -77,14 +74,13 @@ pub fn sierra_lite<const FAC: u8>( image: Image<&[f32], 4>, palette: &[[f32; 4]], ) -> Image<Box<[f32]>, 4> { - let kd = map(palette); let mut image = Image::build(image.width(), image.height()).buf(image.buffer().to_vec().into_boxed_slice()); for (x, y) in image.serpent() { #[rustfmt::skip] unsafe { let p = image.pixel(x, y); - let new = palette[kd.find_nearest(p) as usize]; + let new = palette.best(p); *image.pixel_mut(x, y) = new; let error = p.asub(new); let f = |f| { diff --git a/src/dumb.rs b/src/dumb.rs index c406fac..6f1a800 100644 --- a/src/dumb.rs +++ b/src/dumb.rs @@ -1,27 +1,46 @@ use atools::prelude::*; -pub trait Closest { - fn closest(&self, color: [f32; 4]) -> (f32, [f32; 4], usize); +pub trait Closest<const N: usize> { + fn closest(&self, color: [f32; N]) -> (f32, [f32; N], usize); + fn best(&self, color: [f32; N]) -> [f32; N] { + self.closest(color).1 + } + fn nearest(&self, color: [f32; N]) -> usize { + self.closest(color).2 + } + fn space(&self) -> f32; } -fn euclidean_distance(f: [f32; 4], with: [f32; 4]) -> f32 { - f.asub(with).map(|x| x * x).sum() +fn euclidean_distance<const N: usize>(f: [f32; N], with: [f32; N]) -> f32 { + f.asub(with) + .map(|x| std::intrinsics::fmul_algebraic(x, x)) + .sum() } -impl Closest for &[[f32; 4]] { - fn closest(&self, color: [f32; 4]) -> (f32, [f32; 4], usize) { +impl<const N: usize> Closest<N> for &[[f32; N]] { + /// o(nn) + fn closest(&self, color: [f32; N]) -> (f32, [f32; N], usize) { self.iter() .copied() .enumerate() .map(|(i, x)| (euclidean_distance(x, color), x, i)) .min_by(|x, y| x.0.total_cmp(&y.0)) .unwrap() - // let mut best = (euclidean_distance(self[0], color), self[0], 0); - // for (&c, i) in self[1..].iter().zip(1..) { - // let d = euclidean_distance(c, color); - // if d < best.0 { - // best = (d, c, i); - // } - // } - // best + } + + /// o(nn) + fn space(&self) -> f32 { + self.iter() + .enumerate() + .map(|(i, &x)| { + self.iter() + .enumerate() + .filter(|&(j, _)| j != i) + .map(|(_, &y)| euclidean_distance(y, x)) + .min_by(|a, b| a.total_cmp(b)) + .unwrap() + .sqrt() + }) + .fold(0.0, |x, y| std::intrinsics::fadd_algebraic(x, y)) + / self.len() as f32 } } diff --git a/src/kd.rs b/src/kd.rs deleted file mode 100644 index e7de645..0000000 --- a/src/kd.rs +++ /dev/null @@ -1,219 +0,0 @@ -use atools::prelude::*; - -pub struct KD { - median: [f32; 4], - normal: [f32; 4], - index: u32, - left: Option<Box<KD>>, - right: Option<Box<KD>>, - depth: u32, -} - -impl std::fmt::Debug for KD { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "#{} {:?}", self.index, self.median)?; - let d = self.depth as usize; - if let Some(ref left) = self.left { - write!(f, "\n{} ⬅️ {left:?}", " ".repeat(d))?; - } - if let Some(ref right) = self.right { - write!(f, "\n{} ➡️ {right:?}", " ".repeat(d))?; - } - - Ok(()) - } -} - -struct KDNearest { - index: u32, - distance: f32, -} - -trait Dot { - fn dot(self, other: Self) -> f32; -} - -impl Dot for [f32; 4] { - fn dot(self, other: Self) -> f32 { - self.amul(other).sum() - } -} - -impl KD { - pub fn new(colors: &[[f32; 4]]) -> Self { - assert!(colors.len() < u32::MAX as usize); - let colors = colors.iter().copied().zip(0u32..).collect::<Vec<_>>(); - Self::_new(&mut { colors }, 0).unwrap() - } - - fn _new(colors: &mut [([f32; 4], u32)], depth: u32) -> Option<KD> { - if colors.len() == 0 { - return None; - }; - - let middle = colors.len() / 2; - - let mut sum = [0.; 4]; - let mut sum2 = [0.; 4]; - for &(c, _) in colors.iter() { - sum = sum.aadd(c); - sum2 = sum2.aadd(c.amul(c)); - } - let [r, g, b, a] = { sum2.asub(sum.amul(sum).mul(1.0 / colors.len() as f32)) }; - - let normal = [ - (r > g && r > b && r > a) as u8 as f32, - (g > b && g > a) as u8 as f32, - (b > a) as u8 as f32, - 1., - ]; - // colors.sort_by(|(a, _), (b, _)| ); - let (before, median, after) = colors.select_nth_unstable_by(middle, |(a, _), (b, _)| { - a.dot(normal).partial_cmp(&b.dot(normal)).unwrap() - }); - Some(KD { - median: median.0, - index: median.1, - left: KD::_new(before, depth + 1).map(Box::new), - right: KD::_new(after, depth + 1).map(Box::new), - normal, - depth, - }) - } - - fn _find_nearest(&self, needle: [f32; 4], mut limit: f32) -> Option<KDNearest> { - let mut result = None; - - let diff = needle.asub(self.median); - let distance = diff.dot(diff).sqrt(); - - if distance < limit { - limit = distance; - result = Some(KDNearest { - index: self.index, - distance, - }) - } - - let dot = diff.dot(self.normal); - if dot <= 0.0 { - if let Some(ref left) = self.left - && let Some(nearest) = left._find_nearest(needle, limit) - { - limit = nearest.distance; - result = Some(nearest); - } - - if -dot < limit { - if let Some(ref right) = self.right - && let Some(nearest) = right._find_nearest(needle, limit) - { - result = Some(nearest); - } - } - } else { - if let Some(ref right) = self.right - && let Some(nearest) = right._find_nearest(needle, limit) - { - limit = nearest.distance; - result = Some(nearest); - } - - if dot < limit { - if let Some(ref left) = self.left - && let Some(nearest) = left._find_nearest(needle, limit) - { - result = Some(nearest); - } - } - } - - result - } - - fn _find_nearest_excepting( - &self, - needle: [f32; 4], - mut limit: f32, - ignore_index: u32, - ) -> Option<KDNearest> { - let mut result = None; - - let diff = needle.asub(self.median); - let distance = diff.dot(diff).sqrt(); - - if distance < limit && self.index != ignore_index { - limit = distance; - result = Some(KDNearest { - index: self.index, - distance, - }) - } - - let dot = diff.dot(self.normal); - if dot <= 0.0 { - if let Some(ref left) = self.left - && let Some(nearest) = left._find_nearest_excepting(needle, limit, ignore_index) - { - limit = nearest.distance; - result = Some(nearest); - } - - if -dot < limit { - if let Some(ref right) = self.right - && let Some(nearest) = - right._find_nearest_excepting(needle, limit, ignore_index) - { - result = Some(nearest); - } - } - } else { - if let Some(ref right) = self.right - && let Some(nearest) = right._find_nearest_excepting(needle, limit, ignore_index) - { - limit = nearest.distance; - result = Some(nearest); - } - - if dot < limit { - if let Some(ref left) = self.left - && let Some(nearest) = left._find_nearest_excepting(needle, limit, ignore_index) - { - result = Some(nearest); - } - } - } - - result - } - - pub fn find_nearest(&self, color: [f32; 4]) -> u32 { - self._find_nearest(color, f32::MAX) - .map(|x| x.index) - .unwrap_or(0) - } - - pub fn space(&self, colors: &[[f32; 4]]) -> f32 { - let mut i = colors - .iter() - .zip(0..) - .map(|(c, i)| { - self._find_nearest_excepting(*c, f32::MAX, i) - .unwrap() - .distance - }) - .array_chunks::<256>(); - - let (c, sum) = i.by_ref().fold((0.0, 0.0), |(c, sum), chunk| { - let y = sum_block(chunk) - c; - let t = sum + y; - ((t - sum) - y, t) - }); - (sum + (i.into_remainder().map(sum_block).unwrap_or(0.) - c)) / colors.len() as f32 - } -} - -use std::intrinsics::fadd_algebraic; -fn sum_block(arr: impl IntoIterator<Item = f32>) -> f32 { - arr.into_iter().fold(0.0, |x, y| fadd_algebraic(x, y)) -} @@ -1,6 +1,7 @@ -#![allow(incomplete_features, internal_features)] +#![allow(incomplete_features, internal_features, mixed_script_confusables)] #![feature( isqrt, + vec_into_raw_parts, const_fn_floating_point_arithmetic, inline_const_pat, iter_chain, @@ -23,21 +24,16 @@ pub mod diffusion; pub mod ordered; -mod dumb; -mod kd; +pub mod dumb; use atools::prelude::*; +use dumb::Closest; use fimg::{indexed::IndexedImage, Image}; -use kd::KD; -// type KD = kiddo::immutable::float::kdtree::ImmutableKdTree<f32, u64, 4, 32>; -fn map(colors: &[[f32; 4]]) -> KD { - KD::new(colors) -} -fn dither<'a>( - image: Image<&[f32], 4>, - f: impl FnMut(((usize, usize), &[f32; 4])) -> u32, - pal: &'a [[f32; 4]], -) -> IndexedImage<Box<[u32]>, &'a [[f32; 4]]> { +fn dither<'a, const C: usize>( + image: Image<&[f32], C>, + f: impl FnMut(((usize, usize), &[f32; C])) -> u32, + pal: &'a [[f32; C]], +) -> IndexedImage<Box<[u32]>, &'a [[f32; C]]> { IndexedImage::build(image.width(), image.height()) .pal(pal) .buf( diff --git a/src/main.rs b/src/main.rs index 866e38e..47fe539 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,7 +1,9 @@ #![feature(slice_as_chunks, generic_const_exprs)] +use std::time::Instant; + use atools::prelude::*; use exoquant::SimpleColorSpace; -use remapper::ordered; +use fimg::{DynImage, Image}; fn main() { reemap(); @@ -24,15 +26,24 @@ fn reemap() { // }) // .take(5124) // .collect::<Vec<_>>(); - let pal = fimg::Image::<Box<[f32]>, 4>::from(fimg::Image::open("../endesga.png").as_ref()); + // let mut new = Image::<Vec<u8>, 1>::build(256, 100).alloc(); + // for pixels in unsafe { new.buffer_mut().chunks_mut(256) } { + // pixels.copy_from_slice(&(0..=u8::MAX).collect::<Vec<_>>()); + // } + // new.save("gradient.png"); + let pal = Image::<Box<[f32]>, 4>::from(Image::open("tdata/optimal.png").as_ref()); let pal = pal.flatten(); - // let pal = [ - // [0., 0., 0., 1.], - // [0.25, 0.25, 0.25, 1.], - // [0.5, 0.5, 0.5, 1.], - // [0.75, 0.75, 0.75, 1.], - // [1.; 4], - // ]; + + // let pal = &[ + // [0., 0., 0., 1.], + // [0.25, 0.25, 0.25, 1.], + // [0.5, 0.5, 0.5, 1.], + // [0.75, 0.75, 0.75, 1.], + // [1.; 4], + // ][..]; + // let pal = &[[0.], [1.]][..]; + // let pal = &[[0.], [0.25], [0.5], [0.75], [1.]][..]; + // let pal = &[0.1, 0.2, 0.3, 0.4, 0.5, 0.7, 0.9, 1.0].map(|x| [x])[..]; /*let pal = [ ] @@ -40,48 +51,59 @@ fn reemap() { .map(|x| x.join(255).map(|x| x as f32 * (1.0 / 255.0))); */ // println!("{pal:?}"); - - remapper::ordered::bayer32x32( + // dbg!(pal.space()); + let i = DynImage::open("../fimg/tdata/cat.png").to_rgba(); + // let mut pal = exoquant::generate_palette( + // &i.chunked() + // .map(|&[r, g, b, a]| exoquant::Color::new(r, g, b, a)) + // .collect::<exoquant::Histogram>(), + // &exoquant::SimpleColorSpace::default(), + // &exoquant::optimizer::KMeans, + // 64, + // ) + // .into_iter() + // .map(|exoquant::Color { r, g, b, a }| [r, g, b, a].map(|x| x as f32 * (1.0 / 255.0))) + // .collect::<Vec<_>>(); + // pal.sort_by(|a, b| { + // let lum = |[r, g, b, _]: [f32; 4]| 0.2126 * r + 0.7152 * g + 0.0722 * b; + // lum(*a).total_cmp(&lum(*b)) + // }); + // Image::<_, 4>::build(8, 8) + // .buf(pal.as_slice().as_flattened()) + // .to_u8() + // .save("tdata/optimal.png"); + // i.save("gamma/gray.png"); + let i = i.to_f32(); + // decode(2.2, encode(1.0, i.as_ref())) + // .to_u8() + // .save("gamma/1_0.png"); + // decode(2.2, encode(2.0, i.as_ref())) + // .to_u8() + // .save("gamma/2_0.png"); + // decode(2.2, encode(2.2, i.as_ref())) + // .to_u8() + // .save("gamma/2_2.png"); + // decode(2.2, encode(2.4, i.as_ref())) + // .to_u8() + // .save("gamma/2_4.png"); + let x = remapper::ordered::remap( // fimg::Image::<&[u8], 4>::make::<256, 256>().as_ref(), - fimg::Image::<Vec<u8>, 4>::open("../fimg/tdata/cat.png") - .as_ref() - .to_f32() - .as_ref(), + i.as_ref(), &pal, ) .to() .to_u8() - .show() - .save("yeee.png"); -} - -fn eomap() { - let x = fimg::Image::<Vec<u8>, 4>::open("../fimg/tdata/cat.png"); - let pal = fimg::Image::open("../endesga.png"); - let pal = pal.flatten(); - let res = exoquant::Remapper::new( - &pal.iter() - .map(|&[r, g, b, a]| exoquant::Color::new(r, g, b, a)) - .collect::<Vec<_>>(), - &SimpleColorSpace::default(), - &exoquant::ditherer::FloydSteinberg::new(), + .show(); + let now = Instant::now(); + let x = remapper::ordered::blue( + // fimg::Image::<&[u8], 4>::make::<256, 256>().as_ref(), + i.as_ref(), + &pal, ) - .remap( - &x.chunked() - .map(|&[r, g, b, a]| exoquant::Color::new(r, g, b, a)) - .collect::<Vec<_>>(), - x.width() as usize, - ); - let (width, height) = (x.width() as usize, x.height() as usize); - let pixels = (0..width) - .flat_map(|x_| (0..height).map(move |y| (x_, y))) - .filter_map(move |(x_, y_)| match res[(height - y_ - 1) * width + x_] { - 0 => None, - x => Some(((x_, y_), x)), - }); - let mut preview = fimg::Image::build(x.width(), x.height()).fill([0; 3].join(255)); - for ((x, y), i) in pixels { - unsafe { preview.set_pixel(x as _, (height - y - 1) as _, pal[i as usize]) }; - } - preview.save("eoquan.png"); + .to() + .to_u8(); + dbg!(now.elapsed()); + x.save("yeee.png"); + // .show() + // .save("yeee.png"); } diff --git a/src/ordered.rs b/src/ordered.rs index bef2945..489cbde 100644 --- a/src/ordered.rs +++ b/src/ordered.rs @@ -49,11 +49,11 @@ const BAYER_16X16: [f32; 16 * 16] = threshold(BAYER3); const BAYER_32X32: [f32; 32 * 32] = threshold(BAYER4); const BAYER_64X64: [f32; 64 * 64] = threshold(BAYER5); -fn dither_with<'a, const N: usize>( - image: Image<&[f32], 4>, - mut f: impl FnMut(((usize, usize), &[f32; 4])) -> u32, - palette: &'a [[f32; 4]], -) -> IndexedImage<Box<[u32]>, &'a [[f32; 4]]> { +fn dither_with<'a, const N: usize, const C: usize>( + image: Image<&[f32], C>, + mut f: impl FnMut(((usize, usize), &[f32; C])) -> u32, + palette: &'a [[f32; C]], +) -> IndexedImage<Box<[u32]>, &'a [[f32; C]]> { dither(image, |((x, y), p)| f(((x % N, y % N), p)), palette) } @@ -62,17 +62,16 @@ macro_rules! bayer { /// Ordered dithering via a bayer matrix. /// /// Dont expect too much difference from each of them. - pub fn $i<'a>( - image: Image<&[f32], 4>, - palette: &'a [[f32; 4]], - ) -> IndexedImage<Box<[u32]>, &'a [[f32; 4]]> { - let kd = map(palette); - let r = kd.space(palette); - dither_with::<$j>( + pub fn $i<'a, const C: usize>( + image: Image<&[f32], C>, + palette: &'a [[f32; C]], + ) -> IndexedImage<Box<[u32]>, &'a [[f32; C]]> { + let r = palette.space(); + dither_with::<$j, C>( image.into(), |((x, y), &p)| { let color = p.add(r * $c[x + y * $j]); - kd.find_nearest(color) + palette.closest(color).2 as u32 }, palette, ) @@ -87,95 +86,120 @@ bayer!(bayer16x16, BAYER_16X16, 16); bayer!(bayer32x32, BAYER_32X32, 32); bayer!(bayer64x64, BAYER_64X64, 64); -pub fn remap<'a, 'b>( - image: Image<&'b [f32], 4>, - palette: &'a [[f32; 4]], -) -> IndexedImage<Box<[u32]>, &'a [[f32; 4]]> { - let kd = map(palette); +pub fn remap<'a, const C: usize>( + image: Image<&[f32], C>, + palette: &'a [[f32; C]], +) -> IndexedImage<Box<[u32]>, &'a [[f32; C]]> { // todo!(); IndexedImage::build(image.width(), image.height()) .pal(palette) - .buf(image.chunked().map(|x| kd.find_nearest(*x)).collect()) + .buf( + image + .chunked() + .map(|x| palette.nearest(*x) as u32) + .collect(), + ) } -const BLUE: Image<[f32; 1024 * 1024 * 3], 3> = unsafe { +const BLUE: Image<[f32; 1024 * 1024], 1> = unsafe { Image::new( std::num::NonZero::new(1024).unwrap(), std::num::NonZero::new(1024).unwrap(), - std::mem::transmute(*include_bytes!("../blue.f32")), + std::mem::transmute(*include_bytes!("../blue_1.f32")), ) }; -// todo: figure this out? seems off. -/* -pub fn remap_blue(image: Image<&[f32], 4>, palette: &[[f32; 4]]) -> Image<Box<[f32]>, 4> { - let kd = map(palette); - // Image::<Box<[u8]>, 3>::from(BLUE.as_ref()).show(); - dither(image, |((x, y), p)| { - let (p, al) = p.pop(); - let noise = unsafe { BLUE.pixel(x as u32 % 1024, y as u32 % 1024) }.sub(0.5); - - fn lin_to_srgb(x: f32) -> f32 { - if x.abs() <= 0.0031308 { - x * 12.92 - } else { - (1.055 * x.abs().powf(1.0 / 2.4) - 0.055).copysign(x) - } - } - fn srgb_to_lin(x: f32) -> f32 { - if x.abs() <= 0.04045 { - x * (1.0 / 12.92) - } else { - ((x.abs() + 0.055) * (1.0 / 1.055)).powf(2.4).copysign(x) - } - } +// const Γ: f32 = 2.4; +const A: f32 = 12.92; + +const U: f32 = 0.04045; +const C: f32 = 0.055; +fn srgb_to_lin(x: f32, γ: f32) -> f32 { + // x.powf(1.0 / Γ) + // https://wikimedia.org/api/rest_v1/media/math/render/svg/e401b31b97a8ddcf1de2b87b3606a278a645324e + if x <= U { + // x / A + x * (1.0 / A) + } else { + // x + C / ⎞ ^ Γ + // 1 + C ⎠ + ((x + C) * (1.0 / (1.0 + C))).powf(γ) + } +} - let c = p - .map(srgb_to_lin) - .zip(noise) - .map(|(x, noise)| x + noise) - .map(lin_to_srgb) - .join(al); - // let yuv = [ - // p.amul([0.299, 0.587, 0.114]).sum(), - // p.amul([-0.14713, -0.28886, 0.436]).sum(), - // p.amul([0.615, -0.51499, -0.10001]).sum(), - // ]; - // let c = yuv.zip(noise).map(|(x, noise)| x + noise); - // let c = [ - // c.amul([1., 0., 1.13983]).sum(), - // c.amul([1., -0.39465, -0.58060]).sum(), - // c.amul([1., 2.03211, 0.]).sum(), - // ]; - - // let c = c.join(al); - palette[kd.find_nearest(c) as usize] - }) +const V: f32 = 0.0031308; +fn lin_to_srgb(x: f32, γ: f32) -> f32 { + // x.powf(Γ) + if x <= V { + A * x + } else { + // (1 + C)x¹⸍ᵞ - C + (1.0 + C) * x.powf(1.0 / γ) - C + } } +pub fn encode<const C: usize, T: AsRef<[f32]>>( + γ: f32, image: Image<T, C> +) -> Image<Box<[f32]>, C> { + Image::build(image.width(), image.height()).buf( + image + .chunked() + .flat_map(|x| x.map(|x| srgb_to_lin(x, γ))) + .collect(), + ) +} +pub fn decode<const C: usize, T: AsRef<[f32]>>( + γ: f32, image: Image<T, C> +) -> Image<Box<[f32]>, C> { + Image::build(image.width(), image.height()).buf( + image + .chunked() + .flat_map(|x| x.map(|x| lin_to_srgb(x, γ))) + .collect(), + ) +} + +pub fn blue<'a, const C: usize>( + image: Image<&[f32], C>, + palette: &'a [[f32; C]], +) -> IndexedImage<Box<[u32]>, &'a [[f32; C]]> { + dither_with::<1024, C>( + image, + |((x, y), p)| unsafe { + let [noise] = BLUE.pixel(x as u32, y as u32); + palette.nearest(p.add(noise - 0.5)) as u32 + }, + palette, + ) +} -pub fn remap_triangular(image: Image<&[f32], 4>, palette: &[[f32; 4]]) -> Image<Box<[f32]>, 4> { - let kd = map(palette); - dither(image, |((x, y), p)| { - let (p, al) = p.pop(); - let noise = unsafe { BLUE.pixel(x as u32 % 1024, y as u32 % 1024) }; - let c = p - .zip(noise) - .map(|(x, noise)| { - let noise = if x < (0.5 / 255.) || x > (254.5 / 255.) { - noise - 0.5 - } else { - if noise < 0.5 { - (2.0 * noise).sqrt() - 1.0 - } else { - 1.0 - (2.0 - 2.0 * noise).sqrt() - } - }; - x + noise - }) - .join(al); - palette[kd.find_nearest(c) as usize] - }) +pub fn triangular<'a, const C: usize>( + image: Image<&[f32], C>, + palette: &'a [[f32; C]], +) -> IndexedImage<Box<[u32]>, &'a [[f32; C]]> +where +{ + // https://computergraphics.stackexchange.com/questions/5904/whats-a-proper-way-to-clamp-dither-noise/5952#5952 + fn triangle(x: f32, noise: f32) -> f32 { + let noise = if x < (0.5 / 255.) || x > (254.5 / 255.) { + noise - 0.5 + } else { + if noise < 0.5 { + (2.0 * noise).sqrt() - 1.0 + } else { + 1.0 - (2.0 - 2.0 * noise).sqrt() + } + }; + x + noise * 0.9 + } + + dither_with::<1024, C>( + image, + |((x, y), p)| unsafe { + let [noise] = BLUE.pixel(x as u32, y as u32); + palette.nearest(p.map(|x| triangle(x, noise))) as u32 + }, + palette, + ) } -*/ |