c++wcscolor-profilecolor-management

Color transform from ProPhoto RGB to monitor profile


I have been struggling many days with this issue and can't find any explanation!

Background:

I'm creating a color managed photo editing app on Windows with VC9+MFC, and using WCS(Windows Color System) APIs to convert pixels from photo embedded color profile to the monitor's profile.

My monitor has been calibrated with the "Windows Display Calibration", and created a profile named "CalibratedDisplayProfile-x.icc".

Problem:

When I convert pixels from "ProPhoto RGB" to monitor's profile, the color in the dark area shifts, the tint turns green. This doesn't happen in midtone/highlights if the target profile is sRGB. Here're the screen-shots.

correct & error images

Test:

To simplify the problem, I wrote some test codes to translate a single color, but the test result really makes me confused. The source color "c0" is RGB(0,0,65535), but the output color "c1" is RGB(0,0,0)!! And function "CheckColor" fails with error "Invalid Arguments"...

How could this happen? Am I doing something wrong?

You can download the two profiles here: Color Profiles

Thanks very much!

CString strProfilePath = _T("C:\\Windows\\System32\\spool\\drivers\\color\\");
CString strSrcProfile  = strProfilePath + _T("ProPhoto.icm");
CString strDstProfile  = strProfilePath + _T("CalibratedDisplayProfile-2.icc");
PROFILE pf = {0};
pf.dwType = PROFILE_FILENAME;
pf.pProfileData = (PVOID)strSrcProfile.GetBuffer();
pf.cbDataSize = (strSrcProfile.GetLength() + 1) * sizeof(TCHAR);
HPROFILE hSrcProfile = ::OpenColorProfile( &pf, PROFILE_READ, FILE_SHARE_READ, OPEN_EXISTING );
pf.pProfileData = (PVOID)strDstProfile.GetBuffer();
pf.cbDataSize = (strDstProfile.GetLength() + 1) * sizeof(TCHAR);
HPROFILE hDstProfile = ::OpenColorProfile( &pf, PROFILE_READ, FILE_SHARE_READ, OPEN_EXISTING );

HPROFILE hProfiles[2];
hProfiles[0] = hSrcProfile;
hProfiles[1] = hDstProfile;
DWORD dwIndents[2] = { INTENT_RELATIVE_COLORIMETRIC, INTENT_RELATIVE_COLORIMETRIC };
HTRANSFORM hTransform = ::CreateMultiProfileTransform( hProfiles, 2, dwIndents, 2, BEST_MODE, INDEX_DONT_CARE );

COLOR c0, c1;
c0.rgb.red = 0;
c0.rgb.green = 0;
c0.rgb.blue = 0xffff;
::TranslateColors( hTransform, &c0, 1, COLOR_RGB, &c1, COLOR_RGB );

BYTE btResult = 0;
::CheckColors( hTransform, &c0, 1, COLOR_RGB, &btResult );

::DeleteColorTransform( hTransform );
::CloseColorProfile( hSrcProfile );
::CloseColorProfile( hDstProfile );

Solution

  • I had the exact same issue as yours on Windows 7 SP1 x64. It seems that TranslateColors function is either broken by design or not supposed to be used that way. I think it's Microsoft's fault as they could write more WCS samples on MSDN.

    But I managed to solve the problem using TranslateBitmapBits function instead. Here is a sample:

    bool translateColors(BYTE* srcRgbColors, BYTE* dstRgbColors, DWORD nBytes)
    {
        BOOL bResult = FALSE;
    
        HPROFILE   hSrcProfile       = nullptr;
        HPROFILE   hDstProfile       = nullptr;
        HTRANSFORM hColorTransform   = nullptr;
    
        /* open source sRGB profile */
        wchar_t* srcProfilePath = L"sRGB Color Space Profile.icm";
    
        tagPROFILE targetProfile;
        targetProfile.dwType = PROFILE_FILENAME;
        targetProfile.pProfileData = srcProfilePath;
        targetProfile.cbDataSize = sizeof(wchar_t) * (wcslen(srcProfilePath) + 1);
    
        hSrcProfile = OpenColorProfile(&targetProfile, PROFILE_READ, FILE_SHARE_READ, OPEN_EXISTING);
        if (nullptr == hSrcProfile) goto EXIT;
    
        /* open destination monitor profile */
        wchar_t* dstProfilePath = L"ActiveMonitorProfile.icm";
    
        tagPROFILE destinationProfile;
        destinationProfile.dwType = PROFILE_FILENAME;
        destinationProfile.pProfileData = dstProfilePath;
        destinationProfile.cbDataSize = sizeof(wchar_t) * (wcslen(dstProfilePath) + 1);
    
        hDstProfile = OpenColorProfile(&destinationProfile, PROFILE_READ, FILE_SHARE_READ, OPEN_EXISTING);
        if (nullptr == hDstProfile) goto EXIT;
    
        /* create color transform */
    
        DWORD dwIntent = (DWORD)-1;
        HPROFILE hProfileList[2] = { hSrcProfile, hDstProfile };
    
        hColorTransform = CreateMultiProfileTransform(
            hProfileList,
            2,
            &dwIntent,
            1,
            NORMAL_MODE,
            INDEX_DONT_CARE
        );
    
        if (nullptr == hColorTransform) goto EXIT;
    
        /* transform colors */
        DWORD dwWidth = nBytes / 3; // 3 channels per pixel, 8 bits per channel, RGB format
    
        bResult = TranslateBitmapBits(
            hColorTransform,
            srcRgbColors,
            BM_RGBTRIPLETS,
            dwWidth,        // bitmap width
            1,              // bitmap height
            0,
            dstRgbColors,
            BM_RGBTRIPLETS,
            0,
            nullptr,
            0
        );
    
    EXIT:
        /* free resources */
        if (nullptr != hColorTransform) {
            DeleteColorTransform(hColorTransform);
        }
    
        if (nullptr != hSrcProfile) {
            CloseColorProfile(hSrcProfile);
        }
    
        if (nullptr != hDstProfile) {
            CloseColorProfile(hDstProfile);
        }
    
        return bResult == FALSE ? false : true;
    }
    
    /* example usage */
    BYTE srcBitmapData[3];
    srcBitmapData[0] = 0x1c;
    srcBitmapData[1] = 0x1a;
    srcBitmapData[2] = 0x1a;
    
    BYTE dstOutputBitmapData[3];
    bool bResult = traslateColors(srcBitmapData, dstOutputBitmapData, 3);
    

    Hope this helps.