use crate::error::{Error, Result};
use ocio_rs::TransformDirection;
#[repr(i32)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum PixelFormat {
Invalid = -1,
U8 = 0,
U10 = 1,
U16 = 2,
F16 = 3,
F32 = 4,
Count = 5,
}
impl PixelFormat {
pub fn from_code(code: i32) -> PixelFormat {
match code {
-1 => PixelFormat::Invalid,
0 => PixelFormat::U8,
1 => PixelFormat::U10,
2 => PixelFormat::U16,
3 => PixelFormat::F16,
4 => PixelFormat::F32,
5 => PixelFormat::Count,
_ => PixelFormat::Invalid,
}
}
pub fn code(self) -> i32 {
self as i32
}
}
pub struct OCIOUtils;
impl OCIOUtils {
pub fn new() -> Self {
Self
}
pub fn get_ocio_bit_depth_from_pixel_format(&self, pixel_format: PixelFormat) -> Result<i32> {
let depth = match pixel_format {
PixelFormat::U8 => ocio_rs::BitDepth::Uint8,
PixelFormat::U10 => ocio_rs::BitDepth::Uint10,
PixelFormat::U16 => ocio_rs::BitDepth::Uint16,
PixelFormat::F16 => ocio_rs::BitDepth::F16,
PixelFormat::F32 => ocio_rs::BitDepth::F32,
PixelFormat::Invalid | PixelFormat::Count => ocio_rs::BitDepth::Unknown,
};
Ok(depth as i32)
}
}
pub struct OcioConfig {
inner: ocio_rs::Config,
}
pub struct OcioProcessor {
inner: ocio_rs::CPUProcessor,
}
unsafe impl Send for OcioConfig {}
unsafe impl Sync for OcioConfig {}
unsafe impl Send for OcioProcessor {}
unsafe impl Sync for OcioProcessor {}
impl OcioConfig {
pub fn from_file(path: &str) -> Result<Self> {
let inner = ocio_rs::Config::from_file(path)?;
Ok(OcioConfig { inner })
}
pub fn raw() -> Result<Self> {
let inner = ocio_rs::Config::raw()?;
Ok(OcioConfig { inner })
}
pub fn colorspace_count(&self) -> Result<i32> {
Ok(self.inner.num_color_spaces())
}
pub fn colorspace_name(&self, index: i32) -> Result<String> {
self.inner.color_space_name_by_index(index).ok_or_else(|| {
Error::new(format!(
"OcioConfig::colorspace_name: no color space at index {index}"
))
})
}
pub fn colorspaces(&self) -> Result<Vec<String>> {
let count = self.colorspace_count()?;
let mut out = Vec::with_capacity(count as usize);
for i in 0..count {
out.push(self.colorspace_name(i)?);
}
Ok(out)
}
pub fn role_count(&self) -> Result<i32> {
Ok(self.inner.num_roles())
}
pub fn role_name(&self, index: i32) -> Result<String> {
self.inner
.role_name(index)
.ok_or_else(|| Error::new(format!("OcioConfig::role_name: no role at index {index}")))
}
pub fn roles(&self) -> Result<Vec<String>> {
let count = self.role_count()?;
let mut out = Vec::with_capacity(count as usize);
for i in 0..count {
out.push(self.role_name(i)?);
}
Ok(out)
}
pub fn has_role(&self, role: &str) -> Result<bool> {
Ok(self.inner.has_role(role))
}
pub fn canonical_name(&self, name: &str) -> Result<String> {
Ok(self
.inner
.canonical_name(name)
.unwrap_or_else(|| name.to_string()))
}
pub fn default_display(&self) -> Result<String> {
self.inner
.default_display()
.ok_or_else(|| Error::new("OcioConfig::default_display: config defines no displays"))
}
pub fn default_view(&self, display: &str) -> Result<String> {
self.inner.default_view(display).ok_or_else(|| {
Error::new(format!(
"OcioConfig::default_view: no default view for display '{display}'"
))
})
}
pub fn processor(&self, src: &str, dst: &str) -> Result<OcioProcessor> {
let processor = self.inner.processor(src, dst)?;
Ok(OcioProcessor {
inner: processor.default_cpu_processor()?,
})
}
pub fn display_processor(&self, src: &str, display: &str, view: &str) -> Result<OcioProcessor> {
let processor =
self.inner
.processor_display(src, display, view, TransformDirection::Forward)?;
Ok(OcioProcessor {
inner: processor.default_cpu_processor()?,
})
}
}
impl OcioProcessor {
pub fn apply_rgba(&self, pixel: &mut [f32; 4]) -> Result<()> {
self.inner.try_apply_rgba(pixel)?;
Ok(())
}
}
impl std::fmt::Debug for OcioConfig {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("OcioConfig").finish_non_exhaustive()
}
}
impl std::fmt::Debug for OcioProcessor {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("OcioProcessor").finish_non_exhaustive()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn pixel_format_from_code_maps_known_codes() {
assert_eq!(PixelFormat::from_code(-1), PixelFormat::Invalid);
assert_eq!(PixelFormat::from_code(0), PixelFormat::U8);
assert_eq!(PixelFormat::from_code(1), PixelFormat::U10);
assert_eq!(PixelFormat::from_code(2), PixelFormat::U16);
assert_eq!(PixelFormat::from_code(3), PixelFormat::F16);
assert_eq!(PixelFormat::from_code(4), PixelFormat::F32);
assert_eq!(PixelFormat::from_code(5), PixelFormat::Count);
}
#[test]
fn pixel_format_from_code_maps_unknown_codes_to_invalid() {
assert_eq!(PixelFormat::from_code(-2), PixelFormat::Invalid);
assert_eq!(PixelFormat::from_code(6), PixelFormat::Invalid);
assert_eq!(PixelFormat::from_code(100), PixelFormat::Invalid);
assert_eq!(PixelFormat::from_code(i32::MIN), PixelFormat::Invalid);
assert_eq!(PixelFormat::from_code(i32::MAX), PixelFormat::Invalid);
}
#[test]
fn pixel_format_code_round_trips() {
for fmt in [
PixelFormat::Invalid,
PixelFormat::U8,
PixelFormat::U10,
PixelFormat::U16,
PixelFormat::F16,
PixelFormat::F32,
PixelFormat::Count,
] {
assert_eq!(PixelFormat::from_code(fmt.code()), fmt);
}
}
#[test]
fn pixel_format_codes_have_load_bearing_values() {
assert_eq!(PixelFormat::Invalid.code(), -1);
assert_eq!(PixelFormat::U8.code(), 0);
assert_eq!(PixelFormat::U10.code(), 1);
assert_eq!(PixelFormat::U16.code(), 2);
assert_eq!(PixelFormat::F16.code(), 3);
assert_eq!(PixelFormat::F32.code(), 4);
assert_eq!(PixelFormat::Count.code(), 5);
}
#[test]
fn new_returns_a_value() {
let utils = OCIOUtils::new();
assert_eq!(
utils
.get_ocio_bit_depth_from_pixel_format(PixelFormat::U8)
.unwrap(),
1
);
}
#[test]
fn ocio_bit_depth_maps_supported_formats() {
let utils = OCIOUtils::new();
assert_eq!(
utils
.get_ocio_bit_depth_from_pixel_format(PixelFormat::U8)
.unwrap(),
1
);
assert_eq!(
utils
.get_ocio_bit_depth_from_pixel_format(PixelFormat::U10)
.unwrap(),
2
);
assert_eq!(
utils
.get_ocio_bit_depth_from_pixel_format(PixelFormat::U16)
.unwrap(),
5
);
assert_eq!(
utils
.get_ocio_bit_depth_from_pixel_format(PixelFormat::F16)
.unwrap(),
7
);
assert_eq!(
utils
.get_ocio_bit_depth_from_pixel_format(PixelFormat::F32)
.unwrap(),
8
);
}
#[test]
fn ocio_bit_depth_maps_invalid_and_count_to_unknown() {
let utils = OCIOUtils::new();
assert_eq!(
utils
.get_ocio_bit_depth_from_pixel_format(PixelFormat::Invalid)
.unwrap(),
0
);
assert_eq!(
utils
.get_ocio_bit_depth_from_pixel_format(PixelFormat::Count)
.unwrap(),
0
);
}
#[test]
fn ocio_config_raw_round_trips() {
let config = OcioConfig::raw().expect("raw config should load");
assert!(config.colorspace_count().expect("count should work") > 0);
assert!(
config.has_role("default").expect("has_role should work"),
"raw config should define the default role"
);
let canonical = config
.canonical_name("default")
.expect("canonical should work");
assert_eq!(canonical, "raw");
}
}