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