summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorMarti Maria <marti.maria@littlecms.com>2023-05-15 18:37:34 +0200
committerMarti Maria <marti.maria@littlecms.com>2023-05-15 18:37:34 +0200
commitf4e9f9122a001b71219d9b2d2a6ec5b8c784d785 (patch)
treeb6b5921d0a713b41f436861e7c3d2bdf9f15f560 /src
parentcdf9635b69c3f270684b3e6a0cdeda9b03ce920c (diff)
downloadlcms2-f4e9f9122a001b71219d9b2d2a6ec5b8c784d785.tar.gz
a preliminar implementation of OkLab color space as built-in profile
Oklab is an alternative to CIE Lab. It works with the same logic, but claims to be more perceptually uniform than CIELab. Thanks to Lukas Sommer for the idea and Björn Ottosson for the documentation
Diffstat (limited to 'src')
-rw-r--r--src/cmsvirt.c117
1 files changed, 114 insertions, 3 deletions
diff --git a/src/cmsvirt.c b/src/cmsvirt.c
index 951a8ee..d3b6ab2 100644
--- a/src/cmsvirt.c
+++ b/src/cmsvirt.c
@@ -672,6 +672,117 @@ cmsHPROFILE CMSEXPORT cmsCreate_sRGBProfile(void)
return cmsCreate_sRGBProfileTHR(NULL);
}
+/**
+* Oklab colorspace profile (experimental)
+*
+* This virtual profile cannot be saved as an ICC file
+*/
+cmsHPROFILE cmsCreate_OkLabProfile(cmsContext ctx)
+{
+ cmsStage* XYZPCS = _cmsStageNormalizeFromXyzFloat(ctx);
+ cmsStage* PCSXYZ = _cmsStageNormalizeToXyzFloat(ctx);
+
+ const double M_D65_D50[] =
+ {
+ 1.047886, 0.022919, -0.050216,
+ 0.029582, 0.990484, -0.017079,
+ -0.009252, 0.015073, 0.751678
+ };
+
+ const double M_D50_D65[] =
+ {
+ 0.955513, -0.0230732, 0.063309,
+ -0.0283249, 1.00994, 0.0210548,
+ 0.0123289, -0.0205358, 1.33071
+ };
+
+ cmsStage* D65toD50 = cmsStageAllocMatrix(ctx, 3, 3, M_D65_D50, NULL);
+ cmsStage* D50toD65 = cmsStageAllocMatrix(ctx, 3, 3, M_D50_D65, NULL);
+
+ const double M_D65_LMS[] =
+ {
+ 0.8189330101, 0.3618667424, -0.1288597137,
+ 0.0329845436, 0.9293118715, 0.0361456387,
+ 0.0482003018, 0.2643662691, 0.6338517070
+ };
+
+ const double M_LMS_D65[] =
+ {
+ 1.22701, -0.5578, 0.281256,
+ -0.0405802, 1.11226, -0.0716767,
+ -0.0763813, -0.421482, 1.58616
+ };
+
+ cmsStage* D65toLMS = cmsStageAllocMatrix(ctx, 3, 3, M_D65_LMS, NULL);
+ cmsStage* LMStoD65 = cmsStageAllocMatrix(ctx, 3, 3, M_LMS_D65, NULL);
+
+ cmsToneCurve* CubeRoot = cmsBuildGamma(ctx, 1.0 / 3.0);
+ cmsToneCurve* Cube = cmsBuildGamma(ctx, 3.0);
+
+ cmsToneCurve* Roots[3] = { CubeRoot, CubeRoot, CubeRoot };
+ cmsToneCurve* Cubes[3] = { Cube, Cube, Cube };
+
+ cmsStage* NonLinearityFw = cmsStageAllocToneCurves(ctx, 3, Roots);
+ cmsStage* NonLinearityRv = cmsStageAllocToneCurves(ctx, 3, Cubes);
+
+ const double M_LMSprime_OkLab[] =
+ {
+ 0.2104542553, 0.7936177850, -0.0040720468,
+ 1.9779984951, -2.4285922050, 0.4505937099,
+ 0.0259040371, 0.7827717662, -0.8086757660
+ };
+
+ const double M_OkLab_LMSprime[] =
+ {
+ 1.0, 0.396338, 0.215804,
+ 1.0, -0.105561, -0.0638542,
+ 1.0, -0.0894842, -1.29149
+ };
+
+ cmsStage* LMSprime_OkLab = cmsStageAllocMatrix(ctx, 3, 3, M_LMSprime_OkLab, NULL);
+ cmsStage* OkLab_LMSprime = cmsStageAllocMatrix(ctx, 3, 3, M_OkLab_LMSprime, NULL);
+
+ cmsPipeline* AToB = cmsPipelineAlloc(ctx, 3, 3);
+ cmsPipeline* BToA = cmsPipelineAlloc(ctx, 3, 3);
+
+ cmsHPROFILE hProfile = cmsCreateProfilePlaceholder(ctx);
+
+ cmsSetProfileVersion(hProfile, 4.4);
+
+ cmsSetDeviceClass(hProfile, cmsSigColorSpaceClass);
+ cmsSetColorSpace(hProfile, cmsSig3colorData);
+ cmsSetPCS(hProfile, cmsSigXYZData);
+
+ cmsSetHeaderRenderingIntent(hProfile, INTENT_RELATIVE_COLORIMETRIC);
+
+ /**
+ * Conversion PCS (XYZ/D50) to OkLab
+ */
+ cmsPipelineInsertStage(BToA, cmsAT_END, PCSXYZ);
+ cmsPipelineInsertStage(BToA, cmsAT_END, D50toD65);
+ cmsPipelineInsertStage(BToA, cmsAT_END, D65toLMS);
+ cmsPipelineInsertStage(BToA, cmsAT_END, NonLinearityFw);
+ cmsPipelineInsertStage(BToA, cmsAT_END, LMSprime_OkLab);
+
+ cmsWriteTag(hProfile, cmsSigBToA0Tag, BToA);
+
+ cmsPipelineInsertStage(AToB, cmsAT_END, OkLab_LMSprime);
+ cmsPipelineInsertStage(AToB, cmsAT_END, NonLinearityRv);
+ cmsPipelineInsertStage(AToB, cmsAT_END, LMStoD65);
+ cmsPipelineInsertStage(AToB, cmsAT_END, D65toD50);
+ cmsPipelineInsertStage(AToB, cmsAT_END, XYZPCS);
+
+ cmsWriteTag(hProfile, cmsSigAToB0Tag, AToB);
+
+ cmsPipelineFree(BToA);
+ cmsPipelineFree(AToB);
+
+ cmsFreeToneCurve(CubeRoot);
+ cmsFreeToneCurve(Cube);
+
+ return hProfile;
+}
+
typedef struct {
@@ -1062,9 +1173,9 @@ const cmsAllowedLUT* FindCombination(const cmsPipeline* Lut, cmsBool IsV4, cmsTa
cmsHPROFILE CMSEXPORT cmsTransform2DeviceLink(cmsHTRANSFORM hTransform, cmsFloat64Number Version, cmsUInt32Number dwFlags)
{
cmsHPROFILE hProfile = NULL;
- cmsUInt32Number FrmIn, FrmOut;
- cmsInt32Number ChansIn, ChansOut;
- int ColorSpaceBitsIn, ColorSpaceBitsOut;
+ cmsUInt32Number FrmIn, FrmOut;
+ cmsInt32Number ChansIn, ChansOut;
+ int ColorSpaceBitsIn, ColorSpaceBitsOut;
_cmsTRANSFORM* xform = (_cmsTRANSFORM*) hTransform;
cmsPipeline* LUT = NULL;
cmsStage* mpe;