ezk_image/
lib.rs

1#![doc = include_str!("../README.md")]
2#![warn(unreachable_pub)]
3#![allow(unsafe_op_in_unsafe_fn)]
4// TODO: actually feature gating every single function and use is impossible right now, so until then warnings
5// for unused items are only enabled when all-formats is activated
6#![cfg_attr(not(feature = "all-formats"), allow(unused))]
7
8#[cfg(not(any(
9    feature = "I420",
10    feature = "I422",
11    feature = "I444",
12    feature = "I010",
13    feature = "I012",
14    feature = "I210",
15    feature = "I212",
16    feature = "I410",
17    feature = "I412",
18    feature = "NV12",
19    feature = "P010",
20    feature = "P012",
21    feature = "YUYV",
22    feature = "RGBA",
23    feature = "BGRA",
24    feature = "ARGB",
25    feature = "ABGR",
26    feature = "RGB",
27    feature = "BGR",
28)))]
29compile_error!("At least one image format feature must be enabled");
30
31use formats::*;
32
33mod color;
34mod copy;
35mod crop;
36mod formats;
37mod image;
38mod image_traits;
39#[cfg(feature = "multi-thread")]
40mod multi_thread;
41mod pixel_format;
42mod plane_decs;
43mod planes;
44mod primitive;
45#[cfg(feature = "resize")]
46pub mod resize;
47pub(crate) mod util;
48mod vector;
49
50mod arch {
51    #[cfg(target_arch = "x86")]
52    pub(crate) use std::arch::x86::*;
53    #[cfg(target_arch = "x86_64")]
54    pub(crate) use std::arch::x86_64::*;
55
56    #[cfg(target_arch = "aarch64")]
57    pub(crate) use std::arch::aarch64::*;
58    #[cfg(target_arch = "aarch64")]
59    pub(crate) use std::arch::is_aarch64_feature_detected;
60}
61
62pub use color::{ColorInfo, ColorPrimaries, ColorSpace, ColorTransfer, RgbColorInfo, YuvColorInfo};
63#[doc(hidden)]
64pub use copy::copy;
65pub use crop::{CropError, Cropped, Window};
66pub use image::{BufferKind, Image, ImageError};
67pub use image_traits::{ImageMut, ImageRef, ImageRefExt};
68#[cfg(feature = "multi-thread")]
69pub use multi_thread::convert_multi_thread;
70pub use pixel_format::{BoundsCheckError, PixelFormat};
71pub use planes::*;
72
73/// Errors that may occur when trying to convert an image
74#[derive(Debug, thiserror::Error)]
75pub enum ConvertError {
76    #[error("image dimensions are not divisible by 2")]
77    OddImageDimensions,
78
79    #[error("source image has different size than destination image")]
80    MismatchedImageSize,
81
82    #[error("invalid color info for pixel format")]
83    InvalidColorInfo,
84
85    #[error(transparent)]
86    BoundsCheck(#[from] BoundsCheckError),
87
88    #[error(transparent)]
89    InvalidNumberOfPlanes(#[from] InvalidNumberOfPlanesError),
90}
91
92/// Convert pixel-format and color from the src-image to the specified dst-image.
93///
94/// The given images (or at least their included window) must have dimensions (width, height) divisible by 2.
95#[inline(never)]
96pub fn convert(src: &dyn ImageRef, dst: &mut dyn ImageMut) -> Result<(), ConvertError> {
97    verify_input_windows(src, dst)?;
98
99    if src.format() == dst.format() && src.color() == dst.color() {
100        // No color or pixel conversion needed just copy it over
101        return copy(src, dst);
102    }
103
104    let src_color = src.color();
105    let dst_color = dst.color();
106
107    let reader = read_any_to_rgba(src)?;
108
109    if need_transfer_and_primaries_convert(&src_color, &dst_color) {
110        let reader = TransferAndPrimariesConvert::new(&src_color, &dst_color, reader);
111
112        rgba_to_any(dst, reader)
113    } else {
114        rgba_to_any(dst, reader)
115    }
116}
117
118#[inline(never)]
119fn read_any_to_rgba<'a>(
120    src: &'a dyn ImageRef,
121) -> Result<Box<dyn DynRgbaReader + 'a>, ConvertError> {
122    use PixelFormat::*;
123
124    match src.format() {
125        #[cfg(feature = "I420")]
126        I420 => Ok(Box::new(yuv420::ToRgb::new(
127            &src.color(),
128            yuv420::Read3Plane::<u8>::new(src)?,
129        )?)),
130        #[cfg(feature = "I422")]
131        I422 => Ok(Box::new(yuv422::ToRgb::new(
132            &src.color(),
133            yuv422::Read3Plane::<u8>::new(src)?,
134        )?)),
135        #[cfg(feature = "I444")]
136        I444 => Ok(Box::new(yuv444::ToRgb::new(
137            &src.color(),
138            yuv444::Read3Plane::<u8>::new(src)?,
139        )?)),
140        #[cfg(feature = "I010")]
141        I010 => Ok(Box::new(yuv420::ToRgb::new(
142            &src.color(),
143            yuv420::Read3Plane::<u16>::new(src)?,
144        )?)),
145        #[cfg(feature = "I012")]
146        I012 => Ok(Box::new(yuv420::ToRgb::new(
147            &src.color(),
148            yuv420::Read3Plane::<u16>::new(src)?,
149        )?)),
150        #[cfg(feature = "I210")]
151        I210 => Ok(Box::new(yuv422::ToRgb::new(
152            &src.color(),
153            yuv422::Read3Plane::<u16>::new(src)?,
154        )?)),
155        #[cfg(feature = "I212")]
156        I212 => Ok(Box::new(yuv422::ToRgb::new(
157            &src.color(),
158            yuv422::Read3Plane::<u16>::new(src)?,
159        )?)),
160        #[cfg(feature = "I410")]
161        I410 => Ok(Box::new(yuv444::ToRgb::new(
162            &src.color(),
163            yuv444::Read3Plane::<u16>::new(src)?,
164        )?)),
165        #[cfg(feature = "I412")]
166        I412 => Ok(Box::new(yuv444::ToRgb::new(
167            &src.color(),
168            yuv444::Read3Plane::<u16>::new(src)?,
169        )?)),
170        #[cfg(feature = "NV12")]
171        NV12 => Ok(Box::new(yuv420::ToRgb::new(
172            &src.color(),
173            yuv420::Read2Plane::<u8>::new(src)?,
174        )?)),
175        #[cfg(feature = "P010")]
176        P010 => Ok(Box::new(yuv420::ToRgb::new(
177            &src.color(),
178            yuv420::Read2Plane::<u16>::new(src)?,
179        )?)),
180        #[cfg(feature = "P012")]
181        P012 => Ok(Box::new(yuv420::ToRgb::new(
182            &src.color(),
183            yuv420::Read2Plane::<u16>::new(src)?,
184        )?)),
185        #[cfg(feature = "YUYV")]
186        YUYV => Ok(Box::new(yuv422::ToRgb::new(
187            &src.color(),
188            yuv422::Read1Plane::<u8>::new(src)?,
189        )?)),
190        #[cfg(feature = "RGBA")]
191        RGBA => Ok(Box::new(rgb::ReadRgba::<u8>::new(src)?)),
192        #[cfg(feature = "BGRA")]
193        BGRA => Ok(Box::new(rgb::ReadBgra::<u8>::new(src)?)),
194        #[cfg(feature = "ARGB")]
195        ARGB => Ok(Box::new(rgb::ReadArgb::<u8>::new(src)?)),
196        #[cfg(feature = "ABGR")]
197        ABGR => Ok(Box::new(rgb::ReadAbgr::<u8>::new(src)?)),
198        #[cfg(feature = "RGB")]
199        RGB => Ok(Box::new(rgb::ReadRgb::<u8>::new(src)?)),
200        #[cfg(feature = "BGR")]
201        BGR => Ok(Box::new(rgb::ReadBgr::<u8>::new(src)?)),
202    }
203}
204
205#[inline(never)]
206fn rgba_to_any(dst: &mut dyn ImageMut, reader: impl rgb::RgbaSrc) -> Result<(), ConvertError> {
207    use PixelFormat::*;
208
209    let color = dst.color();
210
211    match dst.format() {
212        #[cfg(feature = "I420")]
213        I420 => yuv420::Write3Plane::<u8, _>::write(dst, yuv420::FromRgb::new(&color, reader)?),
214        #[cfg(feature = "I422")]
215        I422 => yuv422::Write3Plane::<u8, _>::write(dst, yuv422::FromRgb::new(&color, reader)?),
216        #[cfg(feature = "I444")]
217        I444 => yuv444::Write3Plane::<u8, _>::write(dst, yuv444::FromRgb::new(&color, reader)?),
218        #[cfg(feature = "I010")]
219        I010 => yuv420::Write3Plane::<u16, _>::write(dst, yuv420::FromRgb::new(&color, reader)?),
220        #[cfg(feature = "I012")]
221        I012 => yuv420::Write3Plane::<u16, _>::write(dst, yuv420::FromRgb::new(&color, reader)?),
222        #[cfg(feature = "I210")]
223        I210 => yuv422::Write3Plane::<u16, _>::write(dst, yuv422::FromRgb::new(&color, reader)?),
224        #[cfg(feature = "I212")]
225        I212 => yuv422::Write3Plane::<u16, _>::write(dst, yuv422::FromRgb::new(&color, reader)?),
226        #[cfg(feature = "I410")]
227        I410 => yuv444::Write3Plane::<u16, _>::write(dst, yuv444::FromRgb::new(&color, reader)?),
228        #[cfg(feature = "I412")]
229        I412 => yuv444::Write3Plane::<u16, _>::write(dst, yuv444::FromRgb::new(&color, reader)?),
230        #[cfg(feature = "NV12")]
231        NV12 => yuv420::Write2Plane::<u8, _>::write(dst, yuv420::FromRgb::new(&color, reader)?),
232        #[cfg(feature = "P010")]
233        P010 => yuv420::Write2Plane::<u16, _>::write(dst, yuv420::FromRgb::new(&color, reader)?),
234        #[cfg(feature = "P012")]
235        P012 => yuv420::Write2Plane::<u16, _>::write(dst, yuv420::FromRgb::new(&color, reader)?),
236        #[cfg(feature = "YUYV")]
237        YUYV => yuv422::Write1Plane::<u8, _>::write(dst, yuv422::FromRgb::new(&color, reader)?),
238        #[cfg(feature = "RGBA")]
239        RGBA => rgb::WriteRgba::<u8, _>::write(dst, reader),
240        #[cfg(feature = "BGRA")]
241        BGRA => rgb::WriteBgra::<u8, _>::write(dst, reader),
242        #[cfg(feature = "ARGB")]
243        ARGB => rgb::WriteArgb::<u8, _>::write(dst, reader),
244        #[cfg(feature = "ABGR")]
245        ABGR => rgb::WriteAbgr::<u8, _>::write(dst, reader),
246        #[cfg(feature = "RGB")]
247        RGB => rgb::WriteRgb::<u8, _>::write(dst, reader),
248        #[cfg(feature = "BGR")]
249        BGR => rgb::WriteBgr::<u8, _>::write(dst, reader),
250    }
251}
252
253/// Verify that the input values are all valid and safe to move on to
254#[deny(clippy::arithmetic_side_effects)]
255fn verify_input_windows(src: &dyn ImageRef, dst: &dyn ImageMut) -> Result<(), ConvertError> {
256    // Src and Dst window must be the same size
257    if src.width() != dst.width() || src.height() != dst.height() {
258        return Err(ConvertError::MismatchedImageSize);
259    }
260
261    // Src and Dst window must have even dimensions
262    if src.width() % 2 == 1 || src.height() % 2 == 1 {
263        return Err(ConvertError::OddImageDimensions);
264    }
265
266    Ok(())
267}
268
269trait StrictApi {
270    fn strict_add_(self, rhs: Self) -> Self;
271    fn strict_mul_(self, rhs: Self) -> Self;
272}
273
274impl StrictApi for usize {
275    #[track_caller]
276    fn strict_add_(self, rhs: Self) -> Self {
277        self.checked_add(rhs).expect("attempt to add with overflow")
278    }
279
280    #[track_caller]
281    fn strict_mul_(self, rhs: Self) -> Self {
282        self.checked_mul(rhs)
283            .expect("attempt to multiply with overflow")
284    }
285}