Photography has been an interest of mine for many years. I appreciate how pictures allow one to freeze moments in time, and in particular make for souvenirs that recall the feelings and experiences of the moment. I take pride in making that recall as effective as possible, and that includes developing the pictures from RAW files. Developing pictures requires taste, a good eye - and also a good display. Because, if my display’s color is off, I’ll make my picture look good on my display instead of on everyone else’s.

The objective thus becomes getting my display to show my pictures correctly. Of course, one can buy a color accurate display, though their price is difficult for me to justify, especially considering I do photography only as a hobby. More importantly, it’s also not that necessary, given that with a €50 colorimeter one can calibrate their display to be very close to such an expensive one.

In this post, I document how I calibrated my two displays with DisplayCAL and how I use this profile with RawTherapee (my preferred RAW developer). While doing so, I encountered a few gotchas - like, why DisplayCAL reports a perfect delta E, even though colors are still off, and why you have to set your ICC profile in both your computer’s settings and in the editing program you’re using.

Note I ran this on Ubuntu 24.04 Noble and Mint 22.3 Zena, both on X11. In case you’re using something else (e.g. Wayland), things may differ slightly. Also, this is the way I made it work for me; there may be other, preferable ways. (Please, for sure, leave a comment in case there’s anything I could do in a better way, or worse, am explaining incorrectly.)

From pixels to pixels

Before talking about the how, I’d like to shed some light on the why, in particular what happens under the hood when it comes to calibration and why it’s done this way. The flow chart below illustrates the flow, for both color managed applications (like, for example, RawTherapee) and non-color managed ones (like the Ubuntu desktop). Some applications internally do more color transforms (like applying a camera’s ICC profile); I’ve omitted those because these transforms are separate from display calibration.

Transformations applied to visual content as it flows to the display, for both color managed and non-color managed applications. (For those wondering, yes, this atrocious color scheme is on purpose.)

Key is that the calibration is applied in two steps - in particular, gamut mapping and VCGTs. (These two steps also correspond to the terms “calibration” (VCGTs) and “characterization”/”profiling” (gamut mapping); however, in practice, few adhere to this terminology…) The VCGTs (Video Card Gamma Tables), consist of three 1D LUTs (Look Up Tables), one for each color channel (i.e. red, green and blue). Applying the VCGTs makes that each color channel exhibits the desired input-output intensity response, which results in the gray response (rendering of colors with identical R, G and B values) is “correct” - that is, it behaves like the color space (e.g. sRGB) selected in the calibration process. This step alone already yields a significant improvement, and with many modern monitors this makes for a pretty decent rendering.

Upon a closer look, however, you’ll notice that different displays render the same color setpoint with a slightly different hue and especially saturation. The core issue is that their color channels occupy different coordinates in gamut space. As an example, in layman’s terms, to reproduce another display’s pure green, your display may have to add a bit of red and blue. Gamut mapping takes care of, well, mapping the image’s color channels to those of your display.

There’s a plethora of ways to execute this mapping, called rendering intents. The key difference lies in the handling of out-of-gamut colors. Ideally, your display can show any color in your picture; we say that the display covers the picture’s gamut. In practice, this is not always true - for example, as we’ll see further on, my laptop’s display covers only 64% of the sRGB color gamut. There are three common rendering intents:

  • perceptual: compresses the source gamut to fit in the target space. This avoids clipping colors, but comes at the cost that most colors are “wrong”, usually desaturated.
  • absolute colorimetric: shows colors exactly as defined, except for out of gamut colors, which get clipped.
  • relative colorimetric: same as absolute colorimetric, with the difference that the whitepoint gets adjusted to that of your display. You probably want to use relative colorimetric.

A display ICC profile, as generated by tools like DisplayCAL, contains both the VCGT and the gamut mapping, with the latter usually represented as a 3x3 matrix (in case of the Matrix/shaper model). Of note is that, like the figure shows, this gamut mapping is only applied by color managed applications. In practice, this means that your desktop background, file manager etc. show slightly off colors, even after calibration. Correspondingly, applying an ICC profile only to the color managed application is insufficient, because that application won’t set the VCGT tables.

