diff options
author | Marti Maria <marti.maria@littlecms.com> | 2023-05-15 18:37:34 +0200 |
---|---|---|
committer | Marti Maria <marti.maria@littlecms.com> | 2023-05-15 18:37:34 +0200 |
commit | f4e9f9122a001b71219d9b2d2a6ec5b8c784d785 (patch) | |
tree | b6b5921d0a713b41f436861e7c3d2bdf9f15f560 | |
parent | cdf9635b69c3f270684b3e6a0cdeda9b03ce920c (diff) | |
download | lcms2-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.h | 2 | ||||
-rw-r--r-- | src/cmsvirt.c | 117 | ||||
-rw-r--r-- | testbed/testcms2.c | 47 |
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); |