Skip to content

Commit f662359

Browse files
authored
Per-Track and Per-Clip Color (#1887)
Item and Composition now have optional color attributes so that Clips, Tracks and Stacks can have user-assigned colors. A new serializable Color class is introduced for storing assigned user interface colors. Signed-off-by: Spencer Magnusson <[email protected]>
1 parent 69b7ea1 commit f662359

34 files changed

+638
-20
lines changed

docs/tutorials/otio-serialized-schema-only-fields.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ parameters:
4040
### Composition.1
4141

4242
parameters:
43+
- *color*
4344
- *effects*
4445
- *enabled*
4546
- *markers*
@@ -50,6 +51,7 @@ parameters:
5051
### Item.1
5152

5253
parameters:
54+
- *color*
5355
- *effects*
5456
- *enabled*
5557
- *markers*
@@ -132,6 +134,7 @@ parameters:
132134

133135
parameters:
134136
- *active_media_reference_key*
137+
- *color*
135138
- *effects*
136139
- *enabled*
137140
- *markers*
@@ -169,6 +172,7 @@ parameters:
169172
### Gap.1
170173

171174
parameters:
175+
- *color*
172176
- *effects*
173177
- *enabled*
174178
- *markers*
@@ -237,6 +241,7 @@ parameters:
237241
### Stack.1
238242

239243
parameters:
244+
- *color*
240245
- *effects*
241246
- *enabled*
242247
- *markers*
@@ -263,6 +268,7 @@ parameters:
263268
### Track.1
264269

265270
parameters:
271+
- *color*
266272
- *effects*
267273
- *enabled*
268274
- *kind*

docs/tutorials/otio-serialized-schema.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ Should be subclassed (for example by :class:`.Track` and :class:`.Stack`), not u
8888
```
8989

9090
parameters:
91+
- *color*:
9192
- *effects*:
9293
- *enabled*: If true, an Item contributes to compositions. For example, when an audio/video clip is ``enabled=false`` the clip is muted/hidden.
9394
- *markers*:
@@ -106,6 +107,7 @@ None
106107
```
107108

108109
parameters:
110+
- *color*:
109111
- *effects*:
110112
- *enabled*: If true, an Item contributes to compositions. For example, when an audio/video clip is ``enabled=false`` the clip is muted/hidden.
111113
- *markers*:
@@ -290,6 +292,7 @@ Contains a :class:`.MediaReference` and a trim on that media reference.
290292

291293
parameters:
292294
- *active_media_reference_key*:
295+
- *color*:
293296
- *effects*:
294297
- *enabled*: If true, an Item contributes to compositions. For example, when an audio/video clip is ``enabled=false`` the clip is muted/hidden.
295298
- *markers*:
@@ -363,6 +366,7 @@ None
363366
```
364367

365368
parameters:
369+
- *color*:
366370
- *effects*:
367371
- *enabled*: If true, an Item contributes to compositions. For example, when an audio/video clip is ``enabled=false`` the clip is muted/hidden.
368372
- *markers*:
@@ -591,6 +595,7 @@ None
591595
```
592596

593597
parameters:
598+
- *color*:
594599
- *effects*:
595600
- *enabled*: If true, an Item contributes to compositions. For example, when an audio/video clip is ``enabled=false`` the clip is muted/hidden.
596601
- *markers*:
@@ -641,6 +646,7 @@ None
641646
```
642647

643648
parameters:
649+
- *color*:
644650
- *effects*:
645651
- *enabled*: If true, an Item contributes to compositions. For example, when an audio/video clip is ``enabled=false`` the clip is muted/hidden.
646652
- *kind*:

src/opentimelineio/CMakeLists.txt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
set(OPENTIMELINEIO_HEADER_FILES
55
anyDictionary.h
66
anyVector.h
7+
color.h
78
clip.h
89
composable.h
910
composition.h
@@ -38,7 +39,8 @@ set(OPENTIMELINEIO_HEADER_FILES
3839
vectorIndexing.h
3940
version.h)
4041

41-
add_library(opentimelineio ${OTIO_SHARED_OR_STATIC_LIB}
42+
add_library(opentimelineio ${OTIO_SHARED_OR_STATIC_LIB}
43+
color.cpp
4244
clip.cpp
4345
composable.cpp
4446
composition.cpp

src/opentimelineio/clip.cpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,9 @@ Clip::Clip(
1515
AnyDictionary const& metadata,
1616
std::vector<Effect*> const& effects,
1717
std::vector<Marker*> const& markers,
18-
std::string const& active_media_reference_key)
19-
: Parent{ name, source_range, metadata, effects, markers }
18+
std::string const& active_media_reference_key,
19+
std::optional<Color> const& color)
20+
: Parent{ name, source_range, metadata, effects, markers, /*enabled*/ true, color }
2021
, _active_media_reference_key(active_media_reference_key)
2122
{
2223
set_media_reference(media_reference);

src/opentimelineio/clip.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,8 @@ class Clip : public Item
4646
AnyDictionary const& metadata = AnyDictionary(),
4747
std::vector<Effect*> const& effects = std::vector<Effect*>(),
4848
std::vector<Marker*> const& markers = std::vector<Marker*>(),
49-
std::string const& active_media_reference_key = default_media_key);
49+
std::string const& active_media_reference_key = default_media_key,
50+
std::optional<Color> const& color = std::nullopt);
5051

5152
/// @name Media References
5253
///@{

src/opentimelineio/color.cpp

Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
// Copyright Contributors to the OpenTimelineIO project
3+
4+
#include <iomanip>
5+
#include <sstream>
6+
7+
#include "opentimelineio/color.h"
8+
9+
namespace opentimelineio { namespace OPENTIMELINEIO_VERSION {
10+
11+
Color::Color(
12+
double const r,
13+
double const g,
14+
double const b,
15+
double const a,
16+
std::string const& name)
17+
: _name(name),
18+
_r(r),
19+
_g(g),
20+
_b(b),
21+
_a(a) {}
22+
23+
Color::Color(Color const& other) : _name(other.name()),
24+
_r(other.r()),
25+
_g(other.g()),
26+
_b(other.b()),
27+
_a(other.a()) {}
28+
29+
const Color Color::pink(1.0, 0.0, 1.0, 1.0, "Pink");
30+
const Color Color::red(1.0, 0.0, 0.0, 1.0, "Red");
31+
const Color Color::orange(1.0, 0.5, 0.0, 1.0, "Orange");
32+
const Color Color::yellow(1.0, 1.0, 0.0, 1.0, "Yellow");
33+
const Color Color::green(0.0, 1.0, 0.0, 1.0, "Green");
34+
const Color Color::cyan(0.0, 1.0, 1.0, 1.0, "Cyan");
35+
const Color Color::blue(0.0, 0.0, 1.0, 1.0, "Blue");
36+
const Color Color::purple(0.5, 0.0, 0.5, 1.0, "Purple");
37+
const Color Color::magenta(1.0, 0.0, 1.0, 1.0, "Magenta");
38+
const Color Color::black(0.0, 0.0, 0.0, 1.0, "Black");
39+
const Color Color::white(1.0, 1.0, 1.0, 1.0, "White");
40+
const Color Color::transparent(1.0, 1.0, 1.0, 0.0, "Transparent");
41+
42+
Color*
43+
Color::from_hex(std::string const& color)
44+
{
45+
std::string temp = color;
46+
if (temp[0] == '#')
47+
{
48+
temp = temp.substr(1);
49+
}
50+
else if (temp[0] == '0' && (temp[1] == 'x' || temp[1] == 'X'))
51+
{
52+
temp = temp.substr(2);
53+
}
54+
55+
double _r, _g, _b, _a;
56+
// 0xFFFFFFFF (rgba, 255)
57+
int BASE_16 = 16;
58+
double BASE_16_DIV = 255.0;
59+
double BASE_8_DIV = 15.0;
60+
if (temp.length() == 8)
61+
{
62+
_r = std::stoi(temp.substr(0, 2), nullptr, BASE_16) / BASE_16_DIV;
63+
_g = std::stoi(temp.substr(2, 2), nullptr, BASE_16) / BASE_16_DIV;
64+
_b = std::stoi(temp.substr(4, 2), nullptr, BASE_16) / BASE_16_DIV;
65+
_a = std::stoi(temp.substr(6, 2), nullptr, BASE_16) / BASE_16_DIV;
66+
}
67+
// 0xFFFFFF (rgb, 255)
68+
else if (temp.length() == 6)
69+
{
70+
_r = std::stoi(temp.substr(0, 2), nullptr, BASE_16) / BASE_16_DIV;
71+
_g = std::stoi(temp.substr(2, 2), nullptr, BASE_16) / BASE_16_DIV;
72+
_b = std::stoi(temp.substr(4, 2), nullptr, BASE_16) / BASE_16_DIV;
73+
_a = 1.0;
74+
}
75+
// 0xFFF (rgb, 16)
76+
else if (temp.length() == 3)
77+
{
78+
_r = std::stoi(temp.substr(0, 1), nullptr, BASE_16) / BASE_8_DIV;
79+
_g = std::stoi(temp.substr(1, 1), nullptr, BASE_16) / BASE_8_DIV;
80+
_b = std::stoi(temp.substr(2, 1), nullptr, BASE_16) / BASE_8_DIV;
81+
_a = 1.0;
82+
}
83+
// 0xFFFF (rgba, 16)
84+
else if (temp.length() == 4)
85+
{
86+
_r = std::stoi(temp.substr(0, 1), nullptr, BASE_16) / BASE_8_DIV;
87+
_g = std::stoi(temp.substr(1, 1), nullptr, BASE_16) / BASE_8_DIV;
88+
_b = std::stoi(temp.substr(2, 1), nullptr, BASE_16) / BASE_8_DIV;
89+
_a = std::stoi(temp.substr(3, 1), nullptr, BASE_16) / BASE_8_DIV;
90+
}
91+
else {
92+
throw std::invalid_argument("Invalid hex format");
93+
}
94+
return new Color(_r, _g, _b, _a);
95+
}
96+
97+
Color*
98+
Color::from_int_list(std::vector<int> const& color, int bit_depth)
99+
{
100+
double depth = pow(2, bit_depth) - 1.0; // e.g. 8 = 255.0
101+
if (color.size() == 3)
102+
return new Color(color[0] / depth, color[1] / depth, color[2] / depth, 1.0);
103+
else if (color.size() == 4)
104+
return new Color(color[0] / depth, color[1] / depth, color[2] / depth, color[3] / depth);
105+
106+
throw std::invalid_argument("List must have exactly 3 or 4 elements");
107+
}
108+
109+
Color*
110+
Color::from_agbr_int(unsigned int agbr) noexcept
111+
{
112+
auto conv_r = (agbr & 0xFF) / 255.0;
113+
auto conv_g = ((agbr >> 16) & 0xFF) / 255.0;
114+
auto conv_b = ((agbr >> 8) & 0xFF) / 255.0;
115+
auto conv_a = ((agbr >> 24) & 0xFF) / 255.0;
116+
return new Color(conv_r, conv_g, conv_b, conv_a);
117+
}
118+
119+
Color*
120+
Color::from_float_list(std::vector<double> const& color)
121+
{
122+
if (color.size() == 3)
123+
return new Color(color[0], color[1], color[2], 1.0);
124+
else if (color.size() == 4)
125+
return new Color(color[0], color[1], color[2], color[3]);
126+
127+
throw std::invalid_argument("List must have exactly 3 or 4 elements");
128+
}
129+
130+
std::string
131+
Color::to_hex()
132+
{
133+
auto rgba = to_rgba_int_list(8);
134+
std::stringstream ss;
135+
ss << "#";
136+
137+
ss << std::hex << std::setfill('0');
138+
ss << std::setw(2) << rgba[0];
139+
ss << std::setw(2) << rgba[1];
140+
ss << std::setw(2) << rgba[2];
141+
ss << std::setw(2) << rgba[3];
142+
return ss.str();
143+
}
144+
145+
std::vector<int>
146+
Color::to_rgba_int_list(int base)
147+
{
148+
double math_base = pow(2, base) - 1.0;
149+
return {int(_r * math_base), int(_g * math_base), int(_b * math_base), int(_a * math_base)};
150+
}
151+
152+
unsigned int
153+
Color::to_agbr_integer()
154+
{
155+
auto rgba = to_rgba_int_list(8);
156+
return (rgba[3] << 24) + (rgba[2] << 16) + (rgba[1] << 8) + rgba[0];
157+
}
158+
159+
std::vector<double>
160+
Color::to_rgba_float_list()
161+
{
162+
return {_r, _g, _b, _a};
163+
}
164+
165+
}} // namespace opentimelineio::OPENTIMELINEIO_VERSION

src/opentimelineio/color.h

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
// Copyright Contributors to the OpenTimelineIO project
3+
4+
#pragma once
5+
6+
#include <cmath>
7+
#include <vector>
8+
9+
#include "opentimelineio/version.h"
10+
11+
namespace opentimelineio { namespace OPENTIMELINEIO_VERSION {
12+
13+
14+
/// @brief Color consists of red, green, blue,
15+
/// and alpha double floating point values,
16+
/// allowing conversion between different formats.
17+
/// To be considered interoperable,
18+
/// the sRGB transfer function encoded values,
19+
/// ranging between zero and one, are expected to be accurate
20+
/// to within 1/255 of the intended value.
21+
/// Round-trip conversions may not be guaranteed outside that.
22+
/// This class is meant for use in user interface elements,
23+
// like marker or clip coloring, NOT for image pixel content.
24+
class Color
25+
{
26+
public:
27+
struct Schema
28+
{
29+
static auto constexpr name = "Color";
30+
static int constexpr version = 1;
31+
};
32+
33+
Color(
34+
double const r = 1.f,
35+
double const g = 1.f,
36+
double const b = 1.f,
37+
double const a = 1.f,
38+
std::string const& name = "");
39+
40+
Color(Color const& other);
41+
42+
static const Color pink;
43+
static const Color red;
44+
static const Color orange;
45+
static const Color yellow;
46+
static const Color green;
47+
static const Color cyan;
48+
static const Color blue;
49+
static const Color purple;
50+
static const Color magenta;
51+
static const Color black;
52+
static const Color white;
53+
static const Color transparent;
54+
55+
static Color* from_hex(std::string const& color);
56+
static Color* from_int_list(std::vector<int> const& color, int bit_depth);
57+
static Color* from_agbr_int(unsigned int agbr) noexcept;
58+
static Color* from_float_list(std::vector<double> const& color);
59+
60+
friend bool
61+
operator==(Color lhs, Color rhs) noexcept
62+
{
63+
return lhs.to_hex() == rhs.to_hex() && lhs.to_agbr_integer() == rhs.to_agbr_integer();
64+
}
65+
66+
std::string to_hex();
67+
std::vector<int> to_rgba_int_list(int base);
68+
unsigned int to_agbr_integer();
69+
std::vector<double> to_rgba_float_list();
70+
71+
double r() const { return _r; }
72+
double g() const { return _g; }
73+
double b() const { return _b; }
74+
double a() const { return _a; }
75+
std::string name() const { return _name; }
76+
77+
void set_r(double r) { _r = r; }
78+
void set_g(double g) { _g = g; }
79+
void set_b(double b) { _b = b; }
80+
void set_a(double a) { _a = a; }
81+
void set_name(std::string const& name) { _name = name; }
82+
83+
private:
84+
double _r;
85+
double _g;
86+
double _b;
87+
double _a;
88+
std::string _name;
89+
};
90+
91+
}} // namespace opentimelineio::OPENTIMELINEIO_VERSION

0 commit comments

Comments
 (0)