LCMS2 Unbounded ICC Profile Conversions

When Marti Maria rewrote LCMS (LittleCMS) and released it as LCMS2, his motivation wasn't just to accomodate V4 ICC profiles: he designed LCMS2 to work in "unbounded mode". Unbounded mode ICC profile conversions eliminate interim clipping from color space encoding limitations. And when using linear gamma profiles at 32-bit floating point image precision, in unbounded mode there is no gamut clipping even when the image color gamut exceeds the color gamut of the destination color space.

Introduction: About LCMS2 bounded and unbounded ICC profile conversions

Marti Maria's Unbounded Color Engines explains the difference between bounded and unbounded mode ICC profile conversions. Prior to LCMS2, ICC profile conversions were "bounded" by the encoding limitations of all the color spaces involved in any given series of ICC profile conversions. Consider a series of conversions from, say, a camera input profile to XYZ to ProPhoto, and then from ProPhoto to LAB and back to ProPhoto, and then perhaps from ProPhoto to XYZ to sRGB for output (a not unusual series of color space conversions when using a raw processor like RawTherapee, Photivo, or Darktable). In bounded mode, clipping would happen at every step along the way whenever the resulting RGB, LAB, or XYZ values exceed any of these intermediate color spaces' respective encoding ranges. In unbounded mode this "intermediate color space clipping" doesn't happen.

Unbounded mode can take two very different forms: In the usual case, values less than zero and greater than one are carried from one ICC profile conversion to the next and clipping is postponed until the point at which the RGB image is output in a file format that doesn't support negative values or values greater than 1 (eg jpegs, pngs, and 8-bit and 16-bit integer tiffs). However, if the following three conditions are met, then the RGB values aren't clipped at all:

  1. The ICC profile conversions are done at 32-bit floating point precision.
  2. The ICC profiles that are used have true linear gamma curve Tone Reproduction Curves.
  3. The RGB image is output in a file format that does support floating point RGB channel values that are 0.0 and greater than 1.0 (eg PFM, OpenEXR, and floating point tiffs).

This article compares 32-bit floating point bounded and unbounded ICC profile conversions that are output to a file format that supports negative values and values greater than 1.0, and then examines some possible use cases for 32-bit floating point "completely clipless" unbounded mode ICC profile conversions.

Reproducing the examples on this page requires LCMS2 version >2.4, GIMP 2.9 from git, and a Q16+hdri compilation of ImageMagick.

Profiles that can work in unbounded mode

To work in unbounded mode, an ICC matrix profile Tone Reproduction Curve ("TRC") needs to be of a type that can be mathematically and unambiguously extrapolated below 0 and above 1. If the profile TRC is of the right type, unbounded mode works equally well with V2 and V4 profiles.

V2 lookup table profiles can't work in unbounded mode because there is no way to know what the channel curves should do for values below 0.0 or above 1.0. Matrix profiles with "point curves" (eg the standard 1024-point sRGB TRC or a 4096-point approximation of a true gamma curve) also can't be unambigously extrapolated below 0.0 or above 1.0 (even if the point curve merely traces a straight line) and so can't be used in unbounded mode.

ICC matrix profiles with true gamma curve TRCs (eg gamma=1.00, gamma=1.80, etc) can operate in unbounded mode, as can profiles with certain V4 parametric TRCs. There is even a V4 parametric curve replacement for the standard sRGB TRC that works in unbounded mode. And some V4 lookup table profiles have parametric curves that can work in unbounded mode.

Bounded and unbounded profiles used in this article

For the ICC profile conversions performed below:

All profile conversions listed below for both bounded and unbounded mode were done using the relative colorimetric intent; using or not using black point compensation makes no difference as all the profiles have a zero black point. Except when explicitly stated otherwise, all references below to ProPhoto and sRGB should be interpreted to mean the linear light versions of these commonly used RGB color spaces.

Round-trip bounded and unbounded mode 32-bit floating point conversions

Bounded and unbounded mode 32-bit floating point conversions from ProPhoto to sRGB and back

