dcv_color_primitives/
lib.rs

1// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2// SPDX-License-Identifier: MIT-0
3
4// Permission is hereby granted, free of charge, to any person obtaining a copy of this
5// software and associated documentation files (the "Software"), to deal in the Software
6// without restriction, including without limitation the rights to use, copy, modify,
7// merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
8// permit persons to whom the Software is furnished to do so.
9
10// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
11// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
12// PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
13// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
14// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
15// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
16#![cfg_attr(coverage_nightly, feature(coverage_attribute))]
17#![warn(missing_docs)]
18#![deny(trivial_casts)]
19#![deny(trivial_numeric_casts)]
20#![deny(unused_import_braces)]
21#![deny(
22    clippy::complexity,
23    clippy::correctness,
24    clippy::perf,
25    clippy::style,
26    clippy::pedantic
27)]
28#![allow(
29    clippy::too_many_arguments, // API design
30    clippy::missing_safety_doc, // Until we add them...
31    clippy::similar_names, // This requires effort to ensure
32    // Due to vzeroupper use, compiler does not inline intrinsics
33    // but rather creates a function for each one that wraps the operation followed
34    // by vzeroupper().
35    // This is detrimental to performance
36    clippy::inline_always,
37    // Yield false positives
38    clippy::must_use_candidate,
39)]
40
41//! DCV color primitives is a library to perform image color model conversion.
42//!
43//! It is able to convert the following pixel formats:
44//!
45//! | Source pixel format  | Destination pixel formats  |
46//! | -------------------- | -------------------------- |
47//! | ARGB                 | I420, I444, NV12           |
48//! | BGR                  | I420, I444, NV12, RGB      |
49//! | BGRA                 | I420, I444, NV12, RGB      |
50//! | I420                 | BGRA, RGBA                 |
51//! | I444                 | BGRA, RGBA                 |
52//! | NV12                 | BGRA, RGB, RGBA            |
53//! | RGB                  | BGRA                       |
54//!
55//! The supported color models are:
56//! * ycbcr, ITU-R Recommendation BT.601 (standard video system)
57//! * ycbcr, ITU-R Recommendation BT.709 (CSC systems)
58//!
59//! Both standard range (0-235) and full range (0-255) are supported.
60//!
61//! # Examples
62//!
63//! Convert an image from bgra to nv12 (single plane) format, with Bt601 color space:
64//! ```
65//! use dcv_color_primitives as dcp;
66//! use dcp::{convert_image, ColorSpace, ImageFormat, PixelFormat};
67//!
68//! fn convert() {
69//!     const WIDTH: u32 = 640;
70//!     const HEIGHT: u32 = 480;
71//!
72//!     let src_data = Box::new([0u8; 4 * (WIDTH as usize) * (HEIGHT as usize)]);
73//!     let mut dst_data = Box::new([0u8; 3 * (WIDTH as usize) * (HEIGHT as usize) / 2]);
74//!
75//!     let src_format = ImageFormat {
76//!         pixel_format: PixelFormat::Bgra,
77//!         color_space: ColorSpace::Rgb,
78//!         num_planes: 1,
79//!     };
80//!
81//!     let dst_format = ImageFormat {
82//!         pixel_format: PixelFormat::Nv12,
83//!         color_space: ColorSpace::Bt601,
84//!         num_planes: 1,
85//!     };
86//!
87//!     convert_image(
88//!         WIDTH,
89//!         HEIGHT,
90//!         &src_format,
91//!         None,
92//!         &[&*src_data],
93//!         &dst_format,
94//!         None,
95//!         &mut [&mut *dst_data],
96//!     );
97//! }
98//! ```
99//!
100//! Handle conversion errors:
101//! ```
102//! use dcv_color_primitives as dcp;
103//! use dcp::{convert_image, ColorSpace, ImageFormat, PixelFormat};
104//! use std::error;
105//!
106//! fn convert() -> Result<(), Box<dyn error::Error>> {
107//!     const WIDTH: u32 = 640;
108//!     const HEIGHT: u32 = 480;
109//!
110//!     let src_data = Box::new([0u8; 4 * (WIDTH as usize) * (HEIGHT as usize)]);
111//!     let mut dst_data = Box::new([0u8; 3 * (WIDTH as usize) * (HEIGHT as usize) / 2]);
112//!
113//!     let src_format = ImageFormat {
114//!         pixel_format: PixelFormat::Bgra,
115//!         color_space: ColorSpace::Bt709,
116//!         num_planes: 1,
117//!     };
118//!
119//!     let dst_format = ImageFormat {
120//!         pixel_format: PixelFormat::Nv12,
121//!         color_space: ColorSpace::Bt601,
122//!         num_planes: 1,
123//!     };
124//!
125//!     convert_image(
126//!         WIDTH,
127//!         HEIGHT,
128//!         &src_format,
129//!         None,
130//!         &[&*src_data],
131//!         &dst_format,
132//!         None,
133//!         &mut [&mut *dst_data],
134//!     )?;
135//!
136//!     Ok(())
137//! }
138//! ```
139//!
140//! Compute how many bytes are needed to store and image of a given format and size:
141//! ```
142//! use dcv_color_primitives as dcp;
143//! use dcp::{get_buffers_size, ColorSpace, ImageFormat, PixelFormat};
144//! use std::error;
145//!
146//! fn compute_size() -> Result<(), Box<dyn error::Error>> {
147//!     const WIDTH: u32 = 640;
148//!     const HEIGHT: u32 = 480;
149//!     const NUM_PLANES: u32 = 1;
150//!
151//!     let format = ImageFormat {
152//!         pixel_format: PixelFormat::Bgra,
153//!         color_space: ColorSpace::Rgb,
154//!         num_planes: NUM_PLANES,
155//!     };
156//!
157//!     let sizes: &mut [usize] = &mut [0usize; NUM_PLANES as usize];
158//!     get_buffers_size(WIDTH, HEIGHT, &format, None, sizes)?;
159//!
160//!     let buffer: Vec<_> = vec![0u8; sizes[0]];
161//!
162//!     // Do something with buffer
163//!     // --snip--
164//!
165//!     Ok(())
166//! }
167//! ```
168//!
169//! Provide image planes to handle data scattered in multiple buffers that are not
170//! necessarily contiguous:
171//! ```
172//! use dcv_color_primitives as dcp;
173//! use dcp::{convert_image, get_buffers_size, ColorSpace, ImageFormat, PixelFormat};
174//! use std::error;
175//!
176//! fn convert() -> Result<(), Box<dyn error::Error>> {
177//!     const WIDTH: u32 = 640;
178//!     const HEIGHT: u32 = 480;
179//!     const NUM_SRC_PLANES: u32 = 2;
180//!     const NUM_DST_PLANES: u32 = 1;
181//!
182//!     let src_format = ImageFormat {
183//!         pixel_format: PixelFormat::Nv12,
184//!         color_space: ColorSpace::Bt709,
185//!         num_planes: NUM_SRC_PLANES,
186//!     };
187//!
188//!     let src_sizes: &mut [usize] = &mut [0usize; NUM_SRC_PLANES as usize];
189//!     get_buffers_size(WIDTH, HEIGHT, &src_format, None, src_sizes)?;
190//!
191//!     let src_y: Vec<_> = vec![0u8; src_sizes[0]];
192//!     let src_uv: Vec<_> = vec![0u8; src_sizes[1]];
193//!
194//!     let dst_format = ImageFormat {
195//!         pixel_format: PixelFormat::Bgra,
196//!         color_space: ColorSpace::Rgb,
197//!         num_planes: NUM_DST_PLANES,
198//!     };
199//!
200//!     let dst_sizes: &mut [usize] = &mut [0usize; NUM_DST_PLANES as usize];
201//!     get_buffers_size(WIDTH, HEIGHT, &dst_format, None, dst_sizes)?;
202//!
203//!     let mut dst_rgba: Vec<_> = vec![0u8; dst_sizes[0]];
204//!
205//!     convert_image(
206//!         WIDTH,
207//!         HEIGHT,
208//!         &src_format,
209//!         None,
210//!         &[&src_y[..], &src_uv[..]],
211//!         &dst_format,
212//!         None,
213//!         &mut [&mut dst_rgba[..]],
214//!     )?;
215//!
216//!     Ok(())
217//! }
218//! ```
219//!
220//! Provide image strides to convert data which is not tightly packed:
221//! ```
222//! use dcv_color_primitives as dcp;
223//! use dcp::{convert_image, get_buffers_size, ColorSpace, ImageFormat, PixelFormat};
224//! use std::error;
225//!
226//! fn convert() -> Result<(), Box<dyn error::Error>> {
227//!     const WIDTH: u32 = 640;
228//!     const HEIGHT: u32 = 480;
229//!     const NUM_SRC_PLANES: u32 = 1;
230//!     const NUM_DST_PLANES: u32 = 2;
231//!     const RGB_STRIDE: usize = 4 * (((3 * (WIDTH as usize)) + 3) / 4);
232//!
233//!     let src_format = ImageFormat {
234//!         pixel_format: PixelFormat::Bgr,
235//!         color_space: ColorSpace::Rgb,
236//!         num_planes: NUM_SRC_PLANES,
237//!     };
238//!
239//!     let src_strides: &[usize] = &[RGB_STRIDE];
240//!
241//!     let src_sizes: &mut [usize] = &mut [0usize; NUM_SRC_PLANES as usize];
242//!     get_buffers_size(WIDTH, HEIGHT, &src_format, Some(src_strides), src_sizes)?;
243//!
244//!     let src_rgba: Vec<_> = vec![0u8; src_sizes[0]];
245//!
246//!     let dst_format = ImageFormat {
247//!         pixel_format: PixelFormat::Nv12,
248//!         color_space: ColorSpace::Bt709,
249//!         num_planes: NUM_DST_PLANES,
250//!     };
251//!
252//!     let dst_sizes: &mut [usize] = &mut [0usize; NUM_DST_PLANES as usize];
253//!     get_buffers_size(WIDTH, HEIGHT, &dst_format, None, dst_sizes)?;
254//!
255//!     let mut dst_y: Vec<_> = vec![0u8; dst_sizes[0]];
256//!     let mut dst_uv: Vec<_> = vec![0u8; dst_sizes[1]];
257//!
258//!     convert_image(
259//!         WIDTH,
260//!         HEIGHT,
261//!         &src_format,
262//!         Some(src_strides),
263//!         &[&src_rgba[..]],
264//!         &dst_format,
265//!         None,
266//!         &mut [&mut dst_y[..], &mut dst_uv[..]],
267//!     )?;
268//!
269//!     Ok(())
270//! }
271//! ```
272mod color_space;
273mod convert_image;
274mod cpu_info;
275mod dispatcher;
276mod pixel_format;
277mod static_assert;
278
279use cpu_info::{CpuManufacturer, InstructionSet};
280use paste::paste;
281use std::error;
282use std::fmt;
283#[cfg(feature = "test_instruction_sets")]
284use std::sync::atomic::{AtomicI32, Ordering};
285use std::sync::OnceLock;
286
287pub use color_space::ColorSpace;
288pub use pixel_format::{PixelFormat, STRIDE_AUTO};
289
290/// An enumeration of errors.
291#[derive(Debug)]
292#[repr(C)]
293pub enum ErrorKind {
294    /// One or more parameters have invalid values for the called function
295    InvalidValue,
296    /// The combination of parameters is unsupported for the called function
297    InvalidOperation,
298    /// Not enough data was provided to the called function. Typically, provided
299    /// arrays are not correctly sized
300    NotEnoughData,
301}
302
303impl fmt::Display for ErrorKind {
304    #[cfg_attr(coverage_nightly, coverage(off))]
305    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
306        match *self {
307            ErrorKind::InvalidValue => write!(
308                f,
309                "One or more parameters have not legal values for the command"
310            ),
311            ErrorKind::InvalidOperation => write!(
312                f,
313                "The combination of parameters is not legal for the command"
314            ),
315            ErrorKind::NotEnoughData => write!(f, "Not enough data provided"),
316        }
317    }
318}
319
320impl error::Error for ErrorKind {
321    #[cfg_attr(coverage_nightly, coverage(off))]
322    fn cause(&self) -> Option<&dyn error::Error> {
323        None
324    }
325}
326
327/// Describes how the image data is laid out in memory and its color space.
328///
329/// # Note
330/// Not all combinations of pixel format, color space and number of planes
331/// describe a valid image format.
332///
333/// Each pixel format has one or more compatible color spaces:
334///
335/// pixel format        | color space
336/// --------------------|--------------------------------------
337/// `PixelFormat::Argb` | `ColorSpace::Rgb`
338/// `PixelFormat::Bgra` | `ColorSpace::Rgb`
339/// `PixelFormat::Bgr`  | `ColorSpace::Rgb`
340/// `PixelFormat::Rgba` | `ColorSpace::Rgb`
341/// `PixelFormat::Rgb`  | `ColorSpace::Rgb`
342/// `PixelFormat::I444` | `ColorSpace::Bt601(FR)`, `ColorSpace::Bt709(FR)`
343/// `PixelFormat::I422` | `ColorSpace::Bt601(FR)`, `ColorSpace::Bt709(FR)`
344/// `PixelFormat::I420` | `ColorSpace::Bt601(FR)`, `ColorSpace::Bt709(FR)`
345/// `PixelFormat::Nv12` | `ColorSpace::Bt601(FR)`, `ColorSpace::Bt709(FR)`
346///
347/// Some pixel formats might impose additional restrictions on the accepted number of
348/// planes and the image size:
349///
350/// pixel format        | subsampling | w   | h   | #planes | #1     | #2     | #3
351/// --------------------|:-----------:|:---:|:---:|:-------:|:------:|:------:|:-------:
352/// `PixelFormat::Argb` | 4:4:4       |     |     | 1       | argb:4 |        |
353/// `PixelFormat::Bgra` | 4:4:4       |     |     | 1       | bgra:4 |        |
354/// `PixelFormat::Bgr`  | 4:4:4       |     |     | 1       | bgr:3  |        |
355/// `PixelFormat::Rgba` | 4:4:4       |     |     | 1       | rgba:4 |        |
356/// `PixelFormat::Rgb`  | 4:4:4       |     |     | 1       | rgb:3  |        |
357/// `PixelFormat::I444` | 4:4:4       |     |     | 3       | y:1    | u:1    | v:1
358/// `PixelFormat::I422` | 4:2:2       |  2  |     | 1, 3    | y:1    | u:1/2  | v:1/2
359/// `PixelFormat::I420` | 4:2:0       |  2  |  2  | 3       | y:1    | u:1/4  | v:1/4
360/// `PixelFormat::Nv12` | 4:2:0       |  2  |  2  | 1, 2    | y:1    | uv:1/2 |
361///
362/// The values reported in columns `w` and `h`, when specified, indicate that the described
363/// image should have width and height that are multiples of the specified values
364#[derive(Debug)]
365#[repr(C)]
366pub struct ImageFormat {
367    /// Pixel format
368    pub pixel_format: PixelFormat,
369    /// Color space
370    pub color_space: ColorSpace,
371    /// Number of planes
372    pub num_planes: u32,
373}
374
375type ConvertDispatcher =
376    fn(u32, u32, u32, &[usize], &[&[u8]], u32, &[usize], &mut [&mut [u8]]) -> bool;
377
378macro_rules! rgb_to_yuv {
379    ($conv:expr, $set:ident, $src_pf:ident, $dst_pf:ident, $dst_cs:ident) => {
380        paste! {
381            $conv[dispatcher::get_index(
382                dispatcher::get_image_index(
383                    PixelFormat::$src_pf as u32,
384                    ColorSpace::Rgb as u32,
385                    dispatcher::get_pixel_format_mode(PixelFormat::$src_pf as u32),
386                ),
387                dispatcher::get_image_index(
388                    PixelFormat::$dst_pf as u32,
389                    ColorSpace::$dst_cs as u32,
390                    dispatcher::get_pixel_format_mode(PixelFormat::$dst_pf as u32),
391                ),
392            )] = Some(convert_image::$set::[<$src_pf:lower _ $dst_pf:lower _ $dst_cs:lower>])
393        }
394    };
395}
396
397macro_rules! yuv_to_rgb {
398    ($conv:expr, $set:ident, $src_pf:ident, $src_cs:ident, $dst_pf:ident) => {
399        paste! {
400            $conv[dispatcher::get_index(
401                dispatcher::get_image_index(
402                    PixelFormat::$src_pf as u32,
403                    ColorSpace::$src_cs as u32,
404                    dispatcher::get_pixel_format_mode(PixelFormat::$src_pf as u32),
405                ),
406                dispatcher::get_image_index(
407                    PixelFormat::$dst_pf as u32,
408                    ColorSpace::Rgb as u32,
409                    dispatcher::get_pixel_format_mode(PixelFormat::$dst_pf as u32),
410                ),
411            )] = Some(convert_image::$set::[<$src_pf:lower _ $src_cs:lower _ $dst_pf:lower>])
412        }
413    };
414}
415
416macro_rules! rgb_to_rgb {
417    ($conv:expr, $set:ident, $src_pf:ident, $dst_pf:ident) => {
418        paste! {
419            $conv[dispatcher::get_index(
420                dispatcher::get_image_index(
421                    PixelFormat::$src_pf as u32,
422                    ColorSpace::Rgb as u32,
423                    dispatcher::get_pixel_format_mode(PixelFormat::$src_pf as u32),
424                ),
425                dispatcher::get_image_index(
426                    PixelFormat::$dst_pf as u32,
427                    ColorSpace::Rgb as u32,
428                    dispatcher::get_pixel_format_mode(PixelFormat::$dst_pf as u32),
429                ),
430            )] = Some(convert_image::$set::[<$src_pf:lower _ $dst_pf:lower>])
431        }
432    };
433}
434
435macro_rules! set_dispatch_table {
436    ($conv:expr, $set:ident) => {
437        rgb_to_rgb!($conv, $set, Bgr, Rgb);
438        rgb_to_rgb!($conv, $set, Bgra, Rgb);
439        rgb_to_rgb!($conv, $set, Rgb, Bgra);
440        rgb_to_yuv!($conv, $set, Argb, I420, Bt601);
441        rgb_to_yuv!($conv, $set, Argb, I420, Bt601FR);
442        rgb_to_yuv!($conv, $set, Argb, I420, Bt709);
443        rgb_to_yuv!($conv, $set, Argb, I420, Bt709FR);
444        rgb_to_yuv!($conv, $set, Argb, I444, Bt601);
445        rgb_to_yuv!($conv, $set, Argb, I444, Bt601FR);
446        rgb_to_yuv!($conv, $set, Argb, I444, Bt709);
447        rgb_to_yuv!($conv, $set, Argb, I444, Bt709FR);
448        rgb_to_yuv!($conv, $set, Argb, Nv12, Bt601);
449        rgb_to_yuv!($conv, $set, Argb, Nv12, Bt601FR);
450        rgb_to_yuv!($conv, $set, Argb, Nv12, Bt709);
451        rgb_to_yuv!($conv, $set, Argb, Nv12, Bt709FR);
452        rgb_to_yuv!($conv, $set, Bgr, I420, Bt601);
453        rgb_to_yuv!($conv, $set, Bgr, I420, Bt601FR);
454        rgb_to_yuv!($conv, $set, Bgr, I420, Bt709);
455        rgb_to_yuv!($conv, $set, Bgr, I420, Bt709FR);
456        rgb_to_yuv!($conv, $set, Bgr, I444, Bt601);
457        rgb_to_yuv!($conv, $set, Bgr, I444, Bt601FR);
458        rgb_to_yuv!($conv, $set, Bgr, I444, Bt709);
459        rgb_to_yuv!($conv, $set, Bgr, I444, Bt709FR);
460        rgb_to_yuv!($conv, $set, Bgr, Nv12, Bt601);
461        rgb_to_yuv!($conv, $set, Bgr, Nv12, Bt601FR);
462        rgb_to_yuv!($conv, $set, Bgr, Nv12, Bt709);
463        rgb_to_yuv!($conv, $set, Bgr, Nv12, Bt709FR);
464        rgb_to_yuv!($conv, $set, Bgra, I420, Bt601);
465        rgb_to_yuv!($conv, $set, Bgra, I420, Bt601FR);
466        rgb_to_yuv!($conv, $set, Bgra, I420, Bt709);
467        rgb_to_yuv!($conv, $set, Bgra, I420, Bt709FR);
468        rgb_to_yuv!($conv, $set, Bgra, I444, Bt601);
469        rgb_to_yuv!($conv, $set, Bgra, I444, Bt601FR);
470        rgb_to_yuv!($conv, $set, Bgra, I444, Bt709);
471        rgb_to_yuv!($conv, $set, Bgra, I444, Bt709FR);
472        rgb_to_yuv!($conv, $set, Bgra, Nv12, Bt601);
473        rgb_to_yuv!($conv, $set, Bgra, Nv12, Bt601FR);
474        rgb_to_yuv!($conv, $set, Bgra, Nv12, Bt709);
475        rgb_to_yuv!($conv, $set, Bgra, Nv12, Bt709FR);
476        yuv_to_rgb!($conv, $set, I420, Bt601, Bgra);
477        yuv_to_rgb!($conv, $set, I420, Bt601, Rgba);
478        yuv_to_rgb!($conv, $set, I420, Bt601FR, Bgra);
479        yuv_to_rgb!($conv, $set, I420, Bt601FR, Rgba);
480        yuv_to_rgb!($conv, $set, I420, Bt709, Bgra);
481        yuv_to_rgb!($conv, $set, I420, Bt709, Rgba);
482        yuv_to_rgb!($conv, $set, I420, Bt709FR, Bgra);
483        yuv_to_rgb!($conv, $set, I420, Bt709FR, Rgba);
484        yuv_to_rgb!($conv, $set, I444, Bt601, Bgra);
485        yuv_to_rgb!($conv, $set, I444, Bt601, Rgba);
486        yuv_to_rgb!($conv, $set, I444, Bt601FR, Bgra);
487        yuv_to_rgb!($conv, $set, I444, Bt601FR, Rgba);
488        yuv_to_rgb!($conv, $set, I444, Bt709, Bgra);
489        yuv_to_rgb!($conv, $set, I444, Bt709, Rgba);
490        yuv_to_rgb!($conv, $set, I444, Bt709FR, Bgra);
491        yuv_to_rgb!($conv, $set, I444, Bt709FR, Rgba);
492        yuv_to_rgb!($conv, $set, Nv12, Bt601, Bgra);
493        yuv_to_rgb!($conv, $set, Nv12, Bt601, Rgb);
494        yuv_to_rgb!($conv, $set, Nv12, Bt601, Rgba);
495        yuv_to_rgb!($conv, $set, Nv12, Bt601FR, Bgra);
496        yuv_to_rgb!($conv, $set, Nv12, Bt601FR, Rgb);
497        yuv_to_rgb!($conv, $set, Nv12, Bt601FR, Rgba);
498        yuv_to_rgb!($conv, $set, Nv12, Bt709, Bgra);
499        yuv_to_rgb!($conv, $set, Nv12, Bt709, Rgb);
500        yuv_to_rgb!($conv, $set, Nv12, Bt709, Rgba);
501        yuv_to_rgb!($conv, $set, Nv12, Bt709FR, Bgra);
502        yuv_to_rgb!($conv, $set, Nv12, Bt709FR, Rgb);
503        yuv_to_rgb!($conv, $set, Nv12, Bt709FR, Rgba);
504    };
505}
506
507#[cfg(feature = "test_instruction_sets")]
508static TEST_SET: AtomicI32 = AtomicI32::new(-1);
509
510type DispatchTable = [Option<ConvertDispatcher>; dispatcher::TABLE_SIZE];
511
512struct Context {
513    manufacturer: CpuManufacturer,
514    set: InstructionSet,
515    converters: DispatchTable,
516    #[cfg(feature = "test_instruction_sets")]
517    test_converters: [Option<DispatchTable>; 3],
518}
519
520impl Context {
521    pub fn global() -> &'static Context {
522        static INSTANCE: OnceLock<Context> = OnceLock::new();
523        INSTANCE.get_or_init(Context::new)
524    }
525
526    pub fn new() -> Self {
527        let (manufacturer, set) = cpu_info::get();
528        let mut context = Context {
529            manufacturer,
530            set,
531            converters: [None; dispatcher::TABLE_SIZE],
532            #[cfg(feature = "test_instruction_sets")]
533            test_converters: [None; 3],
534        };
535
536        #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
537        match context.set {
538            InstructionSet::X86 => {
539                set_dispatch_table!(context.converters, x86);
540            }
541            InstructionSet::Sse2 => {
542                set_dispatch_table!(context.converters, sse2);
543                #[cfg(feature = "test_instruction_sets")]
544                {
545                    let mut table: DispatchTable = [None; dispatcher::TABLE_SIZE];
546                    set_dispatch_table!(table, x86);
547                    context.test_converters[0] = Some(table);
548                }
549            }
550            InstructionSet::Avx2 => {
551                set_dispatch_table!(context.converters, avx2);
552
553                #[cfg(feature = "test_instruction_sets")]
554                {
555                    let mut table: DispatchTable = [None; dispatcher::TABLE_SIZE];
556                    set_dispatch_table!(table, sse2);
557                    context.test_converters[1] = Some(table);
558
559                    let mut table: DispatchTable = [None; dispatcher::TABLE_SIZE];
560                    set_dispatch_table!(table, x86);
561                    context.test_converters[0] = Some(table);
562                }
563            }
564        }
565
566        #[cfg(target_arch = "aarch64")]
567        match context.set {
568            InstructionSet::X86 => {
569                set_dispatch_table!(context.converters, x86);
570            }
571            InstructionSet::Neon => {
572                set_dispatch_table!(context.converters, neon);
573                #[cfg(feature = "test_instruction_sets")]
574                {
575                    let mut table: DispatchTable = [None; dispatcher::TABLE_SIZE];
576                    set_dispatch_table!(table, x86);
577                    context.test_converters[0] = Some(table);
578                }
579            }
580        }
581
582        // This is the default for wasm32 targets
583        #[cfg(not(any(target_arch = "x86", target_arch = "x86_64", target_arch = "aarch64")))]
584        {
585            set_dispatch_table!(context.converters, x86);
586        }
587
588        context
589    }
590}
591
592/// Returns a description of the algorithms that are best for the running cpu and
593/// available instruction sets
594///
595/// # Examples
596/// ```
597/// use dcv_color_primitives as dcp;
598/// println!("{}", dcp::describe_acceleration());
599/// // => {cpu-manufacturer:Intel,instruction-set:Avx2}
600pub fn describe_acceleration() -> String {
601    let state = Context::global();
602
603    format!(
604        "{{cpu-manufacturer:{:?},instruction-set:{:?}}}",
605        state.manufacturer, state.set
606    )
607}
608
609/// Compute number of bytes required to store an image given its format, dimensions
610/// and optionally its strides
611///
612/// # Arguments
613/// * `width` - Width of the image in pixels
614/// * `height` - Height of the image in pixels
615/// * `format` - Image format
616/// * `strides` - An array of distances in bytes between starts of consecutive lines
617///               in each image planes
618/// * `buffers_size` - An array describing the minimum number of bytes required in each
619///                    image planes
620///
621/// # Examples
622/// Compute how many bytes are needed to store and image of a given format and size
623/// assuming *all planes contain data which is tightly packed*:
624/// ```
625/// use dcv_color_primitives as dcp;
626/// use dcp::{get_buffers_size, ColorSpace, ImageFormat, PixelFormat};
627/// use std::error;
628///
629/// fn compute_size_packed() -> Result<(), Box<dyn error::Error>> {
630///     const WIDTH: u32 = 640;
631///     const HEIGHT: u32 = 480;
632///     const NUM_PLANES: u32 = 2;
633///
634///     let format = ImageFormat {
635///         pixel_format: PixelFormat::Nv12,
636///         color_space: ColorSpace::Bt601,
637///         num_planes: NUM_PLANES,
638///     };
639///
640///     let sizes: &mut [usize] = &mut [0usize; NUM_PLANES as usize];
641///     get_buffers_size(WIDTH, HEIGHT, &format, None, sizes)?;
642///
643///     Ok(())
644/// }
645/// ```
646///
647/// Compute how many bytes are needed to store and image of a given format and size
648/// in which *all planes have custom strides*:
649/// ```
650/// use dcv_color_primitives as dcp;
651/// use dcp::{get_buffers_size, ColorSpace, ImageFormat, PixelFormat};
652/// use std::error;
653///
654/// fn compute_size_custom_strides() -> Result<(), Box<dyn error::Error>> {
655///     const WIDTH: u32 = 640;
656///     const HEIGHT: u32 = 480;
657///     const NUM_PLANES: u32 = 2;
658///     const Y_STRIDE: usize = (WIDTH as usize) + 1;
659///     const UV_STRIDE: usize = (WIDTH as usize) + 3;
660///
661///     let format = ImageFormat {
662///         pixel_format: PixelFormat::Nv12,
663///         color_space: ColorSpace::Bt601,
664///         num_planes: NUM_PLANES,
665///     };
666///
667///     let strides: &[usize] = &[ Y_STRIDE, UV_STRIDE, ];
668///     let sizes: &mut [usize] = &mut [0usize; NUM_PLANES as usize];
669///     get_buffers_size(WIDTH, HEIGHT, &format, Some(strides), sizes)?;
670///
671///     Ok(())
672/// }
673/// ```
674///
675/// Compute how many bytes are needed to store and image of a given format and size
676/// in which *some planes have custom strides*, while *some other are assumed to
677/// contain data which is tightly packed*:
678/// ```
679/// use dcv_color_primitives as dcp;
680/// use dcp::{get_buffers_size, ColorSpace, ImageFormat, PixelFormat, STRIDE_AUTO};
681/// use std::error;
682///
683/// fn compute_size_custom_strides() -> Result<(), Box<dyn error::Error>> {
684///     const WIDTH: u32 = 640;
685///     const HEIGHT: u32 = 480;
686///     const NUM_PLANES: u32 = 2;
687///     const Y_STRIDE: usize = (WIDTH as usize) + 1;
688///
689///     let format = ImageFormat {
690///         pixel_format: PixelFormat::Nv12,
691///         color_space: ColorSpace::Bt601,
692///         num_planes: NUM_PLANES,
693///     };
694///
695///     let strides: &[usize] = &[ Y_STRIDE, STRIDE_AUTO, ];
696///     let sizes: &mut [usize] = &mut [0usize; NUM_PLANES as usize];
697///     get_buffers_size(WIDTH, HEIGHT, &format, Some(strides), sizes)?;
698///
699///     Ok(())
700/// }
701/// ```
702///
703/// Default strides (e.g. the one you would set for tightly packed data) can be set
704/// using the constant [`STRIDE_AUTO`]
705///
706/// # Errors
707///
708/// * [`InvalidValue`] if `width` or `height` violate the [`size constraints`] that might by
709///   imposed by the image pixel format
710///
711/// * [`InvalidValue`] if the image format has a number of planes which is not compatible
712///   with its pixel format
713///
714/// * [`NotEnoughData`] if the strides array is not `None` and its length is less than the
715///   image format number of planes
716///
717/// * [`NotEnoughData`] if the buffers size array is not `None` and its length is less than the
718///   image format number of planes
719///
720/// [`InvalidValue`]: ./enum.ErrorKind.html#variant.InvalidValue
721/// [`NotEnoughData`]: ./enum.ErrorKind.html#variant.NotEnoughData
722/// [`size constraints`]: ./struct.ImageFormat.html#note
723/// [`STRIDE_AUTO`]: ./constant.STRIDE_AUTO.html
724pub fn get_buffers_size(
725    width: u32,
726    height: u32,
727    format: &ImageFormat,
728    strides: Option<&[usize]>,
729    buffers_size: &mut [usize],
730) -> Result<(), ErrorKind> {
731    let pixel_format = format.pixel_format as u32;
732    let last_plane = format.num_planes.wrapping_sub(1);
733    if !pixel_format::is_compatible(pixel_format, width, height, last_plane) {
734        return Err(ErrorKind::InvalidValue);
735    }
736
737    if pixel_format::get_buffers_size(
738        pixel_format,
739        width,
740        height,
741        last_plane,
742        strides.unwrap_or(&pixel_format::DEFAULT_STRIDES),
743        buffers_size,
744    ) {
745        Ok(())
746    } else {
747        Err(ErrorKind::NotEnoughData)
748    }
749}
750
751/// Converts from a color space to another one, applying downsampling/upsampling
752/// to match destination image format.
753///
754/// # Arguments
755/// * `width` - Width of the image to convert in pixels
756/// * `height` - Height of the image to convert in pixels
757/// * `src_format` - Source image format
758/// * `src_strides` - An array of distances in bytes between starts of consecutive lines
759///                   in each source image planes
760/// * `src_buffers` - An array of image buffers in each source color plane
761/// * `dst_format` - Destination image format
762/// * `dst_strides` - An array of distances in bytes between starts of consecutive lines
763///                   in each destination image planes
764/// * `dst_buffers` - An array of image buffers in each destination color plane
765///
766/// # Errors
767///
768/// * [`InvalidValue`] if `width` or `height` violate the [`size constraints`]
769///   that might by imposed by the source and destination image pixel formats
770///
771/// * [`InvalidValue`] if source or destination image formats have a number of planes
772///   which is not compatible with their pixel formats
773///
774/// * [`InvalidOperation`] if there is no available method to convert the image with the
775///   source pixel format to the image with the destination pixel format.
776///
777///   The list of available conversions is specified here:
778///
779///   Source image pixel format       | Supported destination image pixel formats
780///   --------------------------------|------------------------------------------
781///   `PixelFormat::Argb`             | `PixelFormat::I420` [`1`]
782///   `PixelFormat::Argb`             | `PixelFormat::I444` [`1`]
783///   `PixelFormat::Argb`             | `PixelFormat::Nv12` [`1`]
784///   `PixelFormat::Bgra`             | `PixelFormat::I420` [`1`]
785///   `PixelFormat::Bgra`             | `PixelFormat::I444` [`1`]
786///   `PixelFormat::Bgra`             | `PixelFormat::Nv12` [`1`]
787///   `PixelFormat::Bgra`             | `PixelFormat::Rgb`  [`4`]
788///   `PixelFormat::Bgr`              | `PixelFormat::I420` [`1`]
789///   `PixelFormat::Bgr`              | `PixelFormat::I444` [`1`]
790///   `PixelFormat::Bgr`              | `PixelFormat::Nv12` [`1`]
791///   `PixelFormat::Bgr`              | `PixelFormat::Rgb`  [`5`]
792///   `PixelFormat::I420`             | `PixelFormat::Bgra`, `PixelFormat::Rgba` [`2`]
793///   `PixelFormat::I444`             | `PixelFormat::Bgra`, `PixelFormat::Rgba` [`2`]
794///   `PixelFormat::Nv12`             | `PixelFormat::Bgra`, `PixelFormat::Rgb`, `PixelFormat::Rgba` [`2`]
795///   `PixelFormat::Rgb`              | `PixelFormat::Bgra` [`3`]
796///
797/// * [`NotEnoughData`] if the source stride array is not `None` and its length is less than the
798///   source image format number of planes
799///
800/// * [`NotEnoughData`] if the destination stride array is not `None` and its length is less than the
801///   destination image format number of planes
802///
803/// * [`NotEnoughData`] if one or more source/destination buffers does not provide enough data.
804///
805///   The minimum number of bytes to provide for each buffer depends from the image format, dimensions,
806///   and strides (if they are not `None`).
807///
808///   You can compute the buffers' size using [`get_buffers_size`]
809///
810/// # Algorithm 1
811/// Conversion from linear RGB model to ycbcr color model, with 4:2:0 downsampling
812///
813/// If the destination image color space is Bt601, the following formula is applied:
814/// ```text
815/// y  =  0.257 * r + 0.504 * g + 0.098 * b + 16
816/// cb = -0.148 * r - 0.291 * g + 0.439 * b + 128
817/// cr =  0.439 * r - 0.368 * g - 0.071 * b + 128
818/// ```
819///
820/// If the destination image color space is Bt709, the following formula is applied:
821/// ```text
822/// y  =  0.213 * r + 0.715 * g + 0.072 * b + 16
823/// cb = -0.117 * r - 0.394 * g + 0.511 * b + 128
824/// cr =  0.511 * r - 0.464 * g - 0.047 * b + 128
825/// ```
826///
827/// If the destination image color space is `Bt601FR`, the following formula is applied:
828/// ```text
829/// y  =  0.299 * r + 0.587 * g + 0.114 * b
830/// cb = -0.169 * r - 0.331 * g + 0.500 * b + 128
831/// cr =  0.500 * r - 0.419 * g - 0.081 * b + 128
832/// ```
833///
834/// If the destination image color space is `Bt709FR`, the following formula is applied:
835/// ```text
836/// y  =  0.213 * r + 0.715 * g + 0.072 * b
837/// cb = -0.115 * r - 0.385 * g + 0.500 * b + 128
838/// cr =  0.500 * r - 0.454 * g - 0.046 * b + 128
839/// ```
840///
841/// # Algorithm 2
842/// Conversion from ycbcr model to linear RGB model, with 4:4:4 upsampling
843///
844/// If the destination image contains an alpha channel, each component will be set to 255
845///
846/// If the source image color space is `Bt601`, the following formula is applied:
847/// ```text
848/// r = 1.164 * (y - 16) + 1.596 * (cr - 128)
849/// g = 1.164 * (y - 16) - 0.813 * (cr - 128) - 0.392 * (cb - 128)
850/// b = 1.164 * (y - 16) + 2.017 * (cb - 128)
851/// ```
852///
853/// If the source image color space is `Bt709`, the following formula is applied:
854/// ```text
855/// r = 1.164 * (y - 16) + 1.793 * (cr - 128)
856/// g = 1.164 * (y - 16) - 0.534 * (cr - 128) - 0.213 * (cb - 128)
857/// b = 1.164 * (y - 16) + 2.115 * (cb - 128)
858/// ```
859///
860/// If the source image color space is `Bt601FR`, the following formula is applied:
861/// ```text
862/// r = y + 1.402 * (cr - 128)
863/// g = y - 0.714 * (cr - 128) - 0.344 * (cb - 128)
864/// b = y + 1.772 * (cb - 128)
865/// ```
866///
867/// If the source image color space is `Bt709FR`, the following formula is applied:
868/// ```text
869/// r = y + 1.575 * (cr - 128)
870/// g = y - 0.468 * (cr - 128) - 0.187 * (cb - 128)
871/// b = y + 1.856 * (cb - 128)
872/// ```
873///
874/// # Algorithm 3
875/// Conversion from RGB to BGRA
876///
877/// # Algorithm 4
878/// Conversion from BGRA to RGB
879///
880/// # Algorithm 5
881/// Conversion from BGR to RGB
882///
883/// [`InvalidValue`]: ./enum.ErrorKind.html#variant.InvalidValue
884/// [`InvalidOperation`]: ./enum.ErrorKind.html#variant.InvalidOperation
885/// [`NotEnoughData`]: ./enum.ErrorKind.html#variant.NotEnoughData
886/// [`size constraints`]: ./struct.ImageFormat.html#note
887/// [`get_buffers_size`]: ./fn.get_buffers_size.html
888/// [`1`]: ./fn.convert_image.html#algorithm-1
889/// [`2`]: ./fn.convert_image.html#algorithm-2
890/// [`3`]: ./fn.convert_image.html#algorithm-3
891pub fn convert_image(
892    width: u32,
893    height: u32,
894    src_format: &ImageFormat,
895    src_strides: Option<&[usize]>,
896    src_buffers: &[&[u8]],
897    dst_format: &ImageFormat,
898    dst_strides: Option<&[usize]>,
899    dst_buffers: &mut [&mut [u8]],
900) -> Result<(), ErrorKind> {
901    let src_pixel_format = src_format.pixel_format as u32;
902    let dst_pixel_format = dst_format.pixel_format as u32;
903    let src_color_space = src_format.color_space as u32;
904    let dst_color_space = dst_format.color_space as u32;
905
906    // Cross-correlate pixel format with color space. Predicate handles Table 1
907    let src_pf_mode = dispatcher::get_pixel_format_mode(src_pixel_format);
908    let src_cs_mode = dispatcher::get_color_space_mode(src_color_space);
909    let dst_pf_mode = dispatcher::get_pixel_format_mode(dst_pixel_format);
910    let dst_cs_mode = dispatcher::get_color_space_mode(dst_color_space);
911    let src_pf_cs_mismatch = src_pf_mode ^ src_cs_mode;
912    let dst_pf_cs_mismatch = dst_pf_mode ^ dst_cs_mode;
913    if src_pf_cs_mismatch | dst_pf_cs_mismatch {
914        return Err(ErrorKind::InvalidValue);
915    }
916
917    // Cross-correlate pixel format with planes and alignment.
918    // wrapping_sub is wanted. If num_planes is 0, this turns in a very big number that
919    // still represents an invalid number of planes.
920    let last_src_plane = src_format.num_planes.wrapping_sub(1);
921    if !pixel_format::is_compatible(src_pixel_format, width, height, last_src_plane) {
922        return Err(ErrorKind::InvalidValue);
923    }
924
925    let last_dst_plane = dst_format.num_planes.wrapping_sub(1);
926    if !pixel_format::is_compatible(dst_pixel_format, width, height, last_dst_plane) {
927        return Err(ErrorKind::InvalidValue);
928    }
929
930    // Cross-correlate modes.
931    let src_index = dispatcher::get_image_index(src_pixel_format, src_color_space, src_pf_mode);
932    let dst_index = dispatcher::get_image_index(dst_pixel_format, dst_color_space, dst_pf_mode);
933    let index = dispatcher::get_index(src_index, dst_index);
934    let converters = Context::global().converters;
935
936    #[cfg(feature = "test_instruction_sets")]
937    let converters = {
938        let test_converters = Context::global().test_converters;
939        #[allow(clippy::cast_sign_loss)]
940        // Checked: we want the invalid value '-1' to be mapped outside the valid range
941        test_converters
942            .get(TEST_SET.load(Ordering::SeqCst) as usize)
943            .map(|&x| x)
944            .flatten()
945            .unwrap_or(converters)
946    };
947
948    if index >= converters.len() {
949        return Err(ErrorKind::InvalidOperation);
950    }
951
952    let converter = converters[index];
953    match converter {
954        None => Err(ErrorKind::InvalidOperation),
955        Some(image_converter) => {
956            if image_converter(
957                width,
958                height,
959                last_src_plane,
960                src_strides.unwrap_or(&pixel_format::DEFAULT_STRIDES),
961                src_buffers,
962                last_dst_plane,
963                dst_strides.unwrap_or(&pixel_format::DEFAULT_STRIDES),
964                dst_buffers,
965            ) {
966                Ok(())
967            } else {
968                Err(ErrorKind::NotEnoughData)
969            }
970        }
971    }
972}
973
974/// This is for internal use only
975#[cfg(feature = "test_instruction_sets")]
976pub fn initialize_with_instruction_set(instruction_set: &str) {
977    match instruction_set {
978        "x86" => TEST_SET.store(0, Ordering::SeqCst),
979        "sse2" | "neon" => TEST_SET.store(1, Ordering::SeqCst),
980        _ => TEST_SET.store(2, Ordering::SeqCst),
981    };
982}
983
984#[doc(hidden)]
985#[cfg(not(feature = "test_instruction_sets"))]
986pub mod c_api {
987    #![allow(clippy::wildcard_imports)]
988    use super::*; // We are importing everything
989    use pixel_format::{are_planes_compatible, MAX_NUMBER_OF_PLANES};
990    use std::cmp;
991    use std::ffi::CString;
992    use std::mem::{transmute, MaybeUninit};
993    use std::os::raw::c_char;
994    use std::ptr;
995    use std::slice;
996
997    const UNBOUNDED_C_ARRAY: usize = isize::MAX as usize;
998
999    type PlaneArray<'a> = [MaybeUninit<&'a [u8]>; MAX_NUMBER_OF_PLANES];
1000
1001    #[repr(C)]
1002    pub enum Result {
1003        Ok,
1004        Err,
1005    }
1006
1007    unsafe fn set_error(error: *mut ErrorKind, value: ErrorKind) -> self::Result {
1008        if !error.is_null() {
1009            *error = value;
1010        }
1011
1012        self::Result::Err
1013    }
1014
1015    #[no_mangle]
1016    pub extern "C" fn dcp_describe_acceleration() -> *mut c_char {
1017        let acc = describe_acceleration();
1018        if let Ok(s) = CString::new(acc) {
1019            s.into_raw()
1020        } else {
1021            let p: *const c_char = ptr::null();
1022            p.cast_mut()
1023        }
1024    }
1025
1026    #[no_mangle]
1027    pub unsafe extern "C" fn dcp_unref_string(string: *mut c_char) {
1028        if !string.is_null() {
1029            let _unused = CString::from_raw(string);
1030        }
1031    }
1032
1033    #[no_mangle]
1034    pub unsafe extern "C" fn dcp_get_buffers_size(
1035        width: u32,
1036        height: u32,
1037        format: *const ImageFormat,
1038        strides: *const usize,
1039        buffers_size: *mut usize,
1040        error: *mut ErrorKind,
1041    ) -> self::Result {
1042        // Protect from C null pointers
1043        if format.is_null() || buffers_size.is_null() {
1044            return set_error(error, ErrorKind::InvalidValue);
1045        }
1046
1047        // C enums are untrusted in the sense you can cast any value to an enum type
1048        let format = &*format;
1049        let pixel_format = format.pixel_format as u32;
1050        if !dispatcher::is_pixel_format_valid(pixel_format) {
1051            return set_error(error, ErrorKind::InvalidValue);
1052        }
1053
1054        // We assume there is enough data in the buffers
1055        // If the assumption will not hold undefined behaviour occurs (like in C)
1056        let num_planes = format.num_planes as usize;
1057        if !are_planes_compatible(pixel_format, format.num_planes) {
1058            return set_error(error, ErrorKind::InvalidValue);
1059        }
1060
1061        // Convert nullable type to Option
1062        let strides = if strides.is_null() {
1063            None
1064        } else {
1065            Some(slice::from_raw_parts(strides, num_planes))
1066        };
1067
1068        let buffers_size = slice::from_raw_parts_mut(buffers_size, num_planes);
1069        match get_buffers_size(width, height, format, strides, buffers_size) {
1070            Ok(()) => self::Result::Ok,
1071            Err(error_kind) => set_error(error, error_kind),
1072        }
1073    }
1074
1075    #[no_mangle]
1076    pub unsafe extern "C" fn dcp_convert_image(
1077        width: u32,
1078        height: u32,
1079        src_format: *const ImageFormat,
1080        src_strides: *const usize,
1081        src_buffers: *const *const u8,
1082        dst_format: *const ImageFormat,
1083        dst_strides: *const usize,
1084        dst_buffers: *const *mut u8,
1085        error: *mut ErrorKind,
1086    ) -> self::Result {
1087        // Protect from C null pointers
1088        if src_format.is_null()
1089            || dst_format.is_null()
1090            || src_buffers.is_null()
1091            || dst_buffers.is_null()
1092        {
1093            return set_error(error, ErrorKind::InvalidValue);
1094        }
1095
1096        // C enums are untrusted in the sense you can cast any value to an enum type
1097        let src_format: &ImageFormat = &*src_format;
1098        let dst_format: &ImageFormat = &*dst_format;
1099        let src_pixel_format = src_format.pixel_format as u32;
1100        let dst_pixel_format = dst_format.pixel_format as u32;
1101        if !dispatcher::is_pixel_format_valid(src_pixel_format)
1102            || !dispatcher::is_pixel_format_valid(dst_pixel_format)
1103            || !dispatcher::is_color_space_valid(src_format.color_space as u32)
1104            || !dispatcher::is_color_space_valid(dst_format.color_space as u32)
1105        {
1106            return set_error(error, ErrorKind::InvalidValue);
1107        }
1108
1109        // We assume there is enough data in the buffers
1110        // If the assumption will not hold undefined behaviour occurs (like in C)
1111        if !are_planes_compatible(src_pixel_format, src_format.num_planes)
1112            || !are_planes_compatible(dst_pixel_format, dst_format.num_planes)
1113        {
1114            return set_error(error, ErrorKind::InvalidValue);
1115        }
1116
1117        let src_buffers = {
1118            let src_num_planes = src_format.num_planes as usize;
1119            let num_planes = cmp::min(src_num_planes, MAX_NUMBER_OF_PLANES);
1120            let mut src_buf: PlaneArray =
1121                [MaybeUninit::uninit().assume_init(); MAX_NUMBER_OF_PLANES];
1122
1123            for (plane_index, item) in src_buf.iter_mut().enumerate().take(num_planes) {
1124                let ptr = *src_buffers.add(plane_index);
1125                if ptr.is_null() {
1126                    return set_error(error, ErrorKind::InvalidValue);
1127                }
1128
1129                *item = MaybeUninit::new(slice::from_raw_parts(ptr, UNBOUNDED_C_ARRAY));
1130            }
1131
1132            transmute::<PlaneArray, [&[u8]; MAX_NUMBER_OF_PLANES]>(src_buf)
1133        };
1134
1135        let mut dst_buffers = {
1136            let dst_num_planes = dst_format.num_planes as usize;
1137            let num_planes = cmp::min(dst_num_planes, MAX_NUMBER_OF_PLANES);
1138            let mut dst_buf: PlaneArray =
1139                [MaybeUninit::uninit().assume_init(); MAX_NUMBER_OF_PLANES];
1140
1141            for (plane_index, item) in dst_buf.iter_mut().enumerate().take(num_planes) {
1142                let ptr = *dst_buffers.add(plane_index);
1143                if ptr.is_null() {
1144                    return set_error(error, ErrorKind::InvalidValue);
1145                }
1146
1147                *item = MaybeUninit::new(slice::from_raw_parts_mut(ptr, UNBOUNDED_C_ARRAY));
1148            }
1149
1150            transmute::<PlaneArray, [&mut [u8]; MAX_NUMBER_OF_PLANES]>(dst_buf)
1151        };
1152
1153        // Convert nullable type to Option
1154        let src_strides = if src_strides.is_null() {
1155            None
1156        } else {
1157            Some(slice::from_raw_parts(src_strides, UNBOUNDED_C_ARRAY))
1158        };
1159
1160        let dst_strides = if dst_strides.is_null() {
1161            None
1162        } else {
1163            Some(slice::from_raw_parts(dst_strides, UNBOUNDED_C_ARRAY))
1164        };
1165
1166        match convert_image(
1167            width,
1168            height,
1169            src_format,
1170            src_strides,
1171            &src_buffers[..],
1172            dst_format,
1173            dst_strides,
1174            &mut dst_buffers[..],
1175        ) {
1176            Ok(()) => self::Result::Ok,
1177            Err(error_kind) => set_error(error, error_kind),
1178        }
1179    }
1180}