The reason it’s done this way is that, at least historically, consumer GPUs lack the required hardware for gamut mapping. Though many support applying a CTM (Color Transformation Matrix), it’s pretty useless because it gets applied in the color space the display got calibrated to (e.g. sRGB), which is nonlinear by default, while a CTM must be applied in a linear color space. Workstation-class GPUs, and increasingly some consumer GPUs, incorporate degamma-CTM-regamma hardware instead of only a CTM + VCGT, where the degamma part transforms the input signal to a linear space such that the CTM gets applied in a linear color space.

A few comments

  • Recent AMD GPUs come with degamma-CTM-regamma hardware and the Linux kernel DRM subsystem supports this, though not all software makes use of it yet.
  • Apple put proper degamma-CTM-regamma hardware in their Apple Silicon GPUs and forces the whole display pipeline through that, and as a result everything is color managed by default by lack of any other way to draw a pixel on the screen. Good.
  • MS Windows in its SDR mode handles display calibration in the same way as classic Linux, where you thus experience the same issue that non-color managed applications have their gray response corrected but not their color gamut. Meanwhile, their new ACM system (“Auto Color Management”) has yet to reach maturity. In short, the grass isn’t any greener over there.

Generating the ICC profile with DisplayCAL

Install DisplayCAL

I used PyDisplayCAL, a fork of DisplayCAL updated to Python3. The installation through PyPI went smoothly, though it took about a dozen minutes of build time. Used commands (Ubuntu 24.04):

# Install dependencies
sudo apt install build-essential dbus libglib2.0-dev pkg-config \
    libgtk-3-dev libxxf86vm-dev python3-dev python3-venv

# Install DisplayCAL
cd ~
python3 -m venv .venv-displaycal
source .venv-displaycal/bin/activate
pip install displaycal

# Run DisplayCAL
displaycal

As a colorimeter, I’ll be using a Datacolor SpyderX, which are very affordable second hand.

Configure DisplayCAL

Under Display & instrument, we select the display to calibrate (“Monitor 1”) and also the instrument mode (“LCD White LED”, as this is an old LED display with sub-sRGB gamut). We leave drift and correction at their defaults.

Next, in Calibration, we have to set to which color space we want to calibrate our display. Let’s start with the Tone curve, which sets how the display will respond after the VCGTs have been programmed (i.e. after applying calibration). For color managed applications, it doesn’t matter what you set here, as they’ll correct for it. On the other hand, non-color managed applications kind of expect sRGB, so I chose that. Some prefer “Gamma 2.2” here, which renders shadows darker making for a more contrasty look. White level boils down to “how bright is white”. The sRGB standard references 80 cd/m²; however, in practice, this is too dark for use in bright offices, so I put this on “As measured”. Next, there’s Whitepoint, which sets what color white should be rendered as. sRGB asks for a color temperature of 6500K here. Personally, I experience this as tiring after a few hours and prefer 5000K. (Coincidentally, this is also what the print industry uses, who call it “D50”.) For editing pictures, the exact value doesn’t matter much as long as you show your picture on a white background, which, after a few minutes, makes your brain do the required adjustment. DisplayCAL allows for “Interactive display adjustment”, not useful for my laptop display where I can’t adjust much and want to adjust the brightness later on anyway.

Finally, under Profiling, the defaults are pretty good. One can’t easily change the profile name afterwards and it defaults to something vague like “Monitor_1*”, while I prefer to have a more descriptive name.

A last word - before you run calibrate, make sure to also configure your display correctly. The DisplayCAL manual lists many tips; I won’t repeat them here. Basically, make sure that your display behaves consistently; otherwise any calibration attempt will be in vain.

Running DisplayCAL and results

After clicking “Calibrate & profile”, DisplayCAL takes about 40 min to go through many color patches and optimizing the whole to an ICC file. To avoid interfering with the calibration, it’s best to just not touch the computer. After that, the following window pops up:

DisplayCAL provides the option to load the calibration on login. On Ubuntu, though, this doesn’t appear to play well together with Ubuntu’s night light and other color settings, so I instead applied the resulting ICC file through Ubuntu settings (see further on). I therefore also didn’t install the profile using DisplayCAL.

