1 // Copyright 2024 Google LLC
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //     http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 use crate::internal_utils::*;
16 use crate::*;
17 
18 #[derive(Clone, Copy, Debug, PartialEq)]
19 pub struct CleanAperture {
20     pub width: UFraction,
21     pub height: UFraction,
22     pub horiz_off: UFraction,
23     pub vert_off: UFraction,
24 }
25 
26 #[derive(Clone, Copy, Debug, Default)]
27 #[repr(C)]
28 pub struct CropRect {
29     pub x: u32,
30     pub y: u32,
31     pub width: u32,
32     pub height: u32,
33 }
34 
35 impl CropRect {
is_valid(&self, image_width: u32, image_height: u32, pixel_format: PixelFormat) -> bool36     fn is_valid(&self, image_width: u32, image_height: u32, pixel_format: PixelFormat) -> bool {
37         let x_plus_width = checked_add!(self.x, self.width);
38         let y_plus_height = checked_add!(self.y, self.height);
39         if self.width == 0
40             || self.height == 0
41             || x_plus_width.is_err()
42             || y_plus_height.is_err()
43             || x_plus_width.unwrap() > image_width
44             || y_plus_height.unwrap() > image_height
45         {
46             return false;
47         }
48         match pixel_format {
49             PixelFormat::Yuv420 => self.x % 2 == 0 && self.y % 2 == 0,
50             PixelFormat::Yuv422 => self.x % 2 == 0,
51             _ => true,
52         }
53     }
54 
create_from( clap: &CleanAperture, image_width: u32, image_height: u32, pixel_format: PixelFormat, ) -> AvifResult<Self>55     pub fn create_from(
56         clap: &CleanAperture,
57         image_width: u32,
58         image_height: u32,
59         pixel_format: PixelFormat,
60     ) -> AvifResult<Self> {
61         let width: IFraction = clap.width.try_into()?;
62         let height: IFraction = clap.height.try_into()?;
63         let horiz_off: IFraction = clap.horiz_off.try_into()?;
64         let vert_off: IFraction = clap.vert_off.try_into()?;
65         if width.1 <= 0
66             || height.1 <= 0
67             || horiz_off.1 <= 0
68             || vert_off.1 <= 0
69             || width.0 < 0
70             || height.0 < 0
71             || !width.is_integer()
72             || !height.is_integer()
73         {
74             return Err(AvifError::UnknownError("invalid clap".into()));
75         }
76         let clap_width = width.get_i32();
77         let clap_height = height.get_i32();
78         let mut crop_x = IFraction::simplified(i32_from_u32(image_width)?, 2);
79         crop_x.add(&horiz_off)?;
80         crop_x.sub(&IFraction::simplified(clap_width, 2))?;
81         let mut crop_y = IFraction::simplified(i32_from_u32(image_height)?, 2);
82         crop_y.add(&vert_off)?;
83         crop_y.sub(&IFraction::simplified(clap_height, 2))?;
84         if !crop_x.is_integer() || !crop_y.is_integer() || crop_x.0 < 0 || crop_y.0 < 0 {
85             return Err(AvifError::UnknownError("".into()));
86         }
87         let rect = CropRect {
88             x: crop_x.get_u32()?,
89             y: crop_y.get_u32()?,
90             width: u32_from_i32(clap_width)?,
91             height: u32_from_i32(clap_height)?,
92         };
93         if rect.is_valid(image_width, image_height, pixel_format) {
94             Ok(rect)
95         } else {
96             Err(AvifError::UnknownError("".into()))
97         }
98     }
99 }
100 
101 #[cfg(test)]
102 mod tests {
103     use super::*;
104 
105     struct TestParam {
106         image_width: u32,
107         image_height: u32,
108         pixel_format: PixelFormat,
109         clap: CleanAperture,
110         rect: Option<CropRect>,
111     }
112 
113     macro_rules! valid {
114         ($a: expr, $b: expr, $c: ident, $d: expr, $e: expr, $f: expr, $g: expr, $h: expr, $i: expr,
115          $j: expr, $k: expr, $l: expr, $m: expr, $n: expr, $o: expr) => {
116             TestParam {
117                 image_width: $a,
118                 image_height: $b,
119                 pixel_format: PixelFormat::$c,
120                 clap: CleanAperture {
121                     width: UFraction($d, $e),
122                     height: UFraction($f, $g),
123                     horiz_off: UFraction($h, $i),
124                     vert_off: UFraction($j, $k),
125                 },
126                 rect: Some(CropRect {
127                     x: $l,
128                     y: $m,
129                     width: $n,
130                     height: $o,
131                 }),
132             }
133         };
134     }
135 
136     macro_rules! invalid {
137         ($a: expr, $b: expr, $c: ident, $d: expr, $e: expr, $f: expr, $g: expr, $h: expr, $i: expr,
138          $j: expr, $k: expr) => {
139             TestParam {
140                 image_width: $a,
141                 image_height: $b,
142                 pixel_format: PixelFormat::$c,
143                 clap: CleanAperture {
144                     width: UFraction($d, $e),
145                     height: UFraction($f, $g),
146                     horiz_off: UFraction($h, $i),
147                     vert_off: UFraction($j, $k),
148                 },
149                 rect: None,
150             }
151         };
152     }
153 
154     #[rustfmt::skip]
155     const TEST_PARAMS: [TestParam; 20] = [
156         valid!(120, 160, Yuv420, 96, 1, 132, 1, 0, 1, 0, 1, 12, 14, 96, 132),
157         valid!(120, 160, Yuv420, 60, 1, 80, 1, -30i32 as u32, 1, -40i32 as u32, 1, 0, 0, 60, 80),
158         valid!(100, 100, Yuv420, 99, 1, 99, 1, -1i32 as u32, 2, -1i32 as u32, 2, 0, 0, 99, 99),
159         invalid!(120, 160, Yuv420, 96, 0, 132, 1, 0, 1, 0, 1),
160         invalid!(120, 160, Yuv420, 96, -1i32 as u32, 132, 1, 0, 1, 0, 1),
161         invalid!(120, 160, Yuv420, 96, 1, 132, 0, 0, 1, 0, 1),
162         invalid!(120, 160, Yuv420, 96, 1, 132, -1i32 as u32, 0, 1, 0, 1),
163         invalid!(120, 160, Yuv420, 96, 1, 132, 1, 0, 0, 0, 1),
164         invalid!(120, 160, Yuv420, 96, 1, 132, 1, 0, -1i32 as u32, 0, 1),
165         invalid!(120, 160, Yuv420, 96, 1, 132, 1, 0, 1, 0, 0),
166         invalid!(120, 160, Yuv420, 96, 1, 132, 1, 0, 1, 0, -1i32 as u32),
167         invalid!(120, 160, Yuv420, -96i32 as u32, 1, 132, 1, 0, 1, 0, 1),
168         invalid!(120, 160, Yuv420, 0, 1, 132, 1, 0, 1, 0, 1),
169         invalid!(120, 160, Yuv420, 96, 1, -132i32 as u32, 1, 0, 1, 0, 1),
170         invalid!(120, 160, Yuv420, 96, 1, 0, 1, 0, 1, 0, 1),
171         invalid!(120, 160, Yuv420, 96, 5, 132, 1, 0, 1, 0, 1),
172         invalid!(120, 160, Yuv420, 96, 1, 132, 5, 0, 1, 0, 1),
173         invalid!(722, 1024, Yuv420, 385, 1, 330, 1, 103, 1, -308i32 as u32, 1),
174         invalid!(1024, 722, Yuv420, 330, 1, 385, 1, -308i32 as u32, 1, 103, 1),
175         invalid!(99, 99, Yuv420, 99, 1, 99, 1, -1i32 as u32, 2, -1i32 as u32, 2),
176     ];
177 
178     #[test_case::test_matrix(0usize..20)]
valid_clap_to_rect(index: usize)179     fn valid_clap_to_rect(index: usize) {
180         let param = &TEST_PARAMS[index];
181         let rect = CropRect::create_from(
182             &param.clap,
183             param.image_width,
184             param.image_height,
185             param.pixel_format,
186         );
187         if param.rect.is_some() {
188             assert!(rect.is_ok());
189             let rect = rect.unwrap();
190             let expected_rect = param.rect.unwrap_ref();
191             assert_eq!(rect.x, expected_rect.x);
192             assert_eq!(rect.y, expected_rect.y);
193             assert_eq!(rect.width, expected_rect.width);
194             assert_eq!(rect.height, expected_rect.height);
195         } else {
196             assert!(rect.is_err());
197         }
198     }
199 }
200