A cookbook of transfer functions for high dynamic range images.
HDR images generally have values higher than QuantumRange, and may have negative values. A common objective for HDRI transfer functions is to reduce values to the range [0,QuantumRange].
For simplicity, we refer to values as multiples of QuantumRange, so we refer to QuantumRange as "1.0".
For a sample input, we make a grayscale gradient.
%IMG7%magick ^ -size 5000x1 gradient:gray(0%%)-gray(1020%%) ^ -evaluate Subtract 20%% ^ -define quantum:format=floating-point -depth 32 ^ +write htf_samp.miff ^ -format "MIN=%%[fx:minima] MAX=%%[fx:maxima]" ^ info:
MIN=-0.200003 MAX=10
The image is a grayscale gradient, with values in the range [-0.2,10].
The identity transfer, output = input:
call %PICTBAT%graphLineColEx ^ htf_samp.miff htf_samp.miff ^ htf_tf1.png |
In all these graphs, the x-axis is the input, and the y-axis is the output of the transfer function. The axes have different scales. The minimum and maximum values of both axes, as multiples of QuantumRange, are labelled. The green rectangle is where input and output are both in the range [0,1].
The scale of the y-xis varies in the graphs shown on this page.
The -negate function, u' = 1-u. This is a reflection around input=1.0:
%IMG7%magick ^ htf_samp.miff ^ -negate ^ htf_neg.miff call %PICTBAT%graphLineColEx ^ htf_neg.miff htf_samp.miff ^ htf_neg.png |
If we want a reflection around zero, u' = -u, "-function Polynomial" would be useful, but it clamps the output. Instead, we subtract from black:
%IMG7%magick ^ htf_samp.miff ^ -define compose:clamp=off ^ ( +clone -fill Black -colorize 100 ) ^ -compose MinusDst -composite ^ htf_zneg.miff call %PICTBAT%graphLineColEx ^ htf_zneg.miff htf_samp.miff ^ htf_zneg.png |
If we need to, we can make negative values positive, with "-evaluate abs 0":
%IMG7%magick ^ htf_samp.miff ^ -evaluate abs 0 ^ htf_abs.miff call %PICTBAT%graphLineColEx ^ htf_abs.miff htf_samp.miff ^ htf_abs.png |
More usually, we want to transform negative values to zero:
%IMG7%magick ^ htf_samp.miff ^ -evaluate max 0 ^ htf_max.miff call %PICTBAT%graphLineColEx ^ htf_max.miff htf_samp.miff ^ htf_max.png |
The "-clamp" function:
%IMG7%magick ^ htf_samp.miff ^ -clamp ^ htf_clmp.miff call %PICTBAT%graphLineColEx ^ htf_clmp.miff htf_samp.miff ^ htf_clmp.png |
For all inputs, outputs are in the range [0,1]. Inputs in the range [0,1] are unchanged. Inputs outside that range are clamped.
The "-autolevel" function:
%IMG7%magick ^ htf_samp.miff ^ -auto-level ^ htf_autol.miff call %PICTBAT%graphLineColEx ^ htf_autol.miff htf_samp.miff ^ htf_autol.png |
For all inputs, outputs are in the range [0,1].
Divide by the maximum:
%IMG7%magick ^ htf_samp.miff ^ -evaluate Divide %%[fx:maxima] ^ htf_divmax.miff call %PICTBAT%graphLineColEx ^ htf_divmax.miff htf_samp.miff ^ htf_divmax.png |
The maximum input is transformed to 1.0.
%IMG7%magick ^ htf_samp.miff ^ -fx u/(u+1) ^ htf_recip.miff call %PICTBAT%graphLineColEx ^ htf_recip.miff htf_samp.miff ^ htf_tf2.png |
For non-negatives input values, output values are in the range [0,1]. Negative inputs create negative outputs. The gradient at (0,0) is 1.0. At input=1, output=0.5. For large inputs, the output approaches 1.0 and the gradient approaches zero.
%IMG7%magick ^ htf_samp.miff ^ -fx u/(u+1.1) ^ htf_recip2a.miff call %PICTBAT%graphLineColEx ^ htf_recip2a.miff htf_samp.miff ^ htf_tf2a.png |
We introduce a new variable, LW. Input values at LW will transform to 1.0.
set LW=10 %IMG7%magick ^ htf_samp.miff ^ -fx u*(1+u/%LW%/%LW%)/(u+1) ^ htf_recip2.miff call %PICTBAT%graphLineColEx ^ htf_recip2.miff htf_samp.miff ^ htf_recip2.png |
If we choose LW less than the maximum, input values more than LW will transform to values greater than 1.0.
set LW=5 %IMG7%magick ^ htf_samp.miff ^ -fx u*(1+u/%LW%/%LW%)/(u+1) ^ htf_recip3.miff call %PICTBAT%graphLineColEx ^ htf_recip3.miff htf_samp.miff ^ htf_recip3.png |
We can -auto-level, then apply a power curve so 0.1 transforms to 0.5, while the minimum value transforms to 0.0 and the maximum value transforms to 1.0:
%IMG7%magick ^ htf_samp.miff ^ -auto-level ^ -evaluate Pow %%[fx:log(0.5)/log(0.1)] ^ htf_pow1.miff call %PICTBAT%graphLineColEx ^ htf_pow1.miff htf_samp.miff ^ htf_pow1.png |
We can -auto-level and apply a log function, such as any shown on Log cluts. For example, we use a Skip epsilon method, setting epsilon such that input x=0 will transform to output y=0.
for /F "usebackq" %%L in (`%IMG7%magick ^ htf_samp.miff ^ -precision 15 ^ -format "Eps=%%[fx:-minima/(maxima-minima)]\n" ^ info:`) do set %%L %IMG7%magick ^ htf_samp.miff ^ -auto-level ^ -fx "log(u/%Eps%)/log(1/%Eps%)" ^ htf_skpeps.miff call %PICTBAT%graphLineColEx ^ htf_skpeps.miff htf_samp.miff ^ htf_skpeps.png |
Functions shown above for Compress Highlights can be adapted: subtract from black before the function, and "-negate" after the function.
%IMG7%magick ^ htf_samp.miff ^ -define compose:clamp=off ^ ( +clone -fill Black -colorize 100 ) ^ -compose MinusDst -composite ^ -auto-level ^ -evaluate Pow %%[fx:log(0.5)/log(0.1)] ^ -negate ^ htf_pow1s.miff call %PICTBAT%graphLineColEx ^ htf_pow1s.miff htf_samp.miff ^ htf_pow1s.png |
We can use -auto-level and any S-shape curve. For example, the fulcrum2.bat curve from Clut cookbook: parameterised S-curves. Note that parameters F and G are with respect to the input normalised to [0,1]. If we want parameters with respect to the unnormalised input, we need more arithmetic.
set F=0.18 set G=2 %IMG7%magick ^ htf_samp.miff ^ -auto-level ^ -fx "u<%F%?(%F%*pow(u/%F%,%G%)):1-((1-%F%)*pow((1-u)/(1-%F%),%G%))" ^ htf_comprs.miff call %PICTBAT%graphLineColEx ^ htf_comprs.miff htf_samp.miff ^ htf_comprs.png |
HDR images are rarely simple gradients. Real HDR images have detail within relatively narrow dynamic bands. For a method of compressing the dynamic range while retaining detail, see Guided filter: HDR compression.
For convenience, .bat scripts are also available in a single zip file. See Zipped BAT files.
rem Like graphLineCol, but extended for HDR, high dynamic range. rem %1 Nx1 input image to be graphed. rem %2 Nx1 input image. The values in this are used only to establish scale of x-axis. rem %3 output graph image. @rem The region of input and output from 0% to 100% is highlighted in green. @if "%1"=="" findstr /B "rem @rem" %~f0 & exit /B 1 @setlocal enabledelayedexpansion @call echoOffSave set INFILE=%1 set OUTFILE=%~dpn1_glce.png set INX=%2 if "%INX%"=="." set INX= if not "%3"=="" if not "%3"=="." set OUTFILE=%3 set TMPDIR=\temp\ set TMPIN=%TMPDIR%glcx.miff set FMT=^ XOMIN=%%[fx:minima]\n^ XOMAX=%%[fx:maxima]\n^ XNMIN=%%[fx:minima^<0?minima:0]\n^ XNMAX=%%[fx:maxima^>1?maxima:1]\n for /F "usebackq" %%L in (`%IMG7%magick ^ %INX% ^ -precision 15 ^ -format "%FMT%" ^ info:`) do set %%L set FMT=^ XFACT=%%[fx:1/(%XOMAX%-(%XOMIN%))]\n^ XOFFS=%%[fx:-(%XOMIN%)/(%XOMAX%-(%XOMIN%))]\n^ XZERO=%%[fx:-(%XOMIN%)/(%XOMAX%-(%XOMIN%))]\n^ XONE=%%[fx:(1-(%XOMIN%))/(%XOMAX%-(%XOMIN%))]\n for /F "usebackq" %%L in (`%IMG7%magick ^ %INX% ^ -precision 15 ^ -format "%FMT%" ^ info:`) do set %%L set FMT=^ YOMIN=%%[fx:minima]\n^ YOMAX=%%[fx:maxima]\n^ YNMIN=%%[fx:minima^<0?minima:0]\n^ YNMAX=%%[fx:maxima^>1?maxima:1]\n for /F "usebackq" %%L in (`%IMG7%magick ^ %INFILE% ^ -precision 15 ^ -write %TMPIN% ^ -format "%FMT%" ^ info:`) do set %%L set FMT=^ YFACT=%%[fx:1/(%YOMAX%-(%YOMIN%))]\n^ YOFFS=%%[fx:-(%YOMIN%)/(%YOMAX%-(%YOMIN%))]\n^ YZERO=%%[fx:-(%YOMIN%)/(%YOMAX%-(%YOMIN%))]\n^ YONE=%%[fx:(1-(%YOMIN%))/(%YOMAX%-(%YOMIN%))]\n for /F "usebackq" %%L in (`%IMG7%magick ^ %INX% ^ -precision 15 ^ -format "%FMT%" ^ info:`) do set %%L rem See "-function polynomial" bug https://www.imagemagick.org/discourse-server/viewtopic.php?f=3&t=37520 %IM7DEV%magick ^ %TMPIN% ^ -precision 15 ^ -function polynomial %YFACT%,%YZERO% ^ -clamp ^ -format "MIN=%%[fx:minima]\nMAX=%%[fx:maxima]\n" ^ +write info: ^ %TMPIN% if ERRORLEVEL 1 exit /B 1 call %PICTBAT%graphLineCol %TMPIN% . . 0 %OUTFILE% %IM7DEV%magick ^ %OUTFILE% ^ -fill #0f08 -stroke None ^ -draw "rectangle %%[fx:%XZERO%*w],%%[fx:(1-(%YONE%))*h],%%[fx:%XONE%*w],%%[fx:(1-(%YZERO%))*h]" ^ -fill None -stroke #88f8 ^ -draw "line %%[fx:%XZERO%*w],0,%%[fx:%XZERO%*w],%%[fx:h-1]" ^ -draw "line 0,%%[fx:(1-(%YZERO%))*h],%%[fx:w-1],%%[fx:(1-(%YZERO%))*h]" ^ %OUTFILE% if ERRORLEVEL 1 exit /B 1 set FMT=^ XOMINp=%%[fx:%XOMIN%]\n^ XOMAXp=%%[fx:%XOMAX%]\n^ YOMINp=%%[fx:%YOMIN%]\n^ YOMAXp=%%[fx:%YOMAX%]\n for /F "usebackq" %%L in (`%IMG7%magick ^ -precision 5 ^ -print "%FMT%" ^ -exit`) do set %%L call %PICTBAT%graph4labels %OUTFILE% %OUTFILE% %XOMINp% %XOMAXp% %YOMINp% %YOMAXp% call echoRestore endlocal & set mhhOUTFILE=%OUTFILE%&
All images on this page were created by the commands shown, using:
%IMG7%magick -version
Version: ImageMagick 7.0.8-64 Q16 x64 2019-09-08 http://www.imagemagick.org Copyright: Copyright (C) 1999-2018 ImageMagick Studio LLC License: http://www.imagemagick.org/script/license.php Visual C++: 180040629 Features: Cipher DPC HDRI Modules OpenCL OpenMP(2.0) Delegates (built-in): bzlib cairo flif freetype gslib heic jng jp2 jpeg lcms lqr lzma openexr pangocairo png ps raw rsvg tiff webp xml zlib
%IM7DEV%magick -version
Version: ImageMagick 7.0.8-64 Q32 x86_64 2021-01-27 https://imagemagick.org Copyright: © 1999-2019 ImageMagick Studio LLC License: https://imagemagick.org/script/license.php Features: Cipher DPC HDRI Modules OpenMP(4.5) Delegates (built-in): bzlib cairo fftw fontconfig fpx freetype jbig jng jpeg lcms ltdl lzma pangocairo png rsvg tiff webp wmf x xml zlib
Source file for this web page is hdrtransfun.h1. To re-create this web page, execute procH1 hdrtransfun.
This page, including the images, is my copyright. Anyone is permitted to use or adapt any of the code, scripts or images for any purpose, including commercial use.
Anyone is permitted to re-publish this page, but only for non-commercial use.
Anyone is permitted to link to this page, including for commercial use.
Page version v1.0 15-February-2020.
Page created 04-Feb-2021 22:14:34.
Copyright © 2021 Alan Gibson.