As you can read in the figure above, my laptop display came out pretty terribly, with an sRGB coverage of just 64%. (To be clear, I actually do my editing on another display, which does fully cover sRGB.) The following two figures show the calibration and profiling output (click them to enlarge). For the gamut plot, I set the rendering intent to relative colorimetric, which moved the sRGB triangle’s whitepoint to the calibration 5000K one, as that’s how I’ll be using it. The resulting ICC profile gets stored in ~/.local/share/DisplayCAL/storage/<profile name>/<profile name>.icc.

The calibration curves are what's put in the VCGTs. Of note is that the blue line is markedly lower, which is a result of me selecting a target white point that's far warmer than the display's native color temperature.
Meanwhile, the gamut plot shows how this display's primaries situate themselves in the CIE xy colorspace, with the dashed triangle being the limits of the sRGB color space.

What’s left is to install the calibration. In Ubuntu, one can use for this the /usr/share/color/icc directory. Let’s copy it to there:

cd /usr/share/color/icc
sudo cp <profile path>.icc .

Don’t put it in a child directory, as that will keep RawTherapee from finding the profile.

If you want, you can let DisplayCAL create a verification report for the ICC profile. This basically checks whether the VCGTs are being applied correctly and is also useful to quickly check that the display behavior didn’t drift. Though, keep in mind that this doesn’t check much (if anything at all?) related to gamut mapping, which is a key part of a full color managed flow. Therefore, an excellent result here is insufficient to be sure you’re managing colors correctly.

Applying the calibration

Calibration in Ubuntu settings

Ubuntu desktop provides a small GUI under Settings > Color where one can set for each screen the ICC profile to use. Select the display, click “Add profile” and select the profile we just created.

This applies the VCGTs and, assuming you also put in a warm white point like 5000K, you’ll notice a clear switch to this warmer temperature.

digiKam

digiKam supports color management, not only for the previews but also for the thumbnails. To enable it, go to Settings > Configure Digikam… > Color Management > Profiles and, under Color Managed View, select our created display ICC profile.

Note Under the tab Advanced, digiKam allows to specify the rendering intent, though I didn’t notice any difference on how the display ICC profile gets applied, to me it looks like it’s fixed to relative colorimetric (makes sense).

RawTherapee

At the bottom centre of the editing window, there’s a dropdown with the default text “None”. Click it, and select your display profile. That’s it! It’s also very nice that RawTherapee makes it easy to change the profile, which is required when working with a multi-monitor setup. Note that in case your display doesn’t fully cover your output profile (which is usually sRGB), its relative colorimetric rendering intent may clip some colors. To show where in the image clipping occurs, press the two buttons to the right of the dropdown, “Soft-proofing” and “Highlight pixels with out-of-gamut colors”.

RawTherapee without (left) and with (right) the display profile set (and thus gamut mapping enabled). While the difference isn't night-and-day, it's more than sufficient to influence editing decisions. Picture: Mount Brewster after exiting Gargoyle Valley, Cory Pass Loop, Banff

Note In the above image, showing the raw screenshot would be wrong, as by the point that the pixels get captured, RawTherapee already applied the gamut mapping. (Therefore, that raw screenshot only looks like it should on my laptop screen.) I therefore converted it to sRGB by applying the display profile in reverse using ImageMagick:

convert rawtherapee_gamut_mapping_side-by-side_raw.png \
  -profile /usr/share/color/icc/Dell7559_sRGB_5000K_2026-03-08.icc \
  -intent relative \
  -profile /usr/share/color/icc/colord/sRGB.icc \
  rawtherapee_gamut_mapping_side-by-side.png

Further reading

DisplayCAL manual
_Display calibration and profiling with Argyll, Ander’s Torger
ICC profiles, Arch Linux wiki
Color management on Linux, PCode
Display color profiling on Linux, PCode
A Standard Default Color Space for the Internet - sRGB, W3C
If you’ve a wider interest in light and color: Rendering the Visible Spectrum, Brandon Li