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::decoder::Category; 16 use crate::image::*; 17 use crate::internal_utils::*; 18 use crate::*; 19 20 use libyuv_sys::bindings::*; 21 22 impl Image { scale(&mut self, width: u32, height: u32, category: Category) -> AvifResult<()>23 pub fn scale(&mut self, width: u32, height: u32, category: Category) -> AvifResult<()> { 24 if self.width == width && self.height == height { 25 return Ok(()); 26 } 27 if width == 0 || height == 0 { 28 return Err(AvifError::InvalidArgument); 29 } 30 let planes: &[Plane] = match category { 31 Category::Color | Category::Gainmap => &YUV_PLANES, 32 Category::Alpha => &A_PLANE, 33 }; 34 let src = image::Image { 35 width: self.width, 36 height: self.height, 37 depth: self.depth, 38 yuv_format: self.yuv_format, 39 planes: self 40 .planes 41 .as_ref() 42 .iter() 43 .map( 44 |plane| { 45 if plane.is_some() { 46 Some(plane.unwrap_ref().clone()) 47 } else { 48 None 49 } 50 }, 51 ) 52 .collect::<Vec<_>>() 53 .try_into() 54 .unwrap(), 55 row_bytes: self.row_bytes, 56 ..image::Image::default() 57 }; 58 59 self.width = width; 60 self.height = height; 61 if src.has_plane(Plane::Y) || src.has_plane(Plane::A) { 62 if src.width > 16384 || src.height > 16384 { 63 return Err(AvifError::NotImplemented); 64 } 65 if src.has_plane(Plane::Y) && category != Category::Alpha { 66 self.allocate_planes(Category::Color)?; 67 } 68 if src.has_plane(Plane::A) && category == Category::Alpha { 69 self.allocate_planes(Category::Alpha)?; 70 } 71 } 72 for plane in planes { 73 if !src.has_plane(*plane) || !self.has_plane(*plane) { 74 continue; 75 } 76 let src_pd = src.plane_data(*plane).unwrap(); 77 let dst_pd = self.plane_data(*plane).unwrap(); 78 // SAFETY: This function calls into libyuv which is a C++ library. We pass in pointers 79 // and strides to rust slices that are guaranteed to be valid. 80 // 81 // libyuv versions >= 1880 reports a return value here. Older versions do not. Ignore 82 // the return value for now. 83 #[allow(clippy::let_unit_value)] 84 let _ret = unsafe { 85 if src.depth > 8 { 86 let source_ptr = src.planes[plane.to_usize()].unwrap_ref().ptr16(); 87 let dst_ptr = self.planes[plane.to_usize()].unwrap_mut().ptr16_mut(); 88 ScalePlane_12( 89 source_ptr, 90 i32_from_u32(src_pd.row_bytes / 2)?, 91 i32_from_u32(src_pd.width)?, 92 i32_from_u32(src_pd.height)?, 93 dst_ptr, 94 i32_from_u32(dst_pd.row_bytes / 2)?, 95 i32_from_u32(dst_pd.width)?, 96 i32_from_u32(dst_pd.height)?, 97 FilterMode_kFilterBox, 98 ) 99 } else { 100 let source_ptr = src.planes[plane.to_usize()].unwrap_ref().ptr(); 101 let dst_ptr = self.planes[plane.to_usize()].unwrap_mut().ptr_mut(); 102 ScalePlane( 103 source_ptr, 104 i32_from_u32(src_pd.row_bytes)?, 105 i32_from_u32(src_pd.width)?, 106 i32_from_u32(src_pd.height)?, 107 dst_ptr, 108 i32_from_u32(dst_pd.row_bytes)?, 109 i32_from_u32(dst_pd.width)?, 110 i32_from_u32(dst_pd.height)?, 111 FilterMode_kFilterBox, 112 ) 113 } 114 }; 115 } 116 Ok(()) 117 } 118 } 119 120 #[cfg(test)] 121 mod tests { 122 use super::*; 123 use crate::internal_utils::pixels::*; 124 use test_case::test_matrix; 125 126 #[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)127 fn scale(yuv_format: PixelFormat, use_alpha: bool, is_pointer_input: bool) { 128 let mut yuv = image::Image { 129 width: 2, 130 height: 2, 131 depth: 8, 132 yuv_format, 133 ..Default::default() 134 }; 135 136 let planes: &[Plane] = match (yuv_format, use_alpha) { 137 (PixelFormat::Yuv400, false) => &[Plane::Y], 138 (PixelFormat::Yuv400, true) => &[Plane::Y, Plane::A], 139 (_, false) => &YUV_PLANES, 140 (_, true) => &ALL_PLANES, 141 }; 142 let mut values = [ 143 10, 20, // 144 30, 40, 145 ]; 146 for plane in planes { 147 yuv.planes[plane.to_usize()] = Some(if is_pointer_input { 148 Pixels::Pointer(unsafe { 149 PointerSlice::create(values.as_mut_ptr(), values.len()).unwrap() 150 }) 151 } else { 152 Pixels::Buffer(values.to_vec()) 153 }); 154 yuv.row_bytes[plane.to_usize()] = 2; 155 yuv.image_owns_planes[plane.to_usize()] = !is_pointer_input; 156 } 157 let categories: &[Category] = 158 if use_alpha { &[Category::Color, Category::Alpha] } else { &[Category::Color] }; 159 for category in categories { 160 // Scale will update the width and height when scaling YUV planes. Reset it back before 161 // calling it again. 162 yuv.width = 2; 163 yuv.height = 2; 164 assert!(yuv.scale(4, 4, *category).is_ok()); 165 } 166 for plane in planes { 167 let expected_samples: &[u8] = match (yuv_format, plane) { 168 (PixelFormat::Yuv422, Plane::U | Plane::V) => &[ 169 10, 10, // 170 10, 10, // 171 30, 30, // 172 30, 30, 173 ], 174 (PixelFormat::Yuv420, Plane::U | Plane::V) => &[ 175 10, 10, // 176 10, 10, 177 ], 178 (_, _) => &[ 179 10, 13, 18, 20, // 180 15, 18, 23, 25, // 181 25, 28, 33, 35, // 182 30, 33, 38, 40, 183 ], 184 }; 185 match &yuv.planes[plane.to_usize()] { 186 Some(Pixels::Buffer(samples)) => { 187 assert_eq!(*samples, expected_samples) 188 } 189 _ => panic!(), 190 } 191 } 192 } 193 } 194