colorutils_rs/
image_to_oklab.rs

1/*
2 * // Copyright 2024 (c) the Radzivon Bartoshyk. All rights reserved.
3 * //
4 * // Use of this source code is governed by a BSD-style
5 * // license that can be found in the LICENSE file.
6 */
7#[cfg(any(target_arch = "x86_64", target_arch = "x86"))]
8use crate::avx::avx_image_to_oklab;
9use crate::image::ImageConfiguration;
10#[cfg(all(target_arch = "aarch64", target_feature = "neon"))]
11use crate::neon::neon_image_to_oklab;
12use crate::oklch::Oklch;
13#[cfg(any(target_arch = "x86_64", target_arch = "x86"))]
14use crate::sse::sse_image_to_oklab;
15use crate::{
16    bgr_to_linear, bgra_to_linear, rgb_to_linear, rgba_to_linear, Oklab, Rgb, TransferFunction,
17};
18#[cfg(feature = "rayon")]
19use rayon::iter::ParallelIterator;
20#[cfg(feature = "rayon")]
21use rayon::prelude::ParallelSliceMut;
22use std::slice;
23
24#[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq)]
25pub(crate) enum OklabTarget {
26    Oklab = 0,
27    Oklch = 1,
28}
29
30impl From<u8> for OklabTarget {
31    fn from(value: u8) -> Self {
32        match value {
33            0 => OklabTarget::Oklab,
34            1 => OklabTarget::Oklch,
35            _ => {
36                panic!("Not implemented")
37            }
38        }
39    }
40}
41
42#[allow(clippy::type_complexity)]
43fn channels_to_oklab<const CHANNELS_CONFIGURATION: u8, const TARGET: u8>(
44    src: &[u8],
45    src_stride: u32,
46    dst: &mut [f32],
47    dst_stride: u32,
48    width: u32,
49    height: u32,
50    transfer_function: TransferFunction,
51) {
52    let target: OklabTarget = TARGET.into();
53    let image_configuration: ImageConfiguration = CHANNELS_CONFIGURATION.into();
54
55    let channels = image_configuration.get_channels_count();
56
57    let callee = match image_configuration {
58        ImageConfiguration::Rgb => rgb_to_linear,
59        ImageConfiguration::Rgba => rgba_to_linear,
60        ImageConfiguration::Bgra => bgra_to_linear,
61        ImageConfiguration::Bgr => bgr_to_linear,
62    };
63
64    callee(
65        src,
66        src_stride,
67        dst,
68        dst_stride,
69        width,
70        height,
71        transfer_function,
72    );
73
74    let mut _wide_row_handle: Option<unsafe fn(usize, u32, *mut f32, usize) -> usize> = None;
75
76    #[cfg(all(target_arch = "aarch64", target_feature = "neon"))]
77    {
78        _wide_row_handle = Some(neon_image_to_oklab::<CHANNELS_CONFIGURATION, TARGET>);
79    }
80
81    #[cfg(any(target_arch = "x86_64", target_arch = "x86"))]
82    if std::arch::is_x86_feature_detected!("sse4.1") {
83        _wide_row_handle = Some(sse_image_to_oklab::<CHANNELS_CONFIGURATION, TARGET>);
84    }
85
86    #[cfg(any(target_arch = "x86_64", target_arch = "x86"))]
87    if std::arch::is_x86_feature_detected!("avx2") {
88        _wide_row_handle = Some(avx_image_to_oklab::<CHANNELS_CONFIGURATION, TARGET>);
89    }
90
91    let dst_slice_safe_align = unsafe {
92        slice::from_raw_parts_mut(
93            dst.as_mut_ptr() as *mut u8,
94            dst_stride as usize * height as usize,
95        )
96    };
97
98    let iter;
99    #[cfg(feature = "rayon")]
100    {
101        iter = dst_slice_safe_align.par_chunks_exact_mut(dst_stride as usize);
102    }
103
104    #[cfg(not(feature = "rayon"))]
105    {
106        iter = dst_slice_safe_align.chunks_exact_mut(dst_stride as usize);
107    }
108
109    iter.for_each(|dst| unsafe {
110        let mut _cx = 0usize;
111
112        let dst_ptr = dst.as_mut_ptr() as *mut f32;
113
114        if let Some(dispatcher) = _wide_row_handle {
115            _cx = dispatcher(_cx, width, dst_ptr, 0)
116        }
117
118        for x in _cx..width as usize {
119            let px = x * channels;
120
121            let src = dst_ptr.add(px);
122            let r = src
123                .add(image_configuration.get_r_channel_offset())
124                .read_unaligned();
125            let g = src
126                .add(image_configuration.get_g_channel_offset())
127                .read_unaligned();
128            let b = src
129                .add(image_configuration.get_b_channel_offset())
130                .read_unaligned();
131
132            let rgb = Rgb::<f32>::new(r, g, b);
133            let dst_store = dst_ptr.add(px);
134
135            match target {
136                OklabTarget::Oklab => {
137                    let oklab = Oklab::from_linear_rgb(rgb);
138                    dst_store.write_unaligned(oklab.l);
139                    dst_store.add(1).write_unaligned(oklab.a);
140                    dst_store.add(2).write_unaligned(oklab.b);
141                }
142                OklabTarget::Oklch => {
143                    let oklch = Oklch::from_linear_rgb(rgb);
144                    dst_store.write_unaligned(oklch.l);
145                    dst_store.add(1).write_unaligned(oklch.c);
146                    dst_store.add(2).write_unaligned(oklch.h);
147                }
148            }
149
150            if image_configuration.has_alpha() {
151                let a = src
152                    .add(image_configuration.get_a_channel_offset())
153                    .read_unaligned();
154                dst_store.add(3).write_unaligned(a);
155            }
156        }
157    });
158}
159
160/// This function converts RGB to Oklab against D65 white point. This is much more effective than naive direct transformation
161///
162/// # Arguments
163/// * `src` - A slice contains RGB data
164/// * `src_stride` - Bytes per row for src data.
165/// * `width` - Image width
166/// * `height` - Image height
167/// * `dst` - A mutable slice to receive LAB(a) data
168/// * `dst_stride` - Bytes per row for dst data
169/// * `transfer_function` - transfer function to linear colorspace
170pub fn rgb_to_oklab(
171    src: &[u8],
172    src_stride: u32,
173    dst: &mut [f32],
174    dst_stride: u32,
175    width: u32,
176    height: u32,
177    transfer_function: TransferFunction,
178) {
179    channels_to_oklab::<{ ImageConfiguration::Rgb as u8 }, { OklabTarget::Oklab as u8 }>(
180        src,
181        src_stride,
182        dst,
183        dst_stride,
184        width,
185        height,
186        transfer_function,
187    );
188}
189
190/// This function converts RGBA to Oklab against D65 white point and preserving and normalizing alpha channels keeping it at last positions. This is much more effective than naive direct transformation
191///
192/// # Arguments
193/// * `src` - A slice contains RGBA data
194/// * `src_stride` - Bytes per row for src data.
195/// * `width` - Image width
196/// * `height` - Image height
197/// * `dst` - A mutable slice to receive LAB(a) data
198/// * `dst_stride` - Bytes per row for dst data
199/// * `transfer_function` - transfer function to linear colorspace
200pub fn rgba_to_oklab(
201    src: &[u8],
202    src_stride: u32,
203    dst: &mut [f32],
204    dst_stride: u32,
205    width: u32,
206    height: u32,
207    transfer_function: TransferFunction,
208) {
209    channels_to_oklab::<{ ImageConfiguration::Rgba as u8 }, { OklabTarget::Oklab as u8 }>(
210        src,
211        src_stride,
212        dst,
213        dst_stride,
214        width,
215        height,
216        transfer_function,
217    );
218}
219
220/// This function converts BGRA to Oklab against D65 white point and preserving and normalizing alpha channels keeping it at last positions. This is much more effective than naive direct transformation
221///
222/// # Arguments
223/// * `src` - A slice contains BGRA data
224/// * `src_stride` - Bytes per row for src data.
225/// * `width` - Image width
226/// * `height` - Image height
227/// * `dst` - A mutable slice to receive LAB(a) data
228/// * `dst_stride` - Bytes per row for dst data
229/// * `transfer_function` - transfer function to linear colorspace
230pub fn bgra_to_oklab(
231    src: &[u8],
232    src_stride: u32,
233    dst: &mut [f32],
234    dst_stride: u32,
235    width: u32,
236    height: u32,
237    transfer_function: TransferFunction,
238) {
239    channels_to_oklab::<{ ImageConfiguration::Bgra as u8 }, { OklabTarget::Oklab as u8 }>(
240        src,
241        src_stride,
242        dst,
243        dst_stride,
244        width,
245        height,
246        transfer_function,
247    );
248}
249
250/// This function converts BGR to Oklab against D65 white point. This is much more effective than naive direct transformation
251///
252/// # Arguments
253/// * `src` - A slice contains BGR data
254/// * `src_stride` - Bytes per row for src data.
255/// * `width` - Image width
256/// * `height` - Image height
257/// * `dst` - A mutable slice to receive LAB(a) data
258/// * `dst_stride` - Bytes per row for dst data
259/// * `transfer_function` - transfer function to linear colorspace
260pub fn bgr_to_oklab(
261    src: &[u8],
262    src_stride: u32,
263    dst: &mut [f32],
264    dst_stride: u32,
265    width: u32,
266    height: u32,
267    transfer_function: TransferFunction,
268) {
269    channels_to_oklab::<{ ImageConfiguration::Bgr as u8 }, { OklabTarget::Oklab as u8 }>(
270        src,
271        src_stride,
272        dst,
273        dst_stride,
274        width,
275        height,
276        transfer_function,
277    );
278}
279
280/// This function converts RGB to Oklch against D65 white point. This is much more effective than naive direct transformation
281///
282/// # Arguments
283/// * `src` - A slice contains RGB data
284/// * `src_stride` - Bytes per row for src data.
285/// * `width` - Image width
286/// * `height` - Image height
287/// * `dst` - A mutable slice to receive LCH(a) data
288/// * `dst_stride` - Bytes per row for dst data
289/// * `transfer_function` - transfer function to linear colorspace
290pub fn rgb_to_oklch(
291    src: &[u8],
292    src_stride: u32,
293    dst: &mut [f32],
294    dst_stride: u32,
295    width: u32,
296    height: u32,
297    transfer_function: TransferFunction,
298) {
299    channels_to_oklab::<{ ImageConfiguration::Rgb as u8 }, { OklabTarget::Oklch as u8 }>(
300        src,
301        src_stride,
302        dst,
303        dst_stride,
304        width,
305        height,
306        transfer_function,
307    );
308}
309
310/// This function converts RGBA to Oklch against D65 white point and preserving and normalizing alpha channels keeping it at last positions. This is much more effective than naive direct transformation
311///
312/// # Arguments
313/// * `src` - A slice contains RGBA data
314/// * `src_stride` - Bytes per row for src data.
315/// * `width` - Image width
316/// * `height` - Image height
317/// * `dst` - A mutable slice to receive LCH(a) data
318/// * `dst_stride` - Bytes per row for dst data
319/// * `transfer_function` - transfer function to linear colorspace
320pub fn rgba_to_oklch(
321    src: &[u8],
322    src_stride: u32,
323    dst: &mut [f32],
324    dst_stride: u32,
325    width: u32,
326    height: u32,
327    transfer_function: TransferFunction,
328) {
329    channels_to_oklab::<{ ImageConfiguration::Rgba as u8 }, { OklabTarget::Oklch as u8 }>(
330        src,
331        src_stride,
332        dst,
333        dst_stride,
334        width,
335        height,
336        transfer_function,
337    );
338}
339
340/// This function converts BGRA to Oklch against D65 white point and preserving and normalizing alpha channels keeping it at last positions. This is much more effective than naive direct transformation
341///
342/// # Arguments
343/// * `src` - A slice contains BGRA data
344/// * `src_stride` - Bytes per row for src data.
345/// * `width` - Image width
346/// * `height` - Image height
347/// * `dst` - A mutable slice to receive LCH(a) data
348/// * `dst_stride` - Bytes per row for dst data
349/// * `transfer_function` - transfer function to linear colorspace
350pub fn bgra_to_oklch(
351    src: &[u8],
352    src_stride: u32,
353    dst: &mut [f32],
354    dst_stride: u32,
355    width: u32,
356    height: u32,
357    transfer_function: TransferFunction,
358) {
359    channels_to_oklab::<{ ImageConfiguration::Bgra as u8 }, { OklabTarget::Oklch as u8 }>(
360        src,
361        src_stride,
362        dst,
363        dst_stride,
364        width,
365        height,
366        transfer_function,
367    );
368}
369
370/// This function converts BGR to Oklch against D65 white point. This is much more effective than naive direct transformation
371///
372/// # Arguments
373/// * `src` - A slice contains BGR data
374/// * `src_stride` - Bytes per row for src data.
375/// * `width` - Image width
376/// * `height` - Image height
377/// * `dst` - A mutable slice to receive LCH(a) data
378/// * `dst_stride` - Bytes per row for dst data
379/// * `transfer_function` - transfer function to linear colorspace
380pub fn bgr_to_oklch(
381    src: &[u8],
382    src_stride: u32,
383    dst: &mut [f32],
384    dst_stride: u32,
385    width: u32,
386    height: u32,
387    transfer_function: TransferFunction,
388) {
389    channels_to_oklab::<{ ImageConfiguration::Bgr as u8 }, { OklabTarget::Oklch as u8 }>(
390        src,
391        src_stride,
392        dst,
393        dst_stride,
394        width,
395        height,
396        transfer_function,
397    );
398}