Generating mid-tone masks


I have been working through @patdavid’s excellent tutorial on luminosity masks in G’MIC but I am stuck on the mid-tones part. There are probably a bunch of ways to do it but I want to follow the luminosity mask method as closely as possible.

PS ImageMagick can intersect L and D. Would still like to figure out a G’MIC equivalent.
PPS Actually, it appears to work for M only, not subsequent mid-tones.

Image from the tutorial. Originally from Mountains as Far as the Eye Can See (cc-by)




Broken image links and link status
Selection Behaviour
Luminosity Masks Review - A new technique I found and would like to see if these are accurate
(David Tschumperlé) #2

I think command -inrange could help you :

$ gmic -h inrange
  gmic: GREYC's Magic for Image Computing. 
        Version 2.0.3 (pre-release #170713), Copyright (c) 2008-2017, David Tschumperle. 


        Detect pixels whose values are in specified range [min,max], in selected images.
        (eq. to '-ir').

        Example: image.jpg --inrange 25%,75%

Something like :

$ gmic image.jpg --luminance -inrange. 25%,75% -mul[-2,-1]

should do the task ?


This won’t do

Trying to get

from an intersection between “Lights” and “Darks”, whatever that means mathematically.

@patdavid Could you provide images of the masks as independent files? I don’t know how to save the channels in GIMP.

(David Tschumperlé) #4

The command -inrange creates a binary mask (with values in [0,1]), and then when you multiply it with your original image, you should get your midtones image.

(Pat David) #5

I’m digging the file up for you now - should be able to post it shortly.

You can drag a channel from the channels palette onto the layers palette to turn it into a layer.


@David_Tschumperle Luminosity masks aren’t supposed to be binary in nature. Their utility is that they feather luminosity-wise (as opposed to spatially). Instead of multiplying, I would use -blend_fade, which I asked about a while back if you recall, though at the time I was essentially using a D mask.

@patdavid Thanks. Then I can compare my results with yours. ATM I am able to generate L LL LLL and D DD DDD but have difficulty with M MM MMM. Eventually, I would like to figure out a more generalized approach to luminosity-feathered masking.

(Pat David) #7

This is a dead-simple GIMP file of a black-white gradient so it’s easier to see. The L, M, and D masks are generated and there as layers (this should make it clearer to @David_Tschumperle what’s going on with them).

lum-test.xcf (258.4 KB)

Did you follow the tutorial on It mid-tone masks should be the intersection of each L/D pair (L/D, LL/DD, LLL/DDD).

The light mask:

The dark mask:

The mid-tone mask:

(Garry R. Osgood) #9

Well. In the land of the gmic command line, we pull this kind of rabbit out of the hat this way:

gmic -input nps_wear.jpg -luminance. --mul. -1 -normalize 0,1 -fill. ‘if(i>0.5,i#-2,i)’ -normalize. 0,’{2^15-1}’ -output mnt_midtone.png

which in English, takes an RGB image and translates into a grey-tone image by the formula L = 0.222R + 0.717G + 0.061*B, which is G’MIC’s notion of ‘luminance.’ and pretty much fits with the notion of luminance presented in Wikipedia’s Relative luminance article (‘I read it on the Internet, so it must be true.’) :wink: That grey-tone image corresponds to ‘Lights’ in @afre July 23 post.

We multiply ‘Lights’ by negative one to get ‘Darks’ (the negative) and normalize both to the range 0 to 1 so that the follow-on selection logic is straightforward. That leaves us with two images on the stack, the positive, ‘Lights’ followed by the negative ‘Darks’. For every pixel in ‘Lights’ with a luminance l, the corresponding pixel in ‘Darks’ is 1 - l. Ramp and Negative Ramp.

There is a bit of selection logic that is handed off to the G’MIC -fill. command, which instructs it to examine each pixel in ‘Darks.’ If a pixel in ‘Darks’ is running greater than 1/2 in the luminance range, then copy the pixel from ‘Lights’. Otherwise, keep the pixel from ‘Darks.’ That makes the composite: the mid-tone mask, which replaces ‘Darks’ as the second image on the stack.

A little more simply: "If it is darker than the mid-tone, take the pixel from “Lights” (copying the shadows) otherwise take from ‘Darks’ (copying the highlights, but the highlights are dark in the negative). That leaves a composite with the mid tone pixels being lightest (at about 1/2, normalized luminance). and the extreme highlights and shadows from the original tending to black.

The last two commands are exit steps: scale suitable for a 16 bit Portable Networks Graphics file and write it out. That leaves the mid-tone level at grey 32,767 and the black-white ends of the original luminance curve tending to zero, similar to Patrick’s test ramps.

Kids! You Can Try This At Home With Your Very Own Pictures! The Gimp-G’MIC plug-in lets you run snippets of unvarnished gmic code!!!

  1. Fire up GIMP
  2. Load Your Very Own Picture into GIMP
  3. Start the G’MIC plug-in
  4. Choose the ‘Various -> Custom code (global)’ filter.
  5. Replace whatever code is written there with the bold middle steps from the pipeline written at the top of this post (leave out the file-load and file-save bits).
  6. Set ‘Value action’ to ‘Normalize’
  7. Set the Input/Output choice #4 to ‘2nd output’ - the preview should now hold a fair dinkum mid-tone mask preview.
  8. Hit ‘OK’
  9. The midtone mask will reside in Layer 0.

Going by this route, the mid-tone mask will differ from Patrick’s canonical example in that the mid-tone will map to relative luminance 1.0, 255 (8 bit layers), 65,535 (16 bit layers), not 0.5, 127 or 32,767. So sue me. Or go to the Curve tool after the remapping and drag the right end of the value curve to the midway mark. Or tack a -normalize. 0,127 (8 bit layers) -normalize. 0,32767 (16 bit layers) at the end of the G’MIC pipeline, set the ‘Value Action’ widget to ‘None’ and then, not have to worry about using the Curve Tool. Ta-da!

Hope this makes the World a better place.



@grosgood Thanks for the detailed response. I will take a look when I have the time.

My intention is to learn new ways to use the cli. I constantly refer to the reference page, the *.gmic files and your tutorials. Actually applying this stuff is a challenge because I am not a programmer or math wiz. However, it has grown to be a hobby :slight_smile:.

(Thomas) #11

Thanks @grosgood for the explanation!

So, if I want to use this kind of mask in Darktable, can I choose a “parametric luminosity mask” and set the filled top sliders to 50 and the open buttom sliders to 0 and 100% to get the same result?

(Garry R. Osgood) #12

Brief reply - because:
(1) I have not worked with Darktable any where near as with G’MIC, so possibly I don’t know dang-all what I’m talking about, and
(2) Could be, with your question, this thread is wandering out of the land of G’MIC into the land of Darktable, or maybe the Processing thread (sub-topic, the care and feeding of masks), and so the extensive answer should be there, where people with questions in those veins can quickly find answers in those veins. And so that people in charge of keeping this message board a neat, orderly place don’t start developing nervous ticks or anything like that.

So, caveats posted, my answer is:
a. exactly the same? – Damnifino. Probably not. Reasonably sure.
b. Similar? – Yeah. I think maybe so. Could be. Similar Enough.
c. Differences important? – Probably not. But it’s your dime.

Judging from Darktable’s mask preview, you certainly have set up a mask that preferentially filters for the mid-tones, passing the mid-tones at 50% reductions and the extremes at 0%. The intensity of yellow, Darktable’s way of indicating the pixels that are most affected by the parametric mask, are brightest yellow in the right places – the mid-tone pixels, and least yellow in the shadows and highlights.

What I’m not sure about on the Darktable side is the drop-off profile. Patrick’s method gives a straight-line (linear) drop-off from the mid-tone to the extremes; the graph looks like a triangle with the peak at 50%. I don’t
know what Darktable’s drop-off looks like at the settings you made. One of these days, maybe I’ll spelunk around Darktable somewhat more than casually, so that I know something about what I’m talking about.

What you can do is make a gradient (ramp) image: black on one edge, white on the other edge, and two or three or a thousand even steps from one to the other - the linear gradient - and run that image through your filter. You’ll see exactly which tones get squashed and by how much. Then you’ll know more than I do, and could teach me. If you can get me to siddown long enough to read.


EDIT: Ah! Documentation! 3.2.8 Parametric mask! Always good to RTFM. In light of the phrase: “trapezoidal opacity function” I’ve amended my responses. Spend some time with this page. It’s a nice one. – G

(Thomas) #13

@grosgood: Thanks for the detailed answer! I did not want to hijack a G’MIC thread and start a lengthy discussion about Darktable. I just would like to better understand the theory of these masks and a comparison of the implementation in different programs might help.
I will try your suggestion about testing on a linear gradient. Thanks again.

PS: I tried the linear gradient with the settings mentioned above. After inverting the mask I set the tone curve to 0, in this way applying black to the extreme shadows and highlights, while the mid-tones remained unaffected at ~ 50%.


@Thomas_Do Darktable’s blog usually has the goodies :wink::

How to access RGB values for calculation?

@patdavid I copied the gradient from lum-test.xcf to a new file and was able to generate all the masks save for MM and MMM, which ended up being pure black lm-afre.xcf (GIMP 2.8.22).


Here’s more or less what I use in the “spectral tones” view of “image infomap” filter:

-to_rgb -luminance -- {iM/2} -abs -negate -n 0,255

Edit: forgot the negate :crazy_face:

(Garry R. Osgood) #18

Pat published a scheme script (Script-Fu) at automating
the steps of his tutorial (here) and (here). This is an excerpt from the 10-23-2013
version of his script, latest at the registry, that constructs the three mid tone masks. Line numbers, on
the left, are for navigation purposes and are not part of the original script.

    82          ; Now build the mid tone masks
    83          ; Activate "L" mask first
    84          (gimp-image-select-item Image CHANNEL-OP-REPLACE L)
    86          ; And intersect the "D" mask, saving new selection as "M"
    87          (gimp-image-select-item Image CHANNEL-OP-INTERSECT D)
    88          (gimp-item-set-name (car(gimp-selection-save Image)) "M")
    90          ; Select the entire image
    91          (gimp-selection-all Image)
    93          ;Subtract "DD" and "LL" to get "MM"
    94          (gimp-image-select-item Image CHANNEL-OP-SUBTRACT LL)
    95          (gimp-image-select-item Image CHANNEL-OP-SUBTRACT DD)
    96          (gimp-item-set-name (car(gimp-selection-save Image)) "MM")
    98          ; Select the entire image
    99          (gimp-selection-all Image)
   101          ;Subtract "DDD" and "LLL" to get "MMM"
   102          (gimp-image-select-item Image CHANNEL-OP-SUBTRACT LLL)
   103          (gimp-image-select-item Image CHANNEL-OP-SUBTRACT DDD)
   104          (gimp-item-set-name (car(gimp-selection-save Image)) "MMM")

Lines 82 - 88 are consistent with the tutorial. M arises from an intersection of L and D.
Lines 90 - 104 perform 1 - LL - DD and 1 - LLL - DDD. These are successive subtractions from initial “select all” masks and are not consistent with the tutorial, which suggests performing a series of intersections: LL intersect DD
and LLL intersect DDD. The exact words from the tutorial are:

Intersecting Channels for Midtones

To get started, first select the “L“ channel, and set it to the current selection (just like above). Right-Click → Channel to Selection.

Then, Right-Click on the “D“ channel, and choose “Intersect with Selection”.

…[omitted for brevity]…

You can repeat for each of the other levels, creating an MM and MMM if you’d like.

If you follow just the tutorial, and do DD intersect LL => MM and DDD intersect LLL => MMM then MM and MMM will necessarily be black because, for all cases, LL is disjoint with DD and LLL is disjoint with DDD – that is, they do not have any pixels in common. Indeed, they cannot. Here’s why:
No issue with L and D. L is just a simple linear function u. I use u because the logical choice for luminosity, l looks too much like numeral one. D of course is the inverse 1 - u
To make LL, we are told to subtract from L, D. Because we are operating on unsigned quantities, this operation necessarily results in a spline. For the half-open interval [0,…0.5) LL is zero. For the half-open interval [0.5,…,1.0) LL is a rising gradient: 2u - 1.
Similarly, to make DD, we are told to subtract from D, L. As before, the results is a spline. For the half-open interval [0,…,0.5) DD is a descending gradient: 1 - 2u For the half-open interval [0.5,…,1.0) DD is zero.
We can perform similar exercises for the derivation of LLL and DDD. LLL is zero over the half-open interval [0,…,0.667) and for the half open interval [0.667,…,1) is an ascending gradient: 3u - 2. DDD is a descending gradient: 1 - 3u for the half-open interval [0,…,0.333) and is zero for the half open interval [0.333,…,1.0).
If we perform the intersect of LL and DD, we see that one has no overlap with the other. Similarly with LLL and DDD there is a gap from [0.333,…,0.667] where both curves map luminosity to selection 0%. Intersection only furnishes a non-zero mask for L and D.
On the other hand, the procedure encoded in Pat’s script finds MM by 1 - LL - DD and MMM by 1 - LLL - DDD. When I follow these recipes for MM and MMM, I can precisely reproduce the array of masks that Pat presents at the beginning of his tutorial. That leaves me to believe the script has it right, but the tutorial does not.
I’m quite sure that Pat meant to write the tutorial perfectly aligned with his script. But FOSS documentation is often written during stolen moments when we’re not otherwise at our day jobs, dealing with clients, or when dogs are not otherwise leaving doo-doo in the middle of the living room five minutes before the guests arrive for dinner. I’ve written gaffes worse than this and, in my professional documentation projects, have been paid for them – I’m embarrassed to say. I try to get the count right on my fingers, but sometimes I miss because I’m tired. Still and all, at the end of the day, I remain in awe of everything Pat shoots and writes. Sometimes sh*t happens, that’s all.

Hope this helps.


@garagecoder That works as well. Has an ever so slightly different result than @grosgood’s method.

@grosgood Thanks for the pretty pictures and bringing me back to reality.

I kept on thinking that I was doing something wrong, convinced that I was going crazy, but in the back of my mind, a little prisoner called reason was telling me that the mid-tones were 1 - x, 'cause how else could MM and MMM be brighter and wider than M? Turns out it was the yellow brick road after all :sunny:.

As for @patdavid’s script, I may have taken a glance or two in the past but shied away from it because of the scripting. It didn’t work for me in the recent versions of GIMP, so I forgot about it entirely.


@grosgood I am still working through your post :blush: but I am working toward a more generalized approach / understanding to feathered-intensity masking. I picture the end goal might be to figure out how to do a M-like mask that can be centered about any intensity point (from the initial -luminance or whatever) of my choosing; and be able to decide the width and curve of the feathering. I wonder if that would have anything to do with your other post.

(Garry R. Osgood) #21

You’re on to me. I’m writing a tutorial on this topic, and, yes there was a hint of that in the discussion with our colleague @s7habo. The operational premise stems from centering a gaussian bell curve on any grayscale value that one may desire and generating a selection mask based on it.

A Gaussian distribution curve sports a lot of interesting features for “feathered” drop-off, in that the waning selectivity assigned to progressively lighter or darker pixels surrounding the tonal value of interest exhibits the ease-in, ease-out drop off that animators like: nothing abrupt. Ease in the rate of deselection for tonal values which are very much like the tonal value of interest. For tonal values progressively less like the tonal value of interest, deselect at a faster rate, but then, for tonal values quite different, ease out the rate of deselection again.

Here is an illustration of what the drop off looks like, where the ‘tonal value of interest’ is a normalized luminance target of 0.6 :

gmic                                     \
   target=0.6                            \
   selectivity=0.05                      \
   size=1024                             \
   bitdepth=16                           \
   -input '$size',1,1,1,'x/(w-1)'        \
   -fill 'gauss(i-$target,$selectivity)' \
   -normalize 0,'{2^$bitdepth-1}'        \
   -display_graph[-1] 960,540,1,1,0,0,0,0,'relative_luminance','selection_strength' \
   -output. gbell.png


The key lines here are in boldface, everything else is just in the service of illustration setup or breakdown. The key bits of information are:

1 target: this identifies the tonal value of interest, which, in the luminance mask, corresponds to the whitest (most selected) pixels. This numeral is in terms of relative luminance. Multiply it by 2bitdepth: of the graphic file format to get the actual grayscale tone that translates to the “most selected pixel” in the mask.
2 selectivity: this sets the overall rate of drop-off from the tonal value of interest to “tonal values of complete disinterest.” Smaller numbers engender a “tighter” selection – fewer pixels are affected, the drop off is quite rapid. Larger numbers give rise to a “looser”, more inclusive selection. The useful range is [0,…,1], methinks.

-input ‘$size’,1,1,1,‘x/(w-1)’ just introduces a gradient as a test image. A slightly more real-world setup would load a real image of interest, followed by –luminance (to duplicate, and preserve, the original). All this falls quite close to the “soft” -select_color chatter in the other post, for color images are just stacks of grey-scales associated with some color-space rules. The color “value of interest” is just a vector of “tonal values of interest”, so run this logic over the however many grey-scale channels there are in the color space.

It doesn’t take a vast amount of imagination to see this implemented as a gmic-gimp filter, with one slider to pick the “tonal value of interest” and the other slider controlling the “tightness of drop-off”. Noodle the first slider to pick the level of grey that should be most affected by subsequent edit operations. Noodle the second slider to set the degree of isolation between the tonal values of interest and all the other tonal values in the image. I almost think there is a gimp-gmic filter already out there with nearly this behavior; maybe I should be arsed enough to take a closer look at the gmic filter plug-in and the new toys there. One of them is yours, I gather.

Take care,



@grosgood Thanks for connecting the dots again. I have lots of almost-there ideas and snippets of code but need someone more qualified to help me think through the problem. Presently, I am finally able to generate any one of those luminosity masks.