summaryrefslogtreecommitdiff
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
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
-rw-r--r--include/lcms2.h2
-rw-r--r--src/cmsvirt.c117
-rw-r--r--testbed/testcms2.c47
3 files changed, 163 insertions, 3 deletions
diff --git a/include/lcms2.h b/include/lcms2.h
index c361520..8769267 100644
--- a/include/lcms2.h
+++ b/include/lcms2.h
@@ -1634,6 +1634,8 @@ CMSAPI cmsHPROFILE CMSEXPORT cmsCreateXYZProfile(void);
CMSAPI cmsHPROFILE CMSEXPORT cmsCreate_sRGBProfileTHR(cmsContext ContextID);
CMSAPI cmsHPROFILE CMSEXPORT cmsCreate_sRGBProfile(void);
+CMSAPI cmsHPROFILE CMSEXPORT cmsCreate_OkLabProfile(cmsContext ctx);
+
CMSAPI cmsHPROFILE CMSEXPORT cmsCreateBCHSWabstractProfileTHR(cmsContext ContextID,
cmsUInt32Number nLUTPoints,
cmsFloat64Number Bright,
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;
diff --git a/testbed/testcms2.c b/testbed/testcms2.c
index 48230ea..e5a2032 100644
--- a/testbed/testcms2.c
+++ b/testbed/testcms2.c
@@ -8374,6 +8374,52 @@ int Check_sRGB_Rountrips(void)
return 1;
}
+/**
+* Check OKLab colorspace
+*/
+static
+int Check_OkLab(void)
+{
+ cmsHPROFILE hOkLab = cmsCreate_OkLabProfile(NULL);
+ cmsHPROFILE hXYZ = cmsCreateXYZProfile();
+ cmsCIEXYZ xyz, xyz2;
+ cmsCIELab okLab;
+
+#define TYPE_OKLAB_DBL (FLOAT_SH(1)|COLORSPACE_SH(PT_MCH3)|CHANNELS_SH(3)|BYTES_SH(0))
+
+ cmsHTRANSFORM xform = cmsCreateTransform(hXYZ, TYPE_XYZ_DBL, hOkLab, TYPE_OKLAB_DBL, INTENT_RELATIVE_COLORIMETRIC, 0);
+ cmsHTRANSFORM xform2 = cmsCreateTransform(hOkLab, TYPE_OKLAB_DBL, hXYZ, TYPE_XYZ_DBL, INTENT_RELATIVE_COLORIMETRIC, 0);
+
+ /**
+ * D50 should be converted to white by PCS definition
+ */
+ xyz.X = 0.9642; xyz.Y = 1.0000; xyz.Z = 0.8249;
+ cmsDoTransform(xform, &xyz, &okLab, 1);
+ cmsDoTransform(xform2, &okLab, &xyz2, 1);
+
+
+ xyz.X = 1.0; xyz.Y = 0.0; xyz.Z = 0.0;
+ cmsDoTransform(xform, &xyz, &okLab, 1);
+ cmsDoTransform(xform2, &okLab, &xyz2, 1);
+
+
+ xyz.X = 0.0; xyz.Y = 1.0; xyz.Z = 0.0;
+ cmsDoTransform(xform, &xyz, &okLab, 1);
+ cmsDoTransform(xform2, &okLab, &xyz2, 1);
+
+ xyz.X = 0.0; xyz.Y = 0.0; xyz.Z = 1.0;
+ cmsDoTransform(xform, &xyz, &okLab, 1);
+ cmsDoTransform(xform2, &okLab, &xyz2, 1);
+
+
+ cmsDeleteTransform(xform);
+ cmsDeleteTransform(xform2);
+ cmsCloseProfile(hOkLab);
+ cmsCloseProfile(hXYZ);
+
+ return 0;
+}
+
static
cmsHPROFILE createRgbGamma(cmsFloat64Number g)
{
@@ -9492,6 +9538,7 @@ int main(int argc, char* argv[])
Check("Proofing intersection", CheckProofingIntersection);
Check("Empty MLUC", CheckEmptyMLUC);
Check("sRGB round-trips", Check_sRGB_Rountrips);
+ Check("OkLab color space", Check_OkLab);
Check("Gamma space detection", CheckGammaSpaceDetection);
Check("Unbounded mode w/ integer output", CheckIntToFloatTransform);
Check("Corrupted built-in by using cmsWriteRawTag", CheckInducedCorruption);