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 ¶m.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