Cluts can be relative or proportional, and can have log scales.
This page logically follows from Clut cookbook.
See also Log cluts.
The ImageMagick "-clut" operation reads each input pixel value Vin, uses that as an index into the Nx1 clut image, and uses the value at that position in the clut for the output pixel value Vout. We can say the clut is absolute, in the sense that the clut value is used directly for the output pixel.
Vout = CLUTabs[Vin]
This page explores two alternatives to absolute cluts:
We make a sample absolute clut:
call %PICTBAT%mSigClut ^ nacl_abs0.png 256 0.5 0.5 0.5 4 call %PICTBAT%graph1d nacl_abs0.png . . . none |
We can define relative cluts, where clut values are added to input pixel values, and the result is used for output pixel values.
Vout = CLUTrel[Vin] + Vin
If clut values are floating-point, a clut value of zero means "add zero". However, we may want to restrict clut values to the range [0.0,1.0]. For that, we would subtract 0.5 from the result.
Vout = CLUTrel[Vin] + Vin - 0.5
Substituting the absolute equation and solving for CLUTrel[Vin], we get the transformation from CLUTabs[Vin] to CLUTrel[Vin]:
CLUTrel[Vin] = CLUTabs[Vin] - Vin + 0.5
For Vin, we use a gradient. The inverse transformation is:
CLUTabs[Vin] = CLUTrel[Vin] + Vin - 0.5
We show the conversion between absolute and relative cluts:
Convert the sample absolute clut to a relative clut, with 50% offset: %IMG7%magick ^ nacl_abs0.png ^ -set option:MYSIZE %%[fx:w]x%%[fx:h] ^ ( -size %%[MYSIZE] ^ gradient:Black-White ^ ) ^ -compose Mathematics ^ -define compose:args=0,-1,1,0.5 ^ -composite ^ nacl_rel0.png call %PICTBAT%graphLine nacl_rel0.png %IMG7%magick ^ nacl_rel0_gl.png ^ -stroke #0f08 ^ -draw "line 0,%%[fx:h/2],%%[fx:w-1],%%[fx:h/2]" ^ nacl_rel0_gl.png call %PICTBAT%graph4labels ^ nacl_rel0_gl.png nacl_rel0_gl.png ^ 0.0 1.0 "-50%%%%" "+50%%%%" The y-axis shows the amount added, as a percentage of QuantumLevel. |
|
Convert it back to an absolute clut: %IMG7%magick ^ nacl_rel0.png ^ -set option:MYSIZE %%[fx:w]x%%[fx:h] ^ ( -size %%[MYSIZE] ^ gradient:Black-White ^ ) ^ -compose Mathematics ^ -define compose:args=0,1,1,-0.5 ^ -composite ^ nacl_abs1.png call %PICTBAT%graph1d nacl_abs1.png |
How accurate is the round-trip?
%IMG7%magick compare ^ -metric RMSE ^ nacl_abs0.png nacl_abs1.png ^ NULL:
0.998045 (1.52292e-05)
The round-trip is accurate.
We can also define proportional cluts, where clut values are used to multiply input pixel values to make output pixel values.
Vout = CLUTprop[Vin] * Vin
If clut values are floating-point, in the range nearly [0,∞], we can use 100% of QuantumLevel to mean "multiply by one", and almost all multipliers in the range [0,∞] are available. However, we may want to restrict clut values to the range [0,100%]. For that, we define a constant ClutFact that is used to multiply the clut value.
Vout = CLUTprop[Vin] * Vin * ClutFact
For example, when ClutFact is 4, and 0 <= CLUTprop[Vin] <= 100%, then Vin will be multiplied by some value in the range [0,4].
Substituting the absolute equation and solving for CLUTrel[Vin], we get the transformation from CLUTabs[Vin] to CLUTprop[Vin]:
CLUTprop[Vin] = CLUTabs[Vin] / Vin / ClutFact
For Vin, we use a gradient. The inverse transformation is:
CLUTabs[Vin] = CLUTprop[Vin] * Vin * ClutFact
We show the conversion between absolute and proportional cluts:
Convert the absolute to a proportional clut, and draw horizontal line at multiplier *1: set ClutFact=4 set SLINE=^ line ^ 0,%%[fx:h*(1-1/%ClutFact%)],^ %%[fx:w-1],%%[fx:h*(1-1/%ClutFact%)] %IMG7%magick ^ nacl_abs0.png ^ -set option:MYSIZE %%[fx:w]x%%[fx:h] ^ ( -size %%[MYSIZE] ^ gradient:Black-White ^ ) ^ -define compose:clamp=off ^ -compose DivideSrc -composite ^ -evaluate Divide %ClutFact% ^ nacl_prop0.png call %PICTBAT%graphLine nacl_prop0.png %IMG7%magick ^ nacl_prop0_gl.png ^ -stroke #0f08 ^ -draw "%SLINE%" ^ nacl_prop0_gl.png call %PICTBAT%graph4labels ^ nacl_prop0_gl.png nacl_prop0_gl.png ^ 0.0 1.0 "*0" "*4" The y-axis is the multiplier, from zero to 4, labelled "*0" and "*4".
We have divide by zero at x=0. |
|
Convert it back to an absolute clut: %IMG7%magick ^ nacl_prop0.png ^ -evaluate Multiply %ClutFact% ^ -set option:MYSIZE %%[fx:w]x%%[fx:h] ^ ( -size %%[MYSIZE] ^ gradient:Black-White ^ ) ^ -define compose:clamp=off ^ -compose Multiply -composite ^ nacl_abs1p.png call %PICTBAT%graph1d nacl_abs1p.png |
How accurate is the round-trip?
%IMG7%magick compare ^ -metric RMSE ^ nacl_abs0.png nacl_abs1p.png ^ NULL:
0.684653 (1.04471e-05)
The round-trip is accurate.
We can apply a log scale to the proportional clut shown above. The result has an equal spacing in the y-axis for a given factor. We can annotate with whatever factor we wish, such as a factor of 2, so values double or halve at every interval. We can call each interval a photographic stop, also known as an Exposure Value or EV.
Here comes the maths for just the log parts of the log-proportional transformations. In the forwards direction we apply this after the forwards Proportional transformations shown above; in the reverse direction we apply it before the reverse proportional transformation.
When the domain of x is [x0,x1] where 0 < x0 < x1, the range of log(x) would be [log(x0),log(x1)]. The usual subtract and divide transforms the range to [0,1].
y = log(x)-log(x0) log(x1)-log(x0) = log(x/x0) log(x1/x0)
For the reverse:
log(x)-log(x0) = y * log(x1/x0) log(x) = y * log(x1/x0) + log(x0) = log(x1^y/x0^y) + log(x0) = log(x0 * x1^y/x0^y) x = x0 * x1^y / x0^y = x0 * (x1/x0)^y = x1^y / x0^(y-1)
Any logarithm base can be used; the result is the same.
For some purposes we want a symmetrical range, where x0 = 1/x1. This gives a result with the same number of stops above or below unity.
Forwards:
y = log(x/(1/x1)) log(x1/(1/x1)) = log(x*x1) log(x1^2)
Reverse:
x = x1^y * x1^(y-1) = x1^(2*y-1)
ASIDE: For raising one image to the power of another, we use:
magick ^ dst.png src.png ^ -evaluate-sequence Pow ^ out.png
... which implements:
out = srcdst
... where IM normalises src to [0,1] but does not normalise dst. If we want dst=100% to have the effect of raising to the power of one, we need to divide dst by QuantumRange.
%IMG7%magick ^ ( xc:rgb(5%%,50%%,130%%) ^ -evaluate Divide %%[fx:QuantumRange] ^ ) ^ xc:rgb(10%%,20%%,30%%) ^ -evaluate-sequence Pow ^ txt:
# ImageMagick pixel enumeration: 1,1,0,65535,srgb 0,0: (58408,29308,13700) #E428727C3584 srgb(89.1251%,44.7214%,20.9054%)
Note that 0.10.05=0.891251, 0.20.5=0.447214 and 0.31.3=0.209054.
We can also use %[fx:], which is simpler and slower, but fast enough for making cluts:
%IMG7%magick ^ xc:rgb(5%%,50%%,130%%) ^ xc:rgb(10%%,20%%,30%%) ^ -fx pow(v,u) ^ txt:
# ImageMagick pixel enumeration: 1,1,0,65535,srgb 0,0: (58408,29308,13700) #E428727C3584 srgb(89.1251%,44.7214%,20.9054%)
We use the forwards transformation to modify the output of the clut, shown on the y-axis of the graph. We show output values between yLo and yHi, setting these to the reciprocals of each other. When yHi is a power of two, this results in an exact number of stops. As yLo is the reciprocal of yHi, we have an equal number of stops above and below the neutral line.
set yLo=0.25 set yHi=4.0 set SLINE=^ line ^ 0,%%[fx:h*0.5],^ %%[fx:w-1],%%[fx:h*0.5] %IMG7%magick ^ nacl_abs0.png ^ -set option:MYSIZE %%[fx:w]x%%[fx:h] ^ ( -size %%[MYSIZE] ^ gradient:Black-White ^ ) ^ -define compose:clamp=off ^ -compose DivideSrc -composite ^ -fx "log(u/%yLo%)/log(%yHi%/%yLo%)" ^ nacl_log0.png call %PICTBAT%graphLine nacl_log0.png %IMG7%magick ^ nacl_log0_gl.png ^ -stroke #0f08 ^ -draw "%SLINE%" ^ nacl_log0_gl.png call %PICTBAT%graph4labels ^ nacl_log0_gl.png nacl_log0_gl.png ^ "0.0" "1.0" "-2" "+2" The y-axis is the multiplier, from -2 to +2 stops (factor of 0.25 to 4).
We have divide by zero at x=0. |
|
Convert it back to an absolute clut: %IMG7%magick ^ nacl_log0.png ^ -evaluate Divide %%[fx:QuantumRange] ^ ( +clone ^ -evaluate set %%[fx:QuantumRange*%yHi%/%yLo%] ^ ) ^ -evaluate-sequence Pow ^ -evaluate Multiply %yLo% ^ -set option:MYSIZE %%[fx:w]x%%[fx:h] ^ ( -size %%[MYSIZE] ^ gradient:Black-White ^ ) ^ -define compose:clamp=off ^ -compose Multiply -composite ^ nacl_logabs.png call %PICTBAT%graph1d nacl_logabs.png |
How accurate is the round-trip?
%IMG7%magick compare ^ -metric RMSE ^ nacl_abs0.png nacl_logabs.png ^ NULL:
0.526634 (8.03593e-06)
The round-trip is accurate.
We can also apply a log transform to the clut input, pushing the graph towards the right. We use the log cluts: skip epsilon method, showing the top six stops. The choice of six stops is just an example; we might choose 10 stops for digital cameras, or even 16. We need to apply the same log transform to the gradient used for the division.
set E=(1/64) set mscPREPROC=-fx "pow(%E%,(1-u))" call %PICTBAT%mSigClut ^ nacl_abs2.png 256 0.5 0.5 0.5 4 %IMG7%magick ^ nacl_abs2.png ^ -set option:MYSIZE %%[fx:w]x%%[fx:h] ^ ( -size %%[MYSIZE] ^ gradient:Black-White ^ %mscPREPROC% ^ ) ^ -define compose:clamp=off ^ -compose DivideSrc -composite ^ -fx "log(u/%yLo%)/log(%yHi%/%yLo%)" ^ nacl_log2.png set mscPREPROC= call %PICTBAT%graphLine nacl_log2.png %IMG7%magick ^ nacl_log2_gl.png ^ -stroke #0f08 ^ -draw "%SLINE%" ^ nacl_log2_gl.png call %PICTBAT%gridHigh ^ nacl_log2_gl.png nacl_log2_gl.png x w/logtwo(1/%E%) for /F "usebackq" %%L in (`%IMG7%magick identify ^ -precision 2 ^ -format "LabLeft=%%[fx:-logtwo(1/%E%)]" ^ xc:`) do set %%L call %PICTBAT%graph4labels ^ nacl_log2_gl.png nacl_log2_gl.png ^ %LabLeft% 0 -2 +2 |
|
Convert it back to an absolute clut: %IMG7%magick ^ nacl_log2.png ^ -evaluate Divide %%[fx:QuantumRange] ^ ( +clone ^ -evaluate set %%[fx:QuantumRange*%yHi%/%yLo%] ^ ) ^ -evaluate-sequence Pow ^ -evaluate Multiply %yLo% ^ -set option:MYSIZE %%[fx:w]x%%[fx:h] ^ ( -size %%[MYSIZE] ^ gradient:Black-White ^ -fx "pow(%E%,(1-u))" ^ ) ^ -define compose:clamp=off ^ -compose Multiply -composite ^ nacl_log2abs.png rem -fx "log(u/%E%)/log(1/%E%)" call %PICTBAT%graph1d nacl_log2abs.png |
For convenience, .bat scripts are also available in a single zip file. See Zipped BAT files.
I visualise cluts with scripts graph1d.bat and graphLineCol.bat.
rem From image %1 with height=1, rem makes a graph with same width but max height=256. @rem @rem Optional: @rem %2 is background colour (default Khaki). @rem %3 is grid (default 0 = no grid). @rem %4 is output filename. @rem %5 border colour ("none" = no border) [blue] @rem @rem Updated: @rem 11 March 2017 Simplified. @rem 20 June 2017 Restored gradient in output. @rem 8-August-2022 for IM v7. @rem @if "%1"=="" findstr /B "rem @rem" %~f0 & exit /B 1 @setlocal @call echoOffSave call %PICTBAT%setInOut %1 g1d set BACKCOL=%2 if "%BACKCOL%"=="." set BACKCOL= if "%BACKCOL%"=="" set BACKCOL=Khaki set GRID_SPEC=%3 if "%GRID_SPEC%"=="." set GRID_SPEC= if "%GRID_SPEC%"=="" set GRID_SPEC=0 if not "%4"=="" if not "%4"=="." set OUTFILE=%4 set BORDCOL=%5 if "%BORDCOL%"=="." set BORDCOL= if "%BORDCOL%"=="" set BORDCOL=blue for /F "usebackq" %%L ^ in (`%IMG7%magick identify -format "WW=%%w\nHH=%%h" %INFILE%`) ^ do set %%L set S_RESIZE= if %WW% GTR 1000 ( set S_RESIZE=-resize 1000x%HH% set WW=1000 ) set newH=256 if %WW% LSS 256 ( set newH=%WW% ) set S_GRID= if not %GRID_SPEC%==0 ( call %PICTBAT%grid %WW% %newH% 4 3 1 set S_GRID=grid.png -compose Exclusion -composite ) echo %0: WW=%WW% HH=%HH% newH=%newH% OUTFILE=%OUTFILE% set /A newH4=4*%newH% set BRDR= if /I not "%BORDCOL%"=="none" set BRDR=-bordercolor %BORDCOL% -compose Over -border 1x1 %IMG7%magick ^ %INFILE% ^ -scale "%WW%x1^!" ^ -scale "%WW%x%newH4%^!" ^ +write mpr:SCLE ^ -size %WW%x%newH4% gradient: ^ -compose MinusDst -composite ^ -threshold 0 ^ -negate ^ mpr:SCLE ^ +swap ^ -compose CopyOpacity -composite ^ -resize "%WW%x%newH%^!" ^ -background %BACKCOL% ^ -compose Over -layers Flatten ^ %S_GRID% ^ %BRDR% ^ %OUTFILE% call echoRestore
All images on this page were created by the commands shown, using:
%IMG7%magick -version
Version: ImageMagick 7.1.0-42 Q16-HDRI x64 396d87c:20220709 https://imagemagick.org Copyright: (C) 1999 ImageMagick Studio LLC License: https://imagemagick.org/script/license.php Features: Cipher DPC HDRI OpenCL Delegates (built-in): bzlib cairo freetype gslib heic jng jp2 jpeg jxl lcms lqr lzma openexr pangocairo png ps raqm raw rsvg tiff webp xml zip zlib Compiler: Visual Studio 2022 (193231332)
Source file for this web page is nacluts.h1. To re-create this web page, execute "procH1 nacluts" or cookbook.bat.
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 28-April-2020.
Page created 09-Aug-2022 00:25:23.
Copyright © 2022 Alan Gibson.