Three views of the ProPhoto color space (the multicolored wires) surrounding the much smaller sRGB color space (the white blob in the middle), as seen from inside the LAB reference color space using the relative colorimetric intent. The ProPhoto color gamut vastly exceeds the sRGB color gamut.

Bruce Lindbloom's incredibly useful RGB16Million test image will provide a nice demonstration of the difference between LCMS2 bounded and unbounded mode at 32-bit floating point precision. Lindbloom says:

Here is an 8-bit per channel RGB TIFF image file containing exactly one pixel of each of the 16,777,216 possible color values. The image is 4096 × 4096 = 16,777,216 pixels, arranged as 256 slices of the RGB color cube. It is not tagged with any profile. This image may be useful for investigating the effects that image processing or color transformations have on the number of colors in an image.

Screenshots of the RGB16Million test image are posted with the kind permission of Bruce Lindbloom.

Left: The Lindbloom test image after assigning the regular ProPhoto color space. Right: Tificc softproof "gamut check" warnings when sRGB is selected as the proof color space. The areas covered in gray indicate colors that will be clipped upon conversion from ProPhotoRGB to sRGB.

In case you've never seen a gamut check warning before, all the gray areas in the image on the right indicate pixels whose original color in the ProPhoto source color space exceeds the color gamut of the sRGB destination color space. When assigning ProPhoto to the test image and then doing a relative colorimetric conversion to sRGB, all the colors in the areas covered by gray will be clipped to the outer boundaries of the sRGB color space. The pixels covered in gray won't be all be gray after the color conversion! Rather they'll be clipped to whatever color on the surface of the sRGB color gamut happens to be the closest to the ProPhotoRGB colors that "won't fit".

Below I used the LCMS tificc utility to perform round-trip relative colorimetric conversions from ProPhoto to sRGB and back, first using bounded mode at 32-bit floating point precision and then using unbounded mode at 32-bit floating point precision. I used ImageMagick to determine how many pixels have the same RGB values before and after the round-trip conversion.

Here is the procedure for the bounded and unbounded mode conversions:

  1. Assign the regular ProPhoto ICC profile to the 8-bit RGB16million.tif test image and convert the image to a linear version of ProPhoto at 32-bit floating point precision.
  2. Convert the image to a linear version of sRGB.
  3. Convert the image back to a linear version of ProPhoto.
  4. Convert the image back to regular gamma=1.8 ProPhoto.
  5. Convert the image back to an 8-bit image.
  6. Use ImageMagick to create a "difference image" for the RGB16Million test image before and after the round-trip ICC profile conversion.
  7. Use ImageMagick to count the number of solid black pixels in the difference image to determine the number of pixels whose original RGB values were not altered by the round-trip ICC profile conversion.
Bounded mode using 32-bit floating point precision:
The initially assigned ProPhoto color space profile has a true gamma=1.8 TRC, 
but the linear ProPhoto and linear sRGB profiles both use 4096-point linear TRCs.  
So the ICC profile conversions work in bounded mode and 
RGB values are clipped during all four ICC profile conversions:

#1 tificc -c 0 -w 32 -e -t 1 -i pho-g180.icc -o pho-linear4096.icc RGB16Million.tif rgb32b-pro2prolin.tif
#2 tificc -c 0 -w 32 -e -t 1 -i pho-linear4096.icc -o srgb-linear4096.icc rgb32b-pro2prolin.tif rgb32b-pro2prolin2srgblin.tif
#3 tificc -c 0 -w 32 -e -t 1 -i srgb-linear4096.icc -o pho-linear4096.icc rgb32b-pro2prolin2srgblin.tif rgb32b-pro2prolin2srgblin2prolin.tif
#4 tificc -c 0 -w 32 -e -t 1 -i pho-linear4096.icc -o pho-g180.icc rgb32b-pro2prolin2srgblin2prolin.tif rgb32b-pro2prolin2srgblin2prolin2pro.tif
#5 convert rgb32b-pro2prolin2srgblin2prolin2pro.tif -depth 8 rgb32b-8-pro2prolin2srgblin2prolin2pro.tif
#6 composite -compose difference RGB16Million.tif rgb32b-8-pro2prolin2srgblin2prolin2pro.tif difference32b.tif
#7 convert difference32b.tif -fill white +opaque "rgb(0,0,0)" -format %c histogram:info:
   4222382: (  0,  0,  0) #000000 black
  12554834: (255,255,255) #FFFFFF white
[RGB values for 12554834 pixels were altered 
by the bounded round-trip ICC profile conversions.]

Unbounded mode using 32-bit floating point precision:
The initially assigned ProPhoto color space profile has a true gamma=1.8 TRC, 
and the linear gamma ProPhoto and sRGB profiles have a true gamma=1.0 TRC, 
so the ICC profile conversions work in unbounded mode.
The 32-bit floating point tiff file format allows writing RGB values 
that are less than 0 or greater than 1. 
So the RGB values are not clipped during the ICC profile conversions and 
not clipped when the image is written to disk:
#1 tificc -c 0 -w 32 -e -t 1 -i pho-g180.icc -o pho-g100.icc RGB16Million.tif rgb32u-pro2prolin.tif
#2 tificc -c 0 -w 32 -e -t 1 -i pho-g100.icc -o srgb-g100.icc rgb32u-pro2prolin.tif rgb32u-pro2prolin2srgblin.tif
#3 tificc -c 0 -w 32 -e -t 1 -i srgb-g100.icc -o pho-g100.icc rgb32u-pro2prolin2srgblin.tif rgb32u-pro2prolin2srgblin2prolin.tif
#4 tificc -c 0 -w 32 -e -t 1 -i pho-g100.icc -o pho-g180.icc rgb32u-pro2prolin2srgblin2prolin.tif rgb32u-pro2prolin2srgblin2prolin2pro.tif
#5 convert rgb32u-pro2prolin2srgblin2prolin2pro.tif -depth 8 rgb32u-8-pro2prolin2srgblin2prolin2pro.tif
#6 composite -compose difference RGB16Million.tif rgb32u-8-pro2prolin2srgblin2prolin2pro.tif difference32u.tif
#7 convert difference32u.tif -fill white +opaque "rgb(0,0,0)" -format %c histogram:info:
  16777216: (  0,  0,  0) #000000 black 
[Completely lossless: all pixels in the difference image are black, 
so no pixels were altered by the unbounded round-trip ICC profile conversions.]

So after the 32-bit floating point conversion in bounded mode, the not-at-all surprising result is that only 4,222,382 pixels (25%) out of the original 16 million pixels recovered their original RGB values. The RGB values of the remaining pixels were altered by converting to sRGB and back in bounded mode.

But after the 32-bit floating point conversion in unbounded mode, every single pixel recovered its original RGB values. I repeated the above procedure using GIMP 2.9 from git to do the conversions and the results were the same: the unbounded mode conversion from ProPhoto to sRGB and back was completely lossless.

Now that is earth-shaking news: In unbounded mode, the conversion from ProPhoto to sRGB doesn't result in color gamut clipping because colors that would otherwise be clipped instead are encoded using RGB values less than 0.0 and greater than 1.0.

As an aside, although this article doesn't compare bounded and unbounded conversions done at 16-bit integer precision, it turns out that unbounded mode 16-bit integer conversion of the RGB16Million test image resulted in very, very slightly fewer clipped pixels than bounded 32-bit floating point conversion.

Bounded and unbounded mode conversions from a camera input profile to ProPhoto and back

The "camera-to-ProPhoto" test image (the screenshot below on the far left) was created from crops of saturated colors from a wide selection of interpolated raw files. All the raw files were interpolated using dcraw; no additional processing was done to modify the "from the camera" colors. The camera test image also includes crops from various IT8 target shots. One of the target shots was used to create the ArgyllCMS simple linear gamma matrix camera input profile that was applied to all the raw files that were used to create the test image.

I used GIMP 2.9 from git and also tificc to perform 32-bit floating point bounded and unbounded mode conversions from the camera input profile to ProPhoto and back. When using GIMP 2.9, the unbounded mode round-trip conversion was completely lossless, as expected. However, when using tificc, for some reason (a bug?) the unbounded mode round-trip conversion was very far from lossless, even though the exact same type of conversion worked perfectly when converting the RGB16Million test image from ProPhoto to sRGB and back. Also the tificc profile conversion produced a sprinkling of bright red, green, and blue 100% saturated pixels across some of the shadow areas in the original image (see the screenshot below on the far right). Possibly this result is related to a bug that was reported recently (in February, 2013; I think this bug has been fixed but I haven't redone the tests, sorry!) on the LCMS mailing list by one of the RawTherapee developers. So the rest of this page only uses GIMP 2.9 from git for testing.

ProPhotoRGB is thought of as big enough to hold all colors from interpolated camera raw files without clipping. As shown by the magenta areas in the center image above, sometimes converting an interpolated camera raw file to ProPhotoRGB does clip colors. Which colors might be clipped depends on the camera, the camera input profile, and of course the image colors.
  • Above left: The camera test image after assigning a custom camera linear gamma matrix camera input profile, and also after the 32-bit round-trip conversion to ProPhoto and back using GIMP.
  • Above center: When the ICC profile conversion from the camera input profile to ProPhoto is performed in bounded mode, the pixels covered in bright magenta indicate colors that will be clipped during the conversion.
  • Above right: When using tificc the round-trip unbounded mode conversion produced an image speckled with 100% saturated red, green, and blue patches (indicated by white arrows).

To do the bounded mode ICC profile conversions, I created a "bounded" version of my simple linear gamma matrix camera input profile, exactly like the original camera input profile except with a linear 4096-point curve instead of a true gamma=1.0 TRC, and I used the "bounded" ProPhoto-like profile with the same linear 4096-point curve. Here is the procedure for the 32-bit floating point bounded mode round-trip ICC profile conversion using GIMP 2.9:

  1. Open GIMP and open cam.png.
  2. Assign cam-4096linear.icc and export as cam-4096linear.png.
  3. Change precision to 32-bit floating point.
  4. Convert to pho-4096linear.icc and save as cam2pro-bounded.xcf.
  5. Close and then reopen GIMP.
  6. Reopen cam2pro-bounded.xcf, convert to cam-4096linear.icc, and export as cam2pro2cam-bounded.png.
  7. Use ImageMagick to calculate the difference image and count the number of pixels whose RGB values were not changed by the round-trip conversion.
GIMP Bounded mode:
composite -compose difference cam.png cam2pro2cam-bounded.png cam-bounded-difference.png
convert cam-bounded-difference.png -fill white +opaque "rgb(0,0,0)" -format %c histogram:info:
    638042: (    0,    0,    0) #000000000000 black
   5025658: (65535,65535,65535) #FFFFFFFFFFFF white
[RGB values for 5025658 pixels were altered by the bounded conversion.] 

During the 32-bit floating point bounded round-trip relative colorimetric ICC profile conversion from the camera input profile to ProPhoto and back to the camera input profile, only 638,042 pixels (%11) retained their original RGB values. The other 89% of the camera test image pixels had their RGB values modified to some degree.

Now let's rinse, wash, repeat, this time using 32-bit floating point unbounded mode ICC profile conversions, using the unbounded versions (with true gamma=1.0 TRCs) of the camera input and ProPhoto-like profiles.

  1. Open GIMP and open cam.png.
  2. Assign cam-g100.icc and export as cam-g100.png.
  3. Change precision to 32-bit floating point.
  4. Convert to pho-g100.icc and save as cam2pro-unbounded.xcf.
  5. Close and repen GIMP.
  6. Reopen cam2pro-unbounded.xcf, convert to cam-g100.icc, and export as cam2pro2cam-unbounded.png.
  7. Use ImageMagick to calculate the difference image and count the number of pixels whose RGB values were not changed by the round-trip conversion.
GIMP Unbounded mode:
composite -compose difference cam-g100.png cam2pro2cam-unbounded.png cam-unbounded-difference.png
convert cam-unbounded-difference.png -fill white +opaque "rgb(0,0,0)" -format %c histogram:info:
   5663700: (  0,  0,  0) #000000 black
[Completely lossless: no RGB values were altered by the unbounded round trip conversion.]

As every pixel in "cam-unbounded-difference.png" is solid black, every single pixel in the camera test image has exactly the same RGB values before and after the 32-bit floating point unbounded round-trip relative colorimetric ICC profile conversion from the camera input profile to ProPhoto and back to the camera input profile.

For reference purposes, here are the results of the tificc unbounded round-trip conversions between the camera input profile and ProPhoto. The tificc bounded mode conversions exactly matches the GIMP bounded conversions, but the tificc and GIMP unbounded mode conversions gave very different results:

#tificc needs tifs, not pngs:
convert cam.png -depth 16 cam-4096linear.tif
convert cam.png -depth 16 cam-g100.tif

tificc Bounded mode:
tificc -c 0 -w 32 -e -t 1 -i cam-linear4096.icc -o pho-linear4096.icc cam-4096linear.tif cam-bounded2pro.tif
tificc -c 0 -w 32 -e -t 1 -i pho-linear4096.icc -o cam-linear4096.icc cam-bounded2pro.tif cam-bounded2pro2cam.tif
convert cam-bounded2pro2cam.tif -depth 16 cam16-bounded2pro2cam.tif
composite -compose difference cam-4096linear.tif  cam16-bounded2pro2cam.tif cam-bounded2pro-difference.tif
convert cam-bounded2pro-difference.tif -fill white +opaque "rgb(0,0,0)" -format %c histogram:info:
    638042: (    0,    0,    0) #000000000000 black
   5025658: (65535,65535,65535) #FFFFFFFFFFFF white
[RGB values for 5,025,658 pixels were altered by the bounded conversion]

tificc Unbounded mode:

tificc -c 0 -w 32 -e -t 1 -i cam-g100.icc -o pho-g100.icc cam-g100.tif cam-unbounded2pro.tif
tificc -c 0 -w 32 -e -t 1 -i pho-g100.icc -o cam-g100.icc cam-unbounded2pro.tif cam-unbounded2pro2cam.tif
convert cam-unbounded2pro2cam.tif -depth 16 cam16-unbounded2pro2cam.tif
composite -compose difference cam-g100.tif  cam16-unbounded2pro2cam.tif cam-unbounded2pro-difference.tif
convert cam-unbounded2pro-difference.tif -fill white +opaque "rgb(0,0,0)" -format %c histogram:info:
   4353773: (    0,    0,    0) #000000000000 black
   1309927: (65535,65535,65535) #FFFFFFFFFFFF white
[RGB values for 1,309,927 pixels were altered by the "supposed to be unbounded" conversion]

Notice that the GIMP and tificc bounded mode round-trip conversions result in exactly the same numbers of pixels with RGB values that were modified by the round-trip conversion between the bounded version of the camera input profile and the bounded version of ProPhoto. But for the unbounded mode conversions, whereas GIMP produced the expected completely lossless round-trip ICC profile conversion, tificc produced very odd results, again possibly related to an issue reported recently on the LCMS mailing list by one of the RawTherapee developers.

Bounded and unbounded 32-bit floating point conversions from a camera input space to LAB space and back

As GIMP 2.9 does not yet convert from an RGB color space to the LAB color space, for these conversions I used the tificc built-in *lab profile:

Bounded mode:
tificc -c 0 -w 32 -e -t 1 -i cam-linear4096.icc -o *lab cam-4096linear.tif cam-bounded2lab.tif
tificc -c 0 -w 32 -e -t 1 -i *lab -o cam-linear4096.icc cam-bounded2lab.tif cam-bounded2lab2cam.tif
convert cam-bounded2lab2cam.tif -depth 16 cam16-bounded2lab2cam.tif
composite -compose difference cam-4096linear.tif cam16-bounded2lab2cam.tif cam-bounded2lab-difference.tif
convert cam-bounded2lab-difference.tif -fill white +opaque "rgb(0,0,0)" -format %c histogram:info:
    888873: (    0,    0,    0) #000000000000 black
   4774827: (65535,65535,65535) #FFFFFFFFFFFF white
[RGB values for 4,774,827 pixels were altered by the conversion]

Unbounded mode:
tificc -c 0 -w 32 -e -t 1 -i cam-g100.icc -o *lab cam-g100.tif cam-unbounded2lab.tif
tificc -c 0 -w 32 -e -t 1 -i *lab -o cam-g100.icc cam-unbounded2lab.tif cam-unbounded2lab2cam.tif
convert cam-unbounded2lab2cam.tif -depth 16 cam16-unbounded2lab2cam.tif
composite -compose difference cam-g100.tif  cam16-unbounded2lab2cam.tif cam-unbounded2lab-difference.tif
convert cam-unbounded2lab-difference.tif -fill white +opaque "rgb(0,0,0)" -format %c histogram:info:
   3919792: (    0,    0,    0) #000000000000 black
   1743908: (65535,65535,65535) #FFFFFFFFFFFF white
[RGB values for 1,743,908 pixels were altered by the conversion]

In bounded mode, 4,774,827 (84%) of the camera test image pixels had their RGB values modified by the round-trip conversion through LAB space. In unbounded mode, only 1,743,908 pixels (31%) had their RGB values modified by the round-trip conversion through LAB space. I'm not sure why any pixels at all were modified during the unbounded round trip through LAB space, but possibly tificc might not have been patched yet to reflect the latest changes in the LCMS2 from git code.

As an aside, the conversion from the camera matrix input profile to ProPhoto and back uses XYZ as the profile connection space. However, when I tried to do a round-trip conversion through the XYZ space and back to the camera space using the tificc built-in *xyz profile, I got an error message that said "[tificc fatal error]: Unsupported color space of 9 channels".

Use cases for 32-bit floating point LCMS2 unbounded Mode ICC profile conversions

Unbounded mode 32-bit floating point ICC profile conversions are all very well and good, but what are they good for?

Interim ICC profile conversions

In addition to interpolating raw files, today's free and open source raw processors provide sophisticated editing capabilities. So during raw processing, an interpolated raw file might undergo multiple ICC profile conversions, from the camera input space to an RGB working space for editing, possibly to LAB space for additional editing, and to an output space for saving to disk. Using unbounded ICC profile conversions would eliminate clipping from interim color space mismatches.

However (and contrary to what I had previously assumed and written about in this very article), as explained in Section C3 below, actual image editing shouldn't be done on out of gamut colors:

Storing, transporting, and displaying digital images

If your editing software supports writing a 32-bit floating point image to disk, and also supports unbounded ICC profile conversions, then you can convert any image to any RGB working space profile you like (as long as it's of a type that supports unbounded mode ICC profile conversions) and save the image to disk without clipping any color data. Upon opening the image with appropriate editing software, it will display properly on your monitor screen.

However, for actual image editing (as opposed to merely displaying the image), the usefulness of "unbounded image editing" is very limited, as outlined below:

Editing in unbounded RGB working spaces is not a good idea

When doing an unbounded ICC profile conversion, the destination color space's color gamut is also effectively unbounded. So it's tempting to conclude that therefore any unbounded RGB working space, no matter how small its color gamut, can be used for editing any image, no matter how large the image's color gamut might be. In particular, occasionally a software developer will conclude that because unbounded sRGB can be used to encode and display all possible colors, therefore unbounded sRGB is suitable for use as a "universal working space" for image editing.

However, there is a big difference between encoding, displaying, and storing images, versus image editing, and unbounded color spaces should not be used for image editing. It doesn't take too much testing to demonstrate that unbounded RGB color spaces, and in particular the unbounded sRGB color space, shouldn't be used for image editing:

To summarize, unbounded color spaces are fine for encoding and displaying images, but should not be used for editing images.

Conclusion: Where appropriate, use unbounded ICC profile conversions

Summarizing the benefits and limitations of unbounded ICC profile conversions: