• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 super::libyuv;
16 use super::rgb_impl;
17 
18 use crate::image::Plane;
19 use crate::image::YuvRange;
20 use crate::internal_utils::pixels::*;
21 use crate::internal_utils::*;
22 use crate::*;
23 
24 #[repr(C)]
25 #[derive(Clone, Copy, Default, PartialEq)]
26 pub enum Format {
27     Rgb,
28     #[default]
29     Rgba,
30     Argb,
31     Bgr,
32     Bgra,
33     Abgr,
34     Rgb565,
35     Rgba1010102, // https://developer.android.com/reference/android/graphics/Bitmap.Config#RGBA_1010102
36 }
37 
38 impl Format {
offsets(&self) -> [usize; 4]39     pub(crate) fn offsets(&self) -> [usize; 4] {
40         match self {
41             Format::Rgb => [0, 1, 2, 0],
42             Format::Rgba => [0, 1, 2, 3],
43             Format::Argb => [1, 2, 3, 0],
44             Format::Bgr => [2, 1, 0, 0],
45             Format::Bgra => [2, 1, 0, 3],
46             Format::Abgr => [3, 2, 1, 0],
47             Format::Rgb565 | Format::Rgba1010102 => [0; 4],
48         }
49     }
50 
r_offset(&self) -> usize51     pub fn r_offset(&self) -> usize {
52         self.offsets()[0]
53     }
54 
g_offset(&self) -> usize55     pub fn g_offset(&self) -> usize {
56         self.offsets()[1]
57     }
58 
b_offset(&self) -> usize59     pub fn b_offset(&self) -> usize {
60         self.offsets()[2]
61     }
62 
alpha_offset(&self) -> usize63     pub fn alpha_offset(&self) -> usize {
64         self.offsets()[3]
65     }
66 
has_alpha(&self) -> bool67     pub(crate) fn has_alpha(&self) -> bool {
68         !matches!(self, Format::Rgb | Format::Bgr | Format::Rgb565)
69     }
70 }
71 
72 #[repr(C)]
73 #[derive(Clone, Copy, Default)]
74 pub enum ChromaUpsampling {
75     #[default]
76     Automatic,
77     Fastest,
78     BestQuality,
79     Nearest,
80     Bilinear,
81 }
82 
83 impl ChromaUpsampling {
84     #[cfg(feature = "libyuv")]
nearest_neighbor_filter_allowed(&self) -> bool85     pub(crate) fn nearest_neighbor_filter_allowed(&self) -> bool {
86         !matches!(self, Self::Bilinear | Self::BestQuality)
87     }
88 
89     #[cfg(feature = "libyuv")]
bilinear_or_better_filter_allowed(&self) -> bool90     pub(crate) fn bilinear_or_better_filter_allowed(&self) -> bool {
91         !matches!(self, Self::Nearest | Self::Fastest)
92     }
93 }
94 
95 #[repr(C)]
96 #[derive(Clone, Copy, Default)]
97 pub enum ChromaDownsampling {
98     #[default]
99     Automatic,
100     Fastest,
101     BestQuality,
102     Average,
103     SharpYuv,
104 }
105 
106 #[derive(Default)]
107 pub struct Image {
108     pub width: u32,
109     pub height: u32,
110     pub depth: u8,
111     pub format: Format,
112     pub chroma_upsampling: ChromaUpsampling,
113     pub chroma_downsampling: ChromaDownsampling,
114     pub premultiply_alpha: bool,
115     pub is_float: bool,
116     pub max_threads: i32,
117     pub pixels: Option<Pixels>,
118     pub row_bytes: u32,
119 }
120 
121 #[derive(Debug, Default, PartialEq)]
122 pub enum AlphaMultiplyMode {
123     #[default]
124     NoOp,
125     Multiply,
126     UnMultiply,
127 }
128 
129 impl Image {
max_channel(&self) -> u16130     pub(crate) fn max_channel(&self) -> u16 {
131         ((1i32 << self.depth) - 1) as u16
132     }
133 
max_channel_f(&self) -> f32134     pub(crate) fn max_channel_f(&self) -> f32 {
135         self.max_channel() as f32
136     }
137 
create_from_yuv(image: &image::Image) -> Self138     pub fn create_from_yuv(image: &image::Image) -> Self {
139         Self {
140             width: image.width,
141             height: image.height,
142             depth: image.depth,
143             format: Format::Rgba,
144             chroma_upsampling: ChromaUpsampling::Automatic,
145             chroma_downsampling: ChromaDownsampling::Automatic,
146             premultiply_alpha: false,
147             is_float: false,
148             max_threads: 1,
149             pixels: None,
150             row_bytes: 0,
151         }
152     }
153 
pixels(&mut self) -> *mut u8154     pub(crate) fn pixels(&mut self) -> *mut u8 {
155         if self.pixels.is_none() {
156             return std::ptr::null_mut();
157         }
158         match self.pixels.unwrap_mut() {
159             Pixels::Pointer(ptr) => ptr.ptr_mut(),
160             Pixels::Pointer16(ptr) => ptr.ptr_mut() as *mut u8,
161             Pixels::Buffer(buffer) => buffer.as_mut_ptr(),
162             Pixels::Buffer16(buffer) => buffer.as_mut_ptr() as *mut u8,
163         }
164     }
165 
row(&self, row: u32) -> AvifResult<&[u8]>166     pub fn row(&self, row: u32) -> AvifResult<&[u8]> {
167         self.pixels
168             .as_ref()
169             .ok_or(AvifError::NoContent)?
170             .slice(checked_mul!(row, self.row_bytes)?, self.row_bytes)
171     }
172 
row_mut(&mut self, row: u32) -> AvifResult<&mut [u8]>173     pub fn row_mut(&mut self, row: u32) -> AvifResult<&mut [u8]> {
174         self.pixels
175             .as_mut()
176             .ok_or(AvifError::NoContent)?
177             .slice_mut(checked_mul!(row, self.row_bytes)?, self.row_bytes)
178     }
179 
row16(&self, row: u32) -> AvifResult<&[u16]>180     pub fn row16(&self, row: u32) -> AvifResult<&[u16]> {
181         self.pixels
182             .as_ref()
183             .ok_or(AvifError::NoContent)?
184             .slice16(checked_mul!(row, self.row_bytes / 2)?, self.row_bytes / 2)
185     }
186 
row16_mut(&mut self, row: u32) -> AvifResult<&mut [u16]>187     pub fn row16_mut(&mut self, row: u32) -> AvifResult<&mut [u16]> {
188         self.pixels
189             .as_mut()
190             .ok_or(AvifError::NoContent)?
191             .slice16_mut(checked_mul!(row, self.row_bytes / 2)?, self.row_bytes / 2)
192     }
193 
allocate(&mut self) -> AvifResult<()>194     pub fn allocate(&mut self) -> AvifResult<()> {
195         let row_bytes = checked_mul!(self.width, self.pixel_size())?;
196         if self.channel_size() == 1 {
197             let buffer_size: usize = usize_from_u32(checked_mul!(row_bytes, self.height)?)?;
198             let buffer: Vec<u8> = vec![0; buffer_size];
199             self.pixels = Some(Pixels::Buffer(buffer));
200         } else {
201             let buffer_size: usize = usize_from_u32(checked_mul!(row_bytes / 2, self.height)?)?;
202             let buffer: Vec<u16> = vec![0; buffer_size];
203             self.pixels = Some(Pixels::Buffer16(buffer));
204         }
205         self.row_bytes = row_bytes;
206         Ok(())
207     }
208 
depth_valid(&self) -> bool209     pub(crate) fn depth_valid(&self) -> bool {
210         match (self.format, self.is_float, self.depth) {
211             (Format::Rgb565, false, 8) => true,
212             (Format::Rgb565, _, _) => false,
213             (_, true, 16) => true, // IEEE 754 half-precision binary16
214             (_, false, 8 | 10 | 12 | 16) => true,
215             _ => false,
216         }
217     }
218 
has_alpha(&self) -> bool219     pub fn has_alpha(&self) -> bool {
220         match self.format {
221             Format::Rgba | Format::Bgra | Format::Argb | Format::Abgr | Format::Rgba1010102 => true,
222             Format::Rgb | Format::Bgr | Format::Rgb565 => false,
223         }
224     }
225 
channel_size(&self) -> u32226     pub(crate) fn channel_size(&self) -> u32 {
227         match self.depth {
228             8 => 1,
229             10 | 12 | 16 => 2,
230             _ => panic!(),
231         }
232     }
233 
channel_count(&self) -> u32234     pub(crate) fn channel_count(&self) -> u32 {
235         match self.format {
236             Format::Rgba | Format::Bgra | Format::Argb | Format::Abgr => 4,
237             Format::Rgb | Format::Bgr => 3,
238             Format::Rgb565 => 2,
239             Format::Rgba1010102 => 0, // This is never used.
240         }
241     }
242 
pixel_size(&self) -> u32243     pub(crate) fn pixel_size(&self) -> u32 {
244         match self.format {
245             Format::Rgba | Format::Bgra | Format::Argb | Format::Abgr => self.channel_size() * 4,
246             Format::Rgb | Format::Bgr => self.channel_size() * 3,
247             Format::Rgb565 => 2,
248             Format::Rgba1010102 => 4,
249         }
250     }
251 
convert_to_half_float(&mut self) -> AvifResult<()>252     fn convert_to_half_float(&mut self) -> AvifResult<()> {
253         let scale = 1.0 / self.max_channel_f();
254         match libyuv::convert_to_half_float(self, scale) {
255             Ok(_) => return Ok(()),
256             Err(err) => {
257                 if err != AvifError::NotImplemented {
258                     return Err(err);
259                 }
260             }
261         }
262         // This constant comes from libyuv. For details, see here:
263         // https://chromium.googlesource.com/libyuv/libyuv/+/2f87e9a7/source/row_common.cc#3537
264         let reinterpret_f32_as_u32 = |f: f32| u32::from_le_bytes(f.to_le_bytes());
265         let multiplier = 1.925_93e-34 * scale;
266         for y in 0..self.height {
267             let row = self.row16_mut(y)?;
268             for pixel in row {
269                 *pixel = (reinterpret_f32_as_u32((*pixel as f32) * multiplier) >> 13) as u16;
270             }
271         }
272         Ok(())
273     }
274 
convert_from_yuv(&mut self, image: &image::Image) -> AvifResult<()>275     pub fn convert_from_yuv(&mut self, image: &image::Image) -> AvifResult<()> {
276         if !image.has_plane(Plane::Y) || !image.depth_valid() || !self.depth_valid() {
277             return Err(AvifError::ReformatFailed);
278         }
279         if matches!(
280             image.matrix_coefficients,
281             MatrixCoefficients::Reserved
282                 | MatrixCoefficients::Bt2020Cl
283                 | MatrixCoefficients::Smpte2085
284                 | MatrixCoefficients::ChromaDerivedCl
285                 | MatrixCoefficients::Ictcp
286         ) {
287             return Err(AvifError::NotImplemented);
288         }
289         if image.matrix_coefficients == MatrixCoefficients::Ycgco
290             && image.yuv_range == YuvRange::Limited
291         {
292             return Err(AvifError::NotImplemented);
293         }
294         if matches!(
295             image.matrix_coefficients,
296             MatrixCoefficients::YcgcoRe | MatrixCoefficients::YcgcoRo
297         ) {
298             if image.yuv_range == YuvRange::Limited {
299                 return Err(AvifError::NotImplemented);
300             }
301             let bit_offset =
302                 if image.matrix_coefficients == MatrixCoefficients::YcgcoRe { 2 } else { 1 };
303             if image.depth - bit_offset != self.depth {
304                 return Err(AvifError::NotImplemented);
305             }
306         }
307         // Android MediaCodec maps all underlying YUV formats to PixelFormat::Yuv420. So do not
308         // perform this validation for Android MediaCodec. The libyuv wrapper will simply use Bt601
309         // coefficients for this color conversion.
310         #[cfg(not(feature = "android_mediacodec"))]
311         if image.matrix_coefficients == MatrixCoefficients::Identity
312             && !matches!(image.yuv_format, PixelFormat::Yuv444 | PixelFormat::Yuv400)
313         {
314             return Err(AvifError::NotImplemented);
315         }
316 
317         let mut alpha_multiply_mode = AlphaMultiplyMode::NoOp;
318         if image.has_alpha() && self.has_alpha() {
319             if !image.alpha_premultiplied && self.premultiply_alpha {
320                 alpha_multiply_mode = AlphaMultiplyMode::Multiply;
321             } else if image.alpha_premultiplied && !self.premultiply_alpha {
322                 alpha_multiply_mode = AlphaMultiplyMode::UnMultiply;
323             }
324         }
325 
326         let mut converted_with_libyuv: bool = false;
327         let mut alpha_reformatted_with_libyuv = false;
328         if alpha_multiply_mode == AlphaMultiplyMode::NoOp || self.has_alpha() {
329             match libyuv::yuv_to_rgb(image, self) {
330                 Ok(alpha_reformatted) => {
331                     alpha_reformatted_with_libyuv = alpha_reformatted;
332                     converted_with_libyuv = true;
333                 }
334                 Err(err) => {
335                     if err != AvifError::NotImplemented {
336                         return Err(err);
337                     }
338                 }
339             }
340         }
341         if image.yuv_format == PixelFormat::AndroidNv21 || self.format == Format::Rgba1010102 {
342             // These conversions are only supported via libyuv.
343             if converted_with_libyuv {
344                 if image.has_alpha() && matches!(self.format, Format::Rgba1010102) {
345                     // If the source image has an alpha channel, scale them to 2 bits and fill it
346                     // into the rgb image. Otherwise, libyuv writes them as opaque by default.
347                     self.import_alpha_from(image)?;
348                 }
349                 return Ok(());
350             } else {
351                 return Err(AvifError::NotImplemented);
352             }
353         }
354         if self.has_alpha() && !alpha_reformatted_with_libyuv {
355             if image.has_alpha() {
356                 self.import_alpha_from(image)?;
357             } else {
358                 self.set_opaque()?;
359             }
360         }
361         if !converted_with_libyuv {
362             let mut converted_by_fast_path = false;
363             if (matches!(
364                 self.chroma_upsampling,
365                 ChromaUpsampling::Nearest | ChromaUpsampling::Fastest
366             ) || matches!(image.yuv_format, PixelFormat::Yuv444 | PixelFormat::Yuv400))
367                 && (alpha_multiply_mode == AlphaMultiplyMode::NoOp || self.format.has_alpha())
368             {
369                 match rgb_impl::yuv_to_rgb_fast(image, self) {
370                     Ok(_) => converted_by_fast_path = true,
371                     Err(err) => {
372                         if err != AvifError::NotImplemented {
373                             return Err(err);
374                         }
375                     }
376                 }
377             }
378             if !converted_by_fast_path {
379                 rgb_impl::yuv_to_rgb_any(image, self, alpha_multiply_mode)?;
380                 alpha_multiply_mode = AlphaMultiplyMode::NoOp;
381             }
382         }
383         match alpha_multiply_mode {
384             AlphaMultiplyMode::Multiply => self.premultiply_alpha()?,
385             AlphaMultiplyMode::UnMultiply => self.unpremultiply_alpha()?,
386             AlphaMultiplyMode::NoOp => {}
387         }
388         if self.is_float {
389             self.convert_to_half_float()?;
390         }
391         Ok(())
392     }
393 
shuffle_channels_to(self, format: Format) -> AvifResult<Image>394     pub fn shuffle_channels_to(self, format: Format) -> AvifResult<Image> {
395         if self.format == format {
396             return Ok(self);
397         }
398         if self.format == Format::Rgb565 || format == Format::Rgb565 {
399             return Err(AvifError::NotImplemented);
400         }
401 
402         let mut dst = Image {
403             format,
404             pixels: None,
405             row_bytes: 0,
406             ..self
407         };
408         dst.allocate()?;
409 
410         let src_channel_count = self.channel_count();
411         let dst_channel_count = dst.channel_count();
412         let src_offsets = self.format.offsets();
413         let dst_offsets = dst.format.offsets();
414         let src_has_alpha = self.has_alpha();
415         let dst_has_alpha = dst.has_alpha();
416         let dst_max_channel = dst.max_channel();
417         for y in 0..self.height {
418             if self.depth == 8 {
419                 let src_row = self.row(y)?;
420                 let dst_row = &mut dst.row_mut(y)?;
421                 for x in 0..self.width {
422                     let src_pixel_i = (src_channel_count * x) as usize;
423                     let dst_pixel_i = (dst_channel_count * x) as usize;
424                     for c in 0..3 {
425                         dst_row[dst_pixel_i + dst_offsets[c]] =
426                             src_row[src_pixel_i + src_offsets[c]];
427                     }
428                     if dst_has_alpha {
429                         dst_row[dst_pixel_i + dst_offsets[3]] = if src_has_alpha {
430                             src_row[src_pixel_i + src_offsets[3]]
431                         } else {
432                             dst_max_channel as u8
433                         };
434                     }
435                 }
436             } else {
437                 let src_row = self.row16(y)?;
438                 let dst_row = &mut dst.row16_mut(y)?;
439                 for x in 0..self.width {
440                     let src_pixel_i = (src_channel_count * x) as usize;
441                     let dst_pixel_i = (dst_channel_count * x) as usize;
442                     for c in 0..3 {
443                         dst_row[dst_pixel_i + dst_offsets[c]] =
444                             src_row[src_pixel_i + src_offsets[c]];
445                     }
446                     if dst_has_alpha {
447                         dst_row[dst_pixel_i + dst_offsets[3]] = if src_has_alpha {
448                             src_row[src_pixel_i + src_offsets[3]]
449                         } else {
450                             dst_max_channel
451                         };
452                     }
453                 }
454             }
455         }
456         Ok(dst)
457     }
458 }
459 
460 #[cfg(test)]
461 mod tests {
462     use super::*;
463 
464     use crate::image::YuvRange;
465     use crate::image::ALL_PLANES;
466     use crate::image::MAX_PLANE_COUNT;
467     use crate::Category;
468 
469     use test_case::test_case;
470     use test_case::test_matrix;
471 
472     const WIDTH: usize = 3;
473     const HEIGHT: usize = 3;
474     struct YuvParams {
475         width: u32,
476         height: u32,
477         depth: u8,
478         format: PixelFormat,
479         yuv_range: YuvRange,
480         color_primaries: ColorPrimaries,
481         matrix_coefficients: MatrixCoefficients,
482         planes: [[&'static [u16]; HEIGHT]; MAX_PLANE_COUNT],
483     }
484 
485     const YUV_PARAMS: [YuvParams; 1] = [YuvParams {
486         width: WIDTH as u32,
487         height: HEIGHT as u32,
488         depth: 12,
489         format: PixelFormat::Yuv420,
490         yuv_range: YuvRange::Limited,
491         color_primaries: ColorPrimaries::Srgb,
492         matrix_coefficients: MatrixCoefficients::Bt709,
493         planes: [
494             [
495                 &[1001, 1001, 1001],
496                 &[1001, 1001, 1001],
497                 &[1001, 1001, 1001],
498             ],
499             [&[1637, 1637], &[1637, 1637], &[1637, 1637]],
500             [&[3840, 3840], &[3840, 3840], &[3840, 3840]],
501             [&[0, 0, 2039], &[0, 2039, 4095], &[2039, 4095, 4095]],
502         ],
503     }];
504 
505     struct RgbParams {
506         params: (
507             /*yuv_param_index:*/ usize,
508             /*format:*/ Format,
509             /*depth:*/ u8,
510             /*premultiply_alpha:*/ bool,
511             /*is_float:*/ bool,
512         ),
513         expected_rgba: [&'static [u16]; HEIGHT],
514     }
515 
516     const RGB_PARAMS: [RgbParams; 5] = [
517         RgbParams {
518             params: (0, Format::Rgba, 16, true, false),
519             expected_rgba: [
520                 &[0, 0, 0, 0, 0, 0, 0, 0, 32631, 1, 0, 32631],
521                 &[0, 0, 0, 0, 32631, 1, 0, 32631, 65535, 2, 0, 65535],
522                 &[32631, 1, 0, 32631, 65535, 2, 0, 65535, 65535, 2, 0, 65535],
523             ],
524         },
525         RgbParams {
526             params: (0, Format::Rgba, 16, true, true),
527             expected_rgba: [
528                 &[0, 0, 0, 0, 0, 0, 0, 0, 14327, 256, 0, 14327],
529                 &[0, 0, 0, 0, 14327, 256, 0, 14327, 15360, 512, 0, 15360],
530                 &[
531                     14327, 256, 0, 14327, 15360, 512, 0, 15360, 15360, 512, 0, 15360,
532                 ],
533             ],
534         },
535         RgbParams {
536             params: (0, Format::Rgba, 16, false, true),
537             expected_rgba: [
538                 &[15360, 512, 0, 0, 15360, 512, 0, 0, 15360, 512, 0, 14327],
539                 &[15360, 512, 0, 0, 15360, 512, 0, 14327, 15360, 512, 0, 15360],
540                 &[
541                     15360, 512, 0, 14327, 15360, 512, 0, 15360, 15360, 512, 0, 15360,
542                 ],
543             ],
544         },
545         RgbParams {
546             params: (0, Format::Rgba, 16, false, false),
547             expected_rgba: [
548                 &[65535, 2, 0, 0, 65535, 2, 0, 0, 65535, 2, 0, 32631],
549                 &[65535, 2, 0, 0, 65535, 2, 0, 32631, 65535, 2, 0, 65535],
550                 &[65535, 2, 0, 32631, 65535, 2, 0, 65535, 65535, 2, 0, 65535],
551             ],
552         },
553         RgbParams {
554             params: (0, Format::Bgra, 16, true, false),
555             expected_rgba: [
556                 &[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 32631, 32631],
557                 &[0, 0, 0, 0, 0, 1, 32631, 32631, 0, 2, 65535, 65535],
558                 &[0, 1, 32631, 32631, 0, 2, 65535, 65535, 0, 2, 65535, 65535],
559             ],
560         },
561     ];
562 
563     #[test_matrix(0usize..5)]
rgb_conversion(rgb_param_index: usize) -> AvifResult<()>564     fn rgb_conversion(rgb_param_index: usize) -> AvifResult<()> {
565         let rgb_params = &RGB_PARAMS[rgb_param_index];
566         let yuv_params = &YUV_PARAMS[rgb_params.params.0];
567         let mut image = image::Image {
568             width: yuv_params.width,
569             height: yuv_params.height,
570             depth: yuv_params.depth,
571             yuv_format: yuv_params.format,
572             color_primaries: yuv_params.color_primaries,
573             matrix_coefficients: yuv_params.matrix_coefficients,
574             yuv_range: yuv_params.yuv_range,
575             ..image::Image::default()
576         };
577         image.allocate_planes(Category::Color)?;
578         image.allocate_planes(Category::Alpha)?;
579         let yuva_planes = &yuv_params.planes;
580         for plane in ALL_PLANES {
581             let plane_index = plane.as_usize();
582             if yuva_planes[plane_index].is_empty() {
583                 continue;
584             }
585             for y in 0..image.height(plane) {
586                 let row16 = image.row16_mut(plane, y as u32)?;
587                 assert_eq!(row16.len(), yuva_planes[plane_index][y].len());
588                 let dst = &mut row16[..];
589                 dst.copy_from_slice(yuva_planes[plane_index][y]);
590             }
591         }
592 
593         let mut rgb = Image::create_from_yuv(&image);
594         assert_eq!(rgb.width, image.width);
595         assert_eq!(rgb.height, image.height);
596         assert_eq!(rgb.depth, image.depth);
597 
598         rgb.format = rgb_params.params.1;
599         rgb.depth = rgb_params.params.2;
600         rgb.premultiply_alpha = rgb_params.params.3;
601         rgb.is_float = rgb_params.params.4;
602 
603         rgb.allocate()?;
604         rgb.convert_from_yuv(&image)?;
605 
606         for y in 0..rgb.height as usize {
607             let row16 = rgb.row16(y as u32)?;
608             assert_eq!(&row16[..], rgb_params.expected_rgba[y]);
609         }
610         Ok(())
611     }
612 
613     #[test_case(Format::Rgba, &[0, 1, 2, 3])]
614     #[test_case(Format::Abgr, &[3, 2, 1, 0])]
615     #[test_case(Format::Rgb, &[0, 1, 2])]
shuffle_channels_to(format: Format, expected: &[u8])616     fn shuffle_channels_to(format: Format, expected: &[u8]) {
617         let image = Image {
618             width: 1,
619             height: 1,
620             depth: 8,
621             format: Format::Rgba,
622             pixels: Some(Pixels::Buffer(vec![0u8, 1, 2, 3])),
623             row_bytes: 4,
624             ..Default::default()
625         };
626         assert_eq!(
627             image.shuffle_channels_to(format).unwrap().row(0).unwrap(),
628             expected
629         );
630     }
631 }
632