use alloc::boxed::Box;
use alloc::vec::Vec;
use core::any::Any;
use crate::detect::SourceEncodingDetails;
use crate::extensions::Extensions;
use crate::{ImageFormat, ImageInfo, Metadata};
use zenpixels::{PixelBuffer, PixelDescriptor, PixelSlice};
#[non_exhaustive]
pub struct EncodeOutput {
data: Vec<u8>,
format: ImageFormat,
mime_type: &'static str,
extension: &'static str,
extensions: Extensions,
}
#[cfg(target_pointer_width = "64")]
const _: () = assert!(core::mem::size_of::<EncodeOutput>() == 96);
impl EncodeOutput {
pub fn new(data: Vec<u8>, format: ImageFormat) -> Self {
Self {
data,
mime_type: format.mime_type(),
extension: format.extension(),
format,
extensions: Extensions::new(),
}
}
pub fn with_mime_type(mut self, mime_type: &'static str) -> Self {
self.mime_type = mime_type;
self
}
pub fn with_extension(mut self, extension: &'static str) -> Self {
self.extension = extension;
self
}
pub fn into_vec(self) -> Vec<u8> {
self.data
}
pub fn data(&self) -> &[u8] {
&self.data
}
pub fn len(&self) -> usize {
self.data.len()
}
pub fn is_empty(&self) -> bool {
self.data.is_empty()
}
pub fn format(&self) -> ImageFormat {
self.format
}
pub fn mime_type(&self) -> &'static str {
self.mime_type
}
pub fn extension(&self) -> &'static str {
self.extension
}
pub fn with_extras<T: Any + Send + Sync + 'static>(mut self, extras: T) -> Self {
self.extensions.insert(extras);
self
}
pub fn extras<T: Any + Send + Sync + 'static>(&self) -> Option<&T> {
self.extensions.get()
}
pub fn take_extras<T: Any + Send + Sync + 'static>(&mut self) -> Option<T> {
self.extensions.remove()
}
pub fn extensions(&self) -> &Extensions {
&self.extensions
}
pub fn extensions_mut(&mut self) -> &mut Extensions {
&mut self.extensions
}
}
impl Clone for EncodeOutput {
fn clone(&self) -> Self {
Self {
data: self.data.clone(),
format: self.format,
mime_type: self.mime_type,
extension: self.extension,
extensions: self.extensions.clone(),
}
}
}
impl core::fmt::Debug for EncodeOutput {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("EncodeOutput")
.field("data_len", &self.data.len())
.field("format", &self.format)
.field("mime_type", &self.mime_type)
.field("extension", &self.extension)
.field("extensions", &self.extensions)
.finish()
}
}
impl PartialEq for EncodeOutput {
fn eq(&self, other: &Self) -> bool {
self.data == other.data
&& self.format == other.format
&& self.mime_type == other.mime_type
&& self.extension == other.extension
}
}
impl Eq for EncodeOutput {}
impl AsRef<[u8]> for EncodeOutput {
fn as_ref(&self) -> &[u8] {
&self.data
}
}
#[non_exhaustive]
pub struct DecodeOutput {
pixels: PixelBuffer,
info: ImageInfo,
source_encoding: Option<Box<dyn SourceEncodingDetails>>,
extensions: Extensions,
}
#[cfg(target_pointer_width = "64")]
const _: () = assert!(core::mem::size_of::<DecodeOutput>() == 352);
impl DecodeOutput {
pub fn new(pixels: PixelBuffer, info: ImageInfo) -> Self {
Self {
pixels,
info,
source_encoding: None,
extensions: Extensions::new(),
}
}
pub fn with_source_encoding_details<T: SourceEncodingDetails + 'static>(
mut self,
details: T,
) -> Self {
self.source_encoding = Some(Box::new(details));
self
}
pub fn source_encoding_details(&self) -> Option<&dyn SourceEncodingDetails> {
self.source_encoding.as_deref()
}
pub fn take_source_encoding_details(&mut self) -> Option<Box<dyn SourceEncodingDetails>> {
self.source_encoding.take()
}
pub fn with_extras<T: Any + Send + Sync + 'static>(mut self, extras: T) -> Self {
self.extensions.insert(extras);
self
}
pub fn extras<T: Any + Send + Sync + 'static>(&self) -> Option<&T> {
self.extensions.get()
}
pub fn take_extras<T: Any + Send + Sync + 'static>(&mut self) -> Option<T> {
self.extensions.remove()
}
pub fn extensions(&self) -> &Extensions {
&self.extensions
}
pub fn extensions_mut(&mut self) -> &mut Extensions {
&mut self.extensions
}
pub fn pixels(&self) -> PixelSlice<'_> {
self.pixels.as_slice()
}
pub fn into_buffer(self) -> PixelBuffer {
self.pixels
}
pub fn info(&self) -> &ImageInfo {
&self.info
}
pub fn width(&self) -> u32 {
self.pixels.width()
}
pub fn height(&self) -> u32 {
self.pixels.height()
}
pub fn has_alpha(&self) -> bool {
self.pixels.has_alpha()
}
pub fn descriptor(&self) -> PixelDescriptor {
self.pixels.descriptor()
}
pub fn format(&self) -> ImageFormat {
self.info.format
}
pub fn metadata(&self) -> Metadata {
self.info.metadata()
}
}
impl core::fmt::Debug for DecodeOutput {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("DecodeOutput")
.field("pixels", &self.pixels)
.field("format", &self.info.format)
.field("has_source_encoding", &self.source_encoding.is_some())
.finish()
}
}
#[non_exhaustive]
pub struct AnimationFrame<'a> {
pixels: PixelSlice<'a>,
duration_ms: u32,
frame_index: u32,
}
impl<'a> AnimationFrame<'a> {
pub fn new(pixels: PixelSlice<'a>, duration_ms: u32, frame_index: u32) -> Self {
Self {
pixels,
duration_ms,
frame_index,
}
}
pub fn pixels(&self) -> &PixelSlice<'a> {
&self.pixels
}
pub fn duration_ms(&self) -> u32 {
self.duration_ms
}
pub fn frame_index(&self) -> u32 {
self.frame_index
}
pub fn to_owned_frame(&self) -> OwnedAnimationFrame {
let ps = &self.pixels;
let w = ps.width();
let h = ps.rows();
let desc = ps.descriptor();
let bpp = desc.bytes_per_pixel();
let row_bytes = w as usize * bpp;
let mut data = alloc::vec::Vec::with_capacity(h as usize * row_bytes);
for y in 0..h {
data.extend_from_slice(ps.row(y));
}
let pixels = PixelBuffer::from_vec(data, w, h, desc)
.expect("to_owned_frame: buffer sized correctly");
OwnedAnimationFrame {
pixels,
duration_ms: self.duration_ms,
frame_index: self.frame_index,
extensions: Extensions::new(),
}
}
}
impl core::fmt::Debug for AnimationFrame<'_> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("AnimationFrame")
.field("pixels", &self.pixels)
.field("duration_ms", &self.duration_ms)
.field("frame_index", &self.frame_index)
.finish()
}
}
#[non_exhaustive]
pub struct OwnedAnimationFrame {
pixels: PixelBuffer,
duration_ms: u32,
frame_index: u32,
extensions: Extensions,
}
impl OwnedAnimationFrame {
pub fn new(pixels: PixelBuffer, duration_ms: u32, frame_index: u32) -> Self {
Self {
pixels,
duration_ms,
frame_index,
extensions: Extensions::new(),
}
}
pub fn pixels(&self) -> PixelSlice<'_> {
self.pixels.as_slice()
}
pub fn into_buffer(self) -> PixelBuffer {
self.pixels
}
pub fn duration_ms(&self) -> u32 {
self.duration_ms
}
pub fn frame_index(&self) -> u32 {
self.frame_index
}
pub fn as_animation_frame(&self) -> AnimationFrame<'_> {
AnimationFrame::new(self.pixels.as_slice(), self.duration_ms, self.frame_index)
}
pub fn with_extras<T: Any + Send + Sync + 'static>(mut self, extras: T) -> Self {
self.extensions.insert(extras);
self
}
pub fn extras<T: Any + Send + Sync + 'static>(&self) -> Option<&T> {
self.extensions.get()
}
pub fn take_extras<T: Any + Send + Sync + 'static>(&mut self) -> Option<T> {
self.extensions.remove()
}
pub fn extensions(&self) -> &Extensions {
&self.extensions
}
pub fn extensions_mut(&mut self) -> &mut Extensions {
&mut self.extensions
}
}
impl core::fmt::Debug for OwnedAnimationFrame {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("OwnedAnimationFrame")
.field("pixels", &self.pixels)
.field("duration_ms", &self.duration_ms)
.field("frame_index", &self.frame_index)
.field("extensions", &self.extensions)
.finish()
}
}
#[cfg(test)]
mod tests {
use super::*;
use alloc::vec;
use zenpixels::PixelDescriptor;
fn make_rgb8_buffer(w: u32, h: u32) -> PixelBuffer {
PixelBuffer::new(w, h, PixelDescriptor::RGB8_SRGB)
}
fn make_rgba8_buffer(w: u32, h: u32) -> PixelBuffer {
PixelBuffer::new(w, h, PixelDescriptor::RGBA8_SRGB)
}
fn make_gray8_buffer(w: u32, h: u32) -> PixelBuffer {
PixelBuffer::new(w, h, PixelDescriptor::GRAY8_SRGB)
}
#[test]
fn encode_output() {
let output = EncodeOutput::new(vec![1, 2, 3], ImageFormat::Jpeg);
assert_eq!(output.format(), ImageFormat::Jpeg);
assert_eq!(output.mime_type(), "image/jpeg");
assert_eq!(output.extension(), "jpg");
assert_eq!(output.len(), 3);
assert_eq!(output.data(), &[1, 2, 3]);
assert!(!output.is_empty());
assert_eq!(output.into_vec(), vec![1, 2, 3]);
}
#[test]
fn encode_output_mime_extension_override() {
let output = EncodeOutput::new(vec![], ImageFormat::Png)
.with_mime_type("image/apng")
.with_extension("apng");
assert_eq!(output.format(), ImageFormat::Png);
assert_eq!(output.mime_type(), "image/apng");
assert_eq!(output.extension(), "apng");
}
#[test]
fn encode_output_eq() {
let a = EncodeOutput::new(vec![1, 2, 3], ImageFormat::Jpeg);
let b = EncodeOutput::new(vec![1, 2, 3], ImageFormat::Jpeg);
assert_eq!(a, b);
let c = EncodeOutput::new(vec![1, 2, 3], ImageFormat::Png);
assert_ne!(a, c);
}
#[test]
fn decode_output() {
let buf = make_rgb8_buffer(2, 2);
let info = ImageInfo::new(2, 2, ImageFormat::Png);
let output = DecodeOutput::new(buf, info);
assert_eq!(output.width(), 2);
assert_eq!(output.height(), 2);
assert!(!output.has_alpha());
assert_eq!(output.format(), ImageFormat::Png);
}
#[test]
fn decode_output_extras() {
let buf = make_rgb8_buffer(2, 2);
let info = ImageInfo::new(2, 2, ImageFormat::Jpeg);
let mut output = DecodeOutput::new(buf, info).with_extras(42u32);
assert_eq!(output.extras::<u32>(), Some(&42u32));
assert_eq!(output.extras::<u64>(), None);
let taken = output.take_extras::<u32>();
assert_eq!(taken, Some(42u32));
assert!(output.extras::<u32>().is_none());
}
#[test]
fn animation_frame_borrowed() {
let buf = make_rgba8_buffer(4, 4);
let ps = buf.as_slice();
let frame = AnimationFrame::new(ps, 100, 0);
assert_eq!(frame.duration_ms(), 100);
assert_eq!(frame.frame_index(), 0);
assert_eq!(frame.pixels().width(), 4);
assert_eq!(frame.pixels().rows(), 4);
}
#[test]
fn animation_frame_to_owned() {
let buf = make_rgb8_buffer(2, 2);
let ps = buf.as_slice();
let frame = AnimationFrame::new(ps, 50, 3);
let owned = frame.to_owned_frame();
assert_eq!(owned.duration_ms(), 50);
assert_eq!(owned.frame_index(), 3);
assert_eq!(owned.pixels().width(), 2);
assert_eq!(owned.pixels().rows(), 2);
}
#[test]
fn owned_animation_frame_as_animation_frame() {
let buf = make_rgb8_buffer(2, 2);
let owned = OwnedAnimationFrame::new(buf, 100, 5);
let borrowed = owned.as_animation_frame();
assert_eq!(borrowed.duration_ms(), 100);
assert_eq!(borrowed.frame_index(), 5);
}
#[test]
fn owned_animation_frame_into_buffer() {
let buf = make_rgb8_buffer(3, 3);
let owned = OwnedAnimationFrame::new(buf, 200, 0);
let recovered = owned.into_buffer();
assert_eq!(recovered.width(), 3);
assert_eq!(recovered.height(), 3);
}
#[test]
fn animation_frame_debug() {
let buf = make_gray8_buffer(2, 2);
let ps = buf.as_slice();
let frame = AnimationFrame::new(ps, 100, 3);
let s = alloc::format!("{:?}", frame);
assert!(s.contains("AnimationFrame"));
assert!(s.contains("duration_ms: 100"));
assert!(s.contains("frame_index: 3"));
}
#[test]
fn owned_animation_frame_debug() {
let buf = make_rgb8_buffer(2, 2);
let owned = OwnedAnimationFrame::new(buf, 50, 1);
let s = alloc::format!("{:?}", owned);
assert!(s.contains("OwnedAnimationFrame"));
assert!(s.contains("duration_ms: 50"));
assert!(s.contains("frame_index: 1"));
}
}