use crate::config::{ColorSpace, Config, ViewTransform};
use crate::error::{Error, Result};
use crate::transforms::{ColorSpaceTransform, GroupTransform, MatrixTransform, Transform};
use crate::types::{
ColorSpaceDirection, ColorSpaceVisibility, OptimizationFlags, ProcessorCacheFlags,
ReferenceSpaceType, SearchReferenceSpaceType, TransformDirection, ViewTransformDirection,
};
use std::sync::OnceLock;
const BUILTIN_CG_LATEST: &str = "ocio://cg-config-latest";
fn builtin_config() -> Result<&'static Config> {
static BUILTIN: OnceLock<std::result::Result<Config, Error>> = OnceLock::new();
BUILTIN
.get_or_init(|| Config::create_from_builtin_config(BUILTIN_CG_LATEST))
.as_ref()
.map_err(|e| e.clone())
}
struct SuspendCacheGuard<'a> {
config: &'a Config,
flags: ProcessorCacheFlags,
}
impl<'a> SuspendCacheGuard<'a> {
fn new(config: &'a Config) -> Self {
let flags = config.processor_cache_flags();
config.set_processor_cache_flags(ProcessorCacheFlags::OFF);
Self { config, flags }
}
}
impl Drop for SuspendCacheGuard<'_> {
fn drop(&mut self) {
self.config.set_processor_cache_flags(self.flags);
}
}
fn search_type(t: ReferenceSpaceType) -> SearchReferenceSpaceType {
match t {
ReferenceSpaceType::Scene => SearchReferenceSpaceType::Scene,
ReferenceSpaceType::Display => SearchReferenceSpaceType::Display,
}
}
pub fn simplify_transform(gt: &GroupTransform) -> Result<Transform> {
let config = Config::create_raw();
let p = config
.get_processor_for_transform(&Transform::Group(gt.clone()), TransformDirection::Forward)?;
let opt = p.optimized(OptimizationFlags::DEFAULT);
let mut final_gt = opt.create_group_transform();
if final_gt.transforms.len() == 1 {
return Ok(final_gt.transforms.remove(0));
}
Ok(Transform::Group(final_gt))
}
pub fn invert_transform(t: &Transform) -> Transform {
let mut e = t.clone();
e.set_direction(TransformDirection::Inverse);
e
}
pub fn transform_for_dir(cs: &ColorSpace, dir: ColorSpaceDirection) -> Transform {
let other = match dir {
ColorSpaceDirection::ToReference => ColorSpaceDirection::FromReference,
ColorSpaceDirection::FromReference => ColorSpaceDirection::ToReference,
};
if let Some(t) = cs.transform(dir) {
return t.clone();
}
if let Some(t) = cs.transform(other) {
return invert_transform(t);
}
Transform::Matrix(MatrixTransform::default())
}
fn color_space_of_ref_type(config: &Config, ref_type: ReferenceSpaceType) -> Result<String> {
let st = search_type(ref_type);
if config.num_color_spaces_filtered(st, ColorSpaceVisibility::All) > 0 {
let name = config.color_space_name_by_index_filtered(st, ColorSpaceVisibility::All, 0);
if let Some(cs) = config.get_color_space(name) {
return Ok(cs.name().to_string());
}
}
Err(Error::msg(
"Config is lacking any color spaces of the requested reference space type.",
))
}
pub fn ref_space_converter(
src_config: &Config,
dst_config: &Config,
ref_space_type: ReferenceSpaceType,
) -> Result<Transform> {
let builtin = builtin_config()?;
let (src_interchange, src_builtin_interchange) = Config::identify_interchange_space(
src_config,
&color_space_of_ref_type(src_config, ref_space_type)?,
builtin,
&color_space_of_ref_type(builtin, ref_space_type)?,
)?;
let (dst_interchange, dst_builtin_interchange) = Config::identify_interchange_space(
dst_config,
&color_space_of_ref_type(dst_config, ref_space_type)?,
builtin,
&color_space_of_ref_type(builtin, ref_space_type)?,
)?;
let src_cs = src_config
.get_color_space(&src_interchange)
.ok_or_else(|| Error::msg(format!("Could not find color space '{src_interchange}'.")))?;
let src_from_ref = transform_for_dir(src_cs, ColorSpaceDirection::FromReference);
let mut src_builtin_to_dst_builtin = None;
if !src_builtin_interchange.is_empty() && !dst_builtin_interchange.is_empty() {
let cst = ColorSpaceTransform::new(&src_builtin_interchange, &dst_builtin_interchange);
src_builtin_to_dst_builtin = Some(
builtin
.get_processor_for_transform(
&Transform::ColorSpace(cst),
TransformDirection::Forward,
)?
.create_group_transform(),
);
}
let dst_cs = dst_config
.get_color_space(&dst_interchange)
.ok_or_else(|| Error::msg(format!("Could not find color space '{dst_interchange}'.")))?;
let dst_to_ref = transform_for_dir(dst_cs, ColorSpaceDirection::ToReference);
let mut gt = GroupTransform::new();
gt.append(
src_config
.get_processor_for_transform(&src_from_ref, TransformDirection::Forward)?
.create_group_transform(),
);
if let Some(g) = src_builtin_to_dst_builtin {
gt.append(g);
}
gt.append(
dst_config
.get_processor_for_transform(&dst_to_ref, TransformDirection::Forward)?
.create_group_transform(),
);
simplify_transform(>)
}
pub fn transform_is_empty(tr: &Transform) -> bool {
matches!(tr, Transform::Group(g) if g.transforms.is_empty())
}
pub fn update_reference_colorspace(cs: &mut ColorSpace, to_new_reference: &Transform) {
if transform_is_empty(to_new_reference) {
return;
}
let transform_to = cs.transform(ColorSpaceDirection::ToReference).cloned();
if let Some(t) = &transform_to {
let gt = GroupTransform::from_transforms(vec![t.clone(), to_new_reference.clone()]);
cs.set_transform(Some(Transform::Group(gt)), ColorSpaceDirection::ToReference);
}
let transform_from = cs.transform(ColorSpaceDirection::FromReference).cloned();
if let Some(t) = &transform_from {
let inv = invert_transform(to_new_reference);
let gt = GroupTransform::from_transforms(vec![inv, t.clone()]);
cs.set_transform(
Some(Transform::Group(gt)),
ColorSpaceDirection::FromReference,
);
}
if transform_to.is_none() && transform_from.is_none() && !cs.is_data() {
let gt = GroupTransform::from_transforms(vec![to_new_reference.clone()]);
cs.set_transform(Some(Transform::Group(gt)), ColorSpaceDirection::ToReference);
}
}
pub fn update_reference_view(
vt: &mut ViewTransform,
to_new_scene_reference: &Transform,
to_new_display_reference: &Transform,
) {
let empty_scene = transform_is_empty(to_new_scene_reference);
let empty_display = transform_is_empty(to_new_display_reference);
if empty_scene && empty_display {
return;
}
let display_ref = vt.reference_space_type() == ReferenceSpaceType::Display;
if let Some(t) = vt.transform(ViewTransformDirection::ToReference).cloned() {
let mut gt = GroupTransform::new();
if !empty_display {
gt.append(invert_transform(to_new_display_reference));
}
gt.append(t);
if display_ref {
if !empty_display {
gt.append(to_new_display_reference.clone());
}
} else if !empty_scene {
gt.append(to_new_scene_reference.clone());
}
vt.set_transform(
Some(Transform::Group(gt)),
ViewTransformDirection::ToReference,
);
}
if let Some(t) = vt.transform(ViewTransformDirection::FromReference).cloned() {
let mut gt = GroupTransform::new();
if display_ref {
if !empty_display {
gt.append(invert_transform(to_new_display_reference));
}
} else if !empty_scene {
gt.append(invert_transform(to_new_scene_reference));
}
gt.append(t);
if !empty_display {
gt.append(to_new_display_reference.clone());
}
vt.set_transform(
Some(Transform::Group(gt)),
ViewTransformDirection::FromReference,
);
}
}
fn has_color_space_ref_type(config: &Config, ref_type: ReferenceSpaceType) -> bool {
config.num_color_spaces_filtered(search_type(ref_type), ColorSpaceVisibility::All) > 0
}
pub fn initialize_ref_space_converters(
base_config: &Config,
input_config: &Config,
) -> Result<(Transform, Transform)> {
let scene = if has_color_space_ref_type(base_config, ReferenceSpaceType::Scene)
&& has_color_space_ref_type(input_config, ReferenceSpaceType::Scene)
{
ref_space_converter(input_config, base_config, ReferenceSpaceType::Scene)?
} else {
Transform::Group(GroupTransform::new())
};
let display = if has_color_space_ref_type(base_config, ReferenceSpaceType::Display)
&& has_color_space_ref_type(input_config, ReferenceSpaceType::Display)
{
ref_space_converter(input_config, base_config, ReferenceSpaceType::Display)?
} else {
Transform::Group(GroupTransform::new())
};
Ok((scene, display))
}
#[derive(Debug, Clone, PartialEq)]
pub struct Fingerprint {
pub cs_name: String,
pub ref_type: ReferenceSpaceType,
pub vals: Vec<f32>,
}
#[derive(Debug, Clone, Default, PartialEq)]
pub struct ColorSpaceFingerprints {
pub vec: Vec<Fingerprint>,
pub scene_ref_test_vals: Vec<f32>,
pub display_ref_test_vals: Vec<f32>,
}
pub fn calc_color_space_fingerprint(
fingerprints: &ColorSpaceFingerprints,
config: &Config,
cs: &ColorSpace,
) -> Option<Vec<f32>> {
let from_ref = transform_for_dir(cs, ColorSpaceDirection::FromReference);
let cpu = config
.get_processor_for_transform(&from_ref, TransformDirection::Forward)
.ok()?
.optimized_cpu_processor(OptimizationFlags::NONE);
let mut vals = if cs.reference_space_type() == ReferenceSpaceType::Display {
fingerprints.display_ref_test_vals.clone()
} else {
fingerprints.scene_ref_test_vals.clone()
};
cpu.apply_rgba_slice(&mut vals);
Some(vals)
}
fn to_reference_vals(
config: &Config,
names: &[&str],
builtin_name: &str,
vals: &[f32],
) -> Result<Vec<f32>> {
let mut cs = names.iter().find_map(|n| config.get_color_space(n));
let found_name;
if cs.is_none() {
let builtin = builtin_config()?;
found_name = Config::identify_builtin_color_space(config, builtin, builtin_name)?;
cs = config.get_color_space(&found_name);
}
let cs = cs.ok_or_else(|| Error::msg("Color space not found."))?;
let to_ref = transform_for_dir(cs, ColorSpaceDirection::ToReference);
let cpu = config
.get_processor_for_transform(&to_ref, TransformDirection::Forward)?
.optimized_cpu_processor(OptimizationFlags::NONE);
let mut out = vals.to_vec();
cpu.apply_rgba_slice(&mut out);
Ok(out)
}
fn initialize_test_vals(fingerprints: &mut ColorSpaceFingerprints, config: &Config) {
#[rustfmt::skip]
let aces_vals: Vec<f32> = vec![
0.408933127871, 0.106169822808, 0.027842572707, 0.0, 0.374615373650, 0.739417755017, 0.118862613721, 0.0, 0.171696591718, 0.104272268468, 0.786227391453, 0.0, 0.0, 0.0, 0.0, 0.5,
0.037018876439, 0.030827687576, 0.021641700645, 0.0, 1.0, 1.0, 1.0, 1.0,
];
#[rustfmt::skip]
let xyz_vals: Vec<f32> = vec![
0.383684057405, 0.213552088801, 0.030478901760, 0.0, 0.350178969169, 0.657853997550, 0.127445793983, 0.0, 0.173708304342, 0.081402847459, 0.858056140808, 0.0, 0.0, 0.0, 0.0, 0.5,
0.034956685913, 0.033530856964, 0.023553027375, 0.0, 0.950455927052, 1.0, 1.089057750760, 1.0,
];
fingerprints.scene_ref_test_vals = aces_vals.clone();
fingerprints.display_ref_test_vals = xyz_vals.clone();
fingerprints.scene_ref_test_vals = to_reference_vals(
config,
&["aces_interchange", "ACES2065-1", "lin_ap0_scene"],
"aces_interchange",
&aces_vals,
)
.unwrap_or(aces_vals);
if config
.num_color_spaces_filtered(SearchReferenceSpaceType::Display, ColorSpaceVisibility::All)
== 0
{
return;
}
fingerprints.display_ref_test_vals = to_reference_vals(
config,
&["cie_xyz_d65_interchange", "CIE-XYZ-D65", "CIE XYZ-D65"],
"cie_xyz_d65_interchange",
&xyz_vals,
)
.unwrap_or(xyz_vals);
}
pub fn initialize_color_space_fingerprints(config: &Config) -> ColorSpaceFingerprints {
let _guard = SuspendCacheGuard::new(config);
let mut fingerprints = ColorSpaceFingerprints::default();
initialize_test_vals(&mut fingerprints, config);
let n =
config.num_color_spaces_filtered(SearchReferenceSpaceType::All, ColorSpaceVisibility::All);
for i in 0..n {
let name = config.color_space_name_by_index_filtered(
SearchReferenceSpaceType::All,
ColorSpaceVisibility::All,
i,
);
let cs = match config.get_color_space(name) {
Some(cs) if !cs.is_data() => cs,
_ => continue,
};
if cs.has_category("is-unique") {
continue;
}
if cs.transform(ColorSpaceDirection::FromReference).is_some()
&& cs.transform(ColorSpaceDirection::ToReference).is_some()
{
continue;
}
if let Some(vals) = calc_color_space_fingerprint(&fingerprints, config, cs) {
fingerprints.vec.push(Fingerprint {
cs_name: cs.name().to_string(),
ref_type: cs.reference_space_type(),
vals,
});
}
}
fingerprints
}
pub fn find_equivalent_colorspace(
fingerprints: &ColorSpaceFingerprints,
input_config: &Config,
input_cs: &ColorSpace,
) -> String {
if input_cs.is_data() {
return String::new();
}
let input_vals = match calc_color_space_fingerprint(fingerprints, input_config, input_cs) {
Some(v) => v,
None => return String::new(),
};
let abs_tolerance = 5e-3f32;
for fp in &fingerprints.vec {
if fp.ref_type != input_cs.reference_space_type() {
continue;
}
let matched = input_vals.iter().enumerate().all(|(i, v)| {
fp.vals
.get(i)
.map(|w| {
let d = if *v > *w { *v - *w } else { *w - *v };
d <= abs_tolerance
})
.unwrap_or(false)
});
if matched {
return fp.cs_name.clone();
}
}
String::new()
}