use log::{debug, info};
use std::path::{Path, PathBuf};
use std::sync::{Arc, Mutex};
use half::f16 as F16;
use crate::exr::{ExrImpl, ExrLoader};
use crate::utils::media;
fn parse_video_path(path: &Path) -> (PathBuf, Option<usize>) {
let path_str = path.to_string_lossy();
if let Some(at_pos) = path_str.rfind('@') {
let base = &path_str[..at_pos];
let frame_num = &path_str[at_pos + 1..];
if let Ok(num) = frame_num.parse::<usize>() {
return (PathBuf::from(base), Some(num));
}
}
(path.to_path_buf(), None)
}
#[derive(Debug, Clone)]
pub enum PixelBuffer {
U8(Vec<u8>), F16(Vec<F16>), F32(Vec<f32>), }
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum PixelFormat {
Rgba8, RgbaF16, RgbaF32, }
#[derive(Debug, Clone, PartialEq)]
pub enum FrameStatus {
Placeholder, Header, Loading, Loaded, Error, }
#[derive(Debug, Clone)]
struct FrameData {
buffer: Arc<PixelBuffer>, pixel_format: PixelFormat,
width: usize,
height: usize,
status: FrameStatus,
}
#[derive(Debug, Clone)]
pub struct Frame {
data: Arc<Mutex<FrameData>>, filename: Option<PathBuf>, }
#[derive(Debug)]
pub enum FrameError {
#[cfg_attr(not(feature = "openexr"), allow(dead_code))]
Exr(String),
Image(String),
LoadError(String),
UnsupportedFormat(String),
NoFilename,
}
impl std::fmt::Display for FrameError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
FrameError::Exr(e) => write!(f, "EXR error: {}", e),
FrameError::Image(e) => write!(f, "Image error: {}", e),
FrameError::LoadError(e) => write!(f, "Load error: {}", e),
FrameError::UnsupportedFormat(e) => write!(f, "Unsupported format: {}", e),
FrameError::NoFilename => write!(f, "No filename set"),
}
}
}
impl std::error::Error for FrameError {}
impl Frame {
pub fn new(width: usize, height: usize) -> Self {
let mut buffer_u8 = Vec::with_capacity(width * height * 4);
buffer_u8.resize(width * height * 4, 0);
for px in buffer_u8.chunks_exact_mut(4) {
px[1] = 100; px[3] = 255; }
let data = FrameData {
buffer: Arc::new(PixelBuffer::U8(buffer_u8)),
pixel_format: PixelFormat::Rgba8,
width,
height,
status: FrameStatus::Placeholder,
};
Self {
data: Arc::new(Mutex::new(data)),
filename: None,
}
}
pub fn new_unloaded(path: PathBuf) -> Self {
let buffer_u8 = vec![0, 100, 0, 255];
let data = FrameData {
buffer: Arc::new(PixelBuffer::U8(buffer_u8)),
pixel_format: PixelFormat::Rgba8,
width: 1,
height: 1,
status: FrameStatus::Header, };
Self {
data: Arc::new(Mutex::new(data)),
filename: Some(path),
}
}
pub fn set_file(&mut self, path: PathBuf) {
self.filename = Some(path);
self.data.lock().unwrap().status = FrameStatus::Header;
}
pub fn file(&self) -> Option<&PathBuf> {
self.filename.as_ref()
}
fn try_claim_for_loading(&self) -> bool {
let mut data = self.data.lock().unwrap();
if data.status == FrameStatus::Header {
data.status = FrameStatus::Loading;
true
} else {
false }
}
pub fn load(&self) -> Result<(), FrameError> {
let path = self.filename.as_ref().ok_or(FrameError::NoFilename)?.clone();
let (actual_path, frame_num) = parse_video_path(&path);
if !self.try_claim_for_loading() {
return match self.status() {
FrameStatus::Loaded => Ok(()),
FrameStatus::Error => Err(FrameError::Image("Previously failed".into())),
_ => Ok(()), };
}
let ext = actual_path
.extension()
.and_then(|s| s.to_str())
.map(|s| s.to_lowercase())
.unwrap_or_default();
let result = if media::is_video(&actual_path) {
self.load_video(&actual_path, frame_num.unwrap_or(0))
} else {
match ext.as_str() {
"exr" => self.load_exr(&actual_path),
"hdr" => self.load_hdr(&actual_path),
"png" | "jpg" | "jpeg" | "tif" | "tiff" | "tga" => self.load_image(&actual_path),
_ => Err(FrameError::UnsupportedFormat(format!(".{}", ext))),
}
};
match result {
Ok(()) => {
self.data.lock().unwrap().status = FrameStatus::Loaded;
Ok(())
}
Err(e) => {
self.data.lock().unwrap().status = FrameStatus::Error;
Err(e)
}
}
}
fn load_exr<P: AsRef<Path>>(&self, path: P) -> Result<(), FrameError> {
debug!("Loading EXR: {}", path.as_ref().display());
let (buffer, pixel_format, width, height) = ExrImpl::load(path.as_ref())?;
let mut data = self.data.lock().unwrap();
data.buffer = Arc::new(buffer);
data.pixel_format = pixel_format;
data.width = width;
data.height = height;
debug!("Loaded EXR: {}x{} ({:?})", width, height, pixel_format);
Ok(())
}
fn load_hdr<P: AsRef<Path>>(&self, path: P) -> Result<(), FrameError> {
debug!("Loading HDR: {}", path.as_ref().display());
let img = image::open(path.as_ref())
.map_err(|e| FrameError::Image(e.to_string()))?;
let width = img.width() as usize;
let height = img.height() as usize;
let rgb_f32 = img.to_rgb32f();
let rgb_data = rgb_f32.as_raw();
let mut buffer_f32 = Vec::with_capacity(width * height * 4);
for chunk in rgb_data.chunks(3) {
buffer_f32.push(chunk[0]); buffer_f32.push(chunk[1]); buffer_f32.push(chunk[2]); buffer_f32.push(1.0); }
let mut data = self.data.lock().unwrap();
data.buffer = Arc::new(PixelBuffer::F32(buffer_f32));
data.pixel_format = PixelFormat::RgbaF32;
data.width = width;
data.height = height;
info!("Loaded HDR: {}x{} (HDR f32)", width, height);
Ok(())
}
fn load_image<P: AsRef<Path>>(&self, path: P) -> Result<(), FrameError> {
debug!("Loading image: {}", path.as_ref().display());
let img = image::open(path.as_ref())
.map_err(|e| FrameError::Image(e.to_string()))?;
let width = img.width() as usize;
let height = img.height() as usize;
let rgba = img.to_rgba8();
let mut data = self.data.lock().unwrap();
data.buffer = Arc::new(PixelBuffer::U8(rgba.into_raw()));
data.pixel_format = PixelFormat::Rgba8;
data.width = width;
data.height = height;
Ok(())
}
fn load_video<P: AsRef<Path>>(&self, path: P, frame_num: usize) -> Result<(), FrameError> {
debug!("Loading video frame {}: {}", frame_num, path.as_ref().display());
let (buffer, pixel_format, width, height) = crate::video::decode_frame(path.as_ref(), frame_num)?;
let mut data = self.data.lock().unwrap();
data.buffer = Arc::new(buffer);
data.pixel_format = pixel_format;
data.width = width;
data.height = height;
debug!("Loaded video frame {}: {}x{}", frame_num, width, height);
Ok(())
}
pub fn mem(&self) -> usize {
let data = self.data.lock().unwrap();
match data.buffer.as_ref() {
PixelBuffer::U8(vec) => vec.len(), PixelBuffer::F16(vec) => vec.len() * 2, PixelBuffer::F32(vec) => vec.len() * 4, }
}
pub fn status(&self) -> FrameStatus {
self.data.lock().unwrap().status.clone()
}
pub fn set_status(&self, status: FrameStatus) {
self.data.lock().unwrap().status = status;
}
pub fn pixel_buffer(&self) -> Arc<PixelBuffer> {
Arc::clone(&self.data.lock().unwrap().buffer)
}
pub fn pixel_format(&self) -> PixelFormat {
self.data.lock().unwrap().pixel_format
}
pub fn pixels(&self) -> Result<Vec<u8>, FrameError> {
let data = self.data.lock().unwrap();
match data.buffer.as_ref() {
PixelBuffer::U8(vec) => Ok(vec.clone()),
PixelBuffer::F16(_) => Err(FrameError::UnsupportedFormat(
"Frame uses F16 format, use pixel_buffer() for HDR data".into()
)),
PixelBuffer::F32(_) => Err(FrameError::UnsupportedFormat(
"Frame uses F32 format, use pixel_buffer() for HDR data".into()
)),
}
}
#[allow(dead_code)]
pub fn buffer(&self) -> Arc<Mutex<Vec<u8>>> {
Arc::new(Mutex::new(self.pixels().unwrap()))
}
pub fn width(&self) -> usize {
self.data.lock().unwrap().width
}
pub fn height(&self) -> usize {
self.data.lock().unwrap().height
}
pub fn resolution(&self) -> (usize, usize) {
let data = self.data.lock().unwrap();
(data.width, data.height)
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::path::Path;
#[test]
fn test_frame_creation() {
let frame = Frame::new(1920, 1080);
assert_eq!(frame.width(), 1920);
assert_eq!(frame.height(), 1080);
assert_eq!(frame.status(), FrameStatus::Placeholder);
assert_eq!(frame.pixel_format(), PixelFormat::Rgba8);
}
#[test]
fn test_frame_with_file() {
let frame = Frame::new_unloaded(PathBuf::from("test.exr"));
assert_eq!(frame.status(), FrameStatus::Header);
assert!(frame.file().is_some());
assert_eq!(frame.file().unwrap(), Path::new("test.exr"));
}
#[test]
fn test_load_missing_file() {
let frame = Frame::new_unloaded(
PathBuf::from("/nonexistent/path/test.jpg")
);
let result = frame.load();
assert!(result.is_err());
assert_eq!(frame.status(), FrameStatus::Error);
}
#[test]
fn test_pixel_buffer_types() {
let buf_u8 = PixelBuffer::U8(vec![0u8; 1920 * 1080 * 4]);
match buf_u8 {
PixelBuffer::U8(v) => assert_eq!(v.len(), 1920 * 1080 * 4),
_ => panic!("Wrong variant"),
}
let buf_f16 = PixelBuffer::F16(vec![F16::ZERO; 1920 * 1080 * 4]);
match buf_f16 {
PixelBuffer::F16(v) => assert_eq!(v.len(), 1920 * 1080 * 4),
_ => panic!("Wrong variant"),
}
let buf_f32 = PixelBuffer::F32(vec![0.0f32; 1920 * 1080 * 4]);
match buf_f32 {
PixelBuffer::F32(v) => assert_eq!(v.len(), 1920 * 1080 * 4),
_ => panic!("Wrong variant"),
}
}
#[test]
fn test_status_transitions() {
let frame = Frame::new(100, 100);
assert_eq!(frame.status(), FrameStatus::Placeholder);
let frame = Frame::new_unloaded(PathBuf::from("test.png"));
assert_eq!(frame.status(), FrameStatus::Header);
let _ = frame.load();
assert_eq!(frame.status(), FrameStatus::Error);
}
}