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);
4- 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.0f, 0.0f, 0.03f, 0.0f);
6- DEFINE_UI_PARAMS(cyan, cyan, DCTLUI_SLIDER_FLOAT, 0.09f, 0.0f, 1.0f, 0.0f);
7- DEFINE_UI_PARAMS(magenta, magenta, DCTLUI_SLIDER_FLOAT, 0.24f, 0.0f, 1.0f, 0.0f);
8- DEFINE_UI_PARAMS(yellow, yellow, DCTLUI_SLIDER_FLOAT, 0.12f, 0.0f, 1.0f, 0.0f);
1+ DEFINE_UI_PARAMS(th_c, thr c, DCTLUI_SLIDER_FLOAT, 0.15, 0.0, 0.3, 0.0);
2+ DEFINE_UI_PARAMS(th_m, thr m, DCTLUI_SLIDER_FLOAT, 0.15, 0.0, 0.3, 0.0);
3+ DEFINE_UI_PARAMS(th_y, thr y, DCTLUI_SLIDER_FLOAT, 0.15, 0.0, 0.3, 0.0);
4+ DEFINE_UI_PARAMS(d_c, dist c, DCTLUI_SLIDER_FLOAT, 0.1, 0.0, 0.4, 0.0);
5+ DEFINE_UI_PARAMS(d_m, dist m, DCTLUI_SLIDER_FLOAT, 0.2, 0.0, 0.4, 0.0);
6+ DEFINE_UI_PARAMS(d_y, dist y, DCTLUI_SLIDER_FLOAT, 0.1, 0.0, 0.4, 0.0);
97DEFINE_UI_PARAMS(working_colorspace, working space, DCTLUI_COMBO_BOX, 0, {acescct, acescc, acescg}, {acescct, acescc, acescg});
108DEFINE_UI_PARAMS(invert, invert, DCTLUI_CHECK_BOX, 0);
119
@@ -50,37 +48,12 @@ __DEVICE__ float acescc_to_lin(float in) {
5048 }
5149}
5250
53- // calculate compressed distance
54- __DEVICE__ float compress(float x, float l, float t, float p, bool invert) {
55- float cdist;
56- // power(p) compression function plot https://www.desmos.com/calculator/54aytu7hek
57- float s = (l-t)/_powf(_powf((1.0f-t)/(l-t),-p)-1.0f,1.0f/p); // calc y=1 intersect
58- if (l < 1.0001f) {
59- return x; // disable compression, avoid nan
60- }
61- if (x < t) {
62- cdist = x;
63- }
64- else {
65- if (invert == 0) {
66- cdist = t+s*((x-t)/s)/(_powf(1.0f+_powf((x-t)/s,p),1.0f/p)); // compress
67- } else {
68- if (x > (t + s)) {
69- cdist = x; // avoid singularity
70- }
71- cdist = t+s*_powf(-(_powf((x-t)/s,p)/(_powf((x-t)/s,p)-1.0f)),1.0f/p); // uncompress
72- }
73- }
74- return cdist;
75- }
76-
7751__DEVICE__ float3 transform(int p_Width, int p_Height, int p_X, int p_Y, float p_R, float p_G, float p_B)
7852{
7953
80- // source pixels
8154 float3 rgb = make_float3(p_R, p_G, p_B);
8255
83- // working colorspace to linear
56+ // Linearize working colorspace
8457 if (working_colorspace == acescct) {
8558 rgb.x = acescct_to_lin(rgb.x);
8659 rgb.y = acescct_to_lin(rgb.y);
@@ -91,69 +64,50 @@ __DEVICE__ float3 transform(int p_Width, int p_Height, int p_X, int p_Y, float p
9164 rgb.z = acescc_to_lin(rgb.z);
9265 }
9366
94- // thr is the percentage of the core gamut to protect: the complement of threshold.
95- float3 thr = make_float3(
96- 1.0f-_fmaxf(0.00001, threshold_c),
97- 1.0f-_fmaxf(0.00001, threshold_m),
98- 1.0f-_fmaxf(0.00001, threshold_y));
99-
100- // lim is the distance beyond the gamut boundary that will be compressed to the gamut boundary.
101- // lim = 0.2 will compress from a distance of 1.2 from achromatic to 1.0 (the gamut boundary).
102- float3 lim;
103- lim = make_float3(cyan+1.0f, magenta+1.0f, yellow+1.0f);
104-
105- // achromatic axis
106- float ach = _fmaxf(rgb.x, _fmaxf(rgb.y, rgb.z));
107-
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- }
67+ // Amount of outer gamut to affect
68+ float3 th = 1.0f-make_float3(th_c, th_m, th_y);
69+
70+ // Distance limit: How far beyond the gamut boundary to compress
71+ float3 dl = 1.0f+make_float3(d_c, d_m, d_y);
12072
121- // distance from the achromatic axis for each color component aka inverse rgb ratios
122- // distance is normalized by achromatic, so that 1.0f is at gamut boundary. avoid 0 div
123- float3 dist;
124- dist.x = ach_shd == 0.0f ? 0.0f : (ach-rgb.x)/ach_shd;
125- dist.y = ach_shd == 0.0f ? 0.0f : (ach-rgb.y)/ach_shd;
126- dist.z = ach_shd == 0.0f ? 0.0f : (ach-rgb.z)/ach_shd;
73+ // Calculate scale so compression function passes through distance limit: (x=dl, y=1)
74+ float3 s;
75+ s.x = (1.0f-th.x)/_sqrtf(_fmaxf(1.001f, dl.x)-1.0f);
76+ s.y = (1.0f-th.y)/_sqrtf(_fmaxf(1.001f, dl.y)-1.0f);
77+ s.z = (1.0f-th.z)/_sqrtf(_fmaxf(1.001f, dl.z)-1.0f);
78+
79+ // Achromatic axis
80+ float ac = _fmaxf(rgb.x, _fmaxf(rgb.y, rgb.z));
12781
128- // compress distance with user controlled parameterized shaper function
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));
82+ // Inverse RGB Ratios: distance from achromatic axis
83+ float3 d = ac == 0.0f ? make_float3(0.0f) : (ac-rgb)/_fabs(ac);
13384
134- // recalculate rgb from compressed distance and achromatic
135- // effectively this scales each color component relative to achromatic axis by the compressed distance
136- float3 crgb = make_float3(
137- ach-cdist.x*ach_shd,
138- ach-cdist.y*ach_shd,
139- ach-cdist.z*ach_shd);
85+ float3 cd; // Compressed distance
86+ // Parabolic compression function: https://www.desmos.com/calculator/nvhp63hmtj
87+ if (invert == 0) {
88+ cd.x = d.x<th.x?d.x:s.x*_sqrtf(d.x-th.x+s.x*s.x/4.0f)-s.x*_sqrtf(s.x*s.x/4.0f)+th.x;
89+ cd.y = d.y<th.y?d.y:s.y*_sqrtf(d.y-th.y+s.y*s.y/4.0f)-s.y*_sqrtf(s.y*s.y/4.0f)+th.y;
90+ cd.z = d.z<th.z?d.z:s.z*_sqrtf(d.z-th.z+s.z*s.z/4.0f)-s.z*_sqrtf(s.z*s.z/4.0f)+th.z;
91+ } else {
92+ cd.x = d.x<th.x?d.x:_powf(d.x-th.x+s.x*_sqrtf(s.x*s.x/4.0f),2.0f)/(s.x*s.x)-s.x*s.x/4.0f+th.x;
93+ cd.y = d.y<th.y?d.y:_powf(d.y-th.y+s.y*_sqrtf(s.y*s.y/4.0f),2.0f)/(s.y*s.y)-s.y*s.y/4.0f+th.y;
94+ cd.z = d.z<th.z?d.z:_powf(d.z-th.z+s.z*_sqrtf(s.z*s.z/4.0f),2.0f)/(s.z*s.z)-s.z*s.z/4.0f+th.z;
95+ }
14096
141- // catch nans just in case
142- crgb.x = isnan(crgb.x) ? 0.0f : crgb.x;
143- crgb.y = isnan(crgb.y) ? 0.0f : crgb.y;
144- crgb.z = isnan(crgb.z) ? 0.0f : crgb.z;
97+ // Inverse RGB Ratios to RGB
98+ rgb = ac-cd*_fabs(ac);
14599
146- // linear to working colorspace
100+ // Linear to working colorspace
147101 if (working_colorspace == acescct) {
148- crgb .x = lin_to_acescct(crgb .x);
149- crgb .y = lin_to_acescct(crgb .y);
150- crgb .z = lin_to_acescct(crgb .z);
102+ rgb .x = lin_to_acescct(rgb .x);
103+ rgb .y = lin_to_acescct(rgb .y);
104+ rgb .z = lin_to_acescct(rgb .z);
151105 } else if (working_colorspace == acescc) {
152- crgb .x = lin_to_acescc(crgb .x);
153- crgb .y = lin_to_acescc(crgb .y);
154- crgb .z = lin_to_acescc(crgb .z);
106+ rgb .x = lin_to_acescc(rgb .x);
107+ rgb .y = lin_to_acescc(rgb .y);
108+ rgb .z = lin_to_acescc(rgb .z);
155109 }
156110
157- // write output
158- return crgb ;
111+ // Return output RGB
112+ return rgb ;
159113}
0 commit comments