From 283715f098be20c57a71e0c8b41c10ff9382751a Mon Sep 17 00:00:00 2001 From: Kevin Reid Date: Sat, 13 Sep 2025 10:03:22 -0700 Subject: [PATCH 1/2] Make `decode()` produce `Vec<[u8; 4]>` instead of `Vec`. This has identical memory layout, but guarantees that the output will always be a multiple of 4 bytes long and allows the caller to easily manipulate whole pixels. --- src/lib.rs | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index b92eeeb..c882730 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -677,7 +677,7 @@ fn defilter( fn process_scanlines( header: &PngHeader, scanline_data: &mut [u8], - output_rgba: &mut [u8], + output_rgba: &mut [[u8; 4]], ancillary_chunks: &AncillaryChunks, pixel_type: PixelType, ) -> Result<(), DecodeError> { @@ -720,15 +720,11 @@ fn process_scanlines( for (idx, (r, g, b, a)) in scanline_iter.enumerate() { let (output_x, output_y) = (idx, y); - let output_idx = - (output_y as u64 * header.width as u64 * 4) + (output_x as u64 * 4); + let output_idx = (output_y as u64 * header.width as u64) + (output_x as u64); let output_idx: usize = output_idx.try_into().map_err(|_| DecodeError::IntegerOverflow)?; - output_rgba[output_idx] = r; - output_rgba[output_idx + 1] = g; - output_rgba[output_idx + 2] = b; - output_rgba[output_idx + 3] = a; + output_rgba[output_idx] = [r, g, b, a]; } last_scanline.copy_from_slice(current_scanline); @@ -846,14 +842,11 @@ fn process_scanlines( }; let output_idx = - (output_y as u64 * header.width as u64 * 4) + (output_x as u64 * 4); + (output_y as u64 * header.width as u64) + (output_x as u64); let output_idx: usize = output_idx.try_into().map_err(|_| DecodeError::IntegerOverflow)?; - output_rgba[output_idx] = r; - output_rgba[output_idx + 1] = g; - output_rgba[output_idx + 2] = b; - output_rgba[output_idx + 3] = a; + output_rgba[output_idx] = [r, g, b, a]; } last_scanline.copy_from_slice(current_scanline); @@ -886,7 +879,14 @@ fn paeth_predictor(a: i16, b: i16, c: i16) -> u8 { } } -pub fn decode(bytes: &[u8]) -> Result<(PngHeader, Vec), DecodeError> { +/// Decodes the provided PNG into RGBA pixels. +/// +/// The returned [`PngHeader`] contains the image’s size, and other PNG metadata which is not +/// necessary to make use of the pixels (the returned format is always 8-bit-per-component RGBA). +/// +/// The returned [`Vec`] contains the pixels, represented as `[r, g, b, a]` arrays. +/// Its length will be equal to `header.width * header.height`. +pub fn decode(bytes: &[u8]) -> Result<(PngHeader, Vec<[u8; 4]>), DecodeError> { if bytes.len() < PNG_MAGIC_BYTES.len() { return Err(DecodeError::MissingBytes); } @@ -931,7 +931,7 @@ pub fn decode(bytes: &[u8]) -> Result<(PngHeader, Vec), DecodeError> { })?; // For now, output data is always RGBA, 1 byte per channel. - let mut output_rgba = vec![0u8; header.width as usize * header.height as usize * 4]; + let mut output_rgba = vec![[0u8; 4]; header.width as usize * header.height as usize]; process_scanlines( &header, @@ -962,7 +962,7 @@ mod tests { if extension.to_ascii_lowercase().as_str() == "png" { let png_bytes = std::fs::read(&path).unwrap(); - let (_header, decoded) = if path + let (_header, decoded): (PngHeader, Vec<[u8; 4]>) = if path .file_stem() .expect("expected png path to be a file") .to_string_lossy() @@ -973,6 +973,7 @@ mod tests { } else { decode(&png_bytes).unwrap() }; + let decoded: Vec = decoded.into_flattened(); // Uncomment to inspect output.png for debugging. // let image_buf: image::ImageBuffer, _> = From e3a8baf472cf23291852206ff2e30b40e2276df3 Mon Sep 17 00:00:00 2001 From: Kevin Reid Date: Mon, 22 Sep 2025 12:39:23 -0700 Subject: [PATCH 2/2] Document option of using `Vec::into_flattened()`. --- src/lib.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index c882730..1327581 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -886,6 +886,8 @@ fn paeth_predictor(a: i16, b: i16, c: i16) -> u8 { /// /// The returned [`Vec`] contains the pixels, represented as `[r, g, b, a]` arrays. /// Its length will be equal to `header.width * header.height`. +/// (If you need a `Vec` of length `header.width * header.height * 4` instead, you can use +/// [`Vec::into_flattened()`] to convert it.) pub fn decode(bytes: &[u8]) -> Result<(PngHeader, Vec<[u8; 4]>), DecodeError> { if bytes.len() < PNG_MAGIC_BYTES.len() { return Err(DecodeError::MissingBytes);