Skip to content

Commit

Permalink
bug fixes for negative components, remove hexagonal mode, shd_rolloff…
Browse files Browse the repository at this point in the history
… defaults to 0.0
  • Loading branch information
Jed Smith committed Aug 31, 2020
1 parent 89b00c1 commit 167de6b
Show file tree
Hide file tree
Showing 7 changed files with 152 additions and 253 deletions.
47 changes: 21 additions & 26 deletions GamutCompress.blink
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ kernel GamutCompression : public ImageComputationKernel<ePixelWise> {
float cyan;
float magenta;
float yellow;
bool hexagonal;
bool invert;

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

// achromatic with shadow rolloff below shd_rolloff threshold
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)));

// distance from the achromatic axis for each color component aka inverse rgb ratios
// distance is normalized by achromatic, so that 1.0f is at gamut boundary. avoid 0 div
float3 dist = ach_shd == 0 ? float3(0.0f, 0.0f, 0.0f) : (ach-rgb)/ach_shd;
// achromatic shadow rolloff
float ach_shd;
if (shd_rolloff < 0.004f) {
// disable shadow rolloff functionality.
// values below 0.004 cause strange behavior, actually increasing distance in some cases.
// if ach < 0.0 and shd_rolloff is disabled, take absolute value. This preserves negative components after compression.
ach_shd = fabs(ach);
} else {
// lift ach below threshold using a tanh compression function.
// this reduces large distance values in shadow grain, which can cause differences when inverting.
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)));
}

// distance from the achromatic axis for each color component aka inverse rgb ratios.
// we normalize the distance by dividing by achromatic, so that 1.0 is at gamut boundary, avoid 0 division errors.
float3 dist = ach_shd == 0.0f ? float3(0.0f, 0.0f, 0.0f) : (ach-rgb)/ach_shd;

// compress distance with parameterized compression function
float sat;
float3 csat, cdist;
if (hexagonal) {
sat = max(dist.x, max(dist.y, dist.z));
csat = float3(
compress(sat, lim.x, thr.x),
compress(sat, lim.y, thr.y),
compress(sat, lim.z, thr.z));
cdist = sat == 0.0f ? dist : float3(
dist.x * csat.x / sat,
dist.y * csat.y / sat,
dist.z * csat.z / sat);
} else {
cdist = float3(
compress(dist.x, lim.x, thr.x),
compress(dist.y, lim.y, thr.y),
compress(dist.z, lim.z, thr.z));
}
float3 cdist = float3(
compress(dist.x, lim.x, thr.x),
compress(dist.y, lim.y, thr.y),
compress(dist.z, lim.z, thr.z));

// recalculate rgb from compressed distance and achromatic
// effectively this scales each color component relative to achromatic axis by the compressed distance
Expand Down
63 changes: 28 additions & 35 deletions GamutCompress.dctl
Original file line number Diff line number Diff line change
@@ -1,17 +1,16 @@
DEFINE_UI_PARAMS(threshold_r, threshold r, DCTLUI_SLIDER_FLOAT, 0.2f, 0.0f, 0.6f, 0.0f);
DEFINE_UI_PARAMS(threshold_g, threshold g, DCTLUI_SLIDER_FLOAT, 0.2f, 0.0f, 0.6f, 0.0f);
DEFINE_UI_PARAMS(threshold_b, threshold b, DCTLUI_SLIDER_FLOAT, 0.2f, 0.0f, 0.6f, 0.0f);
DEFINE_UI_PARAMS(threshold_c, threshold c, DCTLUI_SLIDER_FLOAT, 0.2f, 0.0f, 0.6f, 0.0f);
DEFINE_UI_PARAMS(threshold_m, threshold m, DCTLUI_SLIDER_FLOAT, 0.2f, 0.0f, 0.6f, 0.0f);
DEFINE_UI_PARAMS(threshold_y, threshold y, DCTLUI_SLIDER_FLOAT, 0.2f, 0.0f, 0.6f, 0.0f);
DEFINE_UI_PARAMS(power, power, DCTLUI_SLIDER_FLOAT, 1.2f, 1.0f, 3.0f, 1.0f);
DEFINE_UI_PARAMS(shd_rolloff, shd rolloff, DCTLUI_SLIDER_FLOAT, 0.03f, 0.0f, 0.1f, 0.0f);
DEFINE_UI_PARAMS(shd_rolloff, shd rolloff, DCTLUI_SLIDER_FLOAT, 0.0f, 0.0f, 0.03f, 0.0f);
DEFINE_UI_PARAMS(cyan, cyan, DCTLUI_SLIDER_FLOAT, 0.09f, 0.0f, 1.0f, 0.0f);
DEFINE_UI_PARAMS(magenta, magenta, DCTLUI_SLIDER_FLOAT, 0.24f, 0.0f, 1.0f, 0.0f);
DEFINE_UI_PARAMS(yellow, yellow, DCTLUI_SLIDER_FLOAT, 0.12f, 0.0f, 1.0f, 0.0f);
DEFINE_UI_PARAMS(hexagonal, hexagonal, DCTLUI_CHECK_BOX, 0);
DEFINE_UI_PARAMS(working_colorspace, working space, DCTLUI_COMBO_BOX, 0, {acescct, acescc, acescg}, {acescct, acescc, acescg});
DEFINE_UI_PARAMS(invert, invert, DCTLUI_CHECK_BOX, 0);


