//! Widening a decoded 8-bit PNG frame to RGBA. //! //! `camo` and `flare` both decode a player-supplied PNG with the same //! `normalize_to_color8() | ALPHA` transformations and then need the same //! step: turning whatever 8-bit colour type came out of that into RGBA //! bytes to re-encode. The only thing that differs between the two call //! sites is the image's dimensions, so this is that step, shared. /// Widens a decoded 8-bit frame of `width` x `height` pixels to RGBA. `None` /// if `data`'s length does not match what `color` and the dimensions imply, /// or if `color` is `Indexed` — the decoder's transformations should already /// have removed it, but this is not the place to assume that held. pub fn to_rgba(data: &[u8], color: png::ColorType, width: u32, height: u32) -> Option> { let pixels = (width as usize).checked_mul(height as usize)?; match color { png::ColorType::Rgba if data.len() == pixels * 4 => Some(data.to_vec()), png::ColorType::Rgb if data.len() == pixels * 3 => Some( data.chunks_exact(3) .flat_map(|p| [p[0], p[1], p[2], 255]) .collect(), ), png::ColorType::GrayscaleAlpha if data.len() == pixels * 2 => Some( data.chunks_exact(2) .flat_map(|p| [p[0], p[0], p[0], p[1]]) .collect(), ), png::ColorType::Grayscale if data.len() == pixels => { Some(data.iter().flat_map(|&g| [g, g, g, 255]).collect()) } _ => None, } } #[cfg(test)] mod tests { use super::*; /// Pins the byte layout `to_rgba` produces, at both the size `camo` calls /// it with and a size shaped like a `flare` screenshot — a regression in /// either caller's PNG path should show up here first. #[test] fn widens_every_colour_type_at_camo_and_flare_sizes() { for (width, height) in [(84u32, 72u32), (320, 200)] { let pixels = (width * height) as usize; let rgb: Vec = (0..pixels).flat_map(|i| [i as u8, 1, 2]).collect(); assert_eq!( to_rgba(&rgb, png::ColorType::Rgb, width, height).unwrap(), rgb.chunks_exact(3) .flat_map(|p| [p[0], p[1], p[2], 255]) .collect::>() ); let gray: Vec = (0..pixels).map(|i| i as u8).collect(); assert_eq!( to_rgba(&gray, png::ColorType::Grayscale, width, height).unwrap(), gray.iter() .flat_map(|&g| [g, g, g, 255]) .collect::>() ); let gray_alpha: Vec = (0..pixels).flat_map(|i| [i as u8, 9]).collect(); assert_eq!( to_rgba(&gray_alpha, png::ColorType::GrayscaleAlpha, width, height).unwrap(), gray_alpha .chunks_exact(2) .flat_map(|p| [p[0], p[0], p[0], p[1]]) .collect::>() ); let rgba: Vec = (0..pixels).flat_map(|i| [i as u8, 1, 2, 3]).collect(); assert_eq!( to_rgba(&rgba, png::ColorType::Rgba, width, height).unwrap(), rgba ); assert!(to_rgba(&gray, png::ColorType::Indexed, width, height).is_none()); } } #[test] fn wrong_length_data_is_refused() { assert!(to_rgba(&[0u8; 3], png::ColorType::Rgba, 84, 72).is_none()); assert!(to_rgba(&[], png::ColorType::Grayscale, 320, 200).is_none()); } }