fix for more image types
bendn 2025-01-25
parent 8aa6365 · commit 3a89624
-rw-r--r--Cargo.lock7
-rw-r--r--Cargo.toml1
-rw-r--r--blue.f32bin12582912 -> 0 bytes
-rw-r--r--blue_1.f32bin0 -> 4194304 bytes
-rw-r--r--src/diffusion.rs18
-rw-r--r--src/diffusion/riemerasma.rs7
-rw-r--r--src/diffusion/sierra.rs10
-rw-r--r--src/dumb.rs47
-rw-r--r--src/kd.rs219
-rw-r--r--src/lib.rs22
-rw-r--r--src/main.rs116
-rw-r--r--src/ordered.rs200
12 files changed, 238 insertions, 409 deletions
diff --git a/Cargo.lock b/Cargo.lock
index f4c6610..fd3eadd 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -106,12 +106,6 @@ dependencies = [
]
[[package]]
-name = "fux_kdtree"
-version = "0.2.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "216bf8256834a87394bf0364f82a92444ee8a00c757630a02a2bf278456f74e7"
-
-[[package]]
name = "getrandom"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -234,7 +228,6 @@ dependencies = [
"car",
"exoquant",
"fimg",
- "fux_kdtree",
"mattr 0.0.3",
"rand",
]
diff --git a/Cargo.toml b/Cargo.toml
index 1df7b6b..cfd33dc 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -11,7 +11,6 @@ fimg = { version = "0.4.43", git = "https://github.com/bend-n/fimg", default-fea
"save",
"scale",
] }
-fux_kdtree = "0.2.0"
mattr = "0.0.3"
rand = "0.8.5"
diff --git a/blue.f32 b/blue.f32
deleted file mode 100644
index f92fc3a..0000000
--- a/blue.f32
+++ /dev/null
Binary files differ
diff --git a/blue_1.f32 b/blue_1.f32
new file mode 100644
index 0000000..c518540
--- /dev/null
+++ b/blue_1.f32
Binary files differ
diff --git a/src/diffusion.rs b/src/diffusion.rs
index 494fa7e..257cfce 100644
--- a/src/diffusion.rs
+++ b/src/diffusion.rs
@@ -5,11 +5,13 @@ 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);
+pub fn atkinson<const N: usize>(
+ image: Image<&[f32], N>,
+ palette: &[[f32; N]],
+) -> Image<Box<[f32]>, N> {
let mut image =
Image::build(image.width(), image.height()).buf(image.buffer().to_vec().into_boxed_slice());
- let eighth = [1. / 8.; 4];
+ let eighth = [1. / 8.; N];
for (x, y) in image.serpent() {
unsafe {
/*
@@ -18,10 +20,10 @@ pub fn atkinson(image: Image<&[f32], 4>, palette: &[[f32; 4]]) -> Image<Box<[f32
1
*/
let p = image.pixel(x, y);
- let new = palette[kd.find_nearest(p) as usize];
+ let new = palette.best(p);
*image.pixel_mut(x, y) = new;
let error = p.asub(new);
- let f = |x: [f32; 4]| x.aadd(error.amul(eighth));
+ let f = |x: [f32; N]| x.aadd(error.amul(eighth));
image.replace(x + 1, y, f);
image.replace(x + 2, y, f);
@@ -39,14 +41,13 @@ 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());
for (x, y) in image.serpent() {
#[rustfmt::skip]
unsafe {
let p = image.pixel(x, y);
- let new = palette[kd.find_nearest(p) as usize];
+ let new = palette.best(p);
*image.pixel_mut(x, y) = new;
let error = p.asub(new);
let f = |f| {
@@ -79,13 +80,12 @@ 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());
for (x, y) in image.serpent() {
unsafe {
let p = image.pixel(x, y);
- let new = palette[kd.find_nearest(p) as usize];
+ let new = palette.best(p);
*image.pixel_mut(x, y) = new;
let error = p.asub(new);
let f = |f| {
diff --git a/src/diffusion/riemerasma.rs b/src/diffusion/riemerasma.rs
index 1d640c3..279acf5 100644
--- a/src/diffusion/riemerasma.rs
+++ b/src/diffusion/riemerasma.rs
@@ -41,7 +41,6 @@ impl<T, const N: usize> Ring<T, N> {
}
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]
@@ -63,7 +62,7 @@ pub fn riemerasma(image: Image<&[f32], 4>, palette: &[[f32; 4]]) -> Image<Box<[f
hl(
level,
UP,
- &mut (&mut (0, 0), &mut errors, &kd, palette, &mut image),
+ &mut (&mut (0, 0), &mut errors, &(), palette, &mut image),
);
fn hl(
level: u32,
@@ -71,7 +70,7 @@ pub fn riemerasma(image: Image<&[f32], 4>, palette: &[[f32; 4]]) -> Image<Box<[f
p: &mut (
&mut (i32, i32),
&mut Ring<[f32; 4], 16>,
- &KD,
+ &(),
&[[f32; 4]],
&mut Image<Box<[f32]>, 4>,
),
@@ -95,7 +94,7 @@ pub fn riemerasma(image: Image<&[f32], 4>, palette: &[[f32; 4]]) -> Image<Box<[f
// 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];
+ let np = p.3.best(px);
p.1.pop_front_push_back(px.asub(np));
*p.4.pixel_mut(x, y) = np;
}
diff --git a/src/diffusion/sierra.rs b/src/diffusion/sierra.rs
index c3f3a3a..4ba814e 100644
--- a/src/diffusion/sierra.rs
+++ b/src/diffusion/sierra.rs
@@ -1,17 +1,15 @@
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());
for (x, y) in image.serpent() {
#[rustfmt::skip]
unsafe {
let p = image.pixel(x, y);
- let new = palette[kd.find_nearest(p) as usize];
+ let new = palette.closest(p).1;
*image.pixel_mut(x, y) = new;
let error = p.asub(new);
let f = |f| {
@@ -43,14 +41,13 @@ 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());
for (x, y) in image.serpent() {
#[rustfmt::skip]
unsafe {
let p = image.pixel(x, y);
- let new = palette[kd.find_nearest(p) as usize];
+ let new = palette.best(p);
*image.pixel_mut(x, y) = new;
let error = p.asub(new);
let f = |f| {
@@ -77,14 +74,13 @@ 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());
for (x, y) in image.serpent() {
#[rustfmt::skip]
unsafe {
let p = image.pixel(x, y);
- let new = palette[kd.find_nearest(p) as usize];
+ let new = palette.best(p);
*image.pixel_mut(x, y) = new;
let error = p.asub(new);
let f = |f| {
diff --git a/src/dumb.rs b/src/dumb.rs
index c406fac..6f1a800 100644
--- a/src/dumb.rs
+++ b/src/dumb.rs
@@ -1,27 +1,46 @@
use atools::prelude::*;
-pub trait Closest {
- fn closest(&self, color: [f32; 4]) -> (f32, [f32; 4], usize);
+pub trait Closest<const N: usize> {
+ fn closest(&self, color: [f32; N]) -> (f32, [f32; N], usize);
+ fn best(&self, color: [f32; N]) -> [f32; N] {
+ self.closest(color).1
+ }
+ fn nearest(&self, color: [f32; N]) -> usize {
+ self.closest(color).2
+ }
+ fn space(&self) -> f32;
}
-fn euclidean_distance(f: [f32; 4], with: [f32; 4]) -> f32 {
- f.asub(with).map(|x| x * x).sum()
+fn euclidean_distance<const N: usize>(f: [f32; N], with: [f32; N]) -> f32 {
+ f.asub(with)
+ .map(|x| std::intrinsics::fmul_algebraic(x, x))
+ .sum()
}
-impl Closest for &[[f32; 4]] {
- fn closest(&self, color: [f32; 4]) -> (f32, [f32; 4], usize) {
+impl<const N: usize> Closest<N> for &[[f32; N]] {
+ /// o(nn)
+ fn closest(&self, color: [f32; N]) -> (f32, [f32; N], usize) {
self.iter()
.copied()
.enumerate()
.map(|(i, x)| (euclidean_distance(x, color), x, i))
.min_by(|x, y| x.0.total_cmp(&y.0))
.unwrap()
- // let mut best = (euclidean_distance(self[0], color), self[0], 0);
- // for (&c, i) in self[1..].iter().zip(1..) {
- // let d = euclidean_distance(c, color);
- // if d < best.0 {
- // best = (d, c, i);
- // }
- // }
- // best
+ }
+
+ /// o(nn)
+ fn space(&self) -> f32 {
+ self.iter()
+ .enumerate()
+ .map(|(i, &x)| {
+ self.iter()
+ .enumerate()
+ .filter(|&(j, _)| j != i)
+ .map(|(_, &y)| euclidean_distance(y, x))
+ .min_by(|a, b| a.total_cmp(b))
+ .unwrap()
+ .sqrt()
+ })
+ .fold(0.0, |x, y| std::intrinsics::fadd_algebraic(x, y))
+ / self.len() as f32
}
}
diff --git a/src/kd.rs b/src/kd.rs
deleted file mode 100644
index e7de645..0000000
--- a/src/kd.rs
+++ /dev/null
@@ -1,219 +0,0 @@
-use atools::prelude::*;
-
-pub struct KD {
- median: [f32; 4],
- normal: [f32; 4],
- index: u32,
- left: Option<Box<KD>>,
- right: Option<Box<KD>>,
- depth: u32,
-}
-
-impl std::fmt::Debug for KD {
- fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
- write!(f, "#{} {:?}", self.index, self.median)?;
- let d = self.depth as usize;
- if let Some(ref left) = self.left {
- write!(f, "\n{} ⬅️ {left:?}", " ".repeat(d))?;
- }
- if let Some(ref right) = self.right {
- write!(f, "\n{} ➡️ {right:?}", " ".repeat(d))?;
- }
-
- Ok(())
- }
-}
-
-struct KDNearest {
- index: u32,
- distance: f32,
-}
-
-trait Dot {
- fn dot(self, other: Self) -> f32;
-}
-
-impl Dot for [f32; 4] {
- fn dot(self, other: Self) -> f32 {
- self.amul(other).sum()
- }
-}
-
-impl KD {
- pub fn new(colors: &[[f32; 4]]) -> Self {
- assert!(colors.len() < u32::MAX as usize);
- let colors = colors.iter().copied().zip(0u32..).collect::<Vec<_>>();
- Self::_new(&mut { colors }, 0).unwrap()
- }
-
- fn _new(colors: &mut [([f32; 4], u32)], depth: u32) -> Option<KD> {
- if colors.len() == 0 {
- return None;
- };
-
- let middle = colors.len() / 2;
-
- let mut sum = [0.; 4];
- let mut sum2 = [0.; 4];
- for &(c, _) in colors.iter() {
- sum = sum.aadd(c);
- sum2 = sum2.aadd(c.amul(c));
- }
- let [r, g, b, a] = { sum2.asub(sum.amul(sum).mul(1.0 / colors.len() as f32)) };
-
- let normal = [
- (r > g && r > b && r > a) as u8 as f32,
- (g > b && g > a) as u8 as f32,
- (b > a) as u8 as f32,
- 1.,
- ];
- // colors.sort_by(|(a, _), (b, _)| );
- let (before, median, after) = colors.select_nth_unstable_by(middle, |(a, _), (b, _)| {
- a.dot(normal).partial_cmp(&b.dot(normal)).unwrap()
- });
- Some(KD {
- median: median.0,
- index: median.1,
- left: KD::_new(before, depth + 1).map(Box::new),
- right: KD::_new(after, depth + 1).map(Box::new),
- normal,
- depth,
- })
- }
-
- fn _find_nearest(&self, needle: [f32; 4], mut limit: f32) -> Option<KDNearest> {
- let mut result = None;
-
- let diff = needle.asub(self.median);
- let distance = diff.dot(diff).sqrt();
-
- if distance < limit {
- limit = distance;
- result = Some(KDNearest {
- index: self.index,
- distance,
- })
- }
-
- let dot = diff.dot(self.normal);
- if dot <= 0.0 {
- if let Some(ref left) = self.left
- && let Some(nearest) = left._find_nearest(needle, limit)
- {
- limit = nearest.distance;
- result = Some(nearest);
- }
-
- if -dot < limit {
- if let Some(ref right) = self.right
- && let Some(nearest) = right._find_nearest(needle, limit)
- {
- result = Some(nearest);
- }
- }
- } else {
- if let Some(ref right) = self.right
- && let Some(nearest) = right._find_nearest(needle, limit)
- {
- limit = nearest.distance;
- result = Some(nearest);
- }
-
- if dot < limit {
- if let Some(ref left) = self.left
- && let Some(nearest) = left._find_nearest(needle, limit)
- {
- result = Some(nearest);
- }
- }
- }
-
- result
- }
-
- fn _find_nearest_excepting(
- &self,
- needle: [f32; 4],
- mut limit: f32,
- ignore_index: u32,
- ) -> Option<KDNearest> {
- let mut result = None;
-
- let diff = needle.asub(self.median);
- let distance = diff.dot(diff).sqrt();
-
- if distance < limit && self.index != ignore_index {
- limit = distance;
- result = Some(KDNearest {
- index: self.index,
- distance,
- })
- }
-
- let dot = diff.dot(self.normal);
- if dot <= 0.0 {
- if let Some(ref left) = self.left
- && let Some(nearest) = left._find_nearest_excepting(needle, limit, ignore_index)
- {
- limit = nearest.distance;
- result = Some(nearest);
- }
-
- if -dot < limit {
- if let Some(ref right) = self.right
- && let Some(nearest) =
- right._find_nearest_excepting(needle, limit, ignore_index)
- {
- result = Some(nearest);
- }
- }
- } else {
- if let Some(ref right) = self.right
- && let Some(nearest) = right._find_nearest_excepting(needle, limit, ignore_index)
- {
- limit = nearest.distance;
- result = Some(nearest);
- }
-
- if dot < limit {
- if let Some(ref left) = self.left
- && let Some(nearest) = left._find_nearest_excepting(needle, limit, ignore_index)
- {
- result = Some(nearest);
- }
- }
- }
-
- result
- }
-
- pub fn find_nearest(&self, color: [f32; 4]) -> u32 {
- self._find_nearest(color, f32::MAX)
- .map(|x| x.index)
- .unwrap_or(0)
- }
-
- pub fn space(&self, colors: &[[f32; 4]]) -> f32 {
- let mut i = colors
- .iter()
- .zip(0..)
- .map(|(c, i)| {
- self._find_nearest_excepting(*c, f32::MAX, i)
- .unwrap()
- .distance
- })
- .array_chunks::<256>();
-
- let (c, sum) = i.by_ref().fold((0.0, 0.0), |(c, sum), chunk| {
- let y = sum_block(chunk) - c;
- let t = sum + y;
- ((t - sum) - y, t)
- });
- (sum + (i.into_remainder().map(sum_block).unwrap_or(0.) - c)) / colors.len() as f32
- }
-}
-
-use std::intrinsics::fadd_algebraic;
-fn sum_block(arr: impl IntoIterator<Item = f32>) -> f32 {
- arr.into_iter().fold(0.0, |x, y| fadd_algebraic(x, y))
-}
diff --git a/src/lib.rs b/src/lib.rs
index 0267653..73d4bcc 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,6 +1,7 @@
-#![allow(incomplete_features, internal_features)]
+#![allow(incomplete_features, internal_features, mixed_script_confusables)]
#![feature(
isqrt,
+ vec_into_raw_parts,
const_fn_floating_point_arithmetic,
inline_const_pat,
iter_chain,
@@ -23,21 +24,16 @@
pub mod diffusion;
pub mod ordered;
-mod dumb;
-mod kd;
+pub mod dumb;
use atools::prelude::*;
+use dumb::Closest;
use fimg::{indexed::IndexedImage, Image};
-use kd::KD;
-// type KD = kiddo::immutable::float::kdtree::ImmutableKdTree<f32, u64, 4, 32>;
-fn map(colors: &[[f32; 4]]) -> KD {
- KD::new(colors)
-}
-fn dither<'a>(
- image: Image<&[f32], 4>,
- f: impl FnMut(((usize, usize), &[f32; 4])) -> u32,
- pal: &'a [[f32; 4]],
-) -> IndexedImage<Box<[u32]>, &'a [[f32; 4]]> {
+fn dither<'a, const C: usize>(
+ image: Image<&[f32], C>,
+ f: impl FnMut(((usize, usize), &[f32; C])) -> u32,
+ pal: &'a [[f32; C]],
+) -> IndexedImage<Box<[u32]>, &'a [[f32; C]]> {
IndexedImage::build(image.width(), image.height())
.pal(pal)
.buf(
diff --git a/src/main.rs b/src/main.rs
index 866e38e..47fe539 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,7 +1,9 @@
#![feature(slice_as_chunks, generic_const_exprs)]
+use std::time::Instant;
+
use atools::prelude::*;
use exoquant::SimpleColorSpace;
-use remapper::ordered;
+use fimg::{DynImage, Image};
fn main() {
reemap();
@@ -24,15 +26,24 @@ fn reemap() {
// })
// .take(5124)
// .collect::<Vec<_>>();
- let pal = fimg::Image::<Box<[f32]>, 4>::from(fimg::Image::open("../endesga.png").as_ref());
+ // let mut new = Image::<Vec<u8>, 1>::build(256, 100).alloc();
+ // for pixels in unsafe { new.buffer_mut().chunks_mut(256) } {
+ // pixels.copy_from_slice(&(0..=u8::MAX).collect::<Vec<_>>());
+ // }
+ // new.save("gradient.png");
+ let pal = Image::<Box<[f32]>, 4>::from(Image::open("tdata/optimal.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],
- // ];
+
+ // 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],
+ // ][..];
+ // let pal = &[[0.], [1.]][..];
+ // let pal = &[[0.], [0.25], [0.5], [0.75], [1.]][..];
+ // let pal = &[0.1, 0.2, 0.3, 0.4, 0.5, 0.7, 0.9, 1.0].map(|x| [x])[..];
/*let pal = [
]
@@ -40,48 +51,59 @@ fn reemap() {
.map(|x| x.join(255).map(|x| x as f32 * (1.0 / 255.0)));
*/
// println!("{pal:?}");
-
- remapper::ordered::bayer32x32(
+ // dbg!(pal.space());
+ let i = DynImage::open("../fimg/tdata/cat.png").to_rgba();
+ // let mut pal = exoquant::generate_palette(
+ // &i.chunked()
+ // .map(|&[r, g, b, a]| exoquant::Color::new(r, g, b, a))
+ // .collect::<exoquant::Histogram>(),
+ // &exoquant::SimpleColorSpace::default(),
+ // &exoquant::optimizer::KMeans,
+ // 64,
+ // )
+ // .into_iter()
+ // .map(|exoquant::Color { r, g, b, a }| [r, g, b, a].map(|x| x as f32 * (1.0 / 255.0)))
+ // .collect::<Vec<_>>();
+ // pal.sort_by(|a, b| {
+ // let lum = |[r, g, b, _]: [f32; 4]| 0.2126 * r + 0.7152 * g + 0.0722 * b;
+ // lum(*a).total_cmp(&lum(*b))
+ // });
+ // Image::<_, 4>::build(8, 8)
+ // .buf(pal.as_slice().as_flattened())
+ // .to_u8()
+ // .save("tdata/optimal.png");
+ // i.save("gamma/gray.png");
+ let i = i.to_f32();
+ // decode(2.2, encode(1.0, i.as_ref()))
+ // .to_u8()
+ // .save("gamma/1_0.png");
+ // decode(2.2, encode(2.0, i.as_ref()))
+ // .to_u8()
+ // .save("gamma/2_0.png");
+ // decode(2.2, encode(2.2, i.as_ref()))
+ // .to_u8()
+ // .save("gamma/2_2.png");
+ // decode(2.2, encode(2.4, i.as_ref()))
+ // .to_u8()
+ // .save("gamma/2_4.png");
+ let x = remapper::ordered::remap(
// fimg::Image::<&[u8], 4>::make::<256, 256>().as_ref(),
- fimg::Image::<Vec<u8>, 4>::open("../fimg/tdata/cat.png")
- .as_ref()
- .to_f32()
- .as_ref(),
+ i.as_ref(),
&pal,
)
.to()
.to_u8()
- .show()
- .save("yeee.png");
-}
-
-fn eomap() {
- 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(
- &pal.iter()
- .map(|&[r, g, b, a]| exoquant::Color::new(r, g, b, a))
- .collect::<Vec<_>>(),
- &SimpleColorSpace::default(),
- &exoquant::ditherer::FloydSteinberg::new(),
+ .show();
+ let now = Instant::now();
+ let x = remapper::ordered::blue(
+ // fimg::Image::<&[u8], 4>::make::<256, 256>().as_ref(),
+ i.as_ref(),
+ &pal,
)
- .remap(
- &x.chunked()
- .map(|&[r, g, b, a]| exoquant::Color::new(r, g, b, a))
- .collect::<Vec<_>>(),
- x.width() as usize,
- );
- let (width, height) = (x.width() as usize, x.height() as usize);
- let pixels = (0..width)
- .flat_map(|x_| (0..height).map(move |y| (x_, y)))
- .filter_map(move |(x_, y_)| match res[(height - y_ - 1) * width + x_] {
- 0 => None,
- x => Some(((x_, y_), x)),
- });
- let mut preview = fimg::Image::build(x.width(), x.height()).fill([0; 3].join(255));
- for ((x, y), i) in pixels {
- unsafe { preview.set_pixel(x as _, (height - y - 1) as _, pal[i as usize]) };
- }
- preview.save("eoquan.png");
+ .to()
+ .to_u8();
+ dbg!(now.elapsed());
+ x.save("yeee.png");
+ // .show()
+ // .save("yeee.png");
}
diff --git a/src/ordered.rs b/src/ordered.rs
index bef2945..489cbde 100644
--- a/src/ordered.rs
+++ b/src/ordered.rs
@@ -49,11 +49,11 @@ 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]]> {
+fn dither_with<'a, const N: usize, const C: usize>(
+ image: Image<&[f32], C>,
+ mut f: impl FnMut(((usize, usize), &[f32; C])) -> u32,
+ palette: &'a [[f32; C]],
+) -> IndexedImage<Box<[u32]>, &'a [[f32; C]]> {
dither(image, |((x, y), p)| f(((x % N, y % N), p)), palette)
}
@@ -62,17 +62,16 @@ macro_rules! bayer {
/// 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>(
+ pub fn $i<'a, const C: usize>(
+ image: Image<&[f32], C>,
+ palette: &'a [[f32; C]],
+ ) -> IndexedImage<Box<[u32]>, &'a [[f32; C]]> {
+ let r = palette.space();
+ dither_with::<$j, C>(
image.into(),
|((x, y), &p)| {
let color = p.add(r * $c[x + y * $j]);
- kd.find_nearest(color)
+ palette.closest(color).2 as u32
},
palette,
)
@@ -87,95 +86,120 @@ 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);
+pub fn remap<'a, const C: usize>(
+ image: Image<&[f32], C>,
+ palette: &'a [[f32; C]],
+) -> IndexedImage<Box<[u32]>, &'a [[f32; C]]> {
// todo!();
IndexedImage::build(image.width(), image.height())
.pal(palette)
- .buf(image.chunked().map(|x| kd.find_nearest(*x)).collect())
+ .buf(
+ image
+ .chunked()
+ .map(|x| palette.nearest(*x) as u32)
+ .collect(),
+ )
}
-const BLUE: Image<[f32; 1024 * 1024 * 3], 3> = unsafe {
+const BLUE: Image<[f32; 1024 * 1024], 1> = unsafe {
Image::new(
std::num::NonZero::new(1024).unwrap(),
std::num::NonZero::new(1024).unwrap(),
- std::mem::transmute(*include_bytes!("../blue.f32")),
+ std::mem::transmute(*include_bytes!("../blue_1.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)
- }
- }
+// const Γ: f32 = 2.4;
+const A: f32 = 12.92;
+
+const U: f32 = 0.04045;
+const C: f32 = 0.055;
+fn srgb_to_lin(x: f32, γ: f32) -> f32 {
+ // x.powf(1.0 / Γ)
+ // https://wikimedia.org/api/rest_v1/media/math/render/svg/e401b31b97a8ddcf1de2b87b3606a278a645324e
+ if x <= U {
+ // x / A
+ x * (1.0 / A)
+ } else {
+ // x + C / ⎞ ^ Γ
+ // 1 + C ⎠
+ ((x + C) * (1.0 / (1.0 + C))).powf(γ)
+ }
+}
- 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]
- })
+const V: f32 = 0.0031308;
+fn lin_to_srgb(x: f32, γ: f32) -> f32 {
+ // x.powf(Γ)
+ if x <= V {
+ A * x
+ } else {
+ // (1 + C)x¹⸍ᵞ - C
+ (1.0 + C) * x.powf(1.0 / γ) - C
+ }
}
+pub fn encode<const C: usize, T: AsRef<[f32]>>(
+ γ: f32, image: Image<T, C>
+) -> Image<Box<[f32]>, C> {
+ Image::build(image.width(), image.height()).buf(
+ image
+ .chunked()
+ .flat_map(|x| x.map(|x| srgb_to_lin(x, γ)))
+ .collect(),
+ )
+}
+pub fn decode<const C: usize, T: AsRef<[f32]>>(
+ γ: f32, image: Image<T, C>
+) -> Image<Box<[f32]>, C> {
+ Image::build(image.width(), image.height()).buf(
+ image
+ .chunked()
+ .flat_map(|x| x.map(|x| lin_to_srgb(x, γ)))
+ .collect(),
+ )
+}
+
+pub fn blue<'a, const C: usize>(
+ image: Image<&[f32], C>,
+ palette: &'a [[f32; C]],
+) -> IndexedImage<Box<[u32]>, &'a [[f32; C]]> {
+ dither_with::<1024, C>(
+ image,
+ |((x, y), p)| unsafe {
+ let [noise] = BLUE.pixel(x as u32, y as u32);
+ palette.nearest(p.add(noise - 0.5)) as u32
+ },
+ palette,
+ )
+}
-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 triangular<'a, const C: usize>(
+ image: Image<&[f32], C>,
+ palette: &'a [[f32; C]],
+) -> IndexedImage<Box<[u32]>, &'a [[f32; C]]>
+where
+{
+ // https://computergraphics.stackexchange.com/questions/5904/whats-a-proper-way-to-clamp-dither-noise/5952#5952
+ fn triangle(x: f32, noise: f32) -> f32 {
+ 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.9
+ }
+
+ dither_with::<1024, C>(
+ image,
+ |((x, y), p)| unsafe {
+ let [noise] = BLUE.pixel(x as u32, y as u32);
+ palette.nearest(p.map(|x| triangle(x, noise))) as u32
+ },
+ palette,
+ )
}
-*/