Color Management on Qt with LittleCMS

By Marti Maria | December 9, 2020

I’ve been using Qt, by Qt Group for years and I must confess I am delighted. Many toolkits promises the mantra “Code once and run everywhere”, but indeed this works with Qt. Qt6 was announced few days ago. They now include some sort of color management on images QColorSpace, but still no neat way to use complex ICC V4 pipelines.

 

Qt

 

In this small article I will show you how to do true RGB color management in Qt, by using LittleCMS, with very few lines of code.

Color management in Qt

Qt provides a object called QImage. This object is reference-counted and quite efficient on memory. It can deal with Gray and RGB in a variety of formats. Some of those formats are elder and not used anymore like the 5-6-5 bits per component. Others, like 16 bits per channel are more advanced and can be used in modern RAW workflows.

So, here is a function that does RGB color management on a Qt QImage. You pass a vector of open profiles and a rendering intent plus some flags. The function returns a duplicate of the QImage with color management applied. And it is quite efficient.

QImage colorManage(QImage& img, QVector<cmsHPROFILE> profiles, int intent, cmsUInt32Number flags)
{

	cmsUInt32Number lcmsFormat = toLcmsFormat(img.format());

	if (lcmsFormat == 0) 			
		return QImage();    // Didn't work
		
	QImage dest(img.width(), img.height(), img.format());

	cmsHTRANSFORM xform = cmsCreateMultiprofileTransform(profiles.data(), profiles.size(), 
                                                         lcmsFormat, lcmsFormat, intent, flags);

	if (xform == nullptr)   // Couldn't create color transform 
		return QImage();

	cmsDoTransformLineStride(xform, img.constBits(), dest.bits(), 
							img.width(), img.height(), img.bytesPerLine(), 
							dest.bytesPerLine(), 0, 0);

	cmsDeleteTransform(xform);

	return dest;
}

Formats

The LittleCMS function cmsDoTransformLineStride() does all the magic. We need to convert from Qt format descriptors to LittleCMS format descriptors, the function below handles some cases. In this very simple way, it supports RGB and Gray in 8 and 16 bits per component, Alpha channels are supported as long as the image is not using premultiplied alpha. To get alpha channel make sure to include cmsFLAGS_COPY_ALPHA in the flags field. This is explained in LittleCMS documentation.

/**
* Convert from Qt format to lcms2 format
*/
static cmsUInt32Number toLcmsFormat(QImage::Format fmt)
{
	switch (fmt)
	{		
	case QImage::Format_ARGB32:  //  (0xAARRGGBB)
	case QImage::Format_RGB32:   //  (0xffRRGGBB)
		return TYPE_BGRA_8;

	case QImage::Format_RGB888:
		return TYPE_RGB_8;       // 24-bit RGB format (8-8-8).

	case QImage::Format_RGBX8888:
	case QImage::Format_RGBA8888:
		return TYPE_RGBA_8;

	case QImage::Format_Grayscale8:
		return TYPE_GRAY_8;

	case QImage::Format_Grayscale16:
		return TYPE_GRAY_16;

	case QImage::Format_RGBA64:
	case QImage::Format_RGBX64:
		return TYPE_RGBA_16;
			
	case QImage::Format_BGR888:
		return TYPE_BGR_8;

	default: 
		return 0;

	}
}

 

Using color management in your Qt application

Please note this is a very simplistic example, I am assuming you want same format as input which effectively limits color space conversions. Also CMYK/Lab/XYZ and other color spaces are not handled. Anyways, the goal of this entry is to show how easy could be to do complete RGB color management with only a few lines.

To use this function you need to do something like this:

QVector<cmsHPROFILE> profiles;

profiles << cmsCreate_sRGBProfile();
profiles << cmsOpenProfileFromFile("MonitorProfile.icc", "r");
      
QImage toDisplay = colorManage(yourImage, profiles, INTENT_PERCEPTUAL, 0);

		.. close the profiles, etc ..

Alas, LittleCMS can deal with images in other color spaces like CMYK, Lab, or XYZ. Qt actually does not support those color spaces in QImage(), but nothing prevents you to use QByteArray or any other container to store it as memory chunks. Happy Qt’ing!