use crate::core::{
attribute::TypedAttribute,
channel_list::{ChannelListRef, ChannelListRefMut},
cppstd::CppString,
error::Error,
preview_image::{PreviewImage, PreviewImageRef},
refptr::{OpaquePtr, Ref, RefMut},
tile_description::TileDescription,
Compression, LineOrder,
};
use openexr_sys as sys;
use imath_traits::{Bound2, Vec2};
type Result<T, E = Error> = std::result::Result<T, E>;
use std::alloc::{GlobalAlloc, Layout, System};
use std::ffi::{CStr, CString};
#[repr(transparent)]
pub struct Header(pub(crate) Box<sys::Imf_Header_t>);
#[repr(transparent)]
pub struct HeaderSlice(pub(crate) Box<[sys::Imf_Header_t]>);
unsafe impl OpaquePtr for Header {
type SysPointee = sys::Imf_Header_t;
type Pointee = Header;
}
pub type HeaderRef<'a, P = Header> = Ref<'a, P>;
pub type HeaderRefMut<'a, P = Header> = RefMut<'a, P>;
impl Header {
pub fn new<B, V>(
data_window: B,
display_window: B,
pixel_aspect_ratio: f32,
screen_window_center: V,
screen_window_width: f32,
line_order: LineOrder,
compression: Compression,
) -> Result<Header>
where
B: Bound2<i32>,
V: Vec2<f32>,
{
unsafe {
let header = System.alloc(Layout::new::<sys::Imf_Header_t>())
as *mut sys::Imf_Header_t;
sys::Imf_Header_ctor(
header,
data_window.as_ptr() as *const sys::Imath_Box2i_t,
display_window.as_ptr() as *const sys::Imath_Box2i_t,
pixel_aspect_ratio,
screen_window_center.as_ptr() as *const sys::Imath_V2f_t,
screen_window_width,
line_order.into(),
compression.into(),
)
.into_result()?;
Ok(Header(Box::from_raw(header)))
}
}
pub fn with_dimensions<V>(
width: i32,
height: i32,
pixel_aspect_ratio: f32,
screen_window_center: V,
screen_window_width: f32,
line_order: LineOrder,
compression: Compression,
) -> Result<Header>
where
V: Vec2<f32>,
{
unsafe {
let header = System.alloc(Layout::new::<sys::Imf_Header_t>())
as *mut sys::Imf_Header_t;
sys::Imf_Header_with_dimensions(
header,
width,
height,
pixel_aspect_ratio,
screen_window_center.as_ptr() as *const sys::Imath_V2f_t,
screen_window_width,
line_order.into(),
compression.into(),
)
.into_result()?;
Ok(Header(Box::from_raw(header)))
}
}
pub fn new_array(num: usize) -> HeaderSlice {
unsafe {
let ptr = System
.alloc(Layout::array::<sys::Imf_Header_t>(num).unwrap())
as *mut sys::Imf_Header_t;
dbg!(std::mem::size_of::<sys::Imf_Header_t>());
let mut array =
Box::from_raw(std::slice::from_raw_parts_mut(ptr, num));
for header in array.iter_mut() {
println!("header: {:p}", header as *mut _);
sys::Imf_Header_with_dimensions(
header,
64,
64,
1.0f32,
[0.0f32, 0.0f32].as_ptr() as *const sys::Imath_V2f_t,
1.0f32,
LineOrder::IncreasingY.into(),
Compression::Zip.into(),
);
}
HeaderSlice(array)
}
}
pub fn from_dimensions(width: i32, height: i32) -> Header {
let mut header = Header::default();
header.set_dimensions(width, height);
header
}
pub fn from_windows<B: Bound2<i32>>(
data_window: B,
display_window: B,
) -> Header {
let mut header = Header::default();
*header.data_window_mut() = data_window;
*header.display_window_mut() = display_window;
header
}
pub fn sanity_check(
&self,
is_tiled: bool,
is_multi_part: bool,
) -> Result<()> {
unsafe {
sys::Imf_Header_sanityCheck(
self.0.as_ref(),
is_tiled,
is_multi_part,
)
.into_result()?;
}
Ok(())
}
pub fn set_max_image_size(max_width: i32, max_height: i32) {
unsafe {
sys::Imf_Header_setMaxImageSize(max_width, max_height)
.into_result()
.unwrap();
}
}
pub fn set_max_tile_size(max_width: i32, max_height: i32) {
unsafe {
sys::Imf_Header_setMaxTileSize(max_width, max_height)
.into_result()
.unwrap();
}
}
pub fn reads_nothing(&mut self) -> bool {
let mut result = false;
unsafe {
sys::Imf_Header_readsNothing(self.0.as_mut(), &mut result)
.into_result()
.unwrap()
};
result
}
}
impl Default for Header {
fn default() -> Header {
Header::with_dimensions(
64,
64,
1.0f32,
[0.0f32, 0.0f32],
1.0f32,
LineOrder::IncreasingY,
Compression::Zip,
)
.unwrap()
}
}
impl Header {
pub fn display_window<B>(&self) -> &B
where
B: Bound2<i32>,
{
unsafe {
let mut ptr = std::ptr::null();
sys::Imf_Header_displayWindow_const(self.0.as_ref(), &mut ptr)
.into_result()
.unwrap();
&*(ptr as *const sys::Imath_Box2i_t as *const B)
}
}
pub fn display_window_mut<B>(&mut self) -> &mut B
where
B: Bound2<i32>,
{
unsafe {
let mut ptr = std::ptr::null_mut();
sys::Imf_Header_displayWindow(self.0.as_mut(), &mut ptr)
.into_result()
.unwrap();
&mut *(ptr as *mut sys::Imath_Box2i_t as *mut B)
}
}
pub fn data_window<B>(&self) -> &B
where
B: Bound2<i32>,
{
unsafe {
let mut ptr = std::ptr::null();
sys::Imf_Header_dataWindow_const(self.0.as_ref(), &mut ptr)
.into_result()
.unwrap();
&*(ptr as *const sys::Imath_Box2i_t as *const B)
}
}
pub fn data_window_mut<B>(&mut self) -> &mut B
where
B: Bound2<i32>,
{
unsafe {
let mut ptr = std::ptr::null_mut();
sys::Imf_Header_dataWindow(self.0.as_mut(), &mut ptr)
.into_result()
.unwrap();
&mut *(ptr as *mut sys::Imath_Box2i_t as *mut B)
}
}
pub fn set_dimensions(&mut self, width: i32, height: i32) {
*self.data_window_mut() = [0, 0, width - 1, height - 1];
*self.display_window_mut() = [0, 0, width - 1, height - 1];
}
pub fn pixel_aspect_ratio(&self) -> f32 {
unsafe {
let mut ptr = std::ptr::null();
sys::Imf_Header_pixelAspectRatio_const(self.0.as_ref(), &mut ptr)
.into_result()
.unwrap();
*ptr
}
}
pub fn set_pixel_aspect_ratio(&mut self, par: f32) {
unsafe {
let mut ptr = std::ptr::null_mut();
sys::Imf_Header_pixelAspectRatio(self.0.as_mut(), &mut ptr)
.into_result()
.unwrap();
*ptr = par;
}
}
pub fn screen_window_center<B>(&self) -> &B
where
B: Bound2<i32>,
{
unsafe {
let mut ptr = std::ptr::null();
sys::Imf_Header_screenWindowCenter_const(self.0.as_ref(), &mut ptr)
.into_result()
.unwrap();
&*(ptr as *const sys::Imath_Box2i_t as *const B)
}
}
pub fn screen_window_center_mut<B>(&mut self) -> &mut B
where
B: Bound2<i32>,
{
unsafe {
let mut ptr = std::ptr::null_mut();
sys::Imf_Header_screenWindowCenter(self.0.as_mut(), &mut ptr)
.into_result()
.unwrap();
&mut *(ptr as *mut sys::Imath_Box2i_t as *mut B)
}
}
pub fn screen_window_width(&self) -> &f32 {
unsafe {
let mut ptr = std::ptr::null();
sys::Imf_Header_screenWindowWidth_const(self.0.as_ref(), &mut ptr)
.into_result()
.unwrap();
&*ptr
}
}
pub fn screen_window_width_mut(&mut self) -> &f32 {
unsafe {
let mut ptr = std::ptr::null_mut();
sys::Imf_Header_screenWindowWidth(self.0.as_mut(), &mut ptr)
.into_result()
.unwrap();
&mut *ptr
}
}
pub fn channels(&self) -> ChannelListRef {
unsafe {
let mut ptr = std::ptr::null();
sys::Imf_Header_channels_const(self.0.as_ref(), &mut ptr)
.into_result()
.unwrap();
ChannelListRef::new(ptr)
}
}
pub fn channels_mut(&mut self) -> ChannelListRefMut {
unsafe {
let mut ptr = std::ptr::null_mut();
sys::Imf_Header_channels(self.0.as_mut(), &mut ptr)
.into_result()
.unwrap();
ChannelListRefMut::new(ptr)
}
}
pub fn line_order(&self) -> LineOrder {
let mut ptr = std::ptr::null();
unsafe {
sys::Imf_Header_lineOrder_const(self.0.as_ref(), &mut ptr)
.into_result()
.unwrap();
(*ptr).into()
}
}
pub fn set_line_order(&mut self, lo: LineOrder) {
unsafe {
let mut ptr = std::ptr::null_mut();
sys::Imf_Header_lineOrder(self.0.as_mut(), &mut ptr)
.into_result()
.unwrap();
*ptr = lo.into();
};
}
pub fn compression(&self) -> Compression {
let mut ptr = std::ptr::null();
unsafe {
sys::Imf_Header_compression_const(self.0.as_ref(), &mut ptr)
.into_result()
.unwrap();
(*ptr).into()
}
}
pub fn set_compression(&mut self, cmp: Compression) {
let mut ptr = std::ptr::null_mut();
unsafe {
sys::Imf_Header_compression(self.0.as_mut(), &mut ptr)
.into_result()
.unwrap();
*ptr = cmp.into();
}
}
}
impl Header {
pub fn name(&self) -> Result<String> {
unsafe {
let mut s = std::ptr::null();
sys::Imf_Header_name_const(self.0.as_ref(), &mut s)
.into_result()
.map(|_| {
let mut cptr = std::ptr::null();
sys::std_string_c_str(s, &mut cptr).into_result().unwrap();
CStr::from_ptr(cptr).to_string_lossy().to_string()
})
.map_err(Error::from)
}
}
pub fn set_name(&mut self, name: &str) {
unsafe {
let s = CppString::new(name);
sys::Imf_Header_setName(self.0.as_mut(), s.0);
}
}
pub fn has_name(&self) -> bool {
unsafe {
let mut v = false;
sys::Imf_Header_hasName(self.0.as_ref(), &mut v);
v
}
}
pub fn image_type(&self) -> Result<ImageType> {
unsafe {
let mut s = std::ptr::null();
sys::Imf_Header_type_const(self.0.as_ref(), &mut s)
.into_result()
.map(|_| {
let mut cptr = std::ptr::null();
sys::std_string_c_str(s, &mut cptr);
match CStr::from_ptr(cptr).to_str().unwrap() {
"scanlineimage" => ImageType::Scanline,
"tiledimage" => ImageType::Tiled,
"deepscanline" => ImageType::DeepScanline,
"deeptile" => ImageType::DeepTiled,
_ => panic!("bad value for image type"),
}
})
.map_err(Error::from)
}
}
pub fn set_image_type(&mut self, image_type: ImageType) {
unsafe {
let s = match image_type {
ImageType::Scanline => CppString::new("scanlineimage"),
ImageType::Tiled => CppString::new("tiledimage"),
ImageType::DeepScanline => CppString::new("deepscanline"),
ImageType::DeepTiled => CppString::new("deeptile"),
};
sys::Imf_Header_setType(self.0.as_mut(), s.0)
.into_result()
.expect("Unexpected exception from Imf_Header_setType");
}
}
pub fn has_image_type(&self) -> bool {
unsafe {
let mut v = false;
sys::Imf_Header_hasType(self.0.as_ref(), &mut v);
v
}
}
pub fn version(&self) -> Result<i32> {
unsafe {
let mut v = std::ptr::null();
sys::Imf_Header_version_const(self.0.as_ref(), &mut v)
.into_result()
.map(|_| *v)
.map_err(Error::from)
}
}
pub fn set_version(&mut self, v: i32) {
unsafe {
sys::Imf_Header_setVersion(self.0.as_mut(), v);
}
}
pub fn has_version(&self) -> bool {
unsafe {
let mut v = false;
sys::Imf_Header_hasVersion(self.0.as_ref(), &mut v);
v
}
}
}
impl Header {
pub fn has_chunk_count(&self) -> bool {
unsafe {
let mut v = false;
sys::Imf_Header_hasChunkCount(self.0.as_ref(), &mut v);
v
}
}
pub fn chunk_count(&self) -> Result<i32> {
unsafe {
let mut ptr = std::ptr::null();
sys::Imf_Header_chunkCount_const(self.0.as_ref(), &mut ptr)
.into_result()
.map(|_| *ptr)
.map_err(Error::from)
}
}
}
impl Header {
pub fn view(&self) -> Result<String> {
unsafe {
let mut s = std::ptr::null();
sys::Imf_Header_view_const(self.0.as_ref(), &mut s)
.into_result()
.map(|_| {
let mut cptr = std::ptr::null();
sys::std_string_c_str(s, &mut cptr);
CStr::from_ptr(cptr).to_string_lossy().to_string()
})
.map_err(Error::from)
}
}
pub fn set_view(&mut self, view: &str) {
unsafe {
let s = CppString::new(view);
sys::Imf_Header_setView(self.0.as_mut(), s.0);
}
}
pub fn has_view(&self) -> bool {
unsafe {
let mut v = false;
sys::Imf_Header_hasView(self.0.as_ref(), &mut v);
v
}
}
}
impl Header {
pub fn tile_description(&self) -> Result<TileDescription> {
let mut ptr = std::ptr::null();
unsafe {
sys::Imf_Header_tileDescription_const(self.0.as_ref(), &mut ptr)
.into_result()
.map(|_| (*ptr).clone().into())
.map_err(Error::from)
}
}
pub fn set_tile_description(&mut self, td: &TileDescription) {
unsafe {
let td = (*td).into();
sys::Imf_Header_setTileDescription(self.0.as_mut(), &td);
}
}
pub fn has_tile_description(&self) -> bool {
unsafe {
let mut v = false;
sys::Imf_Header_hasTileDescription(self.0.as_ref(), &mut v);
v
}
}
}
impl Header {
pub fn preview_image(&self) -> Result<PreviewImageRef> {
unsafe {
let mut ptr = std::ptr::null();
sys::Imf_Header_previewImage_const(self.0.as_ref(), &mut ptr)
.into_result()?;
Ok(PreviewImageRef::new(ptr))
}
}
pub fn set_preview_image(&mut self, pi: &PreviewImage) -> Result<()> {
unsafe {
sys::Imf_Header_setPreviewImage(self.0.as_mut(), pi.0)
.into_result()?;
Ok(())
}
}
pub fn has_preview_image(&self) -> bool {
unsafe {
let mut v = false;
sys::Imf_Header_hasPreviewImage(self.0.as_ref(), &mut v);
v
}
}
}
use paste::paste;
macro_rules! make_find_typed_attribute {
($tn:ident, $sfx:ident) => {
paste! {
use crate::core::attribute::{[<$tn AttributeRef>], [<$tn AttributeRefMut>]};
impl Header {
pub fn [<find_typed_attribute_ $sfx>](
&self,
name: &str,
) -> Option<[<$tn AttributeRef>]> {
let c_name = CString::new(name).expect("Invalid UTF-8 in name");
let mut attr_ptr = std::ptr::null();
unsafe {
sys::[<Imf_Header_findTypedAttribute_ $tn _const>](
self.0.as_ref(),
&mut attr_ptr,
c_name.as_ptr(),
)
};
if !attr_ptr.is_null() {
Some([<$tn AttributeRef>]::new(attr_ptr))
} else {
None
}
}
pub fn [<find_typed_attribute_ $sfx _mut>](
&mut self,
name: &str,
) -> Option<[<$tn AttributeRefMut>]> {
let c_name = CString::new(name).expect("Invalid UTF-8 in name");
let mut attr_ptr = std::ptr::null_mut();
unsafe {
sys::[<Imf_Header_findTypedAttribute_ $tn>](
self.0.as_mut(),
&mut attr_ptr,
c_name.as_ptr(),
)
};
if !attr_ptr.is_null() {
Some([<$tn AttributeRefMut>]::new(attr_ptr))
} else {
None
}
}
}
}
}
}
impl Header {
pub fn insert<A>(&mut self, name: &str, attribute: &A) -> Result<()>
where
A: TypedAttribute,
{
let c_name = CString::new(name).expect("Invalid UTF-8 in name");
unsafe {
sys::Imf_Header_insert(
self.0.as_mut(),
c_name.as_ptr(),
attribute.as_attribute_ptr(),
)
.into_result()?;
}
Ok(())
}
pub fn erase(&mut self, name: &str) -> Result<()> {
let c_name = CString::new(name).expect("Invalid UTF-8 in name");
unsafe {
sys::Imf_Header_erase(self.0.as_mut(), c_name.as_ptr())
.into_result()?;
}
Ok(())
}
}
make_find_typed_attribute!(Int, int);
make_find_typed_attribute!(Float, float);
make_find_typed_attribute!(Double, double);
make_find_typed_attribute!(Chromaticities, chromaticities);
make_find_typed_attribute!(Compression, compression);
make_find_typed_attribute!(DeepImageState, deep_image_state);
make_find_typed_attribute!(Envmap, envmap);
make_find_typed_attribute!(ChannelList, channel_list);
make_find_typed_attribute!(CppVectorFloat, vector_float);
make_find_typed_attribute!(CppVectorString, vector_string);
make_find_typed_attribute!(CppString, string);
make_find_typed_attribute!(LineOrder, line_order);
make_find_typed_attribute!(V2i, v2i);
make_find_typed_attribute!(V2f, v2f);
make_find_typed_attribute!(V2d, v2d);
make_find_typed_attribute!(V3i, v3i);
make_find_typed_attribute!(V3f, v3f);
make_find_typed_attribute!(V3d, v3d);
make_find_typed_attribute!(Box2i, box2i);
make_find_typed_attribute!(Box2f, box2f);
make_find_typed_attribute!(M33f, m33f);
make_find_typed_attribute!(M33d, m33d);
make_find_typed_attribute!(M44f, m44f);
make_find_typed_attribute!(M44d, m44d);
impl Drop for Header {
fn drop(&mut self) {
unsafe {
sys::Imf_Header_dtor(self.0.as_mut());
}
}
}
impl HeaderSlice {
pub fn iter(&self) -> HeaderSliceIter {
HeaderSliceIter {
curr: 0,
len: self.0.len(),
header_slice: self,
}
}
pub fn iter_mut(&mut self) -> HeaderSliceIterMut {
HeaderSliceIterMut {
curr: 0,
len: self.0.len(),
header_slice: self,
}
}
}
pub struct HeaderSliceIter<'s> {
curr: usize,
len: usize,
header_slice: &'s HeaderSlice,
}
impl<'s> Iterator for HeaderSliceIter<'s> {
type Item = HeaderRef<'s>;
fn next(&mut self) -> Option<Self::Item> {
if self.curr == self.len {
None
} else {
let ptr = &((*self.header_slice.0)[self.curr])
as *const sys::Imf_Header_t;
self.curr += 1;
Some(HeaderRef::new(ptr))
}
}
}
pub struct HeaderSliceIterMut<'s> {
curr: usize,
len: usize,
header_slice: &'s mut HeaderSlice,
}
impl<'s> Iterator for HeaderSliceIterMut<'s> {
type Item = HeaderRefMut<'s>;
fn next(&mut self) -> Option<Self::Item> {
if self.curr == self.len {
None
} else {
let ptr = &mut ((*self.header_slice.0)[self.curr])
as *mut sys::Imf_Header_t;
self.curr += 1;
Some(HeaderRefMut::new(ptr))
}
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum ImageType {
Scanline,
Tiled,
DeepScanline,
DeepTiled,
}
#[cfg(test)]
#[test]
fn header_rtrip1() -> Result<()> {
use crate::tests::load_ferris;
use crate::{
core::{
attribute::{
CompressionAttribute, CppStringAttribute,
CppVectorFloatAttribute, CppVectorStringAttribute,
DeepImageStateAttribute, DoubleAttribute, EnvmapAttribute,
FloatAttribute, IntAttribute,
},
cppstd::{CppVectorFloat, CppVectorString},
Envmap,
},
deep::DeepImageState,
rgba::{
rgba::RgbaChannels,
rgba_file::{RgbaInputFile, RgbaOutputFile},
},
};
let (pixels, width, height) = load_ferris();
let mut header = Header::from_dimensions(width, height);
header.insert("at_int", &IntAttribute::from_value(17))?;
header.insert("at_float", &FloatAttribute::from_value(42.0))?;
header.insert("at_double", &DoubleAttribute::from_value(127.0))?;
header.insert(
"at_compression",
&CompressionAttribute::from_value(&Compression::Dwaa),
)?;
header.insert(
"at_deep_image_state",
&DeepImageStateAttribute::from_value(&DeepImageState::NonOverlapping),
)?;
header
.insert("at_envmap", &EnvmapAttribute::from_value(&Envmap::Latlong))?;
header.insert(
"at_vector_float",
&CppVectorFloatAttribute::from_value(&CppVectorFloat::from_slice(&[
1.0, 2.0, 3.0, 4.0,
])),
)?;
header.insert(
"at_vector_string",
&CppVectorStringAttribute::from_value(&CppVectorString::from_slice(&[
"a", "b", "c", "d",
])),
)?;
header
.insert("at_string", &CppStringAttribute::from_value("lorem ipsum"))?;
let mut file = RgbaOutputFile::new(
"header_rtrip1.exr",
&header,
RgbaChannels::WriteRgba,
1,
)?;
file.set_frame_buffer(&pixels, 1, width as usize)?;
unsafe {
file.write_pixels(height)?;
}
std::mem::drop(file);
let file = RgbaInputFile::new("header_rtrip1.exr", 4)?;
assert_eq!(
file.header()
.find_typed_attribute_int("at_int")
.unwrap()
.value(),
&17
);
assert_eq!(
file.header()
.find_typed_attribute_float("at_float")
.unwrap()
.value(),
&42.0
);
assert_eq!(
file.header()
.find_typed_attribute_double("at_double")
.unwrap()
.value(),
&127.0
);
assert_eq!(
file.header()
.find_typed_attribute_compression("at_compression")
.unwrap()
.value(),
Compression::Dwaa
);
assert_eq!(
file.header()
.find_typed_attribute_deep_image_state("at_deep_image_state")
.unwrap()
.value(),
DeepImageState::NonOverlapping,
);
assert_eq!(
file.header()
.find_typed_attribute_envmap("at_envmap")
.unwrap()
.value(),
Envmap::Latlong,
);
assert_eq!(
file.header()
.find_typed_attribute_vector_float("at_vector_float")
.unwrap()
.value()
.as_slice(),
&[1.0f32, 2.0, 3.0, 4.0],
);
assert_eq!(
file.header()
.find_typed_attribute_vector_string("at_vector_string")
.unwrap()
.value()
.to_vec(),
&["a", "b", "c", "d"],
);
assert_eq!(
file.header()
.find_typed_attribute_string("at_string")
.unwrap()
.value(),
"lorem ipsum",
);
assert!(file.header().version().is_err());
assert!(file.header().image_type().is_err());
assert!(file.header().preview_image().is_err());
assert!(file.header().name().is_err());
assert!(file.header().view().is_err());
Ok(())
}