use std::path::Path;
use log::debug;
use crate::frame::{PixelBuffer, PixelFormat, FrameError};
use half::f16 as F16;
pub trait ExrLoader {
fn load(path: &Path) -> Result<(PixelBuffer, PixelFormat, usize, usize), FrameError>;
fn header(path: &Path) -> Result<(usize, usize), FrameError>;
}
#[cfg_attr(feature = "openexr", allow(dead_code))]
pub struct Exr;
impl ExrLoader for Exr {
fn load(path: &Path) -> Result<(PixelBuffer, PixelFormat, usize, usize), FrameError> {
debug!("Loading EXR with exrs: {}", path.display());
let img = image::open(path)
.map_err(|e| {
let err_str = e.to_string();
if err_str.contains("DWAA") || err_str.contains("DWAB") {
return FrameError::UnsupportedFormat(
"DWAA/DWAB compression not supported in default build. \
Build with full support: cargo xtask build --openexr --release".into()
);
}
FrameError::Image(err_str)
})?;
let width = img.width() as usize;
let height = img.height() as usize;
use image::DynamicImage;
let (buffer, pixel_format) = match img {
DynamicImage::ImageRgb8(_) | DynamicImage::ImageRgba8(_) => {
let rgba8 = img.to_rgba8();
(PixelBuffer::U8(rgba8.into_raw()), PixelFormat::Rgba8)
}
DynamicImage::ImageRgb16(_) | DynamicImage::ImageRgba16(_) => {
let rgb16 = img.to_rgba16();
let rgb16_data = rgb16.as_raw();
let mut buffer_f16 = Vec::with_capacity(width * height * 4);
for &val in rgb16_data {
buffer_f16.push(F16::from_f32(val as f32 / 65535.0));
}
(PixelBuffer::F16(buffer_f16), PixelFormat::RgbaF16)
}
DynamicImage::ImageRgb32F(_) | DynamicImage::ImageRgba32F(_) => {
let rgba32f = img.to_rgba32f();
(PixelBuffer::F32(rgba32f.into_raw()), PixelFormat::RgbaF32)
}
_ => {
let rgba8 = img.to_rgba8();
(PixelBuffer::U8(rgba8.into_raw()), PixelFormat::Rgba8)
}
};
debug!("Loaded EXR with exrs: {}x{} ({:?})", width, height, pixel_format);
Ok((buffer, pixel_format, width, height))
}
fn header(path: &Path) -> Result<(usize, usize), FrameError> {
let reader = image::ImageReader::open(path)
.map_err(|e| FrameError::Image(e.to_string()))?;
let (width, height) = reader.into_dimensions()
.map_err(|e| {
let err_str = e.to_string();
if err_str.contains("DWAA") || err_str.contains("DWAB") {
return FrameError::UnsupportedFormat(
"DWAA/DWAB compression not supported in default build. \
Build with full support: cargo xtask build --openexr --release".into()
);
}
FrameError::Image(err_str)
})?;
Ok((width as usize, height as usize))
}
}
#[cfg(feature = "openexr")]
pub struct OpenExr;
#[cfg(feature = "openexr")]
impl ExrLoader for OpenExr {
fn load(path: &Path) -> Result<(PixelBuffer, PixelFormat, usize, usize), FrameError> {
use openexr::prelude::*;
debug!("Loading EXR with openexr-rs: {}", path.display());
let file = RgbaInputFile::new(path, 1)
.map_err(|e| FrameError::Exr(e.to_string()))?;
let header = file.header();
let data_window = header.data_window::<[i32; 4]>();
let width = (data_window[2] - data_window[0] + 1) as usize;
let height = (data_window[3] - data_window[1] + 1) as usize;
let channels = header.channels();
let pixel_type = channels
.iter()
.find(|(name, _)| *name == "R")
.map(|(_, ch)| ch.type_)
.unwrap_or(PixelType::Half.into());
drop(header);
drop(file);
if pixel_type == PixelType::Half.into() {
Self::load_half(path, width, height)
} else if pixel_type == PixelType::Float.into() {
Self::load_float(path, width, height)
} else {
debug!("EXR UINT pixels detected, loading as f16");
Self::load_half(path, width, height)
}
}
fn header(path: &Path) -> Result<(usize, usize), FrameError> {
use openexr::prelude::*;
let file = RgbaInputFile::new(path, 1)
.map_err(|e| FrameError::Exr(e.to_string()))?;
let header = file.header();
let data_window = header.data_window::<[i32; 4]>();
let width = (data_window[2] - data_window[0] + 1) as usize;
let height = (data_window[3] - data_window[1] + 1) as usize;
Ok((width, height))
}
}
#[cfg(feature = "openexr")]
impl OpenExr {
fn load_half(path: &Path, width: usize, height: usize) -> Result<(PixelBuffer, PixelFormat, usize, usize), FrameError> {
use openexr::prelude::*;
let mut file = RgbaInputFile::new(path, 1)
.map_err(|e| FrameError::Exr(e.to_string()))?;
let header = file.header();
let data_window = header.data_window::<[i32; 4]>();
let y_min = data_window[1];
let y_max = data_window[3];
drop(header);
let mut pixels_rgba = vec![Rgba::from_f32(0.0, 0.0, 0.0, 0.0); width * height];
file.set_frame_buffer(&mut pixels_rgba, 1, width)
.map_err(|e| FrameError::Exr(e.to_string()))?;
unsafe {
file.read_pixels(y_min, y_max)
.map_err(|e| FrameError::Exr(e.to_string()))?;
}
let mut buffer_f16 = Vec::with_capacity(width * height * 4);
for pixel in pixels_rgba.iter() {
buffer_f16.push(pixel.r); buffer_f16.push(pixel.g);
buffer_f16.push(pixel.b);
buffer_f16.push(pixel.a);
}
debug!("Loaded EXR HALF with openexr-rs: {}x{} (f16)", width, height);
Ok((PixelBuffer::F16(buffer_f16), PixelFormat::RgbaF16, width, height))
}
fn load_float(path: &Path, width: usize, height: usize) -> Result<(PixelBuffer, PixelFormat, usize, usize), FrameError> {
use openexr::prelude::*;
let file = InputFile::new(path, 1)
.map_err(|e| FrameError::Exr(e.to_string()))?;
let header = file.header();
let data_window = *header.data_window::<[i32; 4]>();
let y_min = data_window[1];
let y_max = data_window[3];
drop(header);
let frame_rgba = Frame::new::<f32, _, _>(&["R", "G", "B", "A"], data_window)
.map_err(|e| FrameError::Exr(e.to_string()))?;
let (_file, mut frames) = file
.into_reader(vec![frame_rgba])
.map_err(|e| FrameError::Exr(e.to_string()))?
.read_pixels(y_min, y_max)
.map_err(|e| FrameError::Exr(e.to_string()))?;
let buffer_f32: Vec<f32> = frames.remove(0).into_vec();
debug!("Loaded EXR FLOAT with openexr-rs: {}x{} (f32, native precision)", width, height);
Ok((PixelBuffer::F32(buffer_f32), PixelFormat::RgbaF32, width, height))
}
}
#[cfg(not(feature = "openexr"))]
pub type ExrImpl = Exr;
#[cfg(feature = "openexr")]
pub type ExrImpl = OpenExr;