I have been looking at the tone equalizer for a while now and I finally have something that may be good enough to show it publicly.
Disclaimer: This is my first time working on a module and the learning curve is steep. This preliminary version has known problems (it may produce deviating results on export and crashes when you make the graph too small).
This is a summary/explanation of what I have changed and why I have changed it.
3rd party example images
Forest scene by @Mattes, Sunbeam highlights a detail in the forest
High contrast garden by @Popanz, High contrasts in a man made wilderness
Basics of the Tone Equalizer
Why is it difficult to raise shadows / lower highlights? If we simply make dark pixels brighter and bright pixels darker, we will loose contrast - especially local contrast and the image will look dull and HDR-ish.
(In all of the example images I went pretty extreme with âwrongâ settings to show the effects.)
To prevent this, we want to treat pixels that are close to each other nearly the same. This leads to the introduction of a mask:
- We convert the image to black and white.
- We blur it.
- We decide based on this mask, how much each image pixel will be brightened or darkened.
This strategy is suitable for preserving small details (local contrast), but it breaks when the differences are too large. In this case we get bright or dark borders around the edges of objects.
This is why the tone equalizer uses a guided filter. This filter will blur uniform areas of the image, but preserve contrasty edges. The âEdges refinement/featheringâ slider will tell the guided filter, what an edge is. Very low values will completely blur the image while high values will detect everything as an edge and blur nothing at all.
It is worth playing with the mask when tone equalizer results donât look good. If the image looks to HDR-ish, the mask may not be blurry enough. If there are artifacts around objects, edge detection is too low.
In the final step, we use a curve, the sliders or the mousewheel to set how much brightness correction should be applied, each brightness level of the mask gets an associated correction.
Note that the âEVâ values that are shown on the histogram and sliders are âmask EVsâ and only very loosely related to the dynamic range of the image. Even without extra âcompensationâ applied, the dynamic range of the mask can easily be half of that of the image, so the shown EV values can be misleading.
The problems with exposure/contrast compensation
The sliders for mask exposure compensation and mask contrast compensation - together with their respective âmagic wandsâ - are notorious for their unpredictable behavior. When they are changed, the mask histogram usually moves roughly in the right direction, but also often jumps around. These sliders change exposure and linear contrast BEFORE applying the guided filter.
Blurring an image reduces its dynamic range and contrast. This is easy to see, the brightest pixel gets averaged with nearby pixels, making it less bright, and similarly the darkest pixel becomes less dark. As a result, the difference between the brightest and darkest pixels (the dynamic range) decreases. However, the extent of this reduction depends on the brightness of the surrounding pixels, which makes the behavior unpredictable.
The main change in this experimental version is swapping the order of the steps:
This way the mask can be modified deterministically.
Side note: In current Darktable the lower section of the masking tab is incorrectly labelled âmask post-processingâ while it actually pre-processes the mask before the guided filter calculation.
Which order is better?
The result when swapping the order of operations is not the same as before. This is why I implemented new (additional) Exposure/Contrast sliders and the old ones are still in the module for backwards compatibility.
If the result is different, which order is better? I would argue that changing mask exposure after the filter is better for this reason:
The original uncompensated contrast of the mask is directly derived from the image. Therefore it provides a somewhat deterministic baseline for setting âEdges refinement/featheringâ, especially when defining settings that should be valid for multiple images (module defaults, presets).
Using contrast compensation artificially âdisconnectsâ the maskâs properties from the image:
This is an extreme example, as this image has a low dynamic range and does not need the tone equalizer. However, before clicking âauto adjustâ, the mask is reasonably blurry - which is what you would want. Clicking the magic wand not only increases the mask contrast (the original intention) but also removes the blurriness completely.
People use the mask contrast compensation to âspread the histogramâ, so they can use all 9 sliders for precise control. I donât think that users are aware of the effects this has on edge detection. Some users even want to work only on shadows or only on highlights and spread the graph even more for this reason. Such adjustments surely require an adjustment to âEdges refinement/featheringâ.
The proposed reversed order does not have these problems. Users can move the histogram around to their heartâs content without effects on edge detection.
Taking things too far
Very strong corrections with the tone equalizer tend to look bad.
Ignoring the âmask EV vs. image EVâ difference, we can make the following observation: Itâs actually the downward slopes of the curve that lead to a loss of contrast. Worse, a downward slope of more than 1 EV per EV in the curve will reverse brightnesses in the image (i.e. area A was darker than B and now B is darker than A).
Curves that spread the downward slope linearly over a long distance, making it less steep, gave nicer results in my (few) tests.
Therefore I think that the sine-like curves from the âcompress shadows/highlightsâ presets are not optimal for compressing the dynamic range. Those curves are the steepest in the midtones, the brightness range that we care about the most.
When there is no desire to keep the blacks/whites unchanged, an alternative would be the straight downwards line that I have seen @s7habo use on Youtube.
Experimental warnings
After noticing the significance of the slope, I added another very experimental feature: coloring the curve to warn the user, if they are doing something that will probably look bad.
Currently implemented ârulesâ:
-
A downward slope of more than 0.5 EV/EV is orange, a downward slope of more than 1 EV/EV is red. For this I compare the 5th and 95th percentile of image and mask to get a very rough estimate of how much the mask dynamic range differs compared to the image.
-
Raising the shadows and lowering the highlights with the guided filter turned off will cause orange warnings.
-
Lowering the shadows and raising the highlights with the guided filter turned on will cause orange warnings. The user probably expects to be able to improve the image contrast here, but the guided filter will work against this.
Note that when users spread out the histogram (i.e. to work only on shadows), the curve is actually much steeper than it looks. Users who use this type of precision editing have to be very gentle to not introduce artifacts.
The updated UI
Auto align is another new functionality that was not yet mentioned. It should automatically align the mask, which is especially useful for presets. Using this option, the mask will also re-align itself, when an upstream module in the pipeline changes the image exposure.
Possible values are:
-
Custom - Set mask contrast and brightness manually (current default).
-
Fully fit - Fit the mask, so the darkest 5% of values are below the -7 marker, 90% are between -7 and -1 and 5% are above -1 (the same target values that the old magic wands used). Mask contrast and brightness sliders are not available in this mode.
-
Align at shadows - Fit the mask, so the darkest 5% of values are below the -7 marker and 95% above. The contrast slider is available in this mode, so the mask histogram can be scaled around the -7EV point. Set it to approximately 1 to work only on shadows.
-
Align at midtones - Fit the mask, so some middle brightness value is locked at -4 EV. The contrast slider is available in this mode, so the mask histogram can be scaled around the -4EV point.
-
Align at highlights - Fit the mask, so the brightest 5% of values are above the -1 marker and 95% below. The contrast slider is available in this mode, so the mask histogram can be scaled around the -1EV point. Set it to approximately 1 to work only on highlights.
Note that all brightnesses below -8EV are treated the same as -8EV and all brightnesses above 0EV are treated as 0EV. I noticed in tutorials that people like to make the histogram smaller, so it is fully contained in the 8EV range. I donât think that this is necessary.
Setting the new controls to auto-align: custom
, scale: 0
, shift: 0
will make the module behave the same as the previous tone equalizer.
Next steps
I hope this is useful.
Over on the AgX thread I learned that some users of this forum build preview versions of darktable with experimental features. Maybe this could be included there (just copy my toneequal.c
). However note that I added new parameters - going back to an old version from this one would be problematic, so donât try this on your production database.