Skip to content

Commit 5d473ea

Browse files
authored
Merge pull request #11 from nick-shaw/feature/ctl
Add first draft CTL implementation.
2 parents 505d536 + 2fcdf9a commit 5d473ea

File tree

2 files changed

+135
-1
lines changed

2 files changed

+135
-1
lines changed

model/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ DCC Implementations for the following software packages are included. For specif
2929
- [DCTL for Resolve Studio](/model/docs/doc-resolve.md)
3030
- [Matchbox for Flame, Scratch and Baselight](/model/docs/doc-flame.md)
3131

32-
A Python implementation is also included.
32+
A Python implementation is also included, as well as a first draft of the CTL which will form part of the deliverable of the Virtual Working Group.
3333

3434
## Default Parameter Values
3535
This [Google Colab notebook](https://colab.research.google.com/drive/1ZMSQhyhXtAYQXfop6qhifXudDiPu4eTV?usp=sharing) shows calculations for the threshold values needed to protect the colors of the ColorChecker24, as defined in Annexe B of [TB-2014-004](http://j.mp/TB-2014-004), and the distance limits needed to map the entirety of a set of common camera encoding gamuts into AP1.

model/gamut_compress.ctl

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
// <ACEStransformID>urn:ampas:aces:transformId:v1.5:LMT.VWG.GamutCompress.a1.v1</ACEStransformID>
2+
// <ACESuserName>ACES 1.3 Look - Gamut Compress</ACESuserName>
3+
4+
//
5+
// "LMT" for compressing out of gamut scene-referred values into AP1.
6+
//
7+
// Input and output are ACES2065-1.
8+
//
9+
10+
//
11+
// Direction:
12+
// By default this transform operates in the forward direction (compressing the gamut).
13+
// If instead an inverse operation is needed (undoing a prior gamut compression), there
14+
// is a runtime flag to do this. In ctlrender, this can be achieved by appending
15+
// '-param1 invert 1' after the '-ctl gamut_compress.ctl' string.
16+
//
17+
18+
19+
20+
import "ACESlib.Transform_Common";
21+
22+
23+
24+
/* --- Gamut Compress Parameters --- */
25+
// Distance from achromatic which will be compressed to the gamut boundary
26+
const float LIM_CYAN = 1.147;
27+
const float LIM_MAGENTA = 1.264;
28+
const float LIM_YELLOW = 1.312;
29+
30+
// Percentage of the core gamut to protect
31+
const float THR_CYAN = 0.815;
32+
const float THR_MAGENTA = 0.803;
33+
const float THR_YELLOW = 0.880;
34+
35+
// Agressiveness of the compression curve
36+
const float PWR = 1.2;
37+
38+
39+
40+
// Calculate compressed distance
41+
float compress(float dist, float lim, float thr, float pwr, bool invert)
42+
{
43+
float comprDist;
44+
float scl;
45+
float nd;
46+
float p;
47+
48+
if (dist < thr) {
49+
comprDist = dist; // No compression below threshold
50+
}
51+
else {
52+
// Calculate scale factor for y = 1 intersect
53+
scl = (lim - thr) / pow(pow((1.0 - thr) / (lim - thr), -pwr) - 1.0, 1.0 / pwr);
54+
55+
// Normalize distance outside threshold by scale factor
56+
nd = (dist - thr) / scl;
57+
p = pow(nd, pwr);
58+
59+
if (!invert) {
60+
comprDist = thr + scl * nd / (pow(1.0 + p, 1.0 / pwr)); // Compress
61+
}
62+
else {
63+
if (dist > (thr + scl)) {
64+
comprDist = dist; // Avoid singularity
65+
}
66+
else {
67+
comprDist = thr + scl * pow(-(p / (p - 1.0)), 1.0 / pwr); // Uncompress
68+
}
69+
}
70+
}
71+
72+
return comprDist;
73+
}
74+
75+
76+
77+
void main
78+
(
79+
input varying float rIn,
80+
input varying float gIn,
81+
input varying float bIn,
82+
input varying float aIn,
83+
output varying float rOut,
84+
output varying float gOut,
85+
output varying float bOut,
86+
output varying float aOut,
87+
input uniform bool invert = false
88+
)
89+
{
90+
// Source values
91+
float ACES[3] = {rIn, gIn, bIn};
92+
93+
// Convert to ACEScg
94+
float linAP1[3] = mult_f3_f44(ACES, AP0_2_AP1_MAT);
95+
96+
// Achromatic axis
97+
float ach = max_f3(linAP1);
98+
99+
// Distance from the achromatic axis for each color component aka inverse RGB ratios
100+
float dist[3];
101+
if (ach == 0.0) {
102+
dist[0] = 0.0;
103+
dist[1] = 0.0;
104+
dist[2] = 0.0;
105+
}
106+
else {
107+
dist[0] = (ach - linAP1[0]) / fabs(ach);
108+
dist[1] = (ach - linAP1[1]) / fabs(ach);
109+
dist[2] = (ach - linAP1[2]) / fabs(ach);
110+
}
111+
112+
// Compress distance with parameterized shaper function
113+
float comprDist[3] = {
114+
compress(dist[0], LIM_CYAN, THR_CYAN, PWR, invert),
115+
compress(dist[1], LIM_MAGENTA, THR_MAGENTA, PWR, invert),
116+
compress(dist[2], LIM_YELLOW, THR_YELLOW, PWR, invert)
117+
};
118+
119+
// Recalculate RGB from compressed distance and achromatic
120+
float comprLinAP1[3] = {
121+
ach - comprDist[0] * fabs(ach),
122+
ach - comprDist[1] * fabs(ach),
123+
ach - comprDist[2] * fabs(ach)
124+
};
125+
126+
// Convert back to ACES2065-1
127+
ACES = mult_f3_f44(comprLinAP1, AP1_2_AP0_MAT);
128+
129+
// Write output
130+
rOut = ACES[0];
131+
gOut = ACES[1];
132+
bOut = ACES[2];
133+
aOut = aIn;
134+
}

0 commit comments

Comments
 (0)