Skip to content

Commit

Permalink
Image to LAB/XYZ improvements
Browse files Browse the repository at this point in the history
  • Loading branch information
awxkee committed Jun 2, 2024
1 parent 381147a commit 0b06462
Show file tree
Hide file tree
Showing 12 changed files with 893 additions and 56 deletions.
16 changes: 8 additions & 8 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Binary file added assets/asset_middle.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
78 changes: 63 additions & 15 deletions src/app/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ use image::io::Reader as ImageReader;
use image::{EncodableLayout, GenericImageView};
#[cfg(target_arch = "x86_64")]
use std::arch::x86_64::*;
use std::mem::transmute;
use std::time::Instant;

#[cfg(target_arch = "x86_64")]
Expand Down Expand Up @@ -36,7 +35,7 @@ fn main() {
// println!("Exp {}", l);
// }

let img = ImageReader::open("./assets/asset.jpg")
let img = ImageReader::open("./assets/asset_middle.jpg")
.unwrap()
.decode()
.unwrap();
Expand All @@ -62,19 +61,68 @@ fn main() {
);
src_bytes = &dst_rgba;

// {
// let mut store: Vec<f32> = vec![];
// let store_stride = width as usize * 3usize * std::mem::size_of::<f32>();
// store.resize(width as usize * 3usize * height as usize, 0f32);
// let mut alpha_store: Vec<f32> = vec![];
// let alpha_stride = width as usize * std::mem::size_of::<f32>();
// 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<f32> = vec![];
// destination.resize(width as usize * height as usize * 4, 0f32);
// let dst_stride = width * 4 * std::mem::size_of::<f32>() 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<u8> = Vec::new();
dst_slice.resize(width as usize * 4 * height as usize, 0u8);

{
let mut lab_store: Vec<f32> = vec![];
let store_stride = width as usize * 4usize * std::mem::size_of::<f32>();
lab_store.resize(width as usize * 4usize * height as usize, 0f32);
let mut alpha_store: Vec<f32> = vec![];
let alpha_stride = width as usize * std::mem::size_of::<f32>();
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<f32> = vec![];
// destination.resize(width as usize * height as usize * 4, 0f32);
// let dst_stride = width * 4 * std::mem::size_of::<f32>() 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::<f32>();
//
// 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::<f32>() as u32,
// &mut dst_slice,
// width * 4,
// width,
// height,
// );
//
src_bytes = &dst_slice;
}

let mut xyz: Vec<f32> = vec![];
xyz.resize(4 * width as usize * height as usize, 0f32);
Expand Down
42 changes: 28 additions & 14 deletions src/image_to_xyz_lab.rs
Original file line number Diff line number Diff line change
@@ -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};
Expand All @@ -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,
Expand Down Expand Up @@ -181,33 +181,47 @@ fn channels_to_xyz<const CHANNELS_CONFIGURATION: u8, const USE_ALPHA: bool, cons

for x in cx..width as usize {
let px = x * channels;
let r = src_slice[px + image_configuration.get_r_channel_offset()];
let g = src_slice[px + image_configuration.get_g_channel_offset()];
let b = src_slice[px + image_configuration.get_b_channel_offset()];
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::<u8>::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;
}
}
}

Expand Down
177 changes: 177 additions & 0 deletions src/image_xyza_laba.rs
Original file line number Diff line number Diff line change
@@ -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<const CHANNELS_CONFIGURATION: u8, const TARGET: u8>(
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::<CHANNELS_CONFIGURATION, TARGET>(
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::<u8>::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,
);
}
Loading

0 comments on commit 0b06462

Please sign in to comment.