snibgo's ImageMagick pages

HDRI transfer functions

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".

Sample input

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].

Basic functions

The identity transfer, output = input:

call %PICTBAT%graphLineColEx ^
  htf_samp.miff htf_samp.miff ^
  htf_tf1.png
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
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
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
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
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
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.

Linear compression

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
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
htf_divmax.png

The maximum input is transformed to 1.0.

Compress highlights

%IMG7%magick ^
  htf_samp.miff ^
  -fx u/(u+1) ^
  htf_recip.miff

call %PICTBAT%graphLineColEx ^
  htf_recip.miff htf_samp.miff ^
  htf_tf2.png
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
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
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
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
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
htf_skpeps.png

Compress shadows

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
htf_pow1s.png

S-curve: compress highlights and shadows

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
htf_comprs.png

Guided filter

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.

Scripts

For convenience, .bat scripts are also available in a single zip file. See Zipped BAT files.

graphLineColEx.bat

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.