// Convert acescg to acescct
// convert acescg to acescct
__DEVICE__ float lin_to_acescct(float in) {
if (in <= 0.0078125f) {
return 10.5402377416545f * in + 0.0729055341958355f;
Expand All @@ -20,7 +19,7 @@ __DEVICE__ float lin_to_acescct(float in) {
}
}

// Convert acescct to acescg
// convert acescct to acescg
__DEVICE__ float acescct_to_lin(float in) {
if (in > 0.155251141552511f) {
return _powf( 2.0f, in*17.52f - 9.72f);
Expand All @@ -29,7 +28,7 @@ __DEVICE__ float acescct_to_lin(float in) {
}
}

// Convert acescg to acescc
// convert acescg to acescc
__DEVICE__ float lin_to_acescc(float in) {
if (in <= 0.0f) {
return -0.3584474886f;
Expand All @@ -40,7 +39,7 @@ __DEVICE__ float lin_to_acescc(float in) {
}
}

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

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

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

// achromatic with shadow rolloff below shd_rolloff threshold
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)));

// achromatic shadow rolloff
float ach_shd;
if (shd_rolloff < 0.004f) {
// disable shadow rolloff functionality.
// values below 0.004 cause strange behavior, actually increasing distance in some cases.
// if ach < 0.0 and shd_rolloff is disabled, take absolute value. This preserves negative components after compression.
ach_shd = _fabs(ach);
} else {
// lift ach below threshold using a tanh compression function.
// this reduces large distance values in shadow grain, which can cause differences when inverting.
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)));
}

