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.
Written February 2013. Updated January 2016.
I wrote this article using ImageMagick version 6. Some of the commands have changed in ImageMagick version 7, but I'm not going to rewrite the article to use the newer commands. A very ImageMagick-savvy person over on the pixls.us forum provided this information, for anyone who might want to follow along the various terminal commands used in this article:
Those ImageMagick commands should work fine for IM v6, or IM v7 if installed “with legacy commands”. I recommend not using “composite” for anything. That is a very old tool, long deprecated. Instead, use “convert … -composite”, with the two input images in the opposite order. That way, a sequence of IM commands can be readily squished into a single command.
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:
- The ICC profile conversions are done at 32-bit floating point precision.
- The ICC profiles that are used have true linear gamma curve Tone Reproduction Curves.
- 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:
- Four of the profiles have true gamma TRCS and so can be used in unbounded mode: "pho-g180.icc" and "pho-g100.icc" have ProPhoto-like primaries and true gamma curves of 1.80 and 1.00, respectively; "srgb-g100.icc" has the sRGB primaries and a true gamma curve of 1.00; and "cam-g100.icc" is a custom camera matrix input profile with a true gamma curve of 1.00.
- Three profiles are bounded counterparts to the unbounded linear gamma ProPhoto-like, sRGB, and camera input profiles: pho-linear4096.icc, srgb-linear4096.icc and cam-linear4096.icc are exactly the same as the corresponding unbounded profiles, except they have linear 4096-point curve TRCS instead of true linear gamma curves. Conversions done using these three profiles only work in bounded mode, regardless of the bit depth of the image or the file format of the output image.
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
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.
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:
- 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.
- Convert the image to a linear version of sRGB.
- Convert the image back to a linear version of ProPhoto.
- Convert the image back to regular gamma=1.8 ProPhoto.
- Convert the image back to an 8-bit image.
- Use ImageMagick to create a "difference image" for the RGB16Million test image before and after the round-trip ICC profile conversion.
- 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.
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:
- Open GIMP and open cam.png.
- Assign cam-4096linear.icc and export as cam-4096linear.png.
- Change precision to 32-bit floating point.
- Convert to pho-4096linear.icc and save as cam2pro-bounded.xcf.
- Close and then reopen GIMP.
- Reopen cam2pro-bounded.xcf, convert to cam-4096linear.icc, and export as cam2pro2cam-bounded.png.
- 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.
- Open GIMP and open cam.png.
- Assign cam-g100.icc and export as cam-g100.png.
- Change precision to 32-bit floating point.
- Convert to pho-g100.icc and save as cam2pro-unbounded.xcf.
- Close and repen GIMP.
- Reopen cam2pro-unbounded.xcf, convert to cam-g100.icc, and export as cam2pro2cam-unbounded.png.
- 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: Handle with care!
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. Editing in unbounded color spaces is possible and useful, but only if you know what you are doing:
- On the one hand, many editing operations, specifically operations based on add and subtract, will produce the exact same results in any unbounded RGB working space.
- On the other hand, many image editing operations, specifically operations based on multiplying or dividing by a color other than gray, are chromaticity dependent, producing different results in different RGB working spaces (even if all the image colors are in gamut with respect to the bounded versions of both working spaces). This means there is no such thing as a "one size fits all, universal color space".
- Many image editing operations produce physically impossible and mathematically meaningless results if performed on colors that are outside the bounded RGB working space's color gamut. Such editing operations include Levels gamma slider adjustments and also any operation that makes use of multiplying and dividing by a non-neutral color.
- One critically important editing operation, correcting an unwanted image color cast:
- uses multiplication, and so is chromaticity dependent, producing different results in different RGB working spaces,
- produces meaningless results if performed on colors that are outside the working space's bounded color gamut, and
- fails completely if not performed in the same RGB working space in which the unwanted color cast was created, even if none of the image colors are out of gamut with respect to some other RGB working space that the artist or photographer might prefer to use.
- Despite the above warnings, unbounded color spaces can be very useful for image editing, allowing the artist to postpone clipping to an output color space until she's ready to bring the colors back into gamut. But to take full advantage of unbounded editing, the artist must keep in mind that multiplication and division by colors encoded using negative RGB channel values does produce meaningless results.
To summarize, unbounded color spaces are fine for encoding and displaying images, but should be handled with care when not be used for editing images.
Conclusion: Where appropriate, use unbounded ICC profile conversions
Summarizing the benefits and limitations of unbounded ICC profile conversions:
- When doing floating point ICC profile conversion in your digital darkroom using, using unbounded mode conversions can result in fewer clipped colors from interim color space gamut mismatches. Unfortunately there are a lot of V4 matrix profiles floating around free/libre software that use point curves instead of true gamma curves or appropriate V4 parametric curves. So it would be beneficial to check to make sure that your chosen ICC profiles do work in unbounded mode, and maybe file a bug report if they don't.
- Editing in unbounded RGB working spaces is possible, indeed very useful, but does require that the artist or photographer understand the limitations of manipulating out of gamut colors.