use alloc::format;
use alloc::string::String;
use alloc::vec::Vec;
use oxideav_png::{PngImage, PngPixelFormat, encode_apng, encode_png_image};
use super::{PixelFormat, Surface};
use crate::core::error::{Error, Result};
pub fn encode(surface: &Surface) -> Result<Vec<u8>> {
let image = to_image(surface);
encode_png_image(&image).map_err(|e| failed(&format!("{e}")))
}
pub fn encode_animation(frames: &[Surface], delay_centiseconds: u16) -> Result<Vec<u8>> {
let Some(first) = frames.first() else {
return Err(failed("an animation needs at least one frame"));
};
for (i, frame) in frames.iter().enumerate() {
if frame.width() != first.width() || frame.height() != first.height() {
return Err(failed(&format!(
"frame {i} is {}x{} but the first is {}x{}; APNG has one IHDR for the whole file",
frame.width(),
frame.height(),
first.width(),
first.height()
)));
}
}
let images: Vec<PngImage> = frames.iter().map(to_image).collect();
encode_apng(&images, delay_centiseconds, 0).map_err(|e| failed(&format!("{e}")))
}
fn to_image(surface: &Surface) -> PngImage {
let (pixel_format, data) = match surface.format() {
PixelFormat::RGB888 => (PngPixelFormat::Rgb24, surface.pixels().to_vec()),
PixelFormat::RGBA8888 => (PngPixelFormat::Rgba, surface.pixels().to_vec()),
_ => {
let mut data = Vec::with_capacity((surface.width() as usize) * 4);
for y in 0..surface.height() {
for x in 0..surface.width() {
let rgb = surface.get(x, y).unwrap_or([0, 0, 0]);
data.extend_from_slice(&[rgb[0], rgb[1], rgb[2], 0xff]);
}
}
(PngPixelFormat::Rgba, data)
}
};
let stride = (surface.width() as usize) * pixel_format.bytes_per_pixel();
PngImage {
width: surface.width(),
height: surface.height(),
pixel_format,
stride,
data,
palette: Vec::new(),
}
}
fn failed(message: &str) -> Error {
Error::Config {
at: String::from("display::png"),
message: String::from(message),
}
}