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