// distance from the achromatic axis for each color component aka inverse rgb ratios
// distance is normalized by achromatic, so that 1.0f is at gamut boundary. avoid 0 div
float3 dist;
Expand All @@ -117,26 +126,10 @@ __DEVICE__ float3 transform(int p_Width, int p_Height, int p_X, int p_Y, float p
dist.z = ach_shd == 0.0f ? 0.0f : (ach-rgb.z)/ach_shd;

// compress distance with user controlled parameterized shaper function
float sat;
float3 csat, cdist;
if (hexagonal) {
// Based on Nick Shaw's variation on the gamut mapping algorithm
// https://community.acescentral.com/t/a-variation-on-jeds-rgb-gamut-mapper/3060
sat = _fmaxf(dist.x, _fmaxf(dist.y, dist.z));
csat = make_float3(
compress(sat, lim.x, thr.x, power, invert),
compress(sat, lim.y, thr.y, power, invert),
compress(sat, lim.z, thr.z, power, invert));
cdist = sat == 0.0f ? dist : make_float3(
dist.x * csat.x / sat,
dist.y * csat.y / sat,
dist.z * csat.z / sat);
} else {
cdist = make_float3(
compress(dist.x, lim.x, thr.x, power, invert),
compress(dist.y, lim.y, thr.y, power, invert),
compress(dist.z, lim.z, thr.z, power, invert));
}
float3 cdist = make_float3(
compress(dist.x, lim.x, thr.x, power, invert),
compress(dist.y, lim.y, thr.y, power, invert),
compress(dist.z, lim.z, thr.z, power, invert));

// recalculate rgb from compressed distance and achromatic
// effectively this scales each color component relative to achromatic axis by the compressed distance
Expand Down
77 changes: 31 additions & 46 deletions GamutCompress.fuse
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ Be wary of high values if accurate inversion is important to you.
Shadow Rolloff
Reduce gamut compression in dark areas below specified value.
Helps reduce invertability issues in negative values from grain.
Make sure there is only shadow grain below the threshold you specify.

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


function Create()
InHexagonal = self:AddInput("hexagonal", "hexagonal", {
LINKID_DataType = "Number",
INPID_InputControl = "CheckboxControl",
INP_MinAllowed = 0.0,
INP_MaxAllowed = 1.0,
INP_Default = 0.0,
})

InThresholdR = self:AddInput("threshold_r", "threshold_r", {
InThresholdR = self:AddInput("threshold_c", "threshold_c", {
LINKID_DataType = "Number",
INPID_InputControl = "SliderControl",
INP_Default = 0.2,
INP_MinAllowed = 0.0001,
INP_MaxScale = 0.6,
})

InThresholdG = self:AddInput("threshold_g", "threshold_g", {
InThresholdG = self:AddInput("threshold_m", "threshold_m", {
LINKID_DataType = "Number",
INPID_InputControl = "SliderControl",
INP_Default = 0.2,
INP_MinAllowed = 0.0001,
INP_MaxScale = 0.6,
})

InThresholdB = self:AddInput("threshold_b", "threshold_b", {
InThresholdB = self:AddInput("threshold_y", "threshold_y", {
LINKID_DataType = "Number",
INPID_InputControl = "SliderControl",
INP_Default = 0.2,
Expand All @@ -118,9 +111,9 @@ function Create()
InShdRolloff = self:AddInput("shd rolloff", "shd rolloff", {
LINKID_DataType = "Number",
INPID_InputControl = "SliderControl",
INP_Default = 0.03,
INP_Default = 0.0,
INP_MinAllowed = 0.0,
INP_MaxScale = 0.1,
INP_MaxScale = 0.03,
})

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

params.hexagonal = InHexagonal:GetValue(req).Value
params.threshold_r = InThresholdR:GetValue(req).Value
params.threshold_g = InThresholdG:GetValue(req).Value
params.threshold_b = InThresholdB:GetValue(req).Value
params.threshold_c = InThresholdR:GetValue(req).Value
params.threshold_m = InThresholdG:GetValue(req).Value
params.threshold_y = InThresholdB:GetValue(req).Value
params.power = InPower:GetValue(req).Value
params.shd_rolloff = InShdRolloff:GetValue(req).Value
params.cyan = InCyan:GetValue(req).Value
Expand All @@ -209,10 +201,9 @@ end


SolidParams = [[
int hexagonal;
float threshold_r;
float threshold_g;
float threshold_b;
float threshold_c;
float threshold_m;
float threshold_y;
float power;
float shd_rolloff;
float cyan;
Expand Down Expand Up @@ -255,9 +246,9 @@ SolidKernel = [[

// thr is the percentage of the core gamut to protect: the complement of threshold.
float3 thr = make_float3(
1.0f-_fmaxf(0.0001f, params->threshold_r),
1.0f-_fmaxf(0.0001f, params->threshold_g),
1.0f-_fmaxf(0.0001f, params->threshold_b));
1.0f-_fmaxf(0.0001f, params->threshold_c),
1.0f-_fmaxf(0.0001f, params->threshold_m),
1.0f-_fmaxf(0.0001f, params->threshold_y));

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

// achromatic with shadow rolloff below shd_rolloff threshold
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)));
// achromatic shadow rolloff
float ach_shd;
if (params->shd_rolloff < 0.004f) {
// disable shadow rolloff functionality.
// values below 0.004 cause strange behavior, actually increasing distance in some cases.
// if ach < 0.0 and shd_rolloff is disabled, take absolute value. This preserves negative components after compression.
ach_shd = _fabs(ach);
} else {
// lift ach below threshold using a tanh compression function.
// this reduces large distance values in shadow grain, which can cause differences when inverting.
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)));
}

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

// compress distance with user controlled parameterized shaper function
float sat;
float3 csat, cdist;
if (params->hexagonal) {
// Based on Nick Shaw's variation on the gamut mapping algorithm
// https://community.acescentral.com/t/a-variation-on-jeds-rgb-gamut-mapper/3060
sat = _fmaxf(dist.x, _fmaxf(dist.y, dist.z));
csat = make_float3(
compress(sat, lim.x, thr.x, params->power, params->invert),
compress(sat, lim.y, thr.y, params->power, params->invert),
compress(sat, lim.z, thr.z, params->power, params->invert));
cdist = sat == 0.0f ? dist : make_float3(
dist.x * csat.x / sat,
dist.y * csat.y / sat,
dist.z * csat.z / sat);
} else {
cdist = make_float3(
compress(dist.x, lim.x, thr.x, params->power, params->invert),
compress(dist.y, lim.y, thr.y, params->power, params->invert),
compress(dist.z, lim.z, thr.z, params->power, params->invert));
}
float3 cdist = make_float3(
compress(dist.x, lim.x, thr.x, params->power, params->invert),
compress(dist.y, lim.y, thr.y, params->power, params->invert),
compress(dist.z, lim.z, thr.z, params->power, params->invert));

// recalculate rgb from compressed distance and achromatic
// effectively this scales each color component relative to achromatic axis by the compressed distance
Expand Down
Loading

0 comments on commit 167de6b

Please sign in to comment.