| // Copyright 2024 Google LLC |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| use crate::image::*; |
| use crate::internal_utils::*; |
| use crate::*; |
| |
| use libyuv_sys::bindings::*; |
| |
| impl Image { |
| pub(crate) fn scale(&mut self, width: u32, height: u32, category: Category) -> AvifResult<()> { |
| if self.width == width && self.height == height { |
| return Ok(()); |
| } |
| if width == 0 || height == 0 { |
| return Err(AvifError::InvalidArgument); |
| } |
| let planes: &[Plane] = match category { |
| Category::Color | Category::Gainmap => &YUV_PLANES, |
| Category::Alpha => &A_PLANE, |
| }; |
| let src = image::Image { |
| width: self.width, |
| height: self.height, |
| depth: self.depth, |
| yuv_format: self.yuv_format, |
| planes: self |
| .planes |
| .as_ref() |
| .iter() |
| .map( |
| |plane| { |
| if plane.is_some() { |
| plane.unwrap_ref().try_clone().ok() |
| } else { |
| None |
| } |
| }, |
| ) |
| .collect::<Vec<_>>() |
| .try_into() |
| .unwrap(), |
| row_bytes: self.row_bytes, |
| ..image::Image::default() |
| }; |
| |
| self.width = width; |
| self.height = height; |
| if src.has_plane(Plane::Y) || src.has_plane(Plane::A) { |
| if src.width > 16384 || src.height > 16384 { |
| return Err(AvifError::NotImplemented); |
| } |
| if src.has_plane(Plane::Y) && category != Category::Alpha { |
| self.allocate_planes(Category::Color)?; |
| } |
| if src.has_plane(Plane::A) && category == Category::Alpha { |
| self.allocate_planes(Category::Alpha)?; |
| } |
| } |
| for plane in planes { |
| if !src.has_plane(*plane) || !self.has_plane(*plane) { |
| continue; |
| } |
| let src_pd = src.plane_data(*plane).unwrap(); |
| let dst_pd = self.plane_data(*plane).unwrap(); |
| // SAFETY: This function calls into libyuv which is a C++ library. We pass in pointers |
| // and strides to rust slices that are guaranteed to be valid. |
| // |
| // libyuv versions >= 1880 reports a return value here. Older versions do not. Ignore |
| // the return value for now. |
| #[allow(clippy::let_unit_value)] |
| let _ret = unsafe { |
| if src.depth > 8 { |
| let source_ptr = src.planes[plane.as_usize()].unwrap_ref().ptr16(); |
| let dst_ptr = self.planes[plane.as_usize()].unwrap_mut().ptr16_mut(); |
| ScalePlane_12( |
| source_ptr, |
| i32_from_u32(src_pd.row_bytes / 2)?, |
| i32_from_u32(src_pd.width)?, |
| i32_from_u32(src_pd.height)?, |
| dst_ptr, |
| i32_from_u32(dst_pd.row_bytes / 2)?, |
| i32_from_u32(dst_pd.width)?, |
| i32_from_u32(dst_pd.height)?, |
| FilterMode_kFilterBox, |
| ) |
| } else { |
| let source_ptr = src.planes[plane.as_usize()].unwrap_ref().ptr(); |
| let dst_ptr = self.planes[plane.as_usize()].unwrap_mut().ptr_mut(); |
| ScalePlane( |
| source_ptr, |
| i32_from_u32(src_pd.row_bytes)?, |
| i32_from_u32(src_pd.width)?, |
| i32_from_u32(src_pd.height)?, |
| dst_ptr, |
| i32_from_u32(dst_pd.row_bytes)?, |
| i32_from_u32(dst_pd.width)?, |
| i32_from_u32(dst_pd.height)?, |
| FilterMode_kFilterBox, |
| ) |
| } |
| }; |
| } |
| Ok(()) |
| } |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| use super::*; |
| use crate::internal_utils::pixels::*; |
| use test_case::test_matrix; |
| |
| #[test_matrix([PixelFormat::Yuv444, PixelFormat::Yuv422, PixelFormat::Yuv420, PixelFormat::Yuv400], [false, true], [false, true])] |
| fn scale(yuv_format: PixelFormat, use_alpha: bool, is_pointer_input: bool) { |
| let mut yuv = image::Image { |
| width: 2, |
| height: 2, |
| depth: 8, |
| yuv_format, |
| ..Default::default() |
| }; |
| |
| let planes: &[Plane] = match (yuv_format, use_alpha) { |
| (PixelFormat::Yuv400, false) => &[Plane::Y], |
| (PixelFormat::Yuv400, true) => &[Plane::Y, Plane::A], |
| (_, false) => &YUV_PLANES, |
| (_, true) => &ALL_PLANES, |
| }; |
| let mut values = [ |
| 10, 20, // |
| 30, 40, |
| ]; |
| for plane in planes { |
| yuv.planes[plane.as_usize()] = Some(if is_pointer_input { |
| Pixels::Pointer(unsafe { |
| PointerSlice::create(values.as_mut_ptr(), values.len()).unwrap() |
| }) |
| } else { |
| Pixels::Buffer(values.to_vec()) |
| }); |
| yuv.row_bytes[plane.as_usize()] = 2; |
| yuv.image_owns_planes[plane.as_usize()] = !is_pointer_input; |
| } |
| let categories: &[Category] = |
| if use_alpha { &[Category::Color, Category::Alpha] } else { &[Category::Color] }; |
| for category in categories { |
| // Scale will update the width and height when scaling YUV planes. Reset it back before |
| // calling it again. |
| yuv.width = 2; |
| yuv.height = 2; |
| assert!(yuv.scale(4, 4, *category).is_ok()); |
| } |
| for plane in planes { |
| let expected_samples: &[u8] = match (yuv_format, plane) { |
| (PixelFormat::Yuv422, Plane::U | Plane::V) => &[ |
| 10, 10, // |
| 10, 10, // |
| 30, 30, // |
| 30, 30, |
| ], |
| (PixelFormat::Yuv420, Plane::U | Plane::V) => &[ |
| 10, 10, // |
| 10, 10, |
| ], |
| (_, _) => &[ |
| 10, 13, 18, 20, // |
| 15, 18, 23, 25, // |
| 25, 28, 33, 35, // |
| 30, 33, 38, 40, |
| ], |
| }; |
| match &yuv.planes[plane.as_usize()] { |
| Some(Pixels::Buffer(samples)) => { |
| assert_eq!(*samples, expected_samples) |
| } |
| _ => panic!(), |
| } |
| } |
| } |
| } |