add basic diffusion
bendn 2024-12-14
parent b60ee5d · commit 251e527
-rw-r--r--Cargo.lock20
-rw-r--r--Cargo.toml3
-rw-r--r--blue.f32bin0 -> 12582912 bytes
-rw-r--r--src/lib.rs105
-rw-r--r--src/main.rs13
5 files changed, 124 insertions, 17 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 57fe57d..fb60c62 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -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",
diff --git a/Cargo.toml b/Cargo.toml
index 276403e..0109ccf 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -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
new file mode 100644
index 0000000..f92fc3a
--- /dev/null
+++ b/blue.f32
Binary files differ
diff --git a/src/lib.rs b/src/lib.rs
index bb1ee5f..23602a9 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -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()