use super::icc_profile;
use crate::color::ColorSpace;
use image::{ExtendedColorType, ImageEncoder, codecs::png::PngEncoder};
pub fn encode_png_with_icc(
rgba: &[u8],
width: u32,
height: u32,
color_space: ColorSpace,
) -> Result<Vec<u8>, String> {
if rgba.len() != (width as usize) * (height as usize) * 4 {
return Err("Internal error: pixel buffer size did not match dimensions.".to_string());
}
let mut bytes = Vec::new();
let mut encoder = PngEncoder::new(&mut bytes);
if color_space != ColorSpace::Srgb {
encoder
.set_icc_profile(icc_profile::build(color_space))
.map_err(|e| e.to_string())?;
}
encoder
.write_image(rgba, width, height, ExtendedColorType::Rgba8)
.map_err(|e| e.to_string())?;
Ok(bytes)
}
#[cfg(test)]
mod tests {
use super::*;
fn fixed_rgba(width: u32, height: u32) -> Vec<u8> {
(0..width * height * 4)
.map(|i| (i * 37 + 11).to_le_bytes()[0])
.collect()
}
#[test]
fn rejects_a_mismatched_buffer_size() {
let err = encode_png_with_icc(&[0, 0, 0, 0], 2, 2, ColorSpace::Srgb).unwrap_err();
assert!(err.contains("did not match dimensions"));
}
#[test]
fn srgb_and_display_p3_encode_to_different_bytes_for_the_same_pixels() {
let rgba = fixed_rgba(4, 3);
let srgb = encode_png_with_icc(&rgba, 4, 3, ColorSpace::Srgb).unwrap();
let p3 = encode_png_with_icc(&rgba, 4, 3, ColorSpace::DisplayP3).unwrap();
assert_ne!(srgb, p3, "an embedded ICC profile must change the bytes");
}
#[test]
fn encoding_is_deterministic_for_identical_input() {
let rgba = fixed_rgba(5, 5);
let a = encode_png_with_icc(&rgba, 5, 5, ColorSpace::Rec2020).unwrap();
let b = encode_png_with_icc(&rgba, 5, 5, ColorSpace::Rec2020).unwrap();
assert_eq!(a, b);
}
}