use super::category_helpers::{extract_items, find_color_space_names};
use super::legacy_viewing_pipeline::LegacyViewingPipeline;
use crate::config::utils::{compare, contain, join, remove, split, trim};
use crate::config::{ColorSpace, Config};
use crate::context::Context;
use crate::error::{Error, Result};
use crate::processor::Processor;
use crate::transforms::{
DisplayViewTransform, ExposureContrastTransform, FileTransform, GroupTransform,
MatrixTransform, Transform,
};
use crate::types::{
ColorSpaceDirection, ExposureContrastStyle, TransformDirection, OCIO_ACTIVE_DISPLAYS_ENVVAR,
OCIO_ACTIVE_VIEWS_ENVVAR,
};
fn dynamic_exposure_contrast() -> ExposureContrastTransform {
ExposureContrastTransform {
style: ExposureContrastStyle::Linear,
pivot: 0.18,
exposure_dynamic: true,
contrast_dynamic: true,
..Default::default()
}
}
fn dynamic_gamma() -> ExposureContrastTransform {
ExposureContrastTransform {
style: ExposureContrastStyle::Video,
pivot: 1.0,
gamma_dynamic: true,
..Default::default()
}
}
pub fn get_processor(
config: &Config,
working_name: &str,
display_name: &str,
view_name: &str,
channel_view: Option<&MatrixTransform>,
direction: TransformDirection,
) -> Result<Processor> {
get_processor_with_context(
config,
config.current_context(),
working_name,
display_name,
view_name,
channel_view,
direction,
)
}
pub fn get_processor_with_context(
config: &Config,
context: &Context,
working_name: &str,
display_name: &str,
view_name: &str,
channel_view: Option<&MatrixTransform>,
direction: TransformDirection,
) -> Result<Processor> {
let mut display_transform = DisplayViewTransform::new(working_name, display_name, view_name);
display_transform.direction = direction;
let processor = config.get_processor_with_context(
context,
&Transform::DisplayView(display_transform.clone()),
TransformDirection::Forward,
)?;
let mut need_exposure = true;
let mut need_gamma = true;
if processor.is_dynamic() {
let grp = processor.create_group_transform();
for tr in &grp.transforms {
if let Transform::ExposureContrast(ex) = tr {
if ex.exposure_dynamic {
need_exposure = false;
}
if ex.gamma_dynamic {
need_gamma = false;
}
}
}
}
if !need_exposure && !need_gamma && channel_view.is_none() {
return Ok(processor);
}
let mut pipeline = LegacyViewingPipeline::new();
pipeline.set_display_view_transform(Some(&display_transform));
if need_exposure {
pipeline.set_linear_cc(Some(&Transform::ExposureContrast(
dynamic_exposure_contrast(),
)));
}
if need_gamma {
pipeline.set_display_cc(Some(&Transform::ExposureContrast(dynamic_gamma())));
}
if let Some(cv) = channel_view {
pipeline.set_channel_view(Some(&Transform::Matrix(cv.clone())));
}
pipeline.get_processor_with_context(config, context)
}
pub fn get_identity_processor(config: &Config) -> Result<Processor> {
let mut group = GroupTransform::new();
group.append(dynamic_exposure_contrast());
group.append(dynamic_gamma());
config.get_processor_for_transform(&Transform::Group(group), TransformDirection::Forward)
}
fn split_trim(list: &str) -> Vec<String> {
split(list, ',')
.iter()
.map(|s| trim(s).to_string())
.collect()
}
fn non_empty_env(name: &str) -> Option<String> {
std::env::var(name).ok().filter(|v| !v.is_empty())
}
fn add_active_display_view(config: &mut Config, display_name: &str, view_name: &str) -> Result<()> {
if let Some(env) = non_empty_env(OCIO_ACTIVE_DISPLAYS_ENVVAR) {
let displays = split_trim(&env);
let accept_all = displays.len() == 1 && displays[0].is_empty();
if !accept_all {
return Err(Error::msg(format!(
"Forbidden to add an active display as '{OCIO_ACTIVE_DISPLAYS_ENVVAR}' controls the active list."
)));
}
} else {
let active = config.active_displays();
if !active.is_empty() {
let mut displays = split_trim(&active);
let accept_all = displays.len() == 1 && displays[0].is_empty();
if !accept_all && !contain(&displays, display_name) {
displays.push(display_name.to_string());
config.set_active_displays(&join(&displays, ','))?;
}
}
}
if let Some(env) = non_empty_env(OCIO_ACTIVE_VIEWS_ENVVAR) {
let views = split_trim(&env);
let accept_all = views.len() == 1 && views[0].is_empty();
if !accept_all {
return Err(Error::msg(format!(
"Forbidden to add an active view as '{OCIO_ACTIVE_VIEWS_ENVVAR}' controls the active list."
)));
}
} else {
let active = config.active_views();
if !active.is_empty() {
let mut views = split_trim(&active);
let accept_all = views.len() == 1 && views[0].is_empty();
if !accept_all && !contain(&views, view_name) {
views.push(view_name.to_string());
config.set_active_views(&join(&views, ','))?;
}
}
}
Ok(())
}
fn remove_active_display_view(
config: &mut Config,
display_name: &str,
view_name: &str,
) -> Result<()> {
if let Some(env) = non_empty_env(OCIO_ACTIVE_DISPLAYS_ENVVAR) {
let displays = split_trim(&env);
let accept_all = displays.len() == 1 && displays[0].is_empty();
if !accept_all {
return Err(Error::msg(format!(
"Forbidden to remove an active display as '{OCIO_ACTIVE_DISPLAYS_ENVVAR}' controls the active list."
)));
}
} else {
let active = config.active_displays();
if !active.is_empty() {
let mut displays = split_trim(&active);
let accept_all = displays.len() == 1 && displays[0].is_empty();
if !accept_all && contain(&displays, display_name) {
let to_remove =
!(0..config.num_displays()).any(|i| compare(&config.display(i), display_name));
if to_remove {
remove(&mut displays, display_name);
config.set_active_displays(&join(&displays, ','))?;
}
}
}
}
if let Some(env) = non_empty_env(OCIO_ACTIVE_VIEWS_ENVVAR) {
let views = split_trim(&env);
let accept_all = views.len() == 1 && views[0].is_empty();
if !accept_all {
return Err(Error::msg(format!(
"Forbidden to remove an active view as '{OCIO_ACTIVE_VIEWS_ENVVAR}' controls the active list."
)));
}
} else {
let active = config.active_views();
if !active.is_empty() {
let mut views = split_trim(&active);
let accept_all = views.len() == 1 && views[0].is_empty();
if !accept_all && contain(&views, view_name) {
let saved_active_displays = config.active_displays();
config.set_active_displays("")?;
let mut to_remove = true;
for disp_idx in 0..config.num_displays() {
if !to_remove {
break;
}
let disp = config.display(disp_idx);
for view_idx in 0..config.num_views(&disp) {
if compare(&config.view(&disp, view_idx), view_name) {
to_remove = false;
break;
}
}
}
config.set_active_displays(&saved_active_displays)?;
if to_remove {
remove(&mut views, view_name);
config.set_active_views(&join(&views, ','))?;
}
}
}
}
Ok(())
}
fn add_display_view_impl(
config: &mut Config,
display_name: &str,
view_name: &str,
look_definition: &str,
color_space: &mut ColorSpace,
user_transform: FileTransform,
connection_color_space_name: &str,
) -> Result<()> {
if display_name.is_empty() {
return Err(Error::msg("Invalid display name."));
}
if view_name.is_empty() {
return Err(Error::msg("Invalid view name."));
}
let mut grp = GroupTransform::new();
{
let connection_cs = config
.get_color_space(connection_color_space_name)
.ok_or_else(|| {
Error::msg(format!(
"Connection color space name '{connection_color_space_name}' does not exist."
))
})?;
if let Some(tr) = connection_cs.transform(ColorSpaceDirection::FromReference) {
grp.append(tr.clone());
} else if let Some(tr) = connection_cs.transform(ColorSpaceDirection::ToReference) {
grp.append(tr.inverted());
}
}
grp.append(user_transform);
let grp = Transform::Group(grp);
grp.validate()?;
add_active_display_view(config, display_name, view_name)?;
color_space.set_transform(Some(grp), ColorSpaceDirection::FromReference);
config.add_color_space(color_space)?;
config.add_display_view(display_name, view_name, color_space.name(), look_definition)
}
#[allow(clippy::too_many_arguments)]
pub fn add_display_view(
config: &mut Config,
display_name: &str,
view_name: &str,
look_definition: &str,
color_space_name: &str,
color_space_family: &str,
color_space_description: &str,
categories: &str,
transform_file_path: &str,
connection_color_space_name: &str,
) -> Result<()> {
let mut color_space = ColorSpace::default();
color_space.set_name(color_space_name);
color_space.set_family(color_space_family);
color_space.set_description(color_space_description);
if config.get_color_space(color_space.name()).is_some() {
return Err(Error::msg(format!(
"Color space name '{}' already exists.",
color_space.name()
)));
}
if !categories.is_empty() {
let cats = extract_items(categories);
if !find_color_space_names(config, &cats).is_empty() {
for cat in &cats {
color_space.add_category(cat);
}
}
}
let file = FileTransform::new(transform_file_path);
add_display_view_impl(
config,
display_name,
view_name,
look_definition,
&mut color_space,
file,
connection_color_space_name,
)
}
pub fn remove_display_view(config: &mut Config, display_name: &str, view_name: &str) -> Result<()> {
let name = config
.display_view_color_space_name(display_name, view_name)
.to_string();
let cs_name = if name.is_empty() {
display_name.to_string()
} else {
name
};
if cs_name.is_empty() {
return Err(Error::msg(format!(
"Missing color space for '{display_name}' and '{view_name}'."
)));
}
config.remove_display_view(display_name, view_name)?;
remove_active_display_view(config, display_name, view_name)?;
if !config.is_color_space_used(&cs_name) {
config.remove_color_space(&cs_name);
}
Ok(())
}