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}