Diffstat (limited to 'src/ordered.rs')
-rw-r--r--src/ordered.rs200
1 files changed, 112 insertions, 88 deletions
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,
+ )
}
-*/