Voici un script CTL traduit par Alberto à partir d’un script dctl (Davinci Resolve) que je lui ai envoyé. Ce script est censé permettre de reproduire la densité des couleurs propre aux émulsions argentique, c’est-à-dire que la saturation ne fonctionne pas comme dans la saturation classique RGB qui conduit à une augmentation la luminance des couleurs, mais qui compense en diminuant la luminance des couleurs saturées. Ça donne des couleurs plus profondes, plus agréables à l’œil.
Voici le contenu du script, qu’Alberto a visiblement choisi de ne pas inclure dans son entrepôt de scripts ctl. Copiez/collez le code dans un nouveau fichier texte, sauvez en .ctl
dans le dossier ctlscripts
.
// Film Density
struct float3 {
float x;
float y;
float z;
};
float3 make_float3(float x, float y, float z)
{
float3 res = { x, y, z };
return res;
}
float fmin(float a, float b)
{
if (a < b) {
return a;
} else {
return b;
}
}
float fmax(float a, float b)
{
if (a > b) {
return a;
} else {
return b;
}
}
float3 RGB_to_HSV(float3 RGB)
{
float3 HSV;
float min = fmin(fmin(RGB.x, RGB.y), RGB.z);
float max = fmax(fmax(RGB.x, RGB.y), RGB.z);
HSV.z = max;
float delta = max - min;
if (max != 0.0) {
HSV.y = delta / max;
} else {
HSV.y = 0.0;
HSV.x = 0.0;
return HSV;
}
if (delta == 0.0) {
HSV.x = 0.0;
} else if (RGB.x == max) {
HSV.x = (RGB.y - RGB.z) / delta;
} else if (RGB.y == max) {
HSV.x = 2.0 + (RGB.z - RGB.x) / delta;
} else {
HSV.x = 4.0 + (RGB.x - RGB.y) / delta;
}
HSV.x = HSV.x * 1.0 / 6.0;
if (HSV.x < 0.0) {
HSV.x = HSV.x + 1.0;
}
return HSV;
}
float3 HSV_to_RGB(float3 HSV_in)
{
float3 HSV = HSV_in;
float3 RGB;
if (HSV.y == 0.0) {
RGB.x = HSV.z;
RGB.y = HSV.z;
RGB.z = HSV.z;
} else {
HSV.x = HSV.x * 6.0;
float i = floor(HSV.x);
float f = HSV.x - i;
if (i >= 0) {
i = fmod(i, 6.0);
} else {
i = fmod(i, 6.0) + 6.0;
}
float p = HSV.z * (1.0 - HSV.y);
float q = HSV.z * (1.0 - HSV.y * f);
float t = HSV.z * (1.0 - HSV.y * (1.0 - f));
if (i == 0) {
RGB.x = HSV.z;
} else if (i == 1.0) {
RGB.x = q;
} else if (i == 2.0) {
RGB.x = p;
} else if (i == 3.0) {
RGB.x = p;
} else if (i == 4.0) {
RGB.x = t;
} else {
RGB.x = HSV.z;
}
if (i == 0) {
RGB.y = t;
} else if (i == 1.0) {
RGB.y = HSV.z;
} else if (i == 2.0) {
RGB.y = HSV.z;
} else if (i == 3.0) {
RGB.y = q;
} else if (i == 4.0) {
RGB.y = p;
} else {
RGB.y = p;
}
if (i == 0) {
RGB.z = p;
} else if (i == 1.0) {
RGB.z = p;
} else if (i == 2.0) {
RGB.z = t;
} else if (i == 3.0) {
RGB.z = HSV.z;
} else if (i == 4.0) {
RGB.z = HSV.z;
} else {
RGB.z = q;
}
}
return RGB;
}
float RGB_to_Sat(float3 RGB)
{
float min = fmin(fmin(RGB.x, RGB.y), RGB.z);
float max = fmax(fmax(RGB.x, RGB.y), RGB.z);
float delta = max - min;
float Sat = 0.0;
if (max != 0.0) {
Sat = delta / max;
}
return Sat;
}
float3 Saturation(float3 RGB_in, float luma, float Sat)
{
float3 RGB = RGB_in;
RGB.x = (1.0 - Sat) * luma + RGB.x * Sat;
RGB.y = (1.0 - Sat) * luma + RGB.y * Sat;
RGB.z = (1.0 - Sat) * luma + RGB.z * Sat;
return RGB;
}
float get_luma(float3 RGB, float Rw, float Gw, float Bw)
{
float R;
float G;
float B;
R = Rw + 1.0 - (Gw / 2.0) - (Bw / 2.0);
G = Gw + 1.0 - (Rw / 2.0) - (Bw / 2.0);
B = Bw + 1.0 - (Rw / 2.0) - (Gw / 2.0);
float luma = (RGB.x * R + RGB.y * G + RGB.z * B) / 3.0;
return luma;
}
float Limiter(float val, float limiter)
{
float alpha;
if (limiter > 1.0) {
alpha = val + (1.0 - limiter) * (1.0 - val);
} else if (limiter >= 0.0) {
if (val >= limiter) {
alpha = 1.0;
} else {
alpha = val / limiter;
}
} else if (limiter < -1.0) {
alpha = (1.0 - val) + (limiter + 1.0) * val;
} else if (val <= (1.0 + limiter)) {
alpha = 1.0;
} else {
alpha = (1.0 - val) / (1.0 - (limiter + 1.0));
}
if (alpha < 0) {
alpha = 0;
} else if (alpha > 1) {
alpha = 1;
}
return alpha;
}
float3 transform(varying float p_R, varying float p_G, varying float p_B,
float p_Den, float p_WR, float p_WG, float p_WB,
float p_LimitS, float p_LimitL, bool p_Display)
{
float3 rgbIn = make_float3(p_R, p_G, p_B);
if (p_Den == 0.0 && p_Display == 0) {
return rgbIn;
}
float WR = 2.0 - p_WR;
float WG = 2.0 - p_WG;
float WB = 2.0 - p_WB;
float luma = get_luma(rgbIn, WR, WG, WB);
float SatA = 1.0 / (p_Den + 1.0);
float3 rgbOut = Saturation(rgbIn, luma, SatA);
float alphaS = 1.0;
float alphaL = 1.0;
float alpha = 1.0;
if (p_LimitS > 0.0) {
float sat = RGB_to_Sat(rgbIn);
alphaS = Limiter(sat, p_LimitS);
alpha = alphaS;
}
if (p_LimitL > 0.0) {
alphaL = (rgbIn.x + rgbIn.y + rgbIn.z) / 3.0;
alphaL = Limiter(alphaL, p_LimitL);
alpha = alpha * alphaL;
}
rgbOut = RGB_to_HSV(rgbOut);
rgbOut.y = rgbOut.y * 1.0 / SatA ;
rgbOut = HSV_to_RGB(rgbOut);
if (alpha < 1.0) {
rgbOut.x = rgbOut.x * alpha + (1.0 - alpha) * rgbIn.x;
rgbOut.y = rgbOut.y * alpha + (1.0 - alpha) * rgbIn.y;
rgbOut.z = rgbOut.z * alpha + (1.0 - alpha) * rgbIn.z;
}
if (p_Display) {
rgbOut.x = alpha;
rgbOut.y = alpha;
rgbOut.z = alpha;
}
return rgbOut;
}
// @ART-colorspace: "rec2020"
// @ART-label: "Film Density"
// @ART-param: ["p_Den", "Film Density", 0, 2, 0, 0.001]
// @ART-param: ["p_WR", "Red Weight", 0, 2, 1, 0.001]
// @ART-param: ["p_WG", "Green Weight", 0, 2, 1, 0.001]
// @ART-param: ["p_WB", "Blue Weight", 0, 2, 1, 0.001]
// @ART-param: ["p_LimitS", "Low Saturation Limiter", 0, 1, 0, 0.001]
// @ART-param: ["p_LimitL", "Low Luma Limiter", 0, 1, 0, 0.001]
// @ART-param: ["p_Display", "Display Alpha"]
void ART_main(varying float r, varying float g, varying float b,
output varying float rout,
output varying float gout,
output varying float bout,
float p_Den, float p_WR, float p_WG, float p_WB,
float p_LimitS, float p_LimitL, bool p_Display)
{
float3 res = transform(r, g, b, p_Den, p_WR, p_WG, p_WB,
p_LimitS, p_LimitL, p_Display);
rout = res.x;
gout = res.y;
bout = res.z;
}