1use super::tokens::TokensManager;
9use super::transform_display::format_transform;
10use super::utils::{compare, contain, fmt_f32, join, lower, remove};
11use crate::error::{Error, Result};
12use crate::transforms::Transform;
13use crate::types::{Allocation, BitDepth, ColorSpaceDirection, ReferenceSpaceType};
14use std::collections::BTreeMap;
15use std::fmt;
16
17const KNOWN_INTERCHANGE_NAMES: [&str; 2] = ["amf_transform_ids", "icc_profile_name"];
18
19pub(crate) fn find_interchange_key(known: &[&'static str], name: &str) -> Result<&'static str> {
21 known
22 .iter()
23 .find(|k| compare(k, name))
24 .copied()
25 .ok_or_else(|| Error::msg(format!("Unknown attribute name '{name}'.")))
26}
27
28#[derive(Debug, Clone, PartialEq)]
31pub struct ColorSpace {
32 name: String,
33 family: String,
34 equality_group: String,
35 description: String,
36 encoding: String,
37 interop_id: String,
38 aliases: Vec<String>,
39 interchange: BTreeMap<String, String>,
40 bit_depth: BitDepth,
41 is_data: bool,
42 reference_space: ReferenceSpaceType,
43 allocation: Allocation,
44 allocation_vars: Vec<f32>,
45 to_reference: Option<Transform>,
46 from_reference: Option<Transform>,
47 categories: TokensManager,
48}
49
50impl Default for ColorSpace {
51 fn default() -> Self {
52 Self::new(ReferenceSpaceType::Scene)
53 }
54}
55
56impl ColorSpace {
57 pub fn new(reference_space: ReferenceSpaceType) -> Self {
59 Self {
60 name: String::new(),
61 family: String::new(),
62 equality_group: String::new(),
63 description: String::new(),
64 encoding: String::new(),
65 interop_id: String::new(),
66 aliases: Vec::new(),
67 interchange: BTreeMap::new(),
68 bit_depth: BitDepth::Unknown,
69 is_data: false,
70 reference_space,
71 allocation: Allocation::Uniform,
72 allocation_vars: Vec::new(),
73 to_reference: None,
74 from_reference: None,
75 categories: TokensManager::new(),
76 }
77 }
78
79 pub fn with_name(name: &str) -> Self {
81 let mut cs = Self::new(ReferenceSpaceType::Scene);
82 cs.set_name(name);
83 cs
84 }
85
86 pub fn name(&self) -> &str {
88 &self.name
89 }
90
91 pub fn set_name(&mut self, name: &str) {
93 self.name = name.to_string();
94 let n = self.name.clone();
95 remove(&mut self.aliases, &n);
96 }
97
98 pub fn num_aliases(&self) -> usize {
100 self.aliases.len()
101 }
102
103 pub fn alias(&self, idx: usize) -> &str {
105 self.aliases.get(idx).map(|s| s.as_str()).unwrap_or("")
106 }
107
108 pub fn aliases(&self) -> &[String] {
110 &self.aliases
111 }
112
113 pub fn has_alias(&self, alias: &str) -> bool {
115 self.aliases.iter().any(|a| compare(a, alias))
116 }
117
118 pub fn add_alias(&mut self, alias: &str) {
120 if !alias.is_empty() && !compare(alias, &self.name) && !contain(&self.aliases, alias) {
121 self.aliases.push(alias.to_string());
122 }
123 }
124
125 pub fn remove_alias(&mut self, alias: &str) {
127 if !alias.is_empty() {
128 remove(&mut self.aliases, alias);
129 }
130 }
131
132 pub fn clear_aliases(&mut self) {
134 self.aliases.clear();
135 }
136
137 pub fn family(&self) -> &str {
139 &self.family
140 }
141 pub fn set_family(&mut self, family: &str) {
143 self.family = family.to_string();
144 }
145 pub fn equality_group(&self) -> &str {
147 &self.equality_group
148 }
149 pub fn set_equality_group(&mut self, group: &str) {
151 self.equality_group = group.to_string();
152 }
153 pub fn description(&self) -> &str {
155 &self.description
156 }
157 pub fn set_description(&mut self, description: &str) {
159 self.description = description.to_string();
160 }
161
162 pub fn interop_id(&self) -> &str {
164 &self.interop_id
165 }
166
167 pub fn set_interop_id(&mut self, id: &str) -> Result<()> {
169 if !id.is_empty() {
170 let allowed = |c: char| {
171 c.is_ascii_digit()
172 || c.is_ascii_lowercase()
173 || matches!(
174 c,
175 '.' | '-'
176 | '_'
177 | '~'
178 | '/'
179 | '*'
180 | '#'
181 | '%'
182 | '^'
183 | '+'
184 | '('
185 | ')'
186 | '['
187 | ']'
188 | '|'
189 | ':'
190 )
191 };
192 if !id.chars().all(allowed) {
193 return Err(Error::msg(format!(
194 "InteropID '{id}' contains invalid characters. Only lowercase a-z, 0-9 and . - _ ~ / * # % ^ + ( ) [ ] | are allowed.\n"
195 )));
196 }
197 if let Some(pos) = id.find(':') {
198 let ns = &id[..pos];
199 let cs = &id[pos + 1..];
200 if ns.is_empty() || cs.is_empty() {
201 return Err(Error::msg(format!(
202 "InteropID '{id}' is not valid. If ':' is used, both the namespace and the color space parts must be non-empty.\n"
203 )));
204 }
205 if cs.contains(':') {
206 return Err(Error::msg(format!(
207 "ERROR: InteropID '{id}' is not valid. Only one ':' is allowed to separate the namespace and the color space.\n"
208 )));
209 }
210 }
211 }
212 self.interop_id = id.to_string();
213 Ok(())
214 }
215
216 pub fn interchange_attribute(&self, name: &str) -> Result<&str> {
219 let key = find_interchange_key(&KNOWN_INTERCHANGE_NAMES, name)?;
220 Ok(self.interchange.get(key).map(|s| s.as_str()).unwrap_or(""))
221 }
222
223 pub fn set_interchange_attribute(&mut self, name: &str, value: &str) -> Result<()> {
225 let key = find_interchange_key(&KNOWN_INTERCHANGE_NAMES, name)?;
226 if value.is_empty() {
227 self.interchange.remove(key);
228 } else {
229 self.interchange.insert(key.to_string(), value.to_string());
230 }
231 Ok(())
232 }
233
234 pub fn interchange_attributes(&self) -> &BTreeMap<String, String> {
236 &self.interchange
237 }
238
239 pub fn bit_depth(&self) -> BitDepth {
241 self.bit_depth
242 }
243 pub fn set_bit_depth(&mut self, bd: BitDepth) {
245 self.bit_depth = bd;
246 }
247 pub fn reference_space_type(&self) -> ReferenceSpaceType {
249 self.reference_space
250 }
251
252 pub fn has_category(&self, category: &str) -> bool {
254 self.categories.has_token(category)
255 }
256 pub fn add_category(&mut self, category: &str) {
258 self.categories.add_token(category);
259 }
260 pub fn remove_category(&mut self, category: &str) {
262 self.categories.remove_token(category);
263 }
264 pub fn num_categories(&self) -> usize {
266 self.categories.num_tokens()
267 }
268 pub fn category(&self, index: usize) -> Option<&str> {
270 self.categories.token(index)
271 }
272 pub fn categories(&self) -> &[String] {
274 self.categories.tokens()
275 }
276 pub fn clear_categories(&mut self) {
278 self.categories.clear_tokens();
279 }
280
281 pub fn encoding(&self) -> &str {
283 &self.encoding
284 }
285 pub fn set_encoding(&mut self, encoding: &str) {
287 self.encoding = encoding.to_string();
288 }
289 pub fn is_data(&self) -> bool {
291 self.is_data
292 }
293 pub fn set_is_data(&mut self, is_data: bool) {
295 self.is_data = is_data;
296 }
297 pub fn allocation(&self) -> Allocation {
299 self.allocation
300 }
301 pub fn set_allocation(&mut self, allocation: Allocation) {
303 self.allocation = allocation;
304 }
305 pub fn allocation_num_vars(&self) -> usize {
307 self.allocation_vars.len()
308 }
309 pub fn allocation_vars(&self) -> &[f32] {
311 &self.allocation_vars
312 }
313 pub fn set_allocation_vars(&mut self, vars: &[f32]) {
315 self.allocation_vars = vars.to_vec();
316 }
317
318 pub fn transform(&self, dir: ColorSpaceDirection) -> Option<&Transform> {
320 match dir {
321 ColorSpaceDirection::ToReference => self.to_reference.as_ref(),
322 ColorSpaceDirection::FromReference => self.from_reference.as_ref(),
323 }
324 }
325
326 pub fn set_transform(&mut self, t: Option<Transform>, dir: ColorSpaceDirection) {
328 match dir {
329 ColorSpaceDirection::ToReference => self.to_reference = t,
330 ColorSpaceDirection::FromReference => self.from_reference = t,
331 }
332 }
333}
334
335impl fmt::Display for ColorSpace {
336 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
337 write!(f, "<ColorSpace referenceSpaceType=")?;
338 match self.reference_space {
339 ReferenceSpaceType::Scene => write!(f, "scene, ")?,
340 ReferenceSpaceType::Display => write!(f, "display, ")?,
341 }
342 write!(f, "name={}, ", self.name)?;
343 if self.aliases.len() == 1 {
344 write!(f, "alias= {}, ", self.aliases[0])?;
345 } else if self.aliases.len() > 1 {
346 write!(f, "aliases=[{}], ", self.aliases.join(", "))?;
347 }
348 if !self.interop_id.is_empty() {
349 write!(f, "interop_id={}, ", self.interop_id)?;
350 }
351 if !self.family.is_empty() {
352 write!(f, "family={}, ", self.family)?;
353 }
354 if !self.equality_group.is_empty() {
355 write!(f, "equalityGroup={}, ", self.equality_group)?;
356 }
357 if self.bit_depth != BitDepth::Unknown {
358 write!(f, "bitDepth={}, ", self.bit_depth.as_str())?;
359 }
360 write!(f, "isData={}", crate::types::bool_to_string(self.is_data))?;
361 if !self.allocation_vars.is_empty() {
362 write!(f, ", allocation={}, ", self.allocation.as_str())?;
363 let vars: Vec<String> = self.allocation_vars.iter().map(|v| fmt_f32(*v)).collect();
364 write!(f, "vars={}", vars.join(" "))?;
365 }
366 if self.num_categories() > 0 {
367 write!(f, ", categories={}", join(self.categories.tokens(), ','))?;
368 }
369 if !self.encoding.is_empty() {
370 write!(f, ", encoding={}", self.encoding)?;
371 }
372 if !self.description.is_empty() {
373 write!(f, ", description={}", self.description)?;
374 }
375 for (k, v) in &self.interchange {
376 write!(f, ", {k}={v}")?;
377 }
378 if let Some(t) = &self.to_reference {
379 write!(f, ",\n {} --> Reference", self.name)?;
380 write!(f, "\n {}", format_transform(t))?;
381 }
382 if let Some(t) = &self.from_reference {
383 write!(f, ",\n Reference --> {}", self.name)?;
384 write!(f, "\n {}", format_transform(t))?;
385 }
386 write!(f, ">")
387 }
388}
389
390#[derive(Debug, Clone, Default)]
393pub struct ColorSpaceSet {
394 color_spaces: Vec<ColorSpace>,
395}
396
397impl PartialEq for ColorSpaceSet {
398 fn eq(&self, other: &Self) -> bool {
400 self.color_spaces.len() == other.color_spaces.len()
401 && self
402 .color_spaces
403 .iter()
404 .all(|cs| other.has_color_space(cs.name()))
405 }
406}
407
408impl ColorSpaceSet {
409 pub fn new() -> Self {
411 Self::default()
412 }
413
414 pub fn num_color_spaces(&self) -> usize {
416 self.color_spaces.len()
417 }
418
419 pub fn color_space_name_by_index(&self, index: usize) -> Option<&str> {
421 self.color_spaces.get(index).map(|c| c.name())
422 }
423
424 pub fn color_space_by_index(&self, index: usize) -> Option<&ColorSpace> {
426 self.color_spaces.get(index)
427 }
428
429 pub fn color_space(&self, name: &str) -> Option<&ColorSpace> {
431 self.color_space_index(name)
432 .and_then(|i| self.color_spaces.get(i))
433 }
434
435 pub fn color_space_index(&self, name: &str) -> Option<usize> {
437 if name.is_empty() {
438 return None;
439 }
440 let s = lower(name);
441 self.color_spaces
442 .iter()
443 .position(|cs| compare(cs.name(), &s) || cs.aliases.iter().any(|a| compare(a, &s)))
444 }
445
446 pub fn has_color_space(&self, name: &str) -> bool {
448 self.color_space_index(name).is_some()
449 }
450
451 pub fn add_color_space(&mut self, cs: &ColorSpace) -> Result<()> {
454 let cs_name = cs.name();
455 if cs_name.is_empty() {
456 return Err(Error::msg("Cannot add a color space with an empty name."));
457 }
458 let mut replace = None;
459 if let Some(idx) = self.color_space_index(cs_name) {
460 if !compare(self.color_spaces[idx].name(), cs_name) {
461 return Err(Error::msg(format!(
462 "Cannot add '{}' color space, existing color space, '{}' is using this name as an alias.",
463 cs_name,
464 self.color_spaces[idx].name()
465 )));
466 }
467 replace = Some(idx);
468 }
469 for alias in &cs.aliases {
470 if let Some(idx) = self.color_space_index(alias) {
471 if Some(idx) != replace {
472 return Err(Error::msg(format!(
473 "Cannot add '{}' color space, it has '{}' alias and existing color space, '{}' is using the same alias.",
474 cs_name,
475 alias,
476 self.color_spaces[idx].name()
477 )));
478 }
479 }
480 }
481 match replace {
482 Some(i) => self.color_spaces[i] = cs.clone(),
483 None => self.color_spaces.push(cs.clone()),
484 }
485 Ok(())
486 }
487
488 pub fn add_color_spaces(&mut self, other: &ColorSpaceSet) -> Result<()> {
490 for cs in &other.color_spaces {
491 self.add_color_space(cs)?;
492 }
493 Ok(())
494 }
495
496 pub fn remove_color_space(&mut self, name: &str) {
498 if name.is_empty() {
499 return;
500 }
501 let n = lower(name);
502 if let Some(pos) = self
503 .color_spaces
504 .iter()
505 .position(|cs| lower(cs.name()) == n)
506 {
507 self.color_spaces.remove(pos);
508 }
509 }
510
511 pub fn remove_color_spaces(&mut self, other: &ColorSpaceSet) {
513 for cs in &other.color_spaces {
514 self.remove_color_space(cs.name());
515 }
516 }
517
518 pub fn clear_color_spaces(&mut self) {
520 self.color_spaces.clear();
521 }
522
523 pub fn iter(&self) -> impl Iterator<Item = &ColorSpace> {
525 self.color_spaces.iter()
526 }
527
528 pub fn union(&self, other: &ColorSpaceSet) -> Result<ColorSpaceSet> {
530 let mut s = self.clone();
531 s.add_color_spaces(other)?;
532 Ok(s)
533 }
534
535 pub fn intersection(&self, other: &ColorSpaceSet) -> Result<ColorSpaceSet> {
537 let mut s = ColorSpaceSet::new();
538 for cs in &other.color_spaces {
539 if self.has_color_space(cs.name()) {
540 s.add_color_space(cs)?;
541 }
542 }
543 Ok(s)
544 }
545
546 pub fn difference(&self, other: &ColorSpaceSet) -> Result<ColorSpaceSet> {
548 let mut s = ColorSpaceSet::new();
549 for cs in &self.color_spaces {
550 if !other.has_color_space(cs.name()) {
551 s.add_color_space(cs)?;
552 }
553 }
554 Ok(s)
555 }
556}