From 0b0646288446137ea9f2dc688a9c0e00f1b561bc Mon Sep 17 00:00:00 2001 From: awxkee Date: Sun, 2 Jun 2024 13:27:33 +0100 Subject: [PATCH] Image to LAB/XYZ improvements --- Cargo.lock | 16 +-- assets/asset_middle.jpg | Bin 0 -> 65617 bytes src/app/src/main.rs | 78 +++++++++--- src/image_to_xyz_lab.rs | 42 ++++--- src/image_xyza_laba.rs | 177 ++++++++++++++++++++++++++ src/lib.rs | 8 ++ src/neon_linear_to_image.rs | 8 +- src/neon_to_xyz_lab.rs | 16 +-- src/neon_to_xyza_laba.rs | 220 +++++++++++++++++++++++++++++++++ src/neon_xyz_lab_to_image.rs | 10 +- src/neon_xyza_laba_to_image.rs | 208 +++++++++++++++++++++++++++++++ src/xyza_laba_to_image.rs | 166 +++++++++++++++++++++++++ 12 files changed, 893 insertions(+), 56 deletions(-) create mode 100644 assets/asset_middle.jpg create mode 100644 src/image_xyza_laba.rs create mode 100644 src/neon_to_xyza_laba.rs create mode 100644 src/neon_xyza_laba_to_image.rs create mode 100644 src/xyza_laba_to_image.rs diff --git a/Cargo.lock b/Cargo.lock index d77723a..a3ad70d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -582,9 +582,9 @@ checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "proc-macro2" -version = "1.0.83" +version = "1.0.84" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b33eb56c327dec362a9e55b3ad14f9d2f0904fb5a5b03b513ab5465399e9f43" +checksum = "ec96c6a92621310b51366f1e28d05ef11489516e93be030060e5fc12024a49d6" dependencies = [ "unicode-ident", ] @@ -749,18 +749,18 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "serde" -version = "1.0.202" +version = "1.0.203" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "226b61a0d411b2ba5ff6d7f73a476ac4f8bb900373459cd00fab8512828ba395" +checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.202" +version = "1.0.203" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6048858004bcff69094cd972ed40a32500f153bd3be9f716b2eed2e8217c4838" +checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba" dependencies = [ "proc-macro2", "quote", @@ -992,9 +992,9 @@ checksum = "53a85b86a771b1c87058196170769dd264f66c0782acf1ae6cc51bfd64b39082" [[package]] name = "winnow" -version = "0.6.8" +version = "0.6.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3c52e9c97a68071b23e836c9380edae937f17b9c4667bd021973efc689f618d" +checksum = "86c949fede1d13936a99f14fafd3e76fd642b556dd2ce96287fbe2e0151bfac6" dependencies = [ "memchr", ] diff --git a/assets/asset_middle.jpg b/assets/asset_middle.jpg new file mode 100644 index 0000000000000000000000000000000000000000..3d5a294ffbf6f7e98533b932a29acdf1801077be GIT binary patch literal 65617 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 = vec![]; - // let store_stride = width as usize * 3usize * std::mem::size_of::(); - // store.resize(width as usize * 3usize * height as usize, 0f32); - // let mut alpha_store: Vec = vec![]; - // let alpha_stride = width as usize * std::mem::size_of::(); - // alpha_store.resize(width as usize * height as usize, 0f32); - // rgba_to_laba(src_bytes, 4u32 * width, &mut store, store_stride as u32, &mut alpha_store, alpha_stride as u32, width, height); - // let mut destination: Vec = vec![]; - // destination.resize(width as usize * height as usize * 4, 0f32); - // let dst_stride = width * 4 * std::mem::size_of::() as u32; - // append_alpha(&mut destination, dst_stride, &store, store_stride as u32, &alpha_store, alpha_stride as u32, width, height); - // } + let mut dst_slice: Vec = Vec::new(); + dst_slice.resize(width as usize * 4 * height as usize, 0u8); + + { + let mut lab_store: Vec = vec![]; + let store_stride = width as usize * 4usize * std::mem::size_of::(); + lab_store.resize(width as usize * 4usize * height as usize, 0f32); + let mut alpha_store: Vec = vec![]; + let alpha_stride = width as usize * std::mem::size_of::(); + alpha_store.resize(width as usize * height as usize, 0f32); + rgba_to_lab_with_alpha( + src_bytes, + 4u32 * width, + &mut lab_store, + store_stride as u32, + width, + height, + ); + // let mut destination: Vec = vec![]; + // destination.resize(width as usize * height as usize * 4, 0f32); + // let dst_stride = width * 4 * std::mem::size_of::() as u32; + // append_alpha(&mut destination, dst_stride, &store, store_stride as u32, &alpha_store, alpha_stride as u32, width, height); + + let lab_stride = width as usize * 3usize * std::mem::size_of::(); + // + // let mut src_shift = 0usize; + // for _ in 0..height as usize { + // let src_ptr = unsafe { (src.as_ptr() as *mut u8).add(src_shift) as *mut f32 }; + // let src_slice = unsafe { slice::from_raw_parts(src_ptr, width as usize * 4) }; + // + // for x in 0..width as usize { + // let px = x * 4; + // lab_store[px] = src_slice[px]; + // lab_store[px + 1] = src_slice[px + 1]; + // lab_store[px + 2] = src_slice[px + 2]; + // a_store[x] = src_slice[px + 3]; + // } + // src_shift += src_stride as usize; + // } + + lab_with_alpha_to_rgba( + &lab_store, + store_stride as u32, + &mut dst_slice, + 4u32 * width, + width, + height, + ); + + // laba_to_srgb( + // &lab_store, + // lab_stride as u32, + // &alpha_store, + // width * std::mem::size_of::() as u32, + // &mut dst_slice, + // width * 4, + // width, + // height, + // ); + // + src_bytes = &dst_slice; + } let mut xyz: Vec = vec![]; xyz.resize(4 * width as usize * height as usize, 0f32); diff --git a/src/image_to_xyz_lab.rs b/src/image_to_xyz_lab.rs index 98a33d2..62725e5 100644 --- a/src/image_to_xyz_lab.rs +++ b/src/image_to_xyz_lab.rs @@ -1,3 +1,6 @@ +#[cfg(any(target_arch = "x86_64", target_arch = "x86"))] +#[allow(unused_imports)] +use crate::avx2_to_xyz_lab::*; use crate::gamma_curves::TransferFunction; use crate::image::ImageConfiguration; use crate::image_to_xyz_lab::XyzTarget::{LAB, XYZ}; @@ -10,9 +13,6 @@ use crate::neon_to_xyz_lab::neon_channels_to_xyz_or_lab; use crate::sse_to_xyz_lab::sse_channels_to_xyz_or_lab; use crate::{Rgb, Xyz, SRGB_TO_XYZ_D65}; use std::slice; -#[cfg(any(target_arch = "x86_64", target_arch = "x86"))] -#[allow(unused_imports)] -use crate::avx2_to_xyz_lab::*; pub(crate) enum XyzTarget { LAB = 0, @@ -181,33 +181,47 @@ fn channels_to_xyz::new(r, g, b); match target { LAB => { let lab = rgb.to_lab(); - dst_slice[x * 3] = lab.l; - dst_slice[x * 3 + 1] = lab.a; - dst_slice[x * 3 + 2] = lab.b; + unsafe { + *dst_slice.get_unchecked_mut(x * 3) = lab.l; + *dst_slice.get_unchecked_mut(x * 3 + 1) = lab.a; + *dst_slice.get_unchecked_mut(x * 3 + 2) = lab.b; + } } XYZ => { let xyz = Xyz::from_rgb(&rgb, &matrix, transfer_function); - dst_slice[x * 3] = xyz.x; - dst_slice[x * 3 + 1] = xyz.y; - dst_slice[x * 3 + 2] = xyz.z; + unsafe { + *dst_slice.get_unchecked_mut(x * 3) = xyz.x; + *dst_slice.get_unchecked_mut(x * 3 + 1) = xyz.y; + *dst_slice.get_unchecked_mut(x * 3 + 2) = xyz.z; + } } } if USE_ALPHA && image_configuration.has_alpha() { - let a = src_slice[px + image_configuration.get_a_channel_offset()]; + let a = unsafe { + *src_slice.get_unchecked(px + image_configuration.get_a_channel_offset()) + }; let a_lin = a as f32 * (1f32 / 255f32); let a_ptr = unsafe { (a_channel.as_mut_ptr() as *mut u8).add(a_offset) as *mut f32 }; let a_slice = unsafe { slice::from_raw_parts_mut(a_ptr, width as usize) }; - a_slice[x] = a_lin; + unsafe { + *a_slice.get_unchecked_mut(x) = a_lin; + } } } diff --git a/src/image_xyza_laba.rs b/src/image_xyza_laba.rs new file mode 100644 index 0000000..acb1890 --- /dev/null +++ b/src/image_xyza_laba.rs @@ -0,0 +1,177 @@ +use crate::image::ImageConfiguration; +use crate::image_to_xyz_lab::XyzTarget; +use crate::image_to_xyz_lab::XyzTarget::{LAB, XYZ}; +use crate::{Rgb, TransferFunction, Xyz, SRGB_TO_XYZ_D65}; +use std::slice; +#[cfg(all( + any(target_arch = "aarch64", target_arch = "arm"), + target_feature = "neon" +))] +use crate::neon_to_xyza_laba::neon_channels_to_xyza_or_laba; + +#[inline(always)] +fn channels_to_xyz_with_alpha( + src: &[u8], + src_stride: u32, + dst: &mut [f32], + dst_stride: u32, + width: u32, + height: u32, + matrix: &[[f32; 3]; 3], + transfer_function: TransferFunction, +) { + let target: XyzTarget = TARGET.into(); + let image_configuration: ImageConfiguration = CHANNELS_CONFIGURATION.into(); + if !image_configuration.has_alpha() { + panic!("Alpha may be set only on images with alpha"); + } + + let mut src_offset = 0usize; + let mut dst_offset = 0usize; + + const CHANNELS: usize = 4; + + let channels = image_configuration.get_channels_count(); + + for _ in 0..height as usize { + #[allow(unused_mut)] + let mut cx = 0usize; + + #[cfg(all( + any(target_arch = "aarch64", target_arch = "arm"), + target_feature = "neon" + ))] + unsafe { + cx = neon_channels_to_xyza_or_laba::( + cx, + src.as_ptr(), + src_offset, + width, + dst.as_mut_ptr(), + dst_offset, + &matrix, + transfer_function, + ) + } + + let src_ptr = unsafe { src.as_ptr().add(src_offset) }; + let dst_ptr = unsafe { (dst.as_mut_ptr() as *mut u8).add(dst_offset) as *mut f32 }; + + let src_slice = unsafe { slice::from_raw_parts(src_ptr, width as usize * channels) }; + let dst_slice = unsafe { slice::from_raw_parts_mut(dst_ptr, width as usize * 4) }; + + for x in cx..width as usize { + let px = x * channels; + let r = unsafe { + *src_slice.get_unchecked(px + image_configuration.get_r_channel_offset()) + }; + let g = unsafe { + *src_slice.get_unchecked(px + image_configuration.get_g_channel_offset()) + }; + let b = unsafe { + *src_slice.get_unchecked(px + image_configuration.get_b_channel_offset()) + }; + + let rgb = Rgb::::new(r, g, b); + match target { + LAB => { + let lab = rgb.to_lab(); + unsafe { + let px = x * CHANNELS; + *dst_slice.get_unchecked_mut(px) = lab.l; + *dst_slice.get_unchecked_mut(px + 1) = lab.a; + *dst_slice.get_unchecked_mut(px + 2) = lab.b; + } + } + XYZ => { + let xyz = Xyz::from_rgb(&rgb, &matrix, transfer_function); + let px = x * CHANNELS; + unsafe { + *dst_slice.get_unchecked_mut(px) = xyz.x; + *dst_slice.get_unchecked_mut(px + 1) = xyz.y; + *dst_slice.get_unchecked_mut(px + 2) = xyz.z; + } + } + } + + let a = unsafe { + *src_slice.get_unchecked(px + image_configuration.get_a_channel_offset()) + }; + let a_lin = a as f32 * (1f32 / 255f32); + unsafe { + *dst_slice.get_unchecked_mut(x * CHANNELS + 3) = a_lin; + } + } + + src_offset += src_stride as usize; + dst_offset += dst_stride as usize; + } +} + +/// This function converts RGBA to CIE L*ab against D65 white point and preserving and normalizing alpha channels keeping it at last positions. This is much more effective than naive direct transformation +/// +/// # Arguments +/// * `src` - A slice contains RGBA data +/// * `src_stride` - Bytes per row for src data. +/// * `width` - Image width +/// * `height` - Image height +/// * `dst` - A mutable slice to receive LAB(a) data +/// * `dst_stride` - Bytes per row for dst data +/// * `a_plane` - A mutable slice to receive XYZ data +/// * `a_stride` - Bytes per row for dst data +pub fn rgba_to_lab_with_alpha( + src: &[u8], + src_stride: u32, + dst: &mut [f32], + dst_stride: u32, + width: u32, + height: u32, +) { + channels_to_xyz_with_alpha::< + { ImageConfiguration::Rgba as u8 }, + { LAB as u8 }, + >( + src, + src_stride, + dst, + dst_stride, + width, + height, + &SRGB_TO_XYZ_D65, + TransferFunction::Srgb, + ); +} + +/// This function converts BGRA to CIE L*ab against D65 white point and preserving and normalizing alpha channels keeping it at last positions. This is much more effective than naive direct transformation +/// +/// # Arguments +/// * `src` - A slice contains BGRA data +/// * `src_stride` - Bytes per row for src data. +/// * `width` - Image width +/// * `height` - Image height +/// * `dst` - A mutable slice to receive LAB(a) data +/// * `dst_stride` - Bytes per row for dst data +/// * `a_plane` - A mutable slice to receive XYZ data +/// * `a_stride` - Bytes per row for dst data +pub fn bgra_to_lab_with_alpha( + src: &[u8], + src_stride: u32, + dst: &mut [f32], + dst_stride: u32, + width: u32, + height: u32, +) { + channels_to_xyz_with_alpha::< + { ImageConfiguration::Bgra as u8 }, + { LAB as u8 }, + >( + src, + src_stride, + dst, + dst_stride, + width, + height, + &SRGB_TO_XYZ_D65, + TransferFunction::Srgb, + ); +} diff --git a/src/lib.rs b/src/lib.rs index 54f8d50..40ddd15 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -30,6 +30,10 @@ mod avx_gamma_curves; mod rgb_expand; mod avx2_to_xyz_lab; mod avx2_utils; +mod image_xyza_laba; +mod neon_to_xyza_laba; +mod xyza_laba_to_image; +mod neon_xyza_laba_to_image; pub use gamma_curves::*; pub use hsl::Hsl; @@ -67,5 +71,9 @@ pub use xyz_lab_to_image::xyza_to_rgba; pub use image_to_linear::*; pub use linear_to_image::*; pub use concat_alpha::append_alpha; +pub use image_xyza_laba::rgba_to_lab_with_alpha; +pub use image_xyza_laba::bgra_to_lab_with_alpha; +pub use xyza_laba_to_image::lab_with_alpha_to_bgra; +pub use xyza_laba_to_image::lab_with_alpha_to_rgba; pub use rgb_expand::*; \ No newline at end of file diff --git a/src/neon_linear_to_image.rs b/src/neon_linear_to_image.rs index 0f5e7f8..a29ee49 100644 --- a/src/neon_linear_to_image.rs +++ b/src/neon_linear_to_image.rs @@ -77,10 +77,10 @@ unsafe fn neon_gamma_vld( + start_cx: usize, + src: *const u8, + src_offset: usize, + width: u32, + dst: *mut f32, + dst_offset: usize, + matrix: &[[f32; 3]; 3], + transfer_function: TransferFunction, +) -> usize { + let target: XyzTarget = TARGET.into(); + let image_configuration: ImageConfiguration = CHANNELS_CONFIGURATION.into(); + let channels = image_configuration.get_channels_count(); + let mut cx = start_cx; + + let transfer = get_neon_linear_transfer(transfer_function); + + let cq1 = vdupq_n_f32(matrix[0][0]); + let cq2 = vdupq_n_f32(matrix[0][1]); + let cq3 = vdupq_n_f32(matrix[0][2]); + let cq4 = vdupq_n_f32(matrix[1][0]); + let cq5 = vdupq_n_f32(matrix[1][1]); + let cq6 = vdupq_n_f32(matrix[1][2]); + let cq7 = vdupq_n_f32(matrix[2][0]); + let cq8 = vdupq_n_f32(matrix[2][1]); + let cq9 = vdupq_n_f32(matrix[2][2]); + + let dst_ptr = (dst as *mut u8).add(dst_offset) as *mut f32; + + while cx + 16 < width as usize { + let (r_chan, g_chan, b_chan, a_chan); + let src_ptr = src.add(src_offset + cx * channels); + match image_configuration { + ImageConfiguration::Rgb | ImageConfiguration::Bgr => { + let ldr = vld3q_u8(src_ptr); + if image_configuration == ImageConfiguration::Rgb { + r_chan = ldr.0; + g_chan = ldr.1; + b_chan = ldr.2; + } else { + r_chan = ldr.2; + g_chan = ldr.1; + b_chan = ldr.0; + } + a_chan = vdupq_n_u8(0); + } + ImageConfiguration::Rgba => { + let ldr = vld4q_u8(src_ptr); + r_chan = ldr.0; + g_chan = ldr.1; + b_chan = ldr.2; + a_chan = ldr.3; + } + ImageConfiguration::Bgra => { + let ldr = vld4q_u8(src_ptr); + r_chan = ldr.2; + g_chan = ldr.1; + b_chan = ldr.0; + a_chan = ldr.3; + } + } + + let r_low = vmovl_u8(vget_low_u8(r_chan)); + let g_low = vmovl_u8(vget_low_u8(g_chan)); + let b_low = vmovl_u8(vget_low_u8(b_chan)); + + let r_low_low = vmovl_u16(vget_low_u16(r_low)); + let g_low_low = vmovl_u16(vget_low_u16(g_low)); + let b_low_low = vmovl_u16(vget_low_u16(b_low)); + + let (mut x_low_low, mut y_low_low, mut z_low_low) = neon_triple_to_xyz( + r_low_low, g_low_low, b_low_low, cq1, cq2, cq3, cq4, cq5, cq6, cq7, cq8, cq9, &transfer, + ); + + match target { + XyzTarget::LAB => { + let (l, a, b) = neon_triple_to_lab(x_low_low, y_low_low, z_low_low); + x_low_low = l; + y_low_low = a; + z_low_low = b; + } + XyzTarget::XYZ => {} + } + + let a_low = vmovl_u8(vget_low_u8(a_chan)); + + let a_low_low = vmulq_n_f32(vcvtq_f32_u32(vmovl_u16(vget_low_u16(a_low))), 1f32 / 255f32); + + let xyz_low_low = float32x4x4_t(x_low_low, y_low_low, z_low_low, a_low_low); + vst4q_f32(dst_ptr.add(cx * 4), xyz_low_low); + + let r_low_high = vmovl_high_u16(r_low); + let g_low_high = vmovl_high_u16(g_low); + let b_low_high = vmovl_high_u16(b_low); + + let (mut x_low_high, mut y_low_high, mut z_low_high) = neon_triple_to_xyz( + r_low_high, g_low_high, b_low_high, cq1, cq2, cq3, cq4, cq5, cq6, cq7, cq8, cq9, + &transfer, + ); + + match target { + XyzTarget::LAB => { + let (l, a, b) = neon_triple_to_lab(x_low_high, y_low_high, z_low_high); + x_low_high = l; + y_low_high = a; + z_low_high = b; + } + XyzTarget::XYZ => {} + } + + let a_low_high = vmulq_n_f32(vcvtq_f32_u32(vmovl_high_u16(a_low)), 1f32 / 255f32); + + let xyz_low_low = float32x4x4_t(x_low_high, y_low_high, z_low_high, a_low_high); + vst4q_f32(dst_ptr.add(cx * 4 + 4 * 4), xyz_low_low); + + let r_high = vmovl_high_u8(r_chan); + let g_high = vmovl_high_u8(g_chan); + let b_high = vmovl_high_u8(b_chan); + + let r_high_low = vmovl_u16(vget_low_u16(r_high)); + let g_high_low = vmovl_u16(vget_low_u16(g_high)); + let b_high_low = vmovl_u16(vget_low_u16(b_high)); + + let (mut x_high_low, mut y_high_low, mut z_high_low) = neon_triple_to_xyz( + r_high_low, g_high_low, b_high_low, cq1, cq2, cq3, cq4, cq5, cq6, cq7, cq8, cq9, + &transfer, + ); + + match target { + XyzTarget::LAB => { + let (l, a, b) = neon_triple_to_lab(x_high_low, y_high_low, z_high_low); + x_high_low = l; + y_high_low = a; + z_high_low = b; + } + XyzTarget::XYZ => {} + } + + let a_high = vmovl_high_u8(a_chan); + let a_high_low = vmulq_n_f32( + vcvtq_f32_u32(vmovl_u16(vget_low_u16(a_high))), + 1f32 / 255f32, + ); + + let xyz_low_low = float32x4x4_t(x_high_low, y_high_low, z_high_low, a_high_low); + vst4q_f32(dst_ptr.add(cx * 4 + 4 * 4 * 2), xyz_low_low); + + let r_high_high = vmovl_high_u16(r_high); + let g_high_high = vmovl_high_u16(g_high); + let b_high_high = vmovl_high_u16(b_high); + + let (mut x_high_high, mut y_high_high, mut z_high_high) = neon_triple_to_xyz( + r_high_high, + g_high_high, + b_high_high, + cq1, + cq2, + cq3, + cq4, + cq5, + cq6, + cq7, + cq8, + cq9, + &transfer, + ); + + match target { + XyzTarget::LAB => { + let (l, a, b) = neon_triple_to_lab(x_high_high, y_high_high, z_high_high); + x_high_high = l; + y_high_high = a; + z_high_high = b; + } + XyzTarget::XYZ => {} + } + + let a_high_high = vmulq_n_f32(vcvtq_f32_u32(vmovl_high_u16(a_high)), 1f32 / 255f32); + + let xyz_low_low = float32x4x4_t(x_high_high, y_high_high, z_high_high, a_high_high); + vst4q_f32(dst_ptr.add(cx * 4 + 4 * 4 * 3), xyz_low_low); + + cx += 16; + } + + cx +} diff --git a/src/neon_xyz_lab_to_image.rs b/src/neon_xyz_lab_to_image.rs index 291f09c..24a9332 100644 --- a/src/neon_xyz_lab_to_image.rs +++ b/src/neon_xyz_lab_to_image.rs @@ -34,7 +34,7 @@ unsafe fn vcubeq_f32(x: float32x4_t) -> float32x4_t { target_feature = "neon" ))] #[inline(always)] -unsafe fn neon_lab_to_xyz( +pub(crate) unsafe fn neon_lab_to_xyz( l: float32x4_t, a: float32x4_t, b: float32x4_t, @@ -64,7 +64,7 @@ unsafe fn neon_lab_to_xyz( target_feature = "neon" ))] #[inline(always)] -unsafe fn neon_xyz_lab_vld< +pub(crate) unsafe fn neon_xyz_lab_vld< const CHANNELS_CONFIGURATION: u8, const USE_ALPHA: bool, const TARGET: u8, @@ -111,9 +111,9 @@ unsafe fn neon_xyz_lab_vld< g_f32 = vmulq_f32(g_f32, v_scale_color); b_f32 = vmulq_f32(b_f32, v_scale_color); ( - vcvtq_u32_f32(r_f32), - vcvtq_u32_f32(g_f32), - vcvtq_u32_f32(b_f32), + vcvtaq_u32_f32(r_f32), + vcvtaq_u32_f32(g_f32), + vcvtaq_u32_f32(b_f32), ) } diff --git a/src/neon_xyza_laba_to_image.rs b/src/neon_xyza_laba_to_image.rs new file mode 100644 index 0000000..4c93138 --- /dev/null +++ b/src/neon_xyza_laba_to_image.rs @@ -0,0 +1,208 @@ +#[allow(unused_imports)] +use crate::image::ImageConfiguration; +#[allow(unused_imports)] +use crate::image_to_xyz_lab::XyzTarget; +#[cfg(all( + any(target_arch = "aarch64", target_arch = "arm"), + target_feature = "neon" +))] +use crate::neon_linear_to_image::get_neon_gamma_transfer; +use crate::neon_math::vcolorq_matrix_f32; +use crate::neon_xyz_lab_to_image::neon_lab_to_xyz; +#[cfg(all( + any(target_arch = "aarch64", target_arch = "arm"), + target_feature = "neon" +))] +#[allow(unused_imports)] +use crate::TransferFunction; +#[cfg(all( + any(target_arch = "aarch64", target_arch = "arm"), + target_feature = "neon" +))] +use std::arch::aarch64::*; + +#[cfg(all( + any(target_arch = "aarch64", target_arch = "arm"), + target_feature = "neon" +))] +#[inline(always)] +pub(crate) unsafe fn neon_xyza_lab_vld( + src: *const f32, + transfer_function: TransferFunction, + c1: float32x4_t, + c2: float32x4_t, + c3: float32x4_t, + c4: float32x4_t, + c5: float32x4_t, + c6: float32x4_t, + c7: float32x4_t, + c8: float32x4_t, + c9: float32x4_t, +) -> (uint32x4_t, uint32x4_t, uint32x4_t, uint32x4_t) { + let target: XyzTarget = TARGET.into(); + let transfer = get_neon_gamma_transfer(transfer_function); + let v_scale_color = vdupq_n_f32(255f32); + let lab_pixel = vld4q_f32(src); + let (mut r_f32, mut g_f32, mut b_f32) = (lab_pixel.0, lab_pixel.1, lab_pixel.2); + + match target { + XyzTarget::LAB => { + let (x, y, z) = neon_lab_to_xyz(r_f32, g_f32, b_f32); + r_f32 = x; + g_f32 = y; + b_f32 = z; + } + _ => {} + } + + let (linear_r, linear_g, linear_b) = + vcolorq_matrix_f32(r_f32, g_f32, b_f32, c1, c2, c3, c4, c5, c6, c7, c8, c9); + + r_f32 = linear_r; + g_f32 = linear_g; + b_f32 = linear_b; + + r_f32 = transfer(r_f32); + g_f32 = transfer(g_f32); + b_f32 = transfer(b_f32); + r_f32 = vmulq_f32(r_f32, v_scale_color); + g_f32 = vmulq_f32(g_f32, v_scale_color); + b_f32 = vmulq_f32(b_f32, v_scale_color); + let a_f32 = vmulq_f32(lab_pixel.3, v_scale_color); + ( + vcvtaq_u32_f32(r_f32), + vcvtaq_u32_f32(g_f32), + vcvtaq_u32_f32(b_f32), + vcvtaq_u32_f32(a_f32), + ) +} + +#[cfg(all( + any(target_arch = "aarch64", target_arch = "arm"), + target_feature = "neon" +))] +#[inline(always)] +pub unsafe fn neon_xyza_to_image( + start_cx: usize, + src: *const f32, + src_offset: usize, + dst: *mut u8, + dst_offset: usize, + width: u32, + matrix: &[[f32; 3]; 3], + transfer_function: TransferFunction, +) -> usize { + let image_configuration: ImageConfiguration = CHANNELS_CONFIGURATION.into(); + if !image_configuration.has_alpha() { + panic!("Alpha may be set only on images with alpha"); + } + + let channels = image_configuration.get_channels_count(); + + let mut cx = start_cx; + + let c1 = vdupq_n_f32(matrix[0][0]); + let c2 = vdupq_n_f32(matrix[0][1]); + let c3 = vdupq_n_f32(matrix[0][2]); + let c4 = vdupq_n_f32(matrix[1][0]); + let c5 = vdupq_n_f32(matrix[1][1]); + let c6 = vdupq_n_f32(matrix[1][2]); + let c7 = vdupq_n_f32(matrix[2][0]); + let c8 = vdupq_n_f32(matrix[2][1]); + let c9 = vdupq_n_f32(matrix[2][2]); + + const CHANNELS: usize = 4usize; + + while cx + 16 < width as usize { + let offset_src_ptr = ((src as *const u8).add(src_offset) as *const f32).add(cx * CHANNELS); + + let src_ptr_0 = offset_src_ptr; + + let (r_row0_, g_row0_, b_row0_, a_row0_) = neon_xyza_lab_vld::( + src_ptr_0, + transfer_function, + c1, + c2, + c3, + c4, + c5, + c6, + c7, + c8, + c9, + ); + + let src_ptr_1 = offset_src_ptr.add(4 * CHANNELS); + + let (r_row1_, g_row1_, b_row1_, a_row1_) = + neon_xyza_lab_vld::( + src_ptr_1, + transfer_function, + c1, + c2, + c3, + c4, + c5, + c6, + c7, + c8, + c9, + ); + + let src_ptr_2 = offset_src_ptr.add(4 * 2 * CHANNELS); + + let (r_row2_, g_row2_, b_row2_, a_row2_) = neon_xyza_lab_vld::( + src_ptr_2, + transfer_function, + c1, + c2, + c3, + c4, + c5, + c6, + c7, + c8, + c9, + ); + + let src_ptr_3 = offset_src_ptr.add(4 * 3 * CHANNELS); + + let (r_row3_, g_row3_, b_row3_, a_row3_) = neon_xyza_lab_vld::( + src_ptr_3, + transfer_function, + c1, + c2, + c3, + c4, + c5, + c6, + c7, + c8, + c9, + ); + + let r_row01 = vcombine_u16(vqmovn_u32(r_row0_), vqmovn_u32(r_row1_)); + let g_row01 = vcombine_u16(vqmovn_u32(g_row0_), vqmovn_u32(g_row1_)); + let b_row01 = vcombine_u16(vqmovn_u32(b_row0_), vqmovn_u32(b_row1_)); + let a_row01 = vcombine_u16(vqmovn_u32(a_row0_), vqmovn_u32(a_row1_)); + + let r_row23 = vcombine_u16(vqmovn_u32(r_row2_), vqmovn_u32(r_row3_)); + let g_row23 = vcombine_u16(vqmovn_u32(g_row2_), vqmovn_u32(g_row3_)); + let b_row23 = vcombine_u16(vqmovn_u32(b_row2_), vqmovn_u32(b_row3_)); + let a_row23 = vcombine_u16(vqmovn_u32(a_row2_), vqmovn_u32(a_row3_)); + + let r_row = vcombine_u8(vqmovn_u16(r_row01), vqmovn_u16(r_row23)); + let g_row = vcombine_u8(vqmovn_u16(g_row01), vqmovn_u16(g_row23)); + let b_row = vcombine_u8(vqmovn_u16(b_row01), vqmovn_u16(b_row23)); + let a_row = vcombine_u8(vqmovn_u16(a_row01), vqmovn_u16(a_row23)); + + let dst_ptr = dst.add(dst_offset + cx * channels); + + let store_rows = uint8x16x4_t(r_row, g_row, b_row, a_row); + vst4q_u8(dst_ptr, store_rows); + + cx += 16; + } + + cx +} diff --git a/src/xyza_laba_to_image.rs b/src/xyza_laba_to_image.rs new file mode 100644 index 0000000..32c1966 --- /dev/null +++ b/src/xyza_laba_to_image.rs @@ -0,0 +1,166 @@ +use std::slice; + +use crate::gamma_curves::TransferFunction; +use crate::image::ImageConfiguration; +use crate::image_to_xyz_lab::XyzTarget; +use crate::image_to_xyz_lab::XyzTarget::{LAB, XYZ}; +#[cfg(all( + any(target_arch = "aarch64", target_arch = "arm"), + target_feature = "neon" +))] +use crate::{Lab, Xyz, XYZ_TO_SRGB_D65}; +#[cfg(all( + any(target_arch = "aarch64", target_arch = "arm"), + target_feature = "neon" +))] +use crate::neon_xyza_laba_to_image::neon_xyza_to_image; + +fn xyz_with_alpha_to_channels( + src: &[f32], + src_stride: u32, + dst: &mut [u8], + dst_stride: u32, + width: u32, + height: u32, + matrix: &[[f32; 3]; 3], + transfer_function: TransferFunction, +) { + let source: XyzTarget = TARGET.into(); + let image_configuration: ImageConfiguration = CHANNELS_CONFIGURATION.into(); + if !image_configuration.has_alpha() { + panic!("Alpha may be set only on images with alpha"); + } + + let mut src_offset = 0usize; + let mut dst_offset = 0usize; + + let channels = image_configuration.get_channels_count(); + + for _ in 0..height as usize { + #[allow(unused_mut)] + let mut cx = 0usize; + + #[cfg(all( + any(target_arch = "aarch64", target_arch = "arm"), + target_feature = "neon" + ))] + unsafe { + cx = neon_xyza_to_image::( + cx, + src.as_ptr(), + src_offset, + dst.as_mut_ptr(), + dst_offset, + width, + &matrix, + transfer_function, + ) + } + + + let src_ptr = unsafe { (src.as_ptr() as *const u8).add(src_offset) as *mut f32 }; + let dst_ptr = unsafe { dst.as_mut_ptr().add(dst_offset) }; + + let src_slice = unsafe { slice::from_raw_parts(src_ptr, width as usize * channels) }; + let dst_slice = unsafe { slice::from_raw_parts_mut(dst_ptr, width as usize * channels) }; + + for x in cx..width as usize { + let px = x * 4; + let l_x = unsafe { *src_slice.get_unchecked(px) }; + let l_y = unsafe { *src_slice.get_unchecked(px + 1) }; + let l_z = unsafe { *src_slice.get_unchecked(px + 2) }; + let rgb; + match source { + LAB => { + let lab = Lab::new(l_x, l_y, l_z); + rgb = lab.to_rgb(); + } + XYZ => { + let xyz = Xyz::new(l_x, l_y, l_z); + rgb = xyz.to_rgb(&matrix, transfer_function); + } + } + + let l_a = unsafe { *src_slice.get_unchecked(px + 3) }; + let a_value = (l_a * 255f32).max(0f32); + unsafe { + *dst_slice + .get_unchecked_mut(x * channels + image_configuration.get_r_channel_offset()) = + rgb.r; + *dst_slice + .get_unchecked_mut(x * channels + image_configuration.get_g_channel_offset()) = + rgb.g; + *dst_slice + .get_unchecked_mut(x * channels + image_configuration.get_b_channel_offset()) = + rgb.b; + dst_slice[x * channels + image_configuration.get_a_channel_offset()] = + a_value as u8; + } + } + + src_offset += src_stride as usize; + dst_offset += dst_stride as usize; + } +} + +/// This function converts LAB with separate alpha channel to RGBA. This is much more effective than naive direct transformation +/// +/// # Arguments +/// * `src` - A slice contains LAB data +/// * `src_stride` - Bytes per row for src data. +/// * `a_plane` - A slice contains Alpha data +/// * `a_stride` - Bytes per row for alpha plane data +/// * `dst` - A mutable slice to receive RGBA data +/// * `dst_stride` - Bytes per row for dst data +/// * `width` - Image width +/// * `height` - Image height +pub fn lab_with_alpha_to_rgba( + src: &[f32], + src_stride: u32, + dst: &mut [u8], + dst_stride: u32, + width: u32, + height: u32, +) { + xyz_with_alpha_to_channels::<{ ImageConfiguration::Rgba as u8 }, { LAB as u8 }>( + src, + src_stride, + dst, + dst_stride, + width, + height, + &XYZ_TO_SRGB_D65, + TransferFunction::Srgb, + ); +} + +/// This function converts LAB with separate alpha channel to BGRA. This is much more effective than naive direct transformation +/// +/// # Arguments +/// * `src` - A slice contains LAB data +/// * `src_stride` - Bytes per row for src data. +/// * `a_plane` - A slice contains Alpha data +/// * `a_stride` - Bytes per row for alpha plane data +/// * `dst` - A mutable slice to receive BGRA data +/// * `dst_stride` - Bytes per row for dst data +/// * `width` - Image width +/// * `height` - Image height +pub fn lab_with_alpha_to_bgra( + src: &[f32], + src_stride: u32, + dst: &mut [u8], + dst_stride: u32, + width: u32, + height: u32, +) { + xyz_with_alpha_to_channels::<{ ImageConfiguration::Bgra as u8 }, { LAB as u8 }>( + src, + src_stride, + dst, + dst_stride, + width, + height, + &XYZ_TO_SRGB_D65, + TransferFunction::Srgb, + ); +}