Visualizing gamuts

By Marti Maria | October 2, 2019

Days ago, a very interesting question arose in the mailing list.

How can I visualiza the gamut of a profile?

Little CMS does not offer direct tools to do that. But with some code, it is easy to do so. Be warned there is some hacking required.

A typical profile can be thought as a “black box” that translates values from a colorimetric space, usually CIE L*a*b*, to a device space. For example, RGB in a screen profile. Since the range of realizable colors (the gamut) is limited on real devices, not all Lab values would have a corresponding RGB. Only the Lab values that are inside monitor gamut would be displayed.

 

ICC workflow

 

But the gamut of CIE L*a*b* is huge! What happens if we feed the profile with a value that cannot be directly translated?

The answer is, the profile applies a mapping that converts all impossible values to valid RGB codes. The simplest way to do that is just clipping, but more sophisticated ways may compress the Lab values over a small RGB range.

At that point, we can think on several strategies to know the gamut of a profile.

  • The first one that comen in mind is to do a roundtrip Lab -> RGB -> Lab, if the Lab value changes, then color has been moved and therefore is out of gamut. This approach is very fast and is what lcms uses for gamut checking. On relative colorimetric this should work on most cases.

On the other hand, some profiles does a very sophisticated mapping, specially on perceptual intent, that “compresses” colors. In this intent the roundtrip trick may not work because this compression.

  • Another method, which is better than the previous is by means of 3D geometric tools.

Since we cannot trust the Lab->RGB direction because gamut mapping may happen, we could use the RGB->Lab in relative colorimetric, this is also called the proofing direction. The idea is to do a sampling on RGB space and then apply some sort of convex hull detection on obtained Lab.

And this is what this article is about. LittleCMS includes an implementation of Jan Morovic’s Segment Maxima, which could help.

Now the hacking part

The code for segment maxima is seldom used. And have some glitches. It can be used, but it is there mainly for investigation purposes.

If we want to visualize gamuts, a good idea would be to increase the resolution of segments from 16 to something bigger, 128 for example.

Modifications in lcms2 source code

You have to modify file cmssm.c and change the definition:

    #define SECTORS 128 

Also, there is a de-activated function at the very bottom that will help us in visualizing the gamut, let’s activate by setting

    #if 1
    
	cmsBool cmsGBDdumpVRML(cmsHANDLE hGBD, const char* fname)

After that, here is the sample program:

	#include "lcms2.h"
    
    cmsHPROFILE hLab, hsRGB;
    cmsHTRANSFORM xform;
    cmsHANDLE hGamut;
    
    
    cmsInt32Number sampler_routine(CMSREGISTER const cmsUInt16Number In[],
    							   CMSREGISTER cmsUInt16Number Out[],
    							   CMSREGISTER void* Cargo)
    {
    	cmsCIELab Lab;
    
    	cmsDoTransform(xform, In, &Lab, 1);
    	cmsGDBAddPoint(hGamut, &Lab);
    	return 1;
    }
    
    
    // Uncomment this in cmssm.c
    cmsBool cmsGBDdumpVRML(cmsHANDLE hGBD, const char* fname);
    
    int main()
    {
    	cmsUInt32Number dimensions[3] = { 66, 66, 66 };
    
    	hLab = cmsCreateLab4Profile(NULL);
    	hsRGB = cmsCreate_sRGBProfile();
    
    	hGamut = cmsGBDAlloc(0);
    
    	xform = cmsCreateTransform(hsRGB, TYPE_RGB_16, hLab, TYPE_Lab_DBL, INTENT_RELATIVE_COLORIMETRIC, 0);
    
    	cmsCloseProfile(hsRGB); cmsCloseProfile(hLab);
    
    	cmsSliceSpace16(3, dimensions, sampler_routine, NULL);
    	
    	cmsDeleteTransform(xform);
    		    
    	cmsGBDdumpVRML(hGamut, "gamut.vrml");
    	cmsGBDFree(hGamut);
    
    	return 0;
    }

A small program, but what does is interesting.

  • It creates a sRGB and Lab profiles. You may want to use your profile or a profile from disk instead of sRGB.

  • Then both profiles are combined into a color transform RGB->Lab. From 16 bits to doubles.

  • Next it comes a call to cmsSlideSpace16(). What this function does, is to sample the RGB space at regular intervals, and call a callback function on each interval. The function is called with RGB components as 16 bits (hence the 16). Out is actually unused and present there because such callbacks can be used for other things, like 3DLUT population.

Our callback just uses the color transform to obtain the Lab value obtained for such RGB, and feeds the segment maxima gamut hull with the Lab value.

  • Once everything is done, we can visualize the gamut by calling the cmsGBDdumpVRML() thing. Note that there are some “holes”. That is, Lab values that are not obtained becase the RGB sampling points didn’t fall on required points. There is a function to fill those missing nodes, but does’t work well if we change the SEGMENTS definition. Ok, I have to review this part in next release.

Locate a good VRML viewer, this one works very well for me http://www.qiew.org/

And just open the gamut.vrml file

Gamut 1

Gamut 2

Finally, you can do some ASCII-ART sliding the gamut by doing this:

	int a, b;
	FILE* ascii_art = fopen("gamut.txt", "wt");
	
	for (a = -128; a < 128; a += 1)   // Set the resolution you wish
	{
		for (b = -128; b < 128; b += 1)
		{
			cmsCIELab Lab;
	
			Lab.L = 30;      // Set the L* for the slide you wish
			Lab.a = a;
			Lab.b = b;
	
			if (cmsGDBCheckPoint(hGamut, &Lab))
			{
				// Paint as inside gamut
				fprintf(ascii_art, "*");
			}
			else
			{
				// Paint as outside gamut
				fprintf(ascii_art, " ");
			}
		}
	
		fprintf(ascii_art, "\n");
	}
	fclose(ascii_art);

You will see the “holes” on the slide. Definitively I have to fix that on next releases.