Hi! I just stumbled upon this thread and thought I'd share an OKLab conversion DCTL I wrote about a year ago. It;s written as a header file to be included in any other DCTL you may need it for. It supports conversion from ACES, Davinci Wide and Rec.709/sRGB.
OKLab_Transform.h:
#line 2
#ifndef ENCODING_ENUMS_DEFINED_IN_UI
enum Encoding
{
gAcc,
gAcct,
gDWI,
gLIN,
g709,
gSRGB
};
#endif
#ifndef COLORSPACE_ENUMS_DEFINED_IN_UI
enum ColorSpace
{
cACES0,
cACES1,
cDWG,
c709
};
#endif
// =============================================================
// Util
// =============================================================
// =============================================================
__DEVICE__ float powCf(float base, float exp)
{
return _copysignf(_powf(_fabs(base), exp), base);
}
__DEVICE__ float3 VecMatMul3x3(const float3 m[3], float3 v)
{
float3 r;
r.x = m[0].x * v.x + m[0].y * v.y + m[0].z * v.z;
r.y = m[1].x * v.x + m[1].y * v.y + m[1].z * v.z;
r.z = m[2].x * v.x + m[2].y * v.y + m[2].z * v.z;
return r;
}
// =============================================================
// Matrices
// =============================================================
// =============================================================
// These matrices are the concatenated forms of (colorspace -> XYZ -> OKlms)
// or, XYZToLMS @ ColorspaceToXYZ and, XYZToColorspace @ LMSToXYZ
// original matrices are included as comments at the bottom of this script
// ACES (AP0)
// ==============================
__CONSTANT__ float3 mat_ACES0_LMS[3] =
{
{0.90454662f, 0.26349909f, -0.15602258f },
{0.35107161f, 0.6766934f, -0.03056591f },
{0.13684644f, 0.19250255f, 0.62038067f }
};
__CONSTANT__ float3 mat_LMS_ACES0[3] =
{
{1.2881401f, -0.58554348f, 0.29511118f },
{-0.67171287f, 1.76268516f, -0.08208556f },
{-0.0757131f, -0.41779486f, 1.57228749f }
};
// ACES (AP1)
// ==============================
__CONSTANT__ float3 mat_ACES1_LMS[3] =
{
{0.64173446f, 0.35314498f, 0.0171437f },
{0.27463463f, 0.63099904f, 0.09156544f },
{0.10036508f, 0.18723743f, 0.66212716f }
};
__CONSTANT__ float3 mat_LMS_ACES1[3] =
{
{2.04479741f, -1.17697875f, 0.10982058f },
{-0.88115384f, 2.15979229f, -0.27586256f },
{-0.06077576f, -0.43234352f, 1.57164623f }
};
// ITU BT.709
// ==============================
__CONSTANT__ float3 mat_709_LMS[3] =
{
{ 0.4122214708f, 0.5363325363f, 0.0514459929f },
{ 0.2119034982f, 0.6806995451f, 0.1073969566f },
{ 0.0883024619f, 0.2817188376f, 0.6299787005f }
};
__CONSTANT__ float3 mat_LMS_709[3] =
{
{ 4.0767416621f, -3.3077115913f, 0.2309699292f },
{ -1.2684380046f, 2.6097574011f, -0.3413193965f },
{ -0.0041960863f, -0.7034186147f, 1.7076147010f }
};
// Davinci Wide
// ==============================
__CONSTANT__ float3 mat_DWG_LMS[3] =
{
{ 0.68570951f, 0.45574409f, -0.14156279f },
{ 0.27427422f, 0.81179945f, -0.08604675f },
{ 0.04351009f, 0.15072461f, 0.80624495f }
};
__CONSTANT__ float3 mat_LMS_DWG[3] =
{
{ 1.8836253f, -1.09713301f, 0.21364045f },
{ -0.63460063f, 1.57752473f, 0.05693684f },
{ 0.01698397f, -0.23570436f, 1.21814431f }
};
// OKLab <-> Cone Response
// ==============================
__CONSTANT__ float3 mat_LMS_LAB[3] =
{
{ 0.2104542553f, 0.7936177850f, -0.0040720468f },
{ 1.9779984951f, -2.4285922050f, 0.4505937099f },
{ 0.0259040371f, 0.7827717662f, -0.8086757660f }
};
__CONSTANT__ float3 mat_LAB_LMS[3] =
{
{ 1.0f, 0.3963377774f, 0.2158037573f },
{ 1.0f, -0.1055613458f, -0.0638541728f },
{ 1.0f, -0.0894841775f, -1.2914855480f }
};
// =============================================================
// Transfer Functions
// =============================================================
// =============================================================
// ACEScc
// ==============================
__DEVICE__ float ACEScc_DecodeBase(float v, float a, float b, float upperClampThreshold, float lowerDecodeThreshold, float two_m16)
{
float out = v;
if (v >= upperClampThreshold)
out = 65504.0f;
else if (v < lowerDecodeThreshold)
out = (_exp2f(v * b - a) - two_m16) * 2.0f;
else
out = _exp2f(v * b - a);
return out;
}
__DEVICE__ float3 ACEScc_Decode(float3 in)
{
const float two_m16 = _exp2f(-16.0f);
const float a = 9.72f;
const float b = 17.52f;
const float lowerDecodeThreshold = (a - 15.0f) / b;
const float upperClampThreshold = (_log2f(65504.0f) + a) / b;
float3 out = in;
out.x = ACEScc_DecodeBase(out.x, a, b, upperClampThreshold, lowerDecodeThreshold, two_m16);
out.y = ACEScc_DecodeBase(out.y, a, b, upperClampThreshold, lowerDecodeThreshold, two_m16);
out.z = ACEScc_DecodeBase(out.z, a, b, upperClampThreshold, lowerDecodeThreshold, two_m16);
return out;
}
__DEVICE__ float ACEScc_EncodeBase(float v, float a, float b, float negConstant, float two_m15, float two_m16)
{
float out;
if (v < 0.0f)
out = negConstant;
else if (v < two_m15)
out = (_log2f(two_m16 + v * 0.5f) + a) / b;
else
out = (_log2f(v) + a) / b;
return out;
}
__DEVICE__ float3 ACEScc_Encode(float3 in)
{
const float two_m16 = _exp2f(-16.0f);
const float two_m15 = _exp2f(-15.0f);
const float a = 9.72f;
const float b = 17.52f;
const float negConstant = (_log2f(two_m16) + a) / b;
float3 out = in;
out.x = ACEScc_EncodeBase(out.x, a, b, negConstant, two_m15, two_m16);
out.y = ACEScc_EncodeBase(out.y, a, b, negConstant, two_m15, two_m16);
out.z = ACEScc_EncodeBase(out.z, a, b, negConstant, two_m15, two_m16);
return out;
}
// ACEScct
// ==============================
__DEVICE__ float3 ACEScct_Encode(float3 in)
{
const float a = 9.72f;
const float b = 17.52f;
const float X_BRK = 0.0078125f;
const float A = 10.5402377416545f;
const float B = 0.0729055341958355f;
float3 out;
out.x = (in.x <= X_BRK) ? (A * in.x + B) : ((_log2f(in.x) + a) / b);
out.y = (in.y <= X_BRK) ? (A * in.y + B) : ((_log2f(in.y) + a) / b);
out.z = (in.z <= X_BRK) ? (A * in.z + B) : ((_log2f(in.z) + a) / b);
return out;
}
__DEVICE__ float3 ACEScct_Decode(float3 in)
{
const float a = 9.72f;
const float b = 17.52f;
const float Y_BRK = 0.155251141552511f;
const float A = 10.5402377416545f;
const float B = 0.0729055341958355f;
float3 out = in;
out.x = (in.x > Y_BRK) ? _exp2f(in.x * b - a) : ((in.x - B) / A);
out.y = (in.y > Y_BRK) ? _exp2f(in.y * b - a) : ((in.y - B) / A);
out.z = (in.z > Y_BRK) ? _exp2f(in.z * b - a) : ((in.z - B) / A);
return out;
}
// Davinci Intermediate
// ==============================
__DEVICE__ float3 DWI_Decode(float3 in)
{
float3 out = in;
float a = 0.0075;
float b = 7.0;
float c = 0.07329248;
float m = 10.44426855;
float log_cut = 0.02740668;
out.x = in.x > log_cut ? powCf(2.0f, (in.x / c) - b) - a : in.x / m;
out.y = in.y > log_cut ? powCf(2.0f, (in.y / c) - b) - a : in.y / m;
out.z = in.z > log_cut ? powCf(2.0f, (in.z / c) - b) - a : in.z / m;
return out;
}
__DEVICE__ float3 DWI_Encode(float3 in)
{
float3 out = in;
float a = 0.0075;
float b = 7.0;
float c = 0.07329248;
float m = 10.44426855;
float lin_cut = 0.00262409;
out.x = in.x > lin_cut ? (_log2f(in.x + a) + b) * c : in.x * m;
out.y = in.y > lin_cut ? (_log2f(in.y + a) + b) * c : in.y * m;
out.z = in.z > lin_cut ? (_log2f(in.z + a) + b) * c : in.z * m;
return out;
}
// ITU BT.709
// ==============================
__DEVICE__ float3 BT709_Decode(float3 in)
{
float3 out = in;
out.x = out.x < 0.081f ? out.x / 4.5f : powCf((out.x + 0.099f) / 1.099f, 1.0f / 0.45f);
out.y = out.y < 0.081f ? out.y / 4.5f : powCf((out.y + 0.099f) / 1.099f, 1.0f / 0.45f);
out.z = out.z < 0.081f ? out.z / 4.5f : powCf((out.z + 0.099f) / 1.099f, 1.0f / 0.45f);
return out;
}
__DEVICE__ float3 BT709_Encode(float3 in)
{
float3 out = in;
out.x = out.x < 0.018 ? out.x * 4.5f : 1.099f * powCf(out.x, 0.45f) - 0.099f;
out.y = out.y < 0.018 ? out.y * 4.5f : 1.099f * powCf(out.y, 0.45f) - 0.099f;
out.z = out.z < 0.018 ? out.z * 4.5f : 1.099f * powCf(out.z, 0.45f) - 0.099f;
return out;
}
// sRGB
// ==============================
__DEVICE__ float3 sRGB_Decode(float3 in)
{
float3 out = in;
out.x = out.x < 0.04045 ? out.x / 12.92f : powCf((out.x + 0.055f) / 1.055f, 2.4f);
out.y = out.y < 0.04045 ? out.y / 12.92f : powCf((out.y + 0.055f) / 1.055f, 2.4f);
out.z = out.z < 0.04045 ? out.z / 12.92f : powCf((out.z + 0.055f) / 1.055f, 2.4f);
return out;
}
__DEVICE__ float3 sRGB_Encode(float3 in)
{
float3 out = in;
out.x = out.x < 0.0031308 ? out.x * 12.92f : 1.055f * powCf(out.x, 1.0f / 2.4f) - 0.055f;
out.y = out.y < 0.0031308 ? out.y * 12.92f : 1.055f * powCf(out.y, 1.0f / 2.4f) - 0.055f;
out.z = out.z < 0.0031308 ? out.z * 12.92f : 1.055f * powCf(out.z, 1.0f / 2.4f) - 0.055f;
return out;
}
// =============================================================
// Convert
// =============================================================
// =============================================================
__DEVICE__ float3 Decode(float3 in, int tFunction)
{
float3 out = in;
switch (tFunction)
{
case gAcc:
out = ACEScc_Decode(in);
break;
case gAcct:
out = ACEScct_Decode(in);
break;
case gDWI:
out = DWI_Decode(in);
break;
case g709:
out = BT709_Decode(in);
break;
case gSRGB:
out = sRGB_Decode(in);
break;
}
return out;
}
__DEVICE__ float3 Encode(float3 in, int tFunction)
{
float3 out = in;
switch (tFunction)
{
case gAcc:
out = ACEScc_Encode(in);
break;
case gAcct:
out = ACEScct_Encode(in);
break;
case gDWI:
out = DWI_Encode(in);
break;
case g709:
out = BT709_Encode(in);
break;
case gSRGB:
out = sRGB_Encode(in);
break;
}
return out;
}
__DEVICE__ float3 OKLab_OKLCh(float3 lab)
{
float C = _hypotf(lab.y, lab.z);
float h = _atan2f(lab.z, lab.y);
return make_float3(lab.x, C, h);
}
__DEVICE__ float3 OKLCh_OKLab(float3 lch)
{
float a = lch.y * cosf(lch.z);
float b = lch.y * sinf(lch.z);
return make_float3(lch.x, a, b);
}
__DEVICE__ float3 RGB_OKLab(float3 rgb, int colorspace)
{
const float3* mat;
switch (colorspace)
{
case cACES0:
mat = mat_ACES0_LMS;
break;
case cACES1:
mat = mat_ACES1_LMS;
break;
case cDWG:
mat = mat_DWG_LMS;
break;
case c709:
mat = mat_709_LMS;
break;
}
float3 lms = VecMatMul3x3(mat, rgb);
float3 lms_;
lms_.x = cbrt(lms.x);
lms_.y = cbrt(lms.y);
lms_.z = cbrt(lms.z);
return VecMatMul3x3(mat_LMS_LAB, lms_);
}
__DEVICE__ float3 OKLab_RGB(float3 lab, int colorspace)
{
const float3* mat;
switch (colorspace)
{
case cACES0:
mat = mat_LMS_ACES0;
break;
case cACES1:
mat = mat_LMS_ACES1;
break;
case cDWG:
mat = mat_LMS_DWG;
break;
case c709:
mat = mat_LMS_709;
break;
}
float3 lms = VecMatMul3x3(mat_LAB_LMS, lab);
lms.x = powCf(lms.x, 3.0f);
lms.y = powCf(lms.y, 3.0f);
lms.z = powCf(lms.z, 3.0f);
return VecMatMul3x3(mat, lms);
}
__DEVICE__ float3 RGB_OkLCh(float3 rgb, int colorspace)
{
float3 lab = RGB_OKLab(rgb, colorspace);
return OKLab_OKLCh(lab);
}
__DEVICE__ float3 OKLCh_RGB(float3 lch, int colorspace)
{
float3 lab = OKLCh_OKLab(lch);
return OKLab_RGB(lab, colorspace);
}
// =============================================================
// Ref Matrices
// =============================================================
// =============================================================
// DWG -> XYZ
// 0.70062239, 0.14877482, 0.10105872
// 0.27411851, 0.87363190, -0.14775041
// -0.09896291, -0.13789533, 1.32591599
// XYZ -> DWG
// 1.51667204, -0.28147805, -0.14696363
// -0.46491710, 1.25142378, 0.17488461
// 0.06484905, 0.10913934, 0.76141462
// 709 -> XYZ
// 0.4123908, 0.35758434, 0.18048079
// 0.21263901, 0.71516868, 0.07219232
// 0.01933082, 0.11919478, 0.95053215
// XYZ -> 709
// 3.24096994, -1.53738318, -0.49861076
// -0.96924364, 1.8759675, 0.04155506
// 0.05563008, -0.20397696 , 1.05697151
// XYZ -> LMS
// 0.8189330101, 0.3618667424, -0.1288597137
// 0.0329845436, 0.9293118715, 0.0361456387
// 0.0482003018, 0.2643662691, 0.6338517070
// LMS -> XYZ
// 1.22701385, -0.55779998, 0.28125615
// -0.04058018, 1.11225687, -0.07167668
// -0.07638128, -0.42148198, 1.58616322
Using it another DCTL looks something like this:
#line 2
#define ENCODING_ENUMS_DEFINED_IN_UI
#define COLORSPACE_ENUMS_DEFINED_IN_UI
#include "OKLab_Transforms.h"
DEFINE_UI_PARAMS(p_InCSpace, Input Color Space, DCTLUI_COMBO_BOX, 2, {cACES0, cACES1, cDWG, c709}, {ACES (AP0), ACES (AP1), Davinci Wide Gamut, Rec.709 / sRGB / BT.1886});
DEFINE_UI_PARAMS(p_InGamma, Input Gamma, DCTLUI_COMBO_BOX, 2, {gAcc, gAcct, gDWI, gLIN, g709, gSRGB}, {ACEScc, ACEScct, Davinci Intermediate, Linear, Rec.709, sRGB});
__DEVICE__ float3 transform(int p_Width, int p_Height, int p_X, int p_Y, float p_R, float p_G, float p_B)
{
float3 in = make_float3(p_R, p_G, p_B);
float3 out = in;
// Convert to OKLCh:
float3 linear = Decode(in, p_InGamma);
float3 oklch = RGB_OkLCh(linear, p_InCSpace);
// Convert back:
linear = OKLCh_RGB(oklch, p_InCSpace);
out = Encode(linear, p_InGamma);
return out;
}