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