v_frame/
pixel.rs

1// Copyright (c) 2017-2021, The rav1e contributors. All rights reserved
2//
3// This source code is subject to the terms of the BSD 2 Clause License and
4// the Alliance for Open Media Patent License 1.0. If the BSD 2 Clause License
5// was not distributed with this source code in the LICENSE file, you can
6// obtain it at www.aomedia.org/license/software. If the Alliance for Open
7// Media Patent License 1.0 was not distributed with this source code in the
8// PATENTS file, you can obtain it at www.aomedia.org/license/patent.
9
10#[cfg(feature = "serialize")]
11use serde::{Deserialize, Serialize};
12
13#[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
14use wasm_bindgen::prelude::*;
15
16use num_traits::{AsPrimitive, FromPrimitive, PrimInt, Signed};
17
18use std::fmt;
19use std::fmt::{Debug, Display};
20use std::mem::size_of;
21use std::ops::AddAssign;
22
23/// Trait for casting between primitive types.
24pub trait CastFromPrimitive<T>: Copy + 'static {
25    /// Casts the given value into `Self`.
26    fn cast_from(v: T) -> Self;
27}
28
29macro_rules! impl_cast_from_primitive {
30  ( $T:ty => $U:ty ) => {
31    impl CastFromPrimitive<$U> for $T {
32      #[inline(always)]
33      fn cast_from(v: $U) -> Self { v as Self }
34    }
35  };
36  ( $T:ty => { $( $U:ty ),* } ) => {
37    $( impl_cast_from_primitive!($T => $U); )*
38  };
39}
40
41// casts to { u8, u16 } are implemented separately using Pixel, so that the
42// compiler understands that CastFromPrimitive<T: Pixel> is always implemented
43impl_cast_from_primitive!(u8 => { u32, u64, usize });
44impl_cast_from_primitive!(u8 => { i8, i64, isize });
45impl_cast_from_primitive!(u16 => { u32, u64, usize });
46impl_cast_from_primitive!(u16 => { i8, i64, isize });
47impl_cast_from_primitive!(i16 => { u32, u64, usize });
48impl_cast_from_primitive!(i16 => { i8, i64, isize });
49impl_cast_from_primitive!(i32 => { u32, u64, usize });
50impl_cast_from_primitive!(i32 => { i8, i64, isize });
51
52pub trait RegisteredPrimitive:
53    PrimInt
54    + AsPrimitive<u8>
55    + AsPrimitive<i16>
56    + AsPrimitive<u16>
57    + AsPrimitive<i32>
58    + AsPrimitive<u32>
59    + AsPrimitive<usize>
60    + CastFromPrimitive<u8>
61    + CastFromPrimitive<i16>
62    + CastFromPrimitive<u16>
63    + CastFromPrimitive<i32>
64    + CastFromPrimitive<u32>
65    + CastFromPrimitive<usize>
66{
67}
68
69impl RegisteredPrimitive for u8 {}
70impl RegisteredPrimitive for u16 {}
71impl RegisteredPrimitive for i16 {}
72impl RegisteredPrimitive for i32 {}
73
74macro_rules! impl_cast_from_pixel_to_primitive {
75    ( $T:ty ) => {
76        impl<T: RegisteredPrimitive> CastFromPrimitive<T> for $T {
77            #[inline(always)]
78            fn cast_from(v: T) -> Self {
79                v.as_()
80            }
81        }
82    };
83}
84
85impl_cast_from_pixel_to_primitive!(u8);
86impl_cast_from_pixel_to_primitive!(i16);
87impl_cast_from_pixel_to_primitive!(u16);
88impl_cast_from_pixel_to_primitive!(i32);
89impl_cast_from_pixel_to_primitive!(u32);
90
91/// Types that can be used as pixel types.
92#[derive(PartialEq, Eq)]
93pub enum PixelType {
94    /// 8 bits per pixel, stored in a `u8`.
95    U8,
96    /// 10 or 12 bits per pixel, stored in a `u16`.
97    U16,
98}
99
100/// A type that can be used as a pixel type.
101pub trait Pixel:
102    RegisteredPrimitive + Into<u32> + Into<i32> + Debug + Display + Send + Sync + 'static
103{
104    type Coeff: Coefficient;
105
106    /// Returns a [`PixelType`] variant corresponding to this type.
107    ///
108    /// [`PixelType`]: enum.PixelType.html
109    fn type_enum() -> PixelType;
110
111    /// Converts stride in pixels to stride in bytes.
112    #[inline]
113    fn to_asm_stride(in_stride: usize) -> isize {
114        (in_stride * size_of::<Self>()) as isize
115    }
116}
117
118impl Pixel for u8 {
119    type Coeff = i16;
120
121    #[inline]
122    fn type_enum() -> PixelType {
123        PixelType::U8
124    }
125}
126
127impl Pixel for u16 {
128    type Coeff = i32;
129
130    #[inline]
131    fn type_enum() -> PixelType {
132        PixelType::U16
133    }
134}
135
136pub trait Coefficient:
137    RegisteredPrimitive + Into<i32> + AddAssign + Signed + Debug + 'static
138{
139    type Pixel: Pixel;
140}
141
142impl Coefficient for i16 {
143    type Pixel = u8;
144}
145impl Coefficient for i32 {
146    type Pixel = u16;
147}
148
149/// Chroma subsampling format
150#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
151#[cfg_attr(feature = "serialize", derive(Serialize, Deserialize))]
152#[cfg_attr(all(target_arch = "wasm32", target_os = "unknown"), wasm_bindgen)]
153#[repr(C)]
154pub enum ChromaSampling {
155    /// Both vertically and horizontally subsampled.
156    #[default]
157    Cs420,
158    /// Horizontally subsampled.
159    Cs422,
160    /// Not subsampled.
161    Cs444,
162    /// Monochrome.
163    Cs400,
164}
165
166impl FromPrimitive for ChromaSampling {
167    fn from_i64(n: i64) -> Option<Self> {
168        use ChromaSampling::*;
169
170        match n {
171            n if n == Cs420 as i64 => Some(Cs420),
172            n if n == Cs422 as i64 => Some(Cs422),
173            n if n == Cs444 as i64 => Some(Cs444),
174            n if n == Cs400 as i64 => Some(Cs400),
175            _ => None,
176        }
177    }
178
179    fn from_u64(n: u64) -> Option<Self> {
180        ChromaSampling::from_i64(n as i64)
181    }
182}
183
184impl fmt::Display for ChromaSampling {
185    fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
186        write!(
187            f,
188            "{}",
189            match self {
190                ChromaSampling::Cs420 => "4:2:0",
191                ChromaSampling::Cs422 => "4:2:2",
192                ChromaSampling::Cs444 => "4:4:4",
193                ChromaSampling::Cs400 => "Monochrome",
194            }
195        )
196    }
197}
198
199impl ChromaSampling {
200    /// Provides the amount to right shift the luma plane dimensions to get the
201    ///  chroma plane dimensions.
202    /// Only values 0 or 1 are ever returned.
203    /// The plane dimensions must also be rounded up to accommodate odd luma plane
204    ///  sizes.
205    /// Cs400 returns None, as there are no chroma planes.
206    pub const fn get_decimation(self) -> Option<(usize, usize)> {
207        use self::ChromaSampling::*;
208        match self {
209            Cs420 => Some((1, 1)),
210            Cs422 => Some((1, 0)),
211            Cs444 => Some((0, 0)),
212            Cs400 => None,
213        }
214    }
215
216    /// Calculates the size of a chroma plane for this sampling type, given the luma plane dimensions.
217    pub const fn get_chroma_dimensions(
218        self,
219        luma_width: usize,
220        luma_height: usize,
221    ) -> (usize, usize) {
222        if let Some((ss_x, ss_y)) = self.get_decimation() {
223            ((luma_width + ss_x) >> ss_x, (luma_height + ss_y) >> ss_y)
224        } else {
225            (0, 0)
226        }
227    }
228}
229
230#[cfg(test)]
231mod test {
232    use super::*;
233
234    #[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
235    use wasm_bindgen_test::*;
236
237    #[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
238    wasm_bindgen_test_configure!(run_in_browser);
239
240    #[cfg_attr(all(target_arch = "wasm32", target_os = "unknown"), wasm_bindgen_test)]
241    #[test]
242    fn asm_stride() {
243        let tests = [(7, 7, 14), (12, 12, 24), (1234, 1234, 2468)];
244
245        for (in_stride, u8_bytes, u16_bytes) in tests {
246            assert_eq!(u8::to_asm_stride(in_stride), u8_bytes);
247            assert_eq!(u16::to_asm_stride(in_stride), u16_bytes);
248        }
249    }
250
251    #[cfg_attr(all(target_arch = "wasm32", target_os = "unknown"), wasm_bindgen_test)]
252    #[test]
253    fn type_enum() {
254        assert!(u8::type_enum() == PixelType::U8);
255        assert!(u16::type_enum() == PixelType::U16);
256    }
257
258    #[cfg_attr(all(target_arch = "wasm32", target_os = "unknown"), wasm_bindgen_test)]
259    #[test]
260    fn chroma_sampling_from_int() {
261        let expected = [
262            (-1, None),
263            (0, Some(ChromaSampling::Cs420)),
264            (1, Some(ChromaSampling::Cs422)),
265            (2, Some(ChromaSampling::Cs444)),
266            (3, Some(ChromaSampling::Cs400)),
267            (4, None),
268        ];
269
270        for (int, chroma_sampling) in expected {
271            let converted = ChromaSampling::from_i32(int);
272            assert_eq!(chroma_sampling, converted);
273
274            let converted_uint = ChromaSampling::from_u32(int as u32);
275            assert_eq!(chroma_sampling, converted_uint);
276        }
277    }
278
279    #[cfg_attr(all(target_arch = "wasm32", target_os = "unknown"), wasm_bindgen_test)]
280    #[test]
281    fn display_chroma_sampling() {
282        use std::fmt::Write;
283
284        let all_cs = [
285            (ChromaSampling::Cs420, "4:2:0"),
286            (ChromaSampling::Cs422, "4:2:2"),
287            (ChromaSampling::Cs444, "4:4:4"),
288            (ChromaSampling::Cs400, "Monochrome"),
289        ];
290
291        for (cs, expected) in all_cs {
292            let mut s = String::new();
293            write!(&mut s, "{cs}").expect("can display");
294            assert_eq!(&s, expected);
295        }
296    }
297
298    #[cfg_attr(all(target_arch = "wasm32", target_os = "unknown"), wasm_bindgen_test)]
299    #[test]
300    fn chroma_sampling_dimensions() {
301        let tests = [
302            ((1024, 768), ChromaSampling::Cs444, (1024, 768)),
303            ((1024, 768), ChromaSampling::Cs422, (512, 768)),
304            ((1024, 768), ChromaSampling::Cs420, (512, 384)),
305            ((1024, 768), ChromaSampling::Cs400, (0, 0)),
306            // Check odd width/height
307            ((1023, 768), ChromaSampling::Cs422, (512, 768)),
308            ((1023, 767), ChromaSampling::Cs420, (512, 384)),
309        ];
310
311        for (luma, cs, expected_chroma) in tests {
312            let chroma = cs.get_chroma_dimensions(luma.0, luma.1);
313            assert_eq!(chroma, expected_chroma);
314        }
315    }
316}