scena 1.7.2

A Rust-native scene-graph renderer with typed scene state, glTF assets, and explicit prepare/render lifecycles.
Documentation
use std::error::Error as StdError;
use std::fmt;
use std::io::Cursor;

#[cfg(not(target_arch = "wasm32"))]
use std::path::Path;

use super::CaptureError;

#[derive(Debug, Clone, PartialEq)]
pub enum CapturePngError {
    Capture(CaptureError),
    InvalidPixelBuffer {
        width: u32,
        height: u32,
        expected_len: usize,
        actual_len: usize,
    },
    Encode {
        reason: String,
    },
    Io {
        path: String,
        reason: String,
    },
}

pub(super) fn encode_png_rgba8(
    width: u32,
    height: u32,
    rgba8: &[u8],
) -> Result<Vec<u8>, CapturePngError> {
    let expected_len = width as usize * height as usize * 4;
    if rgba8.len() != expected_len {
        return Err(CapturePngError::InvalidPixelBuffer {
            width,
            height,
            expected_len,
            actual_len: rgba8.len(),
        });
    }

    let mut bytes = Vec::new();
    {
        let mut encoder = png::Encoder::new(Cursor::new(&mut bytes), width, height);
        encoder.set_color(png::ColorType::Rgba);
        encoder.set_depth(png::BitDepth::Eight);
        let mut writer = encoder
            .write_header()
            .map_err(|error| CapturePngError::Encode {
                reason: error.to_string(),
            })?;
        writer
            .write_image_data(rgba8)
            .map_err(|error| CapturePngError::Encode {
                reason: error.to_string(),
            })?;
    }
    Ok(bytes)
}

#[cfg(not(target_arch = "wasm32"))]
pub(super) fn write_png(path: impl AsRef<Path>, bytes: Vec<u8>) -> Result<(), CapturePngError> {
    let path = path.as_ref();
    std::fs::write(path, bytes).map_err(|error| CapturePngError::Io {
        path: path.display().to_string(),
        reason: error.to_string(),
    })
}

impl From<CaptureError> for CapturePngError {
    fn from(error: CaptureError) -> Self {
        Self::Capture(error)
    }
}

impl fmt::Display for CapturePngError {
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::Capture(error) => error.fmt(formatter),
            Self::InvalidPixelBuffer {
                width,
                height,
                expected_len,
                actual_len,
            } => write!(
                formatter,
                "capture frame buffer for {width}x{height} has {actual_len} bytes; expected {expected_len} RGBA8 bytes"
            ),
            Self::Encode { reason } => write!(formatter, "failed to encode PNG: {reason}"),
            Self::Io { path, reason } => write!(formatter, "failed to write PNG {path}: {reason}"),
        }
    }
}

impl StdError for CapturePngError {
    fn source(&self) -> Option<&(dyn StdError + 'static)> {
        match self {
            Self::Capture(error) => Some(error),
            Self::InvalidPixelBuffer { .. } | Self::Encode { .. } | Self::Io { .. } => None,
        }
    }
}