mm2 / Little-CMS

A free, open source, CMM engine. It provides fast transforms between ICC profiles.
https://www.littlecms.com
MIT License
572 stars 177 forks source link

The cmsCreateTransform may modify the profile in some way #273

Closed mrserb closed 2 years ago

mrserb commented 3 years ago

I have found this issue while working on lcms support in the OpenJDK. Some profiles cannot be saved to the memory if they were used by the cmsCreateTransform(). Tested on LittleCMS 2.12.

For example this profile: https://github.com/openjdk/jdk/blob/master/src/java.desktop/share/classes/sun/java2d/cmm/profiles/PYCC.pf

The test code:

#include "lcms2.h"
#include <stdlib.h>

void LogErrorHandler(cmsContext ContextID, cmsUInt32Number ErrorCode, const char *Text){
    fprintf(stderr, "%s\n", Text);
}

void check(cmsHPROFILE hProfile) {
    cmsUInt32Number pfSize = 0;
    cmsHPROFILE pfSanity = NULL;
    if (cmsSaveProfileToMem(hProfile, NULL, &pfSize)) {
        void* buf = malloc((pfSize));
        if (buf != NULL) {
            if (cmsSaveProfileToMem(hProfile, buf, &pfSize)) {
                pfSanity = cmsOpenProfileFromMem(buf, pfSize);
                cmsCloseProfile(pfSanity);
                fprintf(stderr, "This step passed\n");
            }
            free(buf);
        }
    }
}

int main(void) {
    cmsSetLogErrorHandler(LogErrorHandler);
    cmsHPROFILE hInProfile = cmsOpenProfileFromFile("/.../.../PYCC.pf", "r");
    cmsHPROFILE hOutProfile = cmsCreate_sRGBProfile();

    //// Comment the block below to solve the error
    cmsHTRANSFORM hTransform = cmsCreateTransform(hInProfile, PT_ANY,
                                                  hOutProfile, PT_ANY,
                                                  0, 0);
    cmsDeleteTransform(hTransform);
    //// Comment the block above to solve the error

    check(hInProfile);
    check(hOutProfile);
    cmsCloseProfile(hInProfile);
    cmsCloseProfile(hOutProfile);

    return 0;
}

The code above will print:

LUT is not suitable to be saved as LutAToB
Couldn't write type 'mAB '

But if the block of code is commented(or just remove the cmsCreateTransform and cmsDeleteTransform) out in the example, then cmsSaveProfileToMem/cmsOpenProfileFromMem will work fine.

mm2 commented 3 years ago

Hi, Back from holidays, I apologize for the delay.

After investigation, I found the profile PYCC.pf is broken. It uses for Tag AToB0, which is of type LutAToBType, the following sequence:

A ->CLUT->M->Matrix

This is not allowed in the ICC spec, https://www.color.org/specification/ICC1v43_2010-12.pdf See "10.10.1 lutAToBType, general". The closest allowed configuration does include a "B" step which this profile is missing. Also I tried using the profile inspector tool from ICC. It complains on this tag as well.

To explain this weird behaviour, lcms is relaxed when reading files and strict when writing them. It accepts the profile for read but it cannot write it. Why sometimes you can save it? If you just save the profile after opening it, lcms does not decode the tags but just does a blind memory copy. This is required to make things go fast when embedding profiles. If you use the profile, then parsing happens and the tag cannot be saved anymore.

I agree this is very confusing and should be fixed somehow. I will try to allow saving this profile by using an indentity for B matrix.

mm2 commented 2 years ago

I was going to make lcms to refuse this profile, as it is wrong, but after thinking on that I guess it is better to keep accepting it as a matter of compatibility. The actual behavior is correct, opening such a broken profile is a plus. Allowing to save it would be an error.