Luminosity Masks in Darktable

Posted by Beetle B. on Tue 06 January 2015

When I first got into photoediting, I started off with the free software GIMP. I was a student and couldn’t afford Adobe Photoshop. While GIMP had/has shortcomings, and as much as people like to denigrate it, it was (and likely still is), the best general purpose photoediting tool after Photoshop. I don’t think the other commercial options are as good.

Years ago when I bought my DSLR, my focus switched to RAW development, which GIMP is not well suited to. I started using RAW tools, and if I needed to do serious touchups, I’d open the exported image in GIMP. However, my primary editing remained in the RAW workflow.

Over a year ago, I switched to the excellent free RAW development tool Darktable. I can’t say enough positive things about it. Also, now that life is a lot busier, I rarely use GIMP. After using Darktable and other RAW tools, doing just about anything in GIMP seems like a major pain. Yes, GIMP is a lot more powerful, but I am finally feeling the pain others have complained about. Most of my GIMP usage these days is when I intend to submit a photo to a competition and need to clean up the little details.

I was never a GIMP expert, and recently stumbled upon Pat David’s blog. I learned much from his articles, and I keep wondering whether I should return to GIMP to use his techniques.

I recently read his post on luminosity masks. I tried it on some photos in GIMP and was very impressed. Why doesn’t Darktable have this feature? I posted a request on the darktable mailing list. Soon after, I was informed darktable can do this using parametric masks. This was embarrassing as I actually use these masks all the time - just not in the best way possible.

So I thought I’d post a quick tutorial on using parametric masks in Darktable to get luminosity masks. First, I strongly suggest you read Pat David’s posts and thoroughly understand what’s going on.

If you didn’t understand it, a quick and simplistic explanation follows. Normally, if we make a selection and, say, adjust the brightness dramatically in that selection, we get an ugly transition near the edge of the selection:

A rectangular selection that was brightened.

The quick solution to this was to use a feathered selection. Feathering simply makes the transition less sharp:

A feathered rectangular selection that was brightened.

Better, but still too sharp a transition. You can feather it even more if you wish.

What luminosity masks do is let you select regions in your image in proportion to their brightness. So the L layer in Pat’s article fully selects completely bright pixels, and only partially selects pixels that are half as bright, and doesn’t select pixels that are not bright at all. When you now brighten the image, the effect of the brightening is greatest on the brightest pixels, and least on the darkest pixels. There are no sharp transitions like what I have in my screenshots above.

In that sense, some refer to these masks as self-feathering.

So how can we do this in Darktable?

Consider the following image:

The image we'll work on.

Let’s say I want to brighten it. Let me apply an aggressive curve:

Curve applied to brighten the image.

The result is:

Brightening the whole image.

Let’s use this as a “control” for the effect of luminosity masks.

The L mask

Using Pat’s technique, let’s look at the L mask in GIMP:

The L mask in GIMP.

Brighter areas mean they are “more” selected. This means any operation we perform on the image will be applied more on the brighter pixels.

How do we get this in Darktable?

Go to the Tone Curve module, set blend to parametric mask. Now comes the important part: In the Input sliders, select the top left triangle and move it all the way to the right:

The L mask settings in Darktable.

The resulting mask looks like:

The L mask in Darktable.

So what did I do here? To fully understand it, you should read the parametric masks page in the Darktable manual. By sliding the upper left triangle all the way to right, I told it to fully select the brightest pixels, not select the darkest pixels, and do a linear interpolation for all the intermediate pixels (so a 50% bright pixel is “half” selected).

Another way of looking at it: Apply the module to all the pixels, but apply an opacity on each pixel depending on its luminosity.

So how does the image look with the same curve?

Brighten with the L mask in Darktable.

The D Mask

To create the D mask, Pat selected the whole image, and subtracted the L channel from it.

In Darktable, we simply do the opposite of what we did for the L mask. We now move the top right triangle to the extreme left:

The D mask settings in Darktable.

The mask now looks like:

The D mask in Darktable.

The result of the curve:

Brighten with the D mask in Darktable.

The M Mask

What about medium? Let’s try:

The D mask settings in Darktable.

Here we moved both the upper triangles to the center.

The resulting image is:

Brighten with the M mask in Darktable. Too bright.

This is too strong! If I do the same using Pat’s luminosity masks in GIMP, I get:

Brighten with the M mask in GIMP.

This is not as strong as the Darktable version. What went wrong?

If we read Pat’s description, what he does is intersect the D and L channels. This results in the middle bright pixel only being 50% selected. In our Darktable version, we have it 100% selected. So we compensate by setting the opacity to 50% and we get very similar results to GIMP.

The resulting mask is:

The M mask in Darktable.

The Other Masks

What about the DD mask?

This is obtained by subtracting the L channel from the D mask. The equivalent mask in Darktable is:

The DD mask settings in Darktable.

This is the same as the D mask, but notice I moved the lower right triangle half way to the left. This has the effect that anything that is more than 50% bright will not be selected at all.

The resulting mask is:

The DD mask in Darktable.

If we wanted DDD, we’d move the lower triangle two thirds of the way instead of half.

Technical Details

Why did this work? Let’s jump into the math:

Let the luminosity of a pixel be denoted by \(l_{p}\). A value of 1 means fully bright, 0 means fully dark, and 0.5 means 50% bright.

In the L mask, \(l_{p}\) gives the percentage selection directly (1 means fully selected, 0.5 means half selected, etc).

To get the D mask, we select the whole image (which means each pixel is fully selected), and subtract the luminosity from it. Thus, in the D mask, the “selectedness” is \(1-l_{p}\). So if \(l_{p}\) was very bright (close to 1), it is now barely selected, as \(1-l_{p}\) will be a small number close to 0. Similarly, if it was originally very dark (close to 0), \(1-l_{p}\) is now close to 1 and it is almost fully selected.

Does my Darktable D mask translate to the same thing? Yes, as I believe Darktable does a linear interpolation.

What about the DD mask? Pat obtained it by subtracting the \(L\) channel from the \(D\) channel. In terms of our equations, this is just \(1-2l_{p}\). Note that if \(l_{p}\ge 0.5\), (greater than 50% brightness), then \(1-2l_{p}\le 0\), which means it is not selected at all. Only pixels less than 50% brightness are selected in this mask.

Again, my Darktable DD mask translates to the same mask, as I cut it off at 0.5. Since Darktable uses linear interpolation, the slope from 0.5 to 0 will be double the slope I had in \(D\). Hence, the factor of 2 in \(1-2l_{p}\).

I’m assuming the M mask translates as well but I’m not 100% sure what the algorithm GIMP uses to perform intersection.

Summary

So there you have it: Luminosity masks in Darktable. I wish I had figured this out earlier, as I usually used parametric masks to have sharp edges, and would feather by blurring the mask, which is not nearly as effective!

My one hope is that some day Darktable will add the option to have presets for parametric masks, so that I can just store all these as presets rather than repeatedly applying them for every module individually.