New film-like tone mapping curves - tests/feedback needed

Recently I have been working on film-like tone-mapping curves, trying to derive analytical expressions that provide enough flexibility with few parameters, and a nice-looking compression of highlights.

For the moment I came up with two alternative parameterisations, one that tries to mimic the characteristic curve of invertible (slide) film using only two parameters, and another with more control over the shape but with no direct connection with actual film response.

Slide film emulation
In this case the input values are first converted to Log10 encoding, and then the Log10 values are converted in “simulated film density” using a reverse-S curve similar to the actual response of slide film:

The horizontal Log10 scale is defined such that the “0” point corresponds to 18% gray input (mid-gray), and is mapped to a 18% output density. In other words, mid-gray is preserved in the tone mapping.

The output density D is mapped back to linear luminance in this way:

Y = 1 / 10^D

The direct relationship between linear input/output RGB values is shown in the UI of the tool, with the axes converted to perceptually uniform coordinates to better see the shadows region. The horizontal axis goes from 0 to 2, with mid-gray at 0.5, while the vertical axis goes from 0 to 1:

Linear + log tone mapping
This more flexible parameterisation is composed of three parts:

  • a linear section in the middle, with a configurable slope and end point
  • a knee that smoothly connects the linear section to the origin, with an adjustable shape
  • a shoulder that compresses the highlights using a logarithmic function, with two parameters to control the shape and the end point (at which the output reaches 1).

Here are few plots that show the effect of the different parameters:
linear slope

linear range


shoulder shape

knee strength

The default parameters are adjusted to roughly match Blender’s “Medium High Contrast” filmic preset:

Here are few examples of the new curves in action:

Local contrast restoration
One of the drawbacks of the highlights compression is that is also reduces the local contrast in the bright areas. This effect can be mitigated by applying the tone mapping to a blurred version of the image, and then re-introducing the high-frequency components of the original image in the final tone-mapped result. The effect is often subtle, but noticeable. here is a without/with example:

No local contrast restoration

With local contrast restoration

I am currently working to include the “local contrast protection” into the tone mapping module, so that the same procedure can be a applied with any of the available tone mapping curves…

I would be very much interested in feedback about this new curves and the resulting tone-mapped images…

The code has been pushed to the “stable” branch on GitHub, and new pre-compiled packages should become ready in a short while.

Thanks a lot in advance!


I haven’t done a thorough analysis of it, but I recently used the linear+log tm in the Zorro photo and I liked the result.
So, my feedback for now would be simply showing a resulting image example that I particularly liked.
EDIT: … and thanks for the good work!

1 Like

Hello @Carmelo_DrRaw, I used PhotoFlow after quite some time. It seems a lot faster than before. The tone mapping module is really great. I used it on the Zorro the dog picture and it handled the highlights on his face magnificently. I don’t think I could have got this result in RawTherapee. Fantastic!

1 Like

@Carmelo_DrRaw, I noticed that PhotoFow still overwrites the old image without any warning. It will be nice if it adds a suffix (like in RT) or at least warn the user that you are going to overwrite an existing image.

@shreedhar noob, side question: how can I load your Zorro pfi file?
I saved yours to a different folder and opened it but the image wasn’t loaded…

@gadolf, if I drag my .pfi file onto the PhtoFlow shorcut, it opens the image. May be your raw file is not in the same folder?

The raw and pfi files need to be in the same folder…

Hum, that wasn’t enough.

I edited shreedar’s pfi file and noticed that there is an absolute path to the image.

Besides, he’s on Windows and I’m on Ubuntu.

I put the absolute path to mine and it worked, thanks to all!


Good work, @shreedhar!
You did a pure tone mapping stroke, and the balance between shadows and highlights is great.
I also liked my result, but, because of my settings, I had to use an HSL mask to protect the highlights, which is kind of cheating in terms of using the full power of the tool.
Maybe I got too limited by the tone mapping curve, which I tried to keep inside the boundaries of the graph.
Yours has sky rocketed :rocket: :slightly_smiling_face: but you’ve got the right balance anyway…




Thanks @gadolf. I think that the use of Multiply blend mode for the tone mapping layer was crucial to get the right look. PhotoFlow is really powerful as it allows to use masks and the blend modes at the same time. Right now, I am a bit busy. When I get time, I am plan to write the help files to these tools and send them to @Carmelo_DrRaw. If he finds them useful, he can include in his programme. Right now, it only says, no info is available for almost all tools.


Oh, I haven’t noticed that!
Well, lots of stuff to think about and practice, thanks.

@Carmelo_DrRaw Did you catch my first impressions on “linear+log”? And how certain settings added a kink or a reversal in the curve?

I have been meaning to ask if you could add some values to the graph’s axes. Thanks for your implementations!

Well, today I’m at home due to a bad conjunctivitis, a good opportunity to exercise free time. Besides, you asked for tests, so here’s another one. :wink:
Basically, it’s amazing how quickly you can get excellent results.
This one comes from thread Cannot get close to JPG from Sony Alpha 6000.
I just did White Balance, Dynamic Range Compressor (default values), Tone Mapping (linear+log)), a curve to boost the mid tones and another one just to darken the shadows
I did this edit without comparing to any other image (except the last curve). To my surprise, in the end, the result was much closer to the camera jpg, and to the DxO version (which I find one of the best in that thread, considering what the main goal was).

The only thing I couldn’t tackle was noise reduction - and the last curve somehow hides noise in the shadows.

@Carmelo_DrRaw The tm curve doesn’t seem to refresh anymore with this late version.

DSC09850.pfi (30.5 KB)

Yes, I noticed, and I’m trying to fix it… but it’s good to remind it here, thanks!

I will check if I can reproduce this… one tm in particular, or all of them?

I like it. Both give good results at first glance, the curve is easy to shape.
With this module alone we get at once nice images.
The only points I’ve noticed:

  • linear + log: compression = 0 screws the shoulder (0.1 is good).
  • I’ve lost the pf icon in the task bar (on windows, compared to pf-20181217) Edit: Though I did not drink yesterday …
1 Like

I didn’t tweak them, just used the default and I agree with @phweyland.

With this module alone we get at once nice images
as they are

BTW, is guided filter broken?.. I have a bunch of more recent presets that seems uncotpatible (they crash PhF); also if I select guided filter the program crashes. Please tell if you need terminal output or for me to open issue at github, I think you’ll manage to reproduce easily

That said, BIG thanks … for every bit =)


I won’t pretend I understood half of what you wrote but it is nice to see real word examples AND the graphics with their sexy lines :+1:

1 Like

Same here.

Linear&log and reversible film.
But if I choose filmic new, tweak some settings (and the curve refreshes), then I get back to linear or reversible, then the curve refreshes for these two.

Same here

Re: guided filter


Adding layer of type "Guided filter" (guided_filter)
ImageEditor::set_selected_layer(1): old_id=1  selected_layer_id=1
ImageEditor::set_selected_layer(2): old_id=1  selected_layer_id=2
OperationConfigGUI::disable_editing(): setting editing flag to false
GuidedFilterPar::build: radius=10  threshold=0.075
      ppar->get_output_padding(0)=6  ppar->get_output_caching()=1
GuidedFilterPar::build: radius=2.5  threshold=0.075
Image size: 1236,826
Shrink factor: 0.705811
ImageArea::update(): before vips_resize()
ImageArea::update(): after vips_resize(), outimg: 0x2147f9e6340  outimg2: 0x2147f9e6ca0
      ppar->get_output_padding(0)=1  ppar->get_output_caching()=0
GuidedFilterPar::build: radius=0.3125  threshold=0.075

@afre @chroma_ghost @nosle @gadolf @phweyland
I think I have fixed the issue with the guided filter… could you please test the latest packages?