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::image::*; 16 use crate::internal_utils::*; 17 use crate::*; 18 19 use libyuv_sys::bindings::*; 20 21 impl Image { scale(&mut self, width: u32, height: u32, category: Category) -> AvifResult<()>22 pub(crate) fn scale(&mut self, width: u32, height: u32, category: Category) -> AvifResult<()> { 23 if self.width == width && self.height == height { 24 return Ok(()); 25 } 26 if width == 0 || height == 0 { 27 return Err(AvifError::InvalidArgument); 28 } 29 let planes: &[Plane] = match category { 30 Category::Color | Category::Gainmap => &YUV_PLANES, 31 Category::Alpha => &A_PLANE, 32 }; 33 let src = 34 if category != Category::Alpha && self.yuv_format == PixelFormat::AndroidP010 { 35 // P010 images cannot be scaled using ScalePlane_12 since the U and V planes are 36 // interleaved. Convert them into I010 and then scale each plane using 37 // ScalePlane_12. 38 let mut i010 = image::Image { 39 width: self.width, 40 height: self.height, 41 depth: 10, 42 yuv_format: PixelFormat::Yuv420, 43 ..image::Image::default() 44 }; 45 i010.allocate_planes(Category::Color)?; 46 let src_y_pd = self.plane_data(Plane::Y).unwrap(); 47 let src_uv_pd = self.plane_data(Plane::U).unwrap(); 48 let src_y = self.planes[Plane::Y.as_usize()].unwrap_ref().ptr16(); 49 let src_uv = self.planes[Plane::U.as_usize()].unwrap_ref().ptr16(); 50 let dst_y_pd = i010.plane_data(Plane::Y).unwrap(); 51 let dst_u_pd = i010.plane_data(Plane::U).unwrap(); 52 let dst_v_pd = i010.plane_data(Plane::V).unwrap(); 53 let dst_y = i010.planes[Plane::Y.as_usize()].unwrap_mut().ptr16_mut(); 54 let dst_u = i010.planes[Plane::U.as_usize()].unwrap_mut().ptr16_mut(); 55 let dst_v = i010.planes[Plane::V.as_usize()].unwrap_mut().ptr16_mut(); 56 // SAFETY: This function calls into libyuv which is a C++ library. We pass in 57 // pointers and strides to rust slices that are guaranteed to be valid. 58 let ret = unsafe { 59 P010ToI010( 60 src_y, 61 i32_from_u32(src_y_pd.row_bytes / 2)?, 62 src_uv, 63 i32_from_u32(src_uv_pd.row_bytes / 2)?, 64 dst_y, 65 i32_from_u32(dst_y_pd.row_bytes / 2)?, 66 dst_u, 67 i32_from_u32(dst_u_pd.row_bytes / 2)?, 68 dst_v, 69 i32_from_u32(dst_v_pd.row_bytes / 2)?, 70 i32_from_u32(self.width)?, 71 i32_from_u32(self.height)?, 72 ) 73 }; 74 if ret != 0 { 75 return Err(AvifError::ReformatFailed); 76 } 77 i010 78 } else { 79 image::Image { 80 width: self.width, 81 height: self.height, 82 depth: self.depth, 83 yuv_format: self.yuv_format, 84 planes: self 85 .planes 86 .as_ref() 87 .iter() 88 .map(|plane| { 89 if plane.is_some() { 90 plane.unwrap_ref().try_clone().ok() 91 } else { 92 None 93 } 94 }) 95 .collect::<Vec<_>>() 96 .try_into() 97 .unwrap(), 98 row_bytes: self.row_bytes, 99 ..image::Image::default() 100 } 101 }; 102 103 self.width = width; 104 self.height = height; 105 self.depth = src.depth; 106 self.yuv_format = src.yuv_format; 107 if src.has_plane(Plane::Y) || src.has_plane(Plane::A) { 108 if src.width > 16384 || src.height > 16384 { 109 return Err(AvifError::NotImplemented); 110 } 111 if src.has_plane(Plane::Y) && category != Category::Alpha { 112 self.allocate_planes(Category::Color)?; 113 } 114 if src.has_plane(Plane::A) && category == Category::Alpha { 115 self.allocate_planes(Category::Alpha)?; 116 } 117 } 118 119 if category != Category::Alpha 120 && (self.yuv_format == PixelFormat::AndroidNv12 121 || self.yuv_format == PixelFormat::AndroidNv21) 122 { 123 let src_y_pd = src.plane_data(Plane::Y).unwrap(); 124 let src_uv_pd = src.plane_data(Plane::U).unwrap(); 125 let src_y = src.planes[Plane::Y.as_usize()].unwrap_ref().ptr(); 126 let src_uv = src.planes[Plane::U.as_usize()].unwrap_ref().ptr(); 127 let dst_y_pd = self.plane_data(Plane::Y).unwrap(); 128 let dst_uv_pd = self.plane_data(Plane::U).unwrap(); 129 let dst_y = self.planes[Plane::Y.as_usize()].unwrap_mut().ptr_mut(); 130 let dst_uv = self.planes[Plane::U.as_usize()].unwrap_mut().ptr_mut(); 131 // SAFETY: This function calls into libyuv which is a C++ library. We pass in pointers 132 // and strides to rust slices that are guaranteed to be valid. 133 let ret = unsafe { 134 NV12Scale( 135 src_y, 136 i32_from_u32(src_y_pd.row_bytes)?, 137 src_uv, 138 i32_from_u32(src_uv_pd.row_bytes)?, 139 i32_from_u32(src_y_pd.width)?, 140 i32_from_u32(src_y_pd.height)?, 141 dst_y, 142 i32_from_u32(dst_y_pd.row_bytes)?, 143 dst_uv, 144 i32_from_u32(dst_uv_pd.row_bytes)?, 145 i32_from_u32(dst_y_pd.width)?, 146 i32_from_u32(dst_y_pd.height)?, 147 FilterMode_kFilterBox, 148 ) 149 }; 150 if ret != 0 { 151 return Err(AvifError::ReformatFailed); 152 } else { 153 return Ok(()); 154 } 155 } 156 157 for plane in planes { 158 if !src.has_plane(*plane) || !self.has_plane(*plane) { 159 continue; 160 } 161 let src_pd = src.plane_data(*plane).unwrap(); 162 let dst_pd = self.plane_data(*plane).unwrap(); 163 // SAFETY: This function calls into libyuv which is a C++ library. We pass in pointers 164 // and strides to rust slices that are guaranteed to be valid. 165 // 166 // libyuv versions >= 1880 reports a return value here. Older versions do not. Ignore 167 // the return value for now. 168 #[allow(clippy::let_unit_value)] 169 let _ret = unsafe { 170 if src.depth > 8 { 171 let source_ptr = src.planes[plane.as_usize()].unwrap_ref().ptr16(); 172 let dst_ptr = self.planes[plane.as_usize()].unwrap_mut().ptr16_mut(); 173 ScalePlane_12( 174 source_ptr, 175 i32_from_u32(src_pd.row_bytes / 2)?, 176 i32_from_u32(src_pd.width)?, 177 i32_from_u32(src_pd.height)?, 178 dst_ptr, 179 i32_from_u32(dst_pd.row_bytes / 2)?, 180 i32_from_u32(dst_pd.width)?, 181 i32_from_u32(dst_pd.height)?, 182 FilterMode_kFilterBox, 183 ) 184 } else { 185 let source_ptr = src.planes[plane.as_usize()].unwrap_ref().ptr(); 186 let dst_ptr = self.planes[plane.as_usize()].unwrap_mut().ptr_mut(); 187 ScalePlane( 188 source_ptr, 189 i32_from_u32(src_pd.row_bytes)?, 190 i32_from_u32(src_pd.width)?, 191 i32_from_u32(src_pd.height)?, 192 dst_ptr, 193 i32_from_u32(dst_pd.row_bytes)?, 194 i32_from_u32(dst_pd.width)?, 195 i32_from_u32(dst_pd.height)?, 196 FilterMode_kFilterBox, 197 ) 198 } 199 }; 200 } 201 Ok(()) 202 } 203 } 204 205 #[cfg(test)] 206 mod tests { 207 use super::*; 208 use crate::internal_utils::pixels::*; 209 use test_case::test_matrix; 210 211 #[test_matrix([PixelFormat::Yuv444, PixelFormat::Yuv422, PixelFormat::Yuv420, PixelFormat::Yuv400], [false, true], [false, true])] scale(yuv_format: PixelFormat, use_alpha: bool, is_pointer_input: bool)212 fn scale(yuv_format: PixelFormat, use_alpha: bool, is_pointer_input: bool) { 213 let mut yuv = image::Image { 214 width: 2, 215 height: 2, 216 depth: 8, 217 yuv_format, 218 ..Default::default() 219 }; 220 221 let planes: &[Plane] = match (yuv_format, use_alpha) { 222 (PixelFormat::Yuv400, false) => &[Plane::Y], 223 (PixelFormat::Yuv400, true) => &[Plane::Y, Plane::A], 224 (_, false) => &YUV_PLANES, 225 (_, true) => &ALL_PLANES, 226 }; 227 let mut values = [ 228 10, 20, // 229 30, 40, 230 ]; 231 for plane in planes { 232 yuv.planes[plane.as_usize()] = Some(if is_pointer_input { 233 Pixels::Pointer(unsafe { 234 PointerSlice::create(values.as_mut_ptr(), values.len()).unwrap() 235 }) 236 } else { 237 Pixels::Buffer(values.to_vec()) 238 }); 239 yuv.row_bytes[plane.as_usize()] = 2; 240 yuv.image_owns_planes[plane.as_usize()] = !is_pointer_input; 241 } 242 let categories: &[Category] = 243 if use_alpha { &[Category::Color, Category::Alpha] } else { &[Category::Color] }; 244 for category in categories { 245 // Scale will update the width and height when scaling YUV planes. Reset it back before 246 // calling it again. 247 yuv.width = 2; 248 yuv.height = 2; 249 assert!(yuv.scale(4, 4, *category).is_ok()); 250 } 251 for plane in planes { 252 let expected_samples: &[u8] = match (yuv_format, plane) { 253 (PixelFormat::Yuv422, Plane::U | Plane::V) => &[ 254 10, 10, // 255 10, 10, // 256 30, 30, // 257 30, 30, 258 ], 259 (PixelFormat::Yuv420, Plane::U | Plane::V) => &[ 260 10, 10, // 261 10, 10, 262 ], 263 (_, _) => &[ 264 10, 13, 18, 20, // 265 15, 18, 23, 25, // 266 25, 28, 33, 35, // 267 30, 33, 38, 40, 268 ], 269 }; 270 match &yuv.planes[plane.as_usize()] { 271 Some(Pixels::Buffer(samples)) => { 272 assert_eq!(*samples, expected_samples) 273 } 274 _ => panic!(), 275 } 276 } 277 } 278 } 279