#![allow(dead_code)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ExrChannelType {
Half,
Float,
Uint,
}
impl ExrChannelType {
pub fn byte_size(&self) -> usize {
match self {
Self::Half => 2,
Self::Float => 4,
Self::Uint => 4,
}
}
}
#[derive(Debug, Clone)]
pub struct ExrChannel {
pub name: String,
pub channel_type: ExrChannelType,
}
#[derive(Debug, Clone)]
pub struct ExrExport {
pub width: u32,
pub height: u32,
pub channels: Vec<ExrChannel>,
pub pixels: Vec<Vec<f32>>,
}
impl ExrExport {
pub fn new(width: u32, height: u32) -> Self {
Self {
width,
height,
channels: Vec::new(),
pixels: Vec::new(),
}
}
pub fn add_channel(&mut self, name: &str, channel_type: ExrChannelType) {
self.channels.push(ExrChannel {
name: name.to_string(),
channel_type,
});
let n = (self.width * self.height) as usize;
self.pixels.push(vec![0.0_f32; n]);
}
pub fn channel_count(&self) -> usize {
self.channels.len()
}
pub fn set_pixel(&mut self, channel: usize, x: u32, y: u32, value: f32) {
if channel < self.pixels.len() {
let idx = (y * self.width + x) as usize;
if let Some(v) = self.pixels[channel].get_mut(idx) {
*v = value;
}
}
}
pub fn get_pixel(&self, channel: usize, x: u32, y: u32) -> f32 {
if channel < self.pixels.len() {
let idx = (y * self.width + x) as usize;
self.pixels[channel].get(idx).copied().unwrap_or(0.0)
} else {
0.0
}
}
}
pub fn estimate_exr_bytes(export: &ExrExport) -> usize {
let pixels_per_channel = (export.width * export.height) as usize;
let bytes_per_channel: usize = export
.channels
.iter()
.map(|c| c.channel_type.byte_size() * pixels_per_channel)
.sum();
bytes_per_channel + 2048
}
pub fn exr_metadata_json(export: &ExrExport) -> String {
let ch_names: Vec<String> = export
.channels
.iter()
.map(|c| format!("\"{}\"", c.name))
.collect();
format!(
"{{\"width\":{},\"height\":{},\"channels\":[{}]}}",
export.width,
export.height,
ch_names.join(",")
)
}
pub fn find_channel(export: &ExrExport, name: &str) -> Option<usize> {
export.channels.iter().position(|c| c.name == name)
}
#[cfg(test)]
mod tests {
use super::*;
fn rgba_exr() -> ExrExport {
let mut e = ExrExport::new(4, 4);
e.add_channel("R", ExrChannelType::Half);
e.add_channel("G", ExrChannelType::Half);
e.add_channel("B", ExrChannelType::Half);
e.add_channel("A", ExrChannelType::Float);
e
}
#[test]
fn test_channel_count() {
assert_eq!(rgba_exr().channel_count(), 4);
}
#[test]
fn test_set_get_pixel() {
let mut e = rgba_exr();
e.set_pixel(0, 2, 1, 0.75);
assert!((e.get_pixel(0, 2, 1) - 0.75).abs() < 1e-6);
}
#[test]
fn test_estimate_bytes_positive() {
assert!(estimate_exr_bytes(&rgba_exr()) > 0);
}
#[test]
fn test_metadata_json_contains_channel() {
let json = exr_metadata_json(&rgba_exr());
assert!(json.contains("\"R\""));
}
#[test]
fn test_find_channel_found() {
let e = rgba_exr();
assert_eq!(find_channel(&e, "A"), Some(3));
}
#[test]
fn test_find_channel_not_found() {
let e = rgba_exr();
assert!(find_channel(&e, "Z").is_none());
}
#[test]
fn test_channel_type_byte_size() {
assert_eq!(ExrChannelType::Half.byte_size(), 2);
assert_eq!(ExrChannelType::Float.byte_size(), 4);
}
#[test]
fn test_get_pixel_out_of_bounds() {
let e = rgba_exr();
assert_eq!(e.get_pixel(99, 0, 0), 0.0);
}
}