Diffstat (limited to 'src/ordered.rs')
| -rw-r--r-- | src/ordered.rs | 142 |
1 files changed, 84 insertions, 58 deletions
diff --git a/src/ordered.rs b/src/ordered.rs index 84b7220..bef2945 100644 --- a/src/ordered.rs +++ b/src/ordered.rs @@ -1,5 +1,7 @@ //! # Ordered dithering. //! The way this works is by adding a constant texture to the image, and then quantizing that. +use fimg::indexed::IndexedImage; + use super::*; const fn threshold<const N: usize>(x: [u32; N]) -> [f32; N] { @@ -7,75 +9,95 @@ const fn threshold<const N: usize>(x: [u32; N]) -> [f32; N] { - 0.5 * ((N - 1) as f32 * (1. / N as f32))) } -static BAYER_2X2: [f32; 4] = { - threshold([ - 0, 2, // - 3, 1, - ]) -}; -static BAYER_4X4: [f32; 4 * 4] = { - threshold([ - 0, 8, 2, 10, // - 12, 4, 14, 6, // - 3, 11, 1, 9, // - 15, 7, 13, 5, - ]) -}; - -pub const BAYER_8X8: [f32; 8 * 8] = threshold(mattr::transposed::<_, 8, 8>(car::from_fn!(|p| { - let q = p ^ (p >> 3); - // https://bisqwit.iki.fi/story/howto/dither/jy/ - #[rustfmt::skip] - (((p & 4) >> 2) | ((q & 4) >> 1) - | ((p & 2) << 1) | ((q & 2) << 2) - | ((p & 1) << 4) | ((q & 1) << 5)) as u32 -}))); - -fn dither_with<const N: usize>( - image: Image<&[f32], 4>, - mut f: impl FnMut(((usize, usize), &[f32; 4])) -> [f32; 4], -) -> Image<Box<[f32]>, 4> { - dither(image, |((x, y), p)| f(((x % N, y % N), p))) +const fn next<const N: usize>(input: [u32; N]) -> [u32; N.isqrt() * 2 * N.isqrt() * 2] +where + [(); N.isqrt() * N.isqrt()]:, +{ + next_::<{ N.isqrt() }>(unsafe { std::intrinsics::transmute_unchecked(input) }) } -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] - }) +// https://github.com/surma/surma.dev/blob/master/static/lab/ditherpunk/bayer-worker.js#L6C1-L23C1 +const fn next_<const N: usize>(input: [u32; N * N]) -> [u32; N * 2 * N * 2] { + let mut output = [0; { N * 2 * N * 2 }]; + let base = [0, 2, 3, 1]; + let mut y = 0; + while y != N * 2 { + let mut x = 0; + while x != N * 2 { + output[y * N * 2 + x] = 4 * input[(y % N) * N + (x % N)] + + base[((y >= N) as usize) * 2 + ((x >= N) as usize)]; + x += 1; + } + y += 1; + } + output } +const BAYER0: [u32; 4] = [ + 0, 2, // + 3, 1, +]; +const BAYER1: [u32; 4 * 4] = next(BAYER0); +const BAYER2: [u32; 8 * 8] = next(BAYER1); +const BAYER3: [u32; 16 * 16] = next(BAYER2); +const BAYER4: [u32; 32 * 32] = next(BAYER3); +const BAYER5: [u32; 64 * 64] = next(BAYER4); -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] - }) +const BAYER_2X2: [f32; 4] = threshold(BAYER0); +const BAYER_4X4: [f32; 4 * 4] = threshold(BAYER1); +const BAYER_8X8: [f32; 8 * 8] = threshold(BAYER2); +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]]> { + dither(image, |((x, y), p)| f(((x % N, y % N), p)), palette) } -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] - }) +macro_rules! bayer { + ($i:ident, $c:ident, $j:literal) => { + /// 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>( + image.into(), + |((x, y), &p)| { + let color = p.add(r * $c[x + y * $j]); + kd.find_nearest(color) + }, + palette, + ) + } + }; } -pub fn remap(image: Image<&[f32], 4>, palette: &[[f32; 4]]) -> Image<Box<[f32]>, 4> { +bayer!(bayer2x2, BAYER_2X2, 2); +bayer!(bayer4x4, BAYER_4X4, 4); +bayer!(bayer8x8, BAYER_8X8, 8); +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); // 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(), - ) + IndexedImage::build(image.width(), image.height()) + .pal(palette) + .buf(image.chunked().map(|x| kd.find_nearest(*x)).collect()) } + const BLUE: Image<[f32; 1024 * 1024 * 3], 3> = unsafe { Image::new( std::num::NonZero::new(1024).unwrap(), @@ -84,6 +106,7 @@ const BLUE: Image<[f32; 1024 * 1024 * 3], 3> = unsafe { ) }; // 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(); @@ -130,6 +153,8 @@ pub fn remap_blue(image: Image<&[f32], 4>, palette: &[[f32; 4]]) -> Image<Box<[f }) } + + pub fn remap_triangular(image: Image<&[f32], 4>, palette: &[[f32; 4]]) -> Image<Box<[f32]>, 4> { let kd = map(palette); dither(image, |((x, y), p)| { @@ -153,3 +178,4 @@ pub fn remap_triangular(image: Image<&[f32], 4>, palette: &[[f32; 4]]) -> Image< palette[kd.find_nearest(c) as usize] }) } +*/ |