Skip to content

Commit 428094d

Browse files
authored
Merge pull request #7 from nick-shaw/feature/bare_bones
Feature/bare bones
2 parents 051b51b + 586c6eb commit 428094d

File tree

10 files changed

+222
-1655
lines changed

10 files changed

+222
-1655
lines changed

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@ The following implementations are available in the [model](model) directory:
2828
- Nuke Script for [The Foundry Nuke](https://www.foundry.com/products/nuke)
2929
- Python Script for [Numpy](https://numpy.org/)
3030

31+
## Default Parameter Values
32+
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.
33+
3134
## Research
3235

3336
The [research](research) directory contains a mix of useful resources pertaining to research, experimentation and analysis conducted by the Virtual Working Group.

model/GamutCompress.blink

Lines changed: 17 additions & 173 deletions
Original file line numberDiff line numberDiff line change
@@ -5,186 +5,45 @@ kernel GamutCompression : public ImageComputationKernel<ePixelWise> {
55
param:
66
float3 threshold;
77
float p;
8-
float shd_rolloff;
98
float cyan;
109
float magenta;
1110
float yellow;
1211
float p_Height;
13-
int method;
14-
bool hexagonal;
1512
bool invert;
1613
bool overlay;
1714

1815
local:
1916
float3 thr;
2017
float3 lim;
21-
float pi;
2218

2319
void init() {
24-
pi = 3.14159265359;
25-
2620
// thr is the percentage of the core gamut to preserve
2721
thr = float3(min(0.9999f, threshold.x), min(0.9999f, threshold.y), min(0.9999f, threshold.z));
2822

2923
// lim is the max distance from the gamut boundary that will be compressed
3024
// 0 is a no-op, 1 will compress colors from a distance of 2.0 from achromatic to the gamut boundary
31-
// if method is Reinhard or Power, use the limit as-is
32-
if (method == 1 || method == 2) {
33-
lim = float3(cyan+1.0f, magenta+1.0f, yellow+1.0f);
34-
} else {
35-
// otherwise, we have to bruteforce the value of limit
36-
// such that lim is the value of x where y=1.0f
37-
// importantly, this runs once at the beginning of evaluation, NOT per-pixel!!!
38-
lim = float3(
39-
bisect(max(0.0001f, cyan)+1.0f, thr.x),
40-
bisect(max(0.0001f, magenta)+1.0f, thr.y),
41-
bisect(max(0.0001f, yellow)+1.0f, thr.z));
42-
}
43-
}
44-
45-
// calculate hyperbolic tangent
46-
float tanh( float in) {
47-
float f = exp(2.0f*in);
48-
return (f-1.0f) / (f+1.0f);
49-
}
50-
51-
// calculate inverse hyperbolic tangent
52-
float atanh( float in) {
53-
return log((1.0f+in)/(1.0f-in))/2.0f;
54-
}
55-
56-
// compression function which gives the y=1 x intersect at y=0
57-
float f(float x, float k, float t) {
58-
if (method == 0) {
59-
// natural logarithm compression method
60-
return (exp((1.0f-t+t*log(1.0f-x)-x*t*log(1.0f-x))/(t*(1.0f-x))))*t+x*t-k;
61-
} else if (method == 1 || method == 2) {
62-
return k;
63-
} else if (method == 3) {
64-
// natural exponent compression method
65-
return -log((-x+1.0f)/(t-x))*(-t+x)+t-k;
66-
} else if (method == 4) {
67-
// arctangent compression method
68-
return (2*tan( (pi*(1.0f-t))/(2.0f*(x-t)))*(x-t))/pi+t-k;
69-
} else if (method == 5) {
70-
// hyperbolic tangent compression method
71-
return atanh((1.0f-t)/(x-t))*(x-t)+t-k;
72-
}
73-
}
74-
75-
int _sign(float x) {
76-
return x == 0.0f ? 0.0f : x > 0.0f ? 1.0f : 0.0f;
25+
lim = float3(cyan+1.0f, magenta+1.0f, yellow+1.0f);
7726
}
7827

79-
float bisect(float k, float t) {
80-
// use a simple bisection algorithm to bruteforce the root of f
81-
// returns an approximation of the value of limit
82-
// such that the compression function intersects y=1 at desired value k
83-
// this allows us to specify the max distance we will compress to the gamut boundary
84-
85-
float a, b, c, y;
86-
float tol = 0.0001f; // accuracy of estimate
87-
int nmax = 100; // max iterations
88-
89-
// set up reasonable initial guesses for each method given output ranges of each function
90-
if (method == 0) {
91-
// natural logarithm needs a limit between -inf (linear), and 1 (clip)
92-
a = -15.0f;
93-
b = 0.98f;
94-
} else if (method == 5) {
95-
// tanh needs more precision
96-
a = 1.000001f;
97-
b = 5.0f;
98-
} else {
99-
a = 1.0001f;
100-
b = 5.0f;
101-
}
102-
103-
if (_sign(f(a, k, t)) == _sign(f(b, k, t))) {
104-
// bad estimate. return something close to linear
105-
if ((method == 0) || (method == 2)) {
106-
return -100.0f;
107-
} else {
108-
return 1.999999f;
109-
}
110-
}
111-
c = (a+b)/2.0f;
112-
y = f(c, k, t);
113-
if (fabs(y) <= tol) {
114-
return c; // lucky guess
115-
}
116-
117-
int n = 1;
118-
while ((fabs(y) > tol) && (n <= nmax)) {
119-
if (_sign(y) == _sign(f(a, k, t))) {
120-
a = c;
121-
} else {
122-
b = c;
123-
}
124-
c = (a+b)/2.0f;
125-
y = f(c, k, t);
126-
n += 1;
127-
}
128-
return c;
129-
}
130-
131-
13228
// calculate compressed distance
13329
float compress(float x, float l, float t) {
13430
float cx, s;
13531
if (x < t) {
13632
cx = x;
13733
} else {
138-
if (method == 0) {
139-
// natural logarithm compression method: https://www.desmos.com/calculator/hmzirlw7tj
140-
// inspired by ITU-R BT.2446 http://www.itu.int/pub/R-REP-BT.2446-2019
141-
if (invert == 0) {
142-
cx = t*log(x/t-l)-l*t*log(x/t-l)+t-t*log(1.0f-l)+l*t*log(1.0f-l);
143-
} else {
144-
cx = exp((x-t+t*log(1.0f-l)-l*t*log(1.0f-l))/(t*(1.0f-l)))*t+l*t;
145-
}
146-
} else if (method == 1) {
147-
// simple Reinhard type compression method: https://www.desmos.com/calculator/lkhdtjbodx
148-
if (invert == 0) {
149-
cx = t+1.0f/(1.0f/(x-t)+1.0f/(1.0f-t)-1.0f/(l-t));
150-
} else {
151-
cx = t+1.0f/(1.0f/(x-t)-1.0f/(1.0f-t)+1.0f/(l-t));
152-
}
153-
} else if (method == 2) {
154-
// power(p) compression function plot https://www.desmos.com/calculator/54aytu7hek
155-
if (l < 1.0001) {
156-
return x; // disable compression, avoid nan
157-
}
158-
s = (l-t)/pow(pow((1.0f-t)/(l-t),-p)-1.0f,1.0f/p); // calc y=1 intersect
159-
if (invert == 0) {
160-
cx = t+s*((x-t)/s)/(pow(1.0f+pow((x-t)/s,p),1.0f/p)); // compress
34+
// power(p) compression function plot https://www.desmos.com/calculator/54aytu7hek
35+
if (l < 1.0001) {
36+
return x; // disable compression, avoid nan
37+
}
38+
s = (l-t)/pow(pow((1.0f-t)/(l-t),-p)-1.0f,1.0f/p); // calc y=1 intersect
39+
if (invert == 0) {
40+
cx = t+s*((x-t)/s)/(pow(1.0f+pow((x-t)/s,p),1.0f/p)); // compress
41+
} else {
42+
if (x > (t + s)) {
43+
cx = x; // avoid singularity
16144
} else {
162-
if (x > (t + s)) {
163-
cx = x; // avoid singularity
164-
}
16545
cx = t+s*pow(-(pow((x-t)/s,p)/(pow((x-t)/s,p)-1.0f)),1.0f/p); // uncompress
16646
}
167-
} else if (method == 3) {
168-
// natural exponent compression method: https://www.desmos.com/calculator/s2adnicmmr
169-
if (invert == 0) {
170-
cx = l-(l-t)*exp(-(((x-t)*((1.0f*l)/(l-t))/l)));
171-
} else {
172-
cx = -log((x-l)/(t-l))*(-t+l)/1.0f+t;
173-
}
174-
} else if (method == 4) {
175-
// arctangent compression method: https://www.desmos.com/calculator/h96qmnozpo
176-
if (invert == 0) {
177-
cx = t+(l-t)*2/pi*atan(pi/2*(x-t)/(l-t));
178-
} else {
179-
cx = t+(l-t)*2/pi*tan(pi/2*(x-t)/(l-t));
180-
}
181-
} else if (method == 5) {
182-
// hyperbolic tangent compression method: https://www.desmos.com/calculator/xiwliws24x
183-
if (invert == 0) {
184-
cx = t+(l-t)*tanh(((x-t)/(l-t)));
185-
} else {
186-
cx = t+(l-t)*atanh(x/(l-t)-t/(l-t));
187-
}
18847
}
18948
}
19049
return cx;
@@ -202,36 +61,21 @@ kernel GamutCompression : public ImageComputationKernel<ePixelWise> {
20261
// achromatic axis
20362
float ach = max(rgb.x, max(rgb.y, rgb.z));
20463

205-
// achromatic with shadow rolloff below shd_rolloff threshold
206-
float ach_shd = 1.0f-( (1.0f-ach)<(1.0f-shd_rolloff)?(1.0f-ach):(1.0f-shd_rolloff)+shd_rolloff*tanh((((1.0f-ach)-(1.0f-shd_rolloff))/shd_rolloff)));
207-
20864
// distance from the achromatic axis for each color component aka inverse rgb ratios
20965
// distance is normalized by achromatic, so that 1.0f is at gamut boundary, avoid 0 div
210-
float3 dist = ach_shd == 0 ? float3(0.0f, 0.0f, 0.0f) : (ach-rgb)/ach_shd;
66+
float3 dist = ach == 0 ? float3(0.0f, 0.0f, 0.0f) : (ach-rgb)/fabs(ach);
21167

21268
// compress distance with user controlled parameterized shaper function
21369
float sat;
21470
float3 csat, cdist;
215-
if (hexagonal) {
216-
sat = max(dist.x, max(dist.y, dist.z));
217-
csat = float3(
218-
compress(sat, lim.x, thr.x),
219-
compress(sat, lim.y, thr.y),
220-
compress(sat, lim.z, thr.z));
221-
cdist = sat == 0 ? dist : float3(
222-
dist.x * csat.x / sat,
223-
dist.y * csat.y / sat,
224-
dist.z * csat.z / sat);
225-
} else {
226-
cdist = float3(
227-
compress(dist.x, lim.x, thr.x),
228-
compress(dist.y, lim.y, thr.y),
229-
compress(dist.z, lim.z, thr.z));
230-
}
71+
cdist = float3(
72+
compress(dist.x, lim.x, thr.x),
73+
compress(dist.y, lim.y, thr.y),
74+
compress(dist.z, lim.z, thr.z));
23175

23276
// recalculate rgb from compressed distance and achromatic
23377
// effectively this scales each color component relative to achromatic axis by the compressed distance
234-
float3 crgb = ach-cdist*ach_shd;
78+
float3 crgb = ach-cdist*fabs(ach);
23579

23680
// Graph overlay method based on one by Paul Dore
23781
// https://github.com/baldavenger/DCTLs/tree/master/ACES%20TOOLS

0 commit comments

Comments
 (0)