Skip to content

Commit 167de6b

Browse files
author
Jed Smith
committed
bug fixes for negative components, remove hexagonal mode, shd_rolloff defaults to 0.0
1 parent 89b00c1 commit 167de6b

File tree

7 files changed

+152
-253
lines changed

7 files changed

+152
-253
lines changed

GamutCompress.blink

Lines changed: 21 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ kernel GamutCompression : public ImageComputationKernel<ePixelWise> {
99
float cyan;
1010
float magenta;
1111
float yellow;
12-
bool hexagonal;
1312
bool invert;
1413

1514
local:
@@ -35,6 +34,7 @@ kernel GamutCompression : public ImageComputationKernel<ePixelWise> {
3534
float compress(float x, float l, float t) {
3635
float cdist;
3736
// power(p) compression function plot https://www.desmos.com/calculator/54aytu7hek
37+
// suggested by James Eggleton https://community.acescentral.com/t/gamut-mapping-compression-curves/3073/10
3838
float s = (l-t)/pow(pow((1.0f-t)/(l-t),-p)-1.0f,1.0f/p); // calc y=1 intersect
3939
if (l < 1.0001) {
4040
return x; // disable compression, avoid nan
@@ -62,33 +62,28 @@ kernel GamutCompression : public ImageComputationKernel<ePixelWise> {
6262
// achromatic axis
6363
float ach = max(rgb.x, max(rgb.y, rgb.z));
6464

65-
// achromatic with shadow rolloff below shd_rolloff threshold
66-
float ach_shd = 1.0f-( (1.0f-ach)<(1.0f-shd_rolloff)?(1.0f-ach):
67-
(1.0f-shd_rolloff)+shd_rolloff*tanh((((1.0f-ach)-(1.0f-shd_rolloff))/shd_rolloff)));
68-
69-
// distance from the achromatic axis for each color component aka inverse rgb ratios
70-
// distance is normalized by achromatic, so that 1.0f is at gamut boundary. avoid 0 div
71-
float3 dist = ach_shd == 0 ? float3(0.0f, 0.0f, 0.0f) : (ach-rgb)/ach_shd;
65+
// achromatic shadow rolloff
66+
float ach_shd;
67+
if (shd_rolloff < 0.004f) {
68+
// disable shadow rolloff functionality.
69+
// values below 0.004 cause strange behavior, actually increasing distance in some cases.
70+
// if ach < 0.0 and shd_rolloff is disabled, take absolute value. This preserves negative components after compression.
71+
ach_shd = fabs(ach);
72+
} else {
73+
// lift ach below threshold using a tanh compression function.
74+
// this reduces large distance values in shadow grain, which can cause differences when inverting.
75+
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)));
76+
}
77+
78+
// distance from the achromatic axis for each color component aka inverse rgb ratios.
79+
// we normalize the distance by dividing by achromatic, so that 1.0 is at gamut boundary, avoid 0 division errors.
80+
float3 dist = ach_shd == 0.0f ? float3(0.0f, 0.0f, 0.0f) : (ach-rgb)/ach_shd;
7281

7382
// compress distance with parameterized compression function
74-
float sat;
75-
float3 csat, cdist;
76-
if (hexagonal) {
77-
sat = max(dist.x, max(dist.y, dist.z));
78-
csat = float3(
79-
compress(sat, lim.x, thr.x),
80-
compress(sat, lim.y, thr.y),
81-
compress(sat, lim.z, thr.z));
82-
cdist = sat == 0.0f ? dist : float3(
83-
dist.x * csat.x / sat,
84-
dist.y * csat.y / sat,
85-
dist.z * csat.z / sat);
86-
} else {
87-
cdist = float3(
88-
compress(dist.x, lim.x, thr.x),
89-
compress(dist.y, lim.y, thr.y),
90-
compress(dist.z, lim.z, thr.z));
91-
}
83+
float3 cdist = float3(
84+
compress(dist.x, lim.x, thr.x),
85+
compress(dist.y, lim.y, thr.y),
86+
compress(dist.z, lim.z, thr.z));
9287

9388
// recalculate rgb from compressed distance and achromatic
9489
// effectively this scales each color component relative to achromatic axis by the compressed distance

GamutCompress.dctl

Lines changed: 28 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,16 @@
1-
DEFINE_UI_PARAMS(threshold_r, threshold r, DCTLUI_SLIDER_FLOAT, 0.2f, 0.0f, 0.6f, 0.0f);
2-
DEFINE_UI_PARAMS(threshold_g, threshold g, DCTLUI_SLIDER_FLOAT, 0.2f, 0.0f, 0.6f, 0.0f);
3-
DEFINE_UI_PARAMS(threshold_b, threshold b, DCTLUI_SLIDER_FLOAT, 0.2f, 0.0f, 0.6f, 0.0f);
1+
DEFINE_UI_PARAMS(threshold_c, threshold c, DCTLUI_SLIDER_FLOAT, 0.2f, 0.0f, 0.6f, 0.0f);
2+
DEFINE_UI_PARAMS(threshold_m, threshold m, DCTLUI_SLIDER_FLOAT, 0.2f, 0.0f, 0.6f, 0.0f);
3+
DEFINE_UI_PARAMS(threshold_y, threshold y, DCTLUI_SLIDER_FLOAT, 0.2f, 0.0f, 0.6f, 0.0f);
44
DEFINE_UI_PARAMS(power, power, DCTLUI_SLIDER_FLOAT, 1.2f, 1.0f, 3.0f, 1.0f);
5-
DEFINE_UI_PARAMS(shd_rolloff, shd rolloff, DCTLUI_SLIDER_FLOAT, 0.03f, 0.0f, 0.1f, 0.0f);
5+
DEFINE_UI_PARAMS(shd_rolloff, shd rolloff, DCTLUI_SLIDER_FLOAT, 0.0f, 0.0f, 0.03f, 0.0f);
66
DEFINE_UI_PARAMS(cyan, cyan, DCTLUI_SLIDER_FLOAT, 0.09f, 0.0f, 1.0f, 0.0f);
77
DEFINE_UI_PARAMS(magenta, magenta, DCTLUI_SLIDER_FLOAT, 0.24f, 0.0f, 1.0f, 0.0f);
88
DEFINE_UI_PARAMS(yellow, yellow, DCTLUI_SLIDER_FLOAT, 0.12f, 0.0f, 1.0f, 0.0f);
9-
DEFINE_UI_PARAMS(hexagonal, hexagonal, DCTLUI_CHECK_BOX, 0);
109
DEFINE_UI_PARAMS(working_colorspace, working space, DCTLUI_COMBO_BOX, 0, {acescct, acescc, acescg}, {acescct, acescc, acescg});
1110
DEFINE_UI_PARAMS(invert, invert, DCTLUI_CHECK_BOX, 0);
1211

1312

14-
// Convert acescg to acescct
13+
// convert acescg to acescct
1514
__DEVICE__ float lin_to_acescct(float in) {
1615
if (in <= 0.0078125f) {
1716
return 10.5402377416545f * in + 0.0729055341958355f;
@@ -20,7 +19,7 @@ __DEVICE__ float lin_to_acescct(float in) {
2019
}
2120
}
2221

23-
// Convert acescct to acescg
22+
// convert acescct to acescg
2423
__DEVICE__ float acescct_to_lin(float in) {
2524
if (in > 0.155251141552511f) {
2625
return _powf( 2.0f, in*17.52f - 9.72f);
@@ -29,7 +28,7 @@ __DEVICE__ float acescct_to_lin(float in) {
2928
}
3029
}
3130

32-
// Convert acescg to acescc
31+
// convert acescg to acescc
3332
__DEVICE__ float lin_to_acescc(float in) {
3433
if (in <= 0.0f) {
3534
return -0.3584474886f;
@@ -40,7 +39,7 @@ __DEVICE__ float lin_to_acescc(float in) {
4039
}
4140
}
4241

43-
// Convert acescc to acescg
42+
// convert acescc to acescg
4443
__DEVICE__ float acescc_to_lin(float in) {
4544
if (in < -0.3013698630f) {
4645
return (_powf( 2.0f, in * 17.52f - 9.72f) - _powf( 2.0f, -16.0f)) * 2.0f;
@@ -94,9 +93,9 @@ __DEVICE__ float3 transform(int p_Width, int p_Height, int p_X, int p_Y, float p
9493

9594
// thr is the percentage of the core gamut to protect: the complement of threshold.
9695
float3 thr = make_float3(
97-
1.0f-_fmaxf(0.00001, threshold_r),
98-
1.0f-_fmaxf(0.00001, threshold_g),
99-
1.0f-_fmaxf(0.00001, threshold_b));
96+
1.0f-_fmaxf(0.00001, threshold_c),
97+
1.0f-_fmaxf(0.00001, threshold_m),
98+
1.0f-_fmaxf(0.00001, threshold_y));
10099

101100
// lim is the distance beyond the gamut boundary that will be compressed to the gamut boundary.
102101
// lim = 0.2 will compress from a distance of 1.2 from achromatic to 1.0 (the gamut boundary).
@@ -106,9 +105,19 @@ __DEVICE__ float3 transform(int p_Width, int p_Height, int p_X, int p_Y, float p
106105
// achromatic axis
107106
float ach = _fmaxf(rgb.x, _fmaxf(rgb.y, rgb.z));
108107

109-
// achromatic with shadow rolloff below shd_rolloff threshold
110-
float ach_shd = 1.0f-((1.0f-ach)<(1.0f-shd_rolloff)?(1.0f-ach):(1.0f-shd_rolloff)+shd_rolloff*_tanhf((((1.0f-ach)-(1.0f-shd_rolloff))/shd_rolloff)));
111-
108+
// achromatic shadow rolloff
109+
float ach_shd;
110+
if (shd_rolloff < 0.004f) {
111+
// disable shadow rolloff functionality.
112+
// values below 0.004 cause strange behavior, actually increasing distance in some cases.
113+
// if ach < 0.0 and shd_rolloff is disabled, take absolute value. This preserves negative components after compression.
114+
ach_shd = _fabs(ach);
115+
} else {
116+
// lift ach below threshold using a tanh compression function.
117+
// this reduces large distance values in shadow grain, which can cause differences when inverting.
118+
ach_shd = 1.0f-((1.0f-ach)<(1.0f-shd_rolloff)?(1.0f-ach):(1.0f-shd_rolloff)+shd_rolloff*_tanhf((((1.0f-ach)-(1.0f-shd_rolloff))/shd_rolloff)));
119+
}
120+
112121
// distance from the achromatic axis for each color component aka inverse rgb ratios
113122
// distance is normalized by achromatic, so that 1.0f is at gamut boundary. avoid 0 div
114123
float3 dist;
@@ -117,26 +126,10 @@ __DEVICE__ float3 transform(int p_Width, int p_Height, int p_X, int p_Y, float p
117126
dist.z = ach_shd == 0.0f ? 0.0f : (ach-rgb.z)/ach_shd;
118127

119128
// compress distance with user controlled parameterized shaper function
120-
float sat;
121-
float3 csat, cdist;
122-
if (hexagonal) {
123-
// Based on Nick Shaw's variation on the gamut mapping algorithm
124-
// https://community.acescentral.com/t/a-variation-on-jeds-rgb-gamut-mapper/3060
125-
sat = _fmaxf(dist.x, _fmaxf(dist.y, dist.z));
126-
csat = make_float3(
127-
compress(sat, lim.x, thr.x, power, invert),
128-
compress(sat, lim.y, thr.y, power, invert),
129-
compress(sat, lim.z, thr.z, power, invert));
130-
cdist = sat == 0.0f ? dist : make_float3(
131-
dist.x * csat.x / sat,
132-
dist.y * csat.y / sat,
133-
dist.z * csat.z / sat);
134-
} else {
135-
cdist = make_float3(
136-
compress(dist.x, lim.x, thr.x, power, invert),
137-
compress(dist.y, lim.y, thr.y, power, invert),
138-
compress(dist.z, lim.z, thr.z, power, invert));
139-
}
129+
float3 cdist = make_float3(
130+
compress(dist.x, lim.x, thr.x, power, invert),
131+
compress(dist.y, lim.y, thr.y, power, invert),
132+
compress(dist.z, lim.z, thr.z, power, invert));
140133

141134
// recalculate rgb from compressed distance and achromatic
142135
// effectively this scales each color component relative to achromatic axis by the compressed distance

GamutCompress.fuse

Lines changed: 31 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ Be wary of high values if accurate inversion is important to you.
2727
Shadow Rolloff
2828
Reduce gamut compression in dark areas below specified value.
2929
Helps reduce invertability issues in negative values from grain.
30+
Make sure there is only shadow grain below the threshold you specify.
3031

3132
Max Distance
3233
Per color component control to specify what distance will be compressed to the gamut boundary.
@@ -75,31 +76,23 @@ FuRegisterClass("GamutCompress", CT_Tool, {
7576

7677

7778
function Create()
78-
InHexagonal = self:AddInput("hexagonal", "hexagonal", {
79-
LINKID_DataType = "Number",
80-
INPID_InputControl = "CheckboxControl",
81-
INP_MinAllowed = 0.0,
82-
INP_MaxAllowed = 1.0,
83-
INP_Default = 0.0,
84-
})
85-
86-
InThresholdR = self:AddInput("threshold_r", "threshold_r", {
79+
InThresholdR = self:AddInput("threshold_c", "threshold_c", {
8780
LINKID_DataType = "Number",
8881
INPID_InputControl = "SliderControl",
8982
INP_Default = 0.2,
9083
INP_MinAllowed = 0.0001,
9184
INP_MaxScale = 0.6,
9285
})
9386

94-
InThresholdG = self:AddInput("threshold_g", "threshold_g", {
87+
InThresholdG = self:AddInput("threshold_m", "threshold_m", {
9588
LINKID_DataType = "Number",
9689
INPID_InputControl = "SliderControl",
9790
INP_Default = 0.2,
9891
INP_MinAllowed = 0.0001,
9992
INP_MaxScale = 0.6,
10093
})
10194

102-
InThresholdB = self:AddInput("threshold_b", "threshold_b", {
95+
InThresholdB = self:AddInput("threshold_y", "threshold_y", {
10396
LINKID_DataType = "Number",
10497
INPID_InputControl = "SliderControl",
10598
INP_Default = 0.2,
@@ -118,9 +111,9 @@ function Create()
118111
InShdRolloff = self:AddInput("shd rolloff", "shd rolloff", {
119112
LINKID_DataType = "Number",
120113
INPID_InputControl = "SliderControl",
121-
INP_Default = 0.03,
114+
INP_Default = 0.0,
122115
INP_MinAllowed = 0.0,
123-
INP_MaxScale = 0.1,
116+
INP_MaxScale = 0.03,
124117
})
125118

126119
self:BeginControlNest("max distance limits", "max distance limits", true, {})
@@ -179,10 +172,9 @@ function Process(req)
179172
local node = DVIPComputeNode(req, "SolidKernel", SolidKernel, "SolidParams", SolidParams)
180173
local params = node:GetParamBlock(SolidParams)
181174

182-
params.hexagonal = InHexagonal:GetValue(req).Value
183-
params.threshold_r = InThresholdR:GetValue(req).Value
184-
params.threshold_g = InThresholdG:GetValue(req).Value
185-
params.threshold_b = InThresholdB:GetValue(req).Value
175+
params.threshold_c = InThresholdR:GetValue(req).Value
176+
params.threshold_m = InThresholdG:GetValue(req).Value
177+
params.threshold_y = InThresholdB:GetValue(req).Value
186178
params.power = InPower:GetValue(req).Value
187179
params.shd_rolloff = InShdRolloff:GetValue(req).Value
188180
params.cyan = InCyan:GetValue(req).Value
@@ -209,10 +201,9 @@ end
209201

210202

211203
SolidParams = [[
212-
int hexagonal;
213-
float threshold_r;
214-
float threshold_g;
215-
float threshold_b;
204+
float threshold_c;
205+
float threshold_m;
206+
float threshold_y;
216207
float power;
217208
float shd_rolloff;
218209
float cyan;
@@ -255,9 +246,9 @@ SolidKernel = [[
255246

256247
// thr is the percentage of the core gamut to protect: the complement of threshold.
257248
float3 thr = make_float3(
258-
1.0f-_fmaxf(0.0001f, params->threshold_r),
259-
1.0f-_fmaxf(0.0001f, params->threshold_g),
260-
1.0f-_fmaxf(0.0001f, params->threshold_b));
249+
1.0f-_fmaxf(0.0001f, params->threshold_c),
250+
1.0f-_fmaxf(0.0001f, params->threshold_m),
251+
1.0f-_fmaxf(0.0001f, params->threshold_y));
261252

262253
// lim is the max distance from the gamut boundary that will be compressed
263254
// 0 is a no-op, 1 will compress colors from a distance of 2.0 from achromatic to the gamut boundary
@@ -268,8 +259,18 @@ SolidKernel = [[
268259
// achromatic axis
269260
float ach = _fmaxf(rgb.x, _fmaxf(rgb.y, rgb.z));
270261

271-
// achromatic with shadow rolloff below shd_rolloff threshold
272-
float ach_shd = 1.0f-((1.0f-ach)<(1.0f-params->shd_rolloff)?(1.0f-ach):(1.0f-params->shd_rolloff)+params->shd_rolloff*_tanhf((((1.0f-ach)-(1.0f-params->shd_rolloff))/params->shd_rolloff)));
262+
// achromatic shadow rolloff
263+
float ach_shd;
264+
if (params->shd_rolloff < 0.004f) {
265+
// disable shadow rolloff functionality.
266+
// values below 0.004 cause strange behavior, actually increasing distance in some cases.
267+
// if ach < 0.0 and shd_rolloff is disabled, take absolute value. This preserves negative components after compression.
268+
ach_shd = _fabs(ach);
269+
} else {
270+
// lift ach below threshold using a tanh compression function.
271+
// this reduces large distance values in shadow grain, which can cause differences when inverting.
272+
ach_shd = 1.0f-((1.0f-ach)<(1.0f-params->shd_rolloff)?(1.0f-ach):(1.0f-params->shd_rolloff)+params->shd_rolloff*_tanhf((((1.0f-ach)-(1.0f-params->shd_rolloff))/params->shd_rolloff)));
273+
}
273274

274275
// distance from the achromatic axis for each color component aka inverse rgb ratios
275276
float3 dist;
@@ -278,26 +279,10 @@ SolidKernel = [[
278279
dist.z = ach_shd == 0.0f ? 0.0f : (ach-rgb.z)/ach_shd;
279280

280281
// compress distance with user controlled parameterized shaper function
281-
float sat;
282-
float3 csat, cdist;
283-
if (params->hexagonal) {
284-
// Based on Nick Shaw's variation on the gamut mapping algorithm
285-
// https://community.acescentral.com/t/a-variation-on-jeds-rgb-gamut-mapper/3060
286-
sat = _fmaxf(dist.x, _fmaxf(dist.y, dist.z));
287-
csat = make_float3(
288-
compress(sat, lim.x, thr.x, params->power, params->invert),
289-
compress(sat, lim.y, thr.y, params->power, params->invert),
290-
compress(sat, lim.z, thr.z, params->power, params->invert));
291-
cdist = sat == 0.0f ? dist : make_float3(
292-
dist.x * csat.x / sat,
293-
dist.y * csat.y / sat,
294-
dist.z * csat.z / sat);
295-
} else {
296-
cdist = make_float3(
297-
compress(dist.x, lim.x, thr.x, params->power, params->invert),
298-
compress(dist.y, lim.y, thr.y, params->power, params->invert),
299-
compress(dist.z, lim.z, thr.z, params->power, params->invert));
300-
}
282+
float3 cdist = make_float3(
283+
compress(dist.x, lim.x, thr.x, params->power, params->invert),
284+
compress(dist.y, lim.y, thr.y, params->power, params->invert),
285+
compress(dist.z, lim.z, thr.z, params->power, params->invert));
301286

302287
// recalculate rgb from compressed distance and achromatic
303288
// effectively this scales each color component relative to achromatic axis by the compressed distance

0 commit comments

Comments
 (0)