move some stuff
| -rw-r--r-- | Cargo.toml | 6 | ||||
| -rw-r--r-- | src/diffusion.rs | 112 | ||||
| -rw-r--r-- | src/diffusion/riemerasma.rs | 164 | ||||
| -rw-r--r-- | src/diffusion/sierra.rs | 111 | ||||
| -rw-r--r-- | src/lib.rs | 145 | ||||
| -rw-r--r-- | src/main.rs | 25 | ||||
| -rw-r--r-- | src/ordered.rs | 118 |
7 files changed, 531 insertions, 150 deletions
@@ -13,3 +13,9 @@ fimg = { version = "0.4.43", git = "https://github.com/bend-n/fimg", default-fea ] } fux_kdtree = "0.2.0" rand = "0.8.5" + + +[profile.release] +debug = true +lto = "thin" +codegen-units = 1 diff --git a/src/diffusion.rs b/src/diffusion.rs new file mode 100644 index 0000000..ea58adc --- /dev/null +++ b/src/diffusion.rs @@ -0,0 +1,112 @@ +//! # Error diffusion dithering. +//! The way this works is by finding the amount of error between the quantized color and the original color, and offseting the error to the (next) neighboring pixels. +//! Which neighboring pixels depend on the algorithm chosen. +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); + let mut image = + Image::build(image.width(), image.height()).buf(image.buffer().to_vec().into_boxed_slice()); + let w = image.width(); + let h = image.height(); + let eighth = [1. / 8.; 4]; + for (x, y) in (0..h).flat_map(move |y| (0..w).map(move |x| (x, y))) { + unsafe { + /* + * 1 1 + 1 1 1 + 1 + */ + let p = image.pixel(x, y); + let new = palette[kd.find_nearest(p) as usize]; + *image.pixel_mut(x, y) = new; + let error = p.asub(new); + let f = |x: [f32; 4]| x.aadd(error.amul(eighth)); + image.replace(x + 1, y, f); + image.replace(x + 2, y, f); + + image.replace(x.wrapping_sub(1), y + 1, f); + image.replace(x, y + 1, f); + image.replace(x + 1, y + 1, f); + + image.replace(x, y + 2, f); + } + } + image +} + +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()); + let w = image.width(); + let h = image.height(); + for (x, y) in (0..h).flat_map(move |y| (0..w).map(move |x| (x, y))) { + #[rustfmt::skip] + unsafe { + let p = image.pixel(x, y); + let new = palette[kd.find_nearest(p) as usize]; + *image.pixel_mut(x, y) = new; + let error = p.asub(new); + let f = |f| { + move |x: [f32; 4]| { + x.aadd(error.amul([(f as f32 / 48.) * const { FAC as f32 * (1.0 / 255.) }; 4])) + } + }; + /* * 7 5 + 3 5 7 5 3 + 1 3 5 3 1*/ + image.replace(x + 1, y, f(7)); + image.replace(x + 2, y, f(5)); + let y = y + 1; + image.replace(x.wrapping_sub(2), y, f(3)); + image.replace(x.wrapping_sub(1), y, f(5)); + image.replace(x , y, f(7)); + image.replace(x + 1, y, f(5)); + image.replace(x + 2, y, f(3)); + let y = y + 1; + image.replace(x.wrapping_sub(2), y, f(1)); + image.replace(x.wrapping_sub(1), y, f(3)); + image.replace(x , y, f(5)); + image.replace(x + 1, y, f(3)); + image.replace(x + 2, y, f(1)); + } + } + image +} +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()); + let w = image.width(); + let h = image.height(); + for (x, y) in (0..h).flat_map(move |y| (0..w).map(move |x| (x, y))) { + unsafe { + let p = image.pixel(x, y); + let new = palette[kd.find_nearest(p) as usize]; + *image.pixel_mut(x, y) = new; + let error = p.asub(new); + let f = |f| { + move |x: [f32; 4]| { + x.aadd(error.amul([(f as f32 / 48.) * const { FAC as f32 * (1.0 / 255.) }; 4])) + } + }; + /* + * 7 + 3 5 1 */ + image.replace(x + 1, y, f(7)); + image.replace(x.wrapping_sub(1), y + 1, f(3)); + image.replace(x, y + 1, f(5)); + image.replace(x + 1, y + 1, f(1)); + } + } + image +} diff --git a/src/diffusion/riemerasma.rs b/src/diffusion/riemerasma.rs new file mode 100644 index 0000000..09b18bc --- /dev/null +++ b/src/diffusion/riemerasma.rs @@ -0,0 +1,164 @@ +//! https://www.compuphase.com/riemer.htm +use std::{collections::VecDeque, mem::MaybeUninit}; + +use super::*; + +#[test] +fn x() { + let mut q = Ring::new([0.0, 2.0, 5.0]); + dbg!(q.iter().collect::<Vec<_>>()); + assert_eq!(q.pop_front_push_back(6.0), 0.0); + dbg!(q.iter().collect::<Vec<_>>()); + assert_eq!(q.pop_front_push_back(3.0), 2.0); + dbg!(q.iter().collect::<Vec<_>>()); + assert_eq!(q.pop_front_push_back(4.0), 5.0); + dbg!(q.iter().collect::<Vec<_>>()); + assert_eq!(q.pop_front_push_back(7.0), 6.0); + dbg!(q.iter().collect::<Vec<_>>()); +} + +pub struct Ring<T, const N: usize> { + arr: [T; N], + front: u8, +} +impl<T, const N: usize> Ring<T, N> { + pub fn new(contents: [T; N]) -> Self { + Ring { + arr: contents, + front: 0, + } + } + pub fn pop_front_push_back(&mut self, push_back: T) -> T { + unsafe { + let e = std::mem::replace(self.arr.get_unchecked_mut(self.front as usize), push_back); + self.front += 1; + self.front %= N as u8; + e + } + } + + pub fn iter(&self) -> impl Iterator<Item = &T> { + self.arr.iter().cycle().skip(self.front as _).take(N as _) + } +} + +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] + const WEIGH: [f32; 16] = [0.0625, 0.07518906, 0.09045432, 0.10881881, 0.13091174, 0.15749009, 0.18946451, 0.22793055, 0.27420613, 0.32987684, 0.39685008, 0.47742057, 0.57434887, 0.69095606, 0.8312374, 1.0]; + let mut errors = Ring::<[f32; 4], 16>::new([[0.; 4]; 16]); + let mut level = image.width().max(image.height()).ilog2(); + if (1 << level) < image.width().max(image.height()) { + level += 1; + } + // static mut visualization: Image<[u8; 256 * 256], 1> = fimg::make!(1 channels 256 x 256); + // static mut stage: u8 = 0; + enum Dir { + UP, + DOWN, + LEFT, + RIGHT, + } + use Dir::*; + hl( + level, + UP, + &mut (&mut (0, 0), &mut errors, &kd, palette, &mut image), + ); + fn hl( + level: u32, + dir: Dir, + p: &mut ( + &mut (i32, i32), + &mut Ring<[f32; 4], 16>, + &KD, + &[[f32; 4]], + &mut Image<Box<[f32]>, 4>, + ), + ) { + macro_rules! mv { + ($dir: expr) => {{ + unsafe { + if p.0 .0 >= 0 + && p.0 .0 < p.4.width() as i32 + && p.0 .1 >= 0 + && p.0 .1 < p.4.height() as i32 + { + let error = + p.1.iter() + .zip(WEIGH) + .map(|(&a, b)| a.mul(b)) + .fold([0.; 4], |acc, x| acc.aadd(x)) + .div(WEIGH.len() as f32); + let (x, y) = *p.0; + let (x, y) = (x as u32, y as u32); + // 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]; + p.1.pop_front_push_back(px.asub(np)); + *p.4.pixel_mut(x, y) = np; + } + match $dir { + LEFT => p.0 .0 -= 1, + RIGHT => p.0 .0 += 1, + UP => p.0 .1 -= 1, + DOWN => p.0 .1 += 1, + } + } + }}; + } + macro_rules! dir { + (^) => { + UP + }; + (>) => { + RIGHT + }; + (<) => { + LEFT + }; + (v) => { + DOWN + }; + } + macro_rules! pattern { + ($($x:tt)+) => {{ + $(mv!(dir!($x));)+ + }}; + } + macro_rules! hilbert { + ($a1:tt $b1:tt $a2:tt $b2:tt $a3:tt $b3:tt $b4:tt) => {{ + hl(level - 1, dir!($a1), p); + mv!(dir!($b1)); + + hl(level - 1, dir!($a2), p); + mv!(dir!($b2)); + + hl(level - 1, dir!($a3), p); + mv!(dir!($b3)); + + hl(level - 1, dir!($b4), p); + }}; + } + if level == 1 { + match dir { + LEFT => pattern!(>v<), + RIGHT => pattern!(<^>), + UP => pattern!(v>^), + DOWN => pattern!(^<v), + } + } else { + match dir { + LEFT => hilbert!(^> <v < < v), + RIGHT => hilbert!(v< >^ > > ^), + UP => hilbert!(<v ^> ^^ >), + DOWN => hilbert!(>^ v< v v <), + } + } + } + // unsafe { visualization.as_ref().show() }; + image +} diff --git a/src/diffusion/sierra.rs b/src/diffusion/sierra.rs new file mode 100644 index 0000000..4177f1b --- /dev/null +++ b/src/diffusion/sierra.rs @@ -0,0 +1,111 @@ +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()); + let w = image.width(); + let h = image.height(); + for (x, y) in (0..h).flat_map(move |y| (0..w).map(move |x| (x, y))) { + #[rustfmt::skip] + unsafe { + let p = image.pixel(x, y); + let new = palette[kd.find_nearest(p) as usize]; + *image.pixel_mut(x, y) = new; + let error = p.asub(new); + let f = |f| { + move |x: [f32; 4]| { + x.aadd(error.amul([(f as f32 / 32.) * const { FAC as f32 * (1.0 / 255.) }; 4])) + } + }; + /* * 5 3 + 2 4 5 4 2 + 2 3 2 */ + image.replace(x + 1, y, f(5)); + image.replace(x + 2, y, f(3)); + let y = y + 1; + image.replace(x.wrapping_sub(2), y, f(2)); + image.replace(x.wrapping_sub(1), y, f(4)); + image.replace(x , y, f(5)); + image.replace(x + 1, y, f(4)); + image.replace(x + 2, y, f(2)); + let y = y + 1; + image.replace(x.wrapping_sub(1), y, f(2)); + image.replace(x , y, f(3)); + image.replace(x + 1, y, f(2)); + } + } + image +} + +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()); + let w = image.width(); + let h = image.height(); + for (x, y) in (0..h).flat_map(move |y| (0..w).map(move |x| (x, y))) { + #[rustfmt::skip] + unsafe { + let p = image.pixel(x, y); + let new = palette[kd.find_nearest(p) as usize]; + *image.pixel_mut(x, y) = new; + let error = p.asub(new); + let f = |f| { + move |x: [f32; 4]| { + x.aadd(error.amul([(f as f32 / 16.) * const { FAC as f32 * (1.0 / 255.) }; 4])) + } + }; + /* * 4 3 + 1 2 3 2 1 */ + image.replace(x + 1, y, f(4)); + image.replace(x + 2, y, f(3)); + let y = y + 1; + image.replace(x.wrapping_sub(2), y, f(1)); + image.replace(x.wrapping_sub(1), y, f(2)); + image.replace(x , y, f(3)); + image.replace(x + 1, y, f(2)); + image.replace(x + 2, y, f(1)); + } + } + image +} + +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()); + let w = image.width(); + let h = image.height(); + for (x, y) in (0..h).flat_map(move |y| (0..w).map(move |x| (x, y))) { + #[rustfmt::skip] + unsafe { + let p = image.pixel(x, y); + let new = palette[kd.find_nearest(p) as usize]; + *image.pixel_mut(x, y) = new; + let error = p.asub(new); + let f = |f| { + move |x: [f32; 4]| { + x.aadd(error.amul([(f as f32 / 4.) * const { FAC as f32 * (1.0 / 255.) }; 4])) + } + }; + #[allow(warnings)] + /** 2 + 1 1 */ + image.replace(x + 1, y, f(2)); + let y = y + 1; + image.replace(x.wrapping_sub(1), y, f(1)); + image.replace(x , y, f(1)); + } + } + image +} @@ -1,6 +1,9 @@ +#![allow(incomplete_features, internal_features)] #![feature( + inline_const_pat, const_option, adt_const_params, + stmt_expr_attributes, iter_array_chunks, let_chains, effects, @@ -13,6 +16,10 @@ array_windows, iter_map_windows )] + +pub mod diffusion; +pub mod ordered; + mod dumb; mod kd; use atools::prelude::*; @@ -73,141 +80,3 @@ fn dither_with<const N: usize>( ) -> Image<Box<[f32]>, 4> { dither(image, |((x, y), p)| f(((x % N, y % N), p))) } -const BLUE: Image<[f32; 1024 * 1024 * 3], 3> = unsafe { - Image::new( - std::num::NonZero::new(1024).unwrap(), - std::num::NonZero::new(1024).unwrap(), - std::mem::transmute(*include_bytes!("../blue.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); - let c = p.zip(noise).map(|(x, noise)| x + noise).join(al); - palette[kd.find_nearest(c) as usize] - }) -} - -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 remap_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()); - let w = image.width(); - let h = image.height(); - let fac = FAC as f32 * (1.0 / 255.0); - let 七 = 7. / 16. * fac; - let 三 = 3. / 16. * fac; - let 五 = 5. / 16. * fac; - let 一 = 1. / 16. * fac; - for (x, y) in (0..h).flat_map(move |y| (0..w).map(move |x| (x, y))) { - unsafe { - let p = image.pixel(x, y); - let new = palette[kd.find_nearest(p) as usize]; - *image.pixel_mut(x, y) = new; - let error = p.asub(new); - let f = |f| move |x: [f32; 4]| x.aadd(error.amul([f; 4])); - image.replace(x + 1, y, f(七)); - image.replace(x.wrapping_sub(1), y + 1, f(三)); - image.replace(x, y + 1, f(五)); - image.replace(x + 1, y + 1, f(一)); - } - } - image -} - -pub fn remap_atkinson(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()); - let w = image.width(); - let h = image.height(); - let eighth = [1. / 8.; 4]; - for (x, y) in (0..h).flat_map(move |y| (0..w).map(move |x| (x, y))) { - unsafe { - let p = image.pixel(x, y); - let new = palette[kd.find_nearest(p) as usize]; - *image.pixel_mut(x, y) = new; - let error = p.asub(new); - let f = |x: [f32; 4]| x.aadd(error.amul(eighth)); - image.replace(x + 1, y, f); - image.replace(x + 2, y, f); - - image.replace(x.wrapping_sub(1), y + 1, f); - image.replace(x, y + 1, f); - image.replace(x + 1, y + 1, f); - - image.replace(x, y + 2, f); - } - } - image -} - -pub fn remap_bayer_2x2(image: Image<&[f32], 4>, palette: &[[f32; 4]]) -> Image<Box<[f32]>, 4> { - let kd = map(palette); - let r = kd.space(palette); - dither_with::<2>(image, |((x, y), &p)| { - let color = p.add(r * BAYER_2X2[x + y * 2]); - palette[kd.find_nearest(color) as usize] - }) -} - -pub fn remap_bayer_4x4(image: Image<&[f32], 4>, palette: &[[f32; 4]]) -> Image<Box<[f32]>, 4> { - let kd = map(palette); - let r = kd.space(palette); - dither_with::<4>(image, |((x, y), &p)| { - let color = p.add(r * BAYER_4X4[x + y * 4]); - palette[kd.find_nearest(color) as usize] - }) -} - -pub fn remap_bayer_8x8(image: Image<&[f32], 4>, palette: &[[f32; 4]]) -> Image<Box<[f32]>, 4> { - let kd = map(palette); - let r = kd.space(palette); - dither_with::<8>(image, |((x, y), &p)| { - let color = p.add(r * BAYER_8X8[x + y * 8]); - palette[kd.find_nearest(color) as usize] - }) -} - -pub fn remap(image: Image<&[f32], 4>, palette: &[[f32; 4]]) -> Image<Box<[f32]>, 4> { - let kd = map(palette); - // todo!(); - Image::build(image.width(), image.height()).buf( - image - .chunked() - .flat_map(|x| palette[kd.find_nearest(*x) as usize]) - // .map(|&x| palette.closest(x).1) - .collect(), - ) -} diff --git a/src/main.rs b/src/main.rs index 83496c1..a7f2ce4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,7 +4,7 @@ use exoquant::SimpleColorSpace; fn main() { reemap(); - eomap(); + // eomap(); // let mut rng = rand::thread_rng(); // let pal = std::iter::repeat_with(|| { // let a: [f32; 3] = std::array::from_fn(|_| rng.next_u64() as f32 / u64::MAX as f32); @@ -25,11 +25,11 @@ fn reemap() { let pal = fimg::Image::<Box<[f32]>, 4>::from(fimg::Image::open("../endesga.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], + // [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 = [ @@ -40,13 +40,14 @@ fn reemap() { // println!("{pal:?}"); fimg::Image::<Box<[u8]>, 4>::from( - remapper::remap_triangular( + remapper::diffusion::riemerasma( fimg::Image::<Box<[f32]>, 4>::from( - fimg::Image::<Vec<u8>, 4>::open("../fimg/tdata/cat.png") - // .show() - // .scale::<fimg::scale::Nearest>(800, 480) - // .show() - .as_ref(), + fimg::Image::<&[u8], 4>::make::<256, 256>().as_ref(), + // fimg::Image::<Vec<u8>, 4>::open("../fimg/tdata/cat.png") + // .show() + // .scale::<fimg::scale::Nearest>(800, 480) + // .show() + // .as_ref(), ) .as_ref(), &pal, diff --git a/src/ordered.rs b/src/ordered.rs new file mode 100644 index 0000000..d36adbc --- /dev/null +++ b/src/ordered.rs @@ -0,0 +1,118 @@ +//! # Ordered dithering. +//! The way this works is by adding a constant texture to the image, and then quantizing that. +use super::*; +pub fn remap_bayer_2x2(image: Image<&[f32], 4>, palette: &[[f32; 4]]) -> Image<Box<[f32]>, 4> { + let kd = map(palette); + let r = kd.space(palette); + dither_with::<2>(image, |((x, y), &p)| { + let color = p.add(r * BAYER_2X2[x + y * 2]); + palette[kd.find_nearest(color) as usize] + }) +} + +pub fn remap_bayer_4x4(image: Image<&[f32], 4>, palette: &[[f32; 4]]) -> Image<Box<[f32]>, 4> { + let kd = map(palette); + let r = kd.space(palette); + dither_with::<4>(image, |((x, y), &p)| { + let color = p.add(r * BAYER_4X4[x + y * 4]); + palette[kd.find_nearest(color) as usize] + }) +} + +pub fn remap_bayer_8x8(image: Image<&[f32], 4>, palette: &[[f32; 4]]) -> Image<Box<[f32]>, 4> { + let kd = map(palette); + let r = kd.space(palette); + dither_with::<8>(image, |((x, y), &p)| { + let color = p.add(r * BAYER_8X8[x + y * 8]); + palette[kd.find_nearest(color) as usize] + }) +} + +pub fn remap(image: Image<&[f32], 4>, palette: &[[f32; 4]]) -> Image<Box<[f32]>, 4> { + let kd = map(palette); + // todo!(); + Image::build(image.width(), image.height()).buf( + image + .chunked() + .flat_map(|x| palette[kd.find_nearest(*x) as usize]) + // .map(|&x| palette.closest(x).1) + .collect(), + ) +} +const BLUE: Image<[f32; 1024 * 1024 * 3], 3> = unsafe { + Image::new( + std::num::NonZero::new(1024).unwrap(), + std::num::NonZero::new(1024).unwrap(), + std::mem::transmute(*include_bytes!("../blue.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) + } + } + + 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] + }) +} + +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] + }) +} |