From dff5b4e9331b41e6d5b57bfcb65a542c27661ccf Mon Sep 17 00:00:00 2001 From: bendn Date: Tue, 3 Dec 2024 08:56:31 +0700 Subject: [PATCH] add float conversions --- src/convert.rs | 85 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 85 insertions(+) diff --git a/src/convert.rs b/src/convert.rs index 2b0d192..fbc0f75 100644 --- a/src/convert.rs +++ b/src/convert.rs @@ -1,6 +1,11 @@ //! define From's for images. //! these conversions are defined by [`PFrom`]. use crate::{pixels::convert::PFrom, Image, Pack}; +use core::intrinsics::{fmul_algebraic, fsub_algebraic, transmute_unchecked as transmute}; +use std::{ + mem::MaybeUninit as MU, + simd::{prelude::*, SimdElement, StdFloat}, +}; fn map(image: Image<&[u8], A>) -> Image, B> where @@ -93,3 +98,83 @@ where unsafe { Self::new(value.width, value.height, buf) } } } + +fn u8_to_f32(x: u8) -> f32 { + let magic = 2.0f32.powf(23.); + // x = 2^23 + x + let x = f32::from_bits((x as u32) ^ magic.to_bits()); + fmul_algebraic(fsub_algebraic(x, magic), 1.0 / 255.0) +} + +fn u8s_to_f32s(x: u8x8) -> f32x8 { + let x = x.cast::(); + let magic = (1 << 23) as f32; + // SAFETY: its a simd, i can do what i want with it + let x = unsafe { transmute::<_, f32x8>(x ^ Simd::splat(magic.to_bits())) }; + x.mul_add(Simd::splat(1.0 / 255.0), Simd::splat(-magic / 255.0)) +} + +// notice: this f32 better be in range 0.0-1.0 +fn f32_to_u8(x: f32) -> u8 { + let magic = (1 << 23) as f32; + (x.mul_add(255.0, magic).to_bits() ^ magic.to_bits()) as u8 +} + +fn f32s_to_u8s(x: f32x8) -> u8x8 { + let magic = (1 << 23) as f32; + (x.mul_add(Simd::splat(255.0), Simd::splat(magic)).cast() ^ Simd::splat(magic.to_bits())).cast() +} + +fn mapping( + x: &[T], + mut f: impl FnMut(Simd) -> Simd, + mut single: impl FnMut(T) -> U, +) -> Vec +where + T: SimdElement, + U: SimdElement, + [(); (size_of::>() == size_of::<[T; 8]>()) as usize - 1]:, +{ + let mut out = Vec::with_capacity(x.len()); + let to = out.spare_capacity_mut(); + let (to, to_rest) = to.as_chunks_mut::<8>(); + let (from, from_rest) = x.as_chunks::<8>(); + for (&line, into) in from.iter().zip(to) { + // SAFETY: safe transmute (see condition) + unsafe { *into = transmute::<_, [MU; 8]>(f(Simd::from_array(line))) }; + } + for (i, &from) in from_rest.iter().enumerate() { + // SAFETY: compiler doesnt like it when i zip this + unsafe { to_rest.get_unchecked_mut(i) }.write(single(from)); + } + // SAFETY: initialized. + unsafe { out.set_len(x.len()) }; + out +} + +impl From> for Image, N> { + /// Reduce to 0.0-1.0 from 0-255. + fn from(value: Image<&[u8], N>) -> Self { + // SAFETY: length unchanged + unsafe { value.mapped(|x| mapping(x, u8s_to_f32s, u8_to_f32).into_boxed_slice()) } + } +} + +impl From> for Image, N> { + /// Expand to 0-255 from 0.0-1.0 + fn from(value: Image<&[f32], N>) -> Self { + // SAFETY: length unchanged + unsafe { value.mapped(|x| mapping(x, f32s_to_u8s, f32_to_u8).into_boxed_slice()) } + } +} + +#[test] +fn roundtrip() { + let original = Image::<_, 3>::open("tdata/small_cat.png"); + assert!( + Image::, 3>::from(Image::, 3>::from(original.as_ref()).as_ref(),) + // .show() + .bytes() + == original.bytes() + ); +}