add basic diffusion
| -rw-r--r-- | Cargo.lock | 20 | ||||
| -rw-r--r-- | Cargo.toml | 3 | ||||
| -rw-r--r-- | blue.f32 | bin | 0 -> 12582912 bytes | |||
| -rw-r--r-- | src/lib.rs | 105 | ||||
| -rw-r--r-- | src/main.rs | 13 |
5 files changed, 124 insertions, 17 deletions
@@ -66,19 +66,27 @@ checksum = "5048aeecde83b7455a63b44aa54da5321d6171cff2adb8a5959849e142e5e4aa" [[package]] name = "fdeflate" -version = "0.3.6" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07c6f4c64c1d33a3111c4466f7365ebdcc37c5bd1ea0d62aae2e3d722aacbedb" +checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c" dependencies = [ "simd-adler32", ] [[package]] +name = "fer" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a02dba6a60cd31533cf16561ced53239686d18f1464bff49579dd320fcea081" + +[[package]] name = "fimg" version = "0.4.43" +source = "git+https://github.com/bend-n/fimg#3e3ca7b2ee24e1a62b8b49d54bf903248371f010" dependencies = [ "atools", "clipline", + "fer", "libc", "mattr", "png", @@ -115,9 +123,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.167" +version = "0.2.168" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09d6582e104315a817dff97f75133544b2e094ee22447d2acf4a74e189ba06fc" +checksum = "5aaeb2981e0606ca11d79718f8bb01164f1d6ed75080182d3abf017e6d244b6d" [[package]] name = "mattr" @@ -137,9 +145,9 @@ dependencies = [ [[package]] name = "png" -version = "0.17.14" +version = "0.17.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52f9d46a34a05a6a57566bc2bfae066ef07585a6e3fa30fbbdff5936380623f0" +checksum = "b67582bd5b65bdff614270e2ea89a1cf15bef71245cc1e5f7ea126977144211d" dependencies = [ "bitflags", "crc32fast", @@ -7,8 +7,9 @@ edition = "2021" atools = "0.1.5" car = "0.1.1" exoquant = "0.2.0" -fimg = { version = "0.4.43", path = "../fimg", default-features = false, features = [ +fimg = { version = "0.4.43", git = "https://github.com/bend-n/fimg", default-features = false, features = [ "save", + "scale", ] } fux_kdtree = "0.2.0" rand = "0.8.5" diff --git a/blue.f32 b/blue.f32 Binary files differnew file mode 100644 index 0000000..f92fc3a --- /dev/null +++ b/blue.f32 @@ -1,4 +1,5 @@ #![feature( + const_option, adt_const_params, iter_array_chunks, let_chains, @@ -16,7 +17,6 @@ mod dumb; mod kd; use atools::prelude::*; use dumb::Closest; -use exoquant::Remapper; use fimg::Image; use kd::KD; // type KD = kiddo::immutable::float::kdtree::ImmutableKdTree<f32, u64, 4, 32>; @@ -62,16 +62,111 @@ fn dither( image .rows() .enumerate() - .flat_map(|(x, p)| p.iter().enumerate().map(move |(y, p)| ((x % 2, y % 2), p))) + .flat_map(|(x, p)| p.iter().enumerate().map(move |(y, p)| ((x, y), p))) .flat_map(f) .collect(), ) } +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))) +} + +pub fn remap_triangular(image: Image<&[f32], 4>, palette: &[[f32; 4]]) -> Image<Box<[f32]>, 4> { + let kd = map(palette); + 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")), + ) + }; + 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 - 0.2 + }) + .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(image, |((x, y), &p)| { + dither_with::<2>(image, |((x, y), &p)| { let color = p.add(r * BAYER_2X2[x + y * 2]); palette[kd.find_nearest(color) as usize] }) @@ -80,7 +175,7 @@ pub fn remap_bayer_2x2(image: Image<&[f32], 4>, palette: &[[f32; 4]]) -> Image<B 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(image, |((x, y), &p)| { + dither_with::<4>(image, |((x, y), &p)| { let color = p.add(r * BAYER_4X4[x + y * 4]); palette[kd.find_nearest(color) as usize] }) @@ -89,7 +184,7 @@ pub fn remap_bayer_4x4(image: Image<&[f32], 4>, palette: &[[f32; 4]]) -> Image<B 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(image, |((x, y), &p)| { + dither_with::<8>(image, |((x, y), &p)| { let color = p.add(r * BAYER_8X8[x + y * 8]); palette[kd.find_nearest(color) as usize] }) diff --git a/src/main.rs b/src/main.rs index f2d45f0..83496c1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,7 +1,6 @@ #![feature(slice_as_chunks, generic_const_exprs)] use atools::prelude::*; use exoquant::SimpleColorSpace; -use rand::{Rng, RngCore}; fn main() { reemap(); @@ -41,9 +40,13 @@ fn reemap() { // println!("{pal:?}"); fimg::Image::<Box<[u8]>, 4>::from( - remapper::remap_bayer_8x8( + remapper::remap_triangular( fimg::Image::<Box<[f32]>, 4>::from( - fimg::Image::<Vec<u8>, 4>::open("../fimg/tdata/cat.png").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, @@ -54,7 +57,7 @@ fn reemap() { } fn eomap() { - let x = fimg::Image::<Vec<u8>, 4>::open("../drawing-1.png"); + 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( @@ -62,7 +65,7 @@ fn eomap() { .map(|&[r, g, b, a]| exoquant::Color::new(r, g, b, a)) .collect::<Vec<_>>(), &SimpleColorSpace::default(), - &exoquant::ditherer::Ordered, + &exoquant::ditherer::FloydSteinberg::new(), ) .remap( &x.chunked() |