-rw-r--r--Cargo.toml6
-rw-r--r--src/diffusion.rs112
-rw-r--r--src/diffusion/riemerasma.rs164
-rw-r--r--src/diffusion/sierra.rs111
-rw-r--r--src/lib.rs145
-rw-r--r--src/main.rs25
-rw-r--r--src/ordered.rs118
7 files changed, 531 insertions, 150 deletions
diff --git a/Cargo.toml b/Cargo.toml
index 0109ccf..6e38bb3 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -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
+}
diff --git a/src/lib.rs b/src/lib.rs
index e948531..acf4e8b 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -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]
+ })
+}