We can do a linear transformation of levels by using either -level or +level, with a suitable change of parameters. For some situations, it isn't obvious which of these we should use.
Suppose we want to make an image brighter. We want black to become 50% gray. Any pixels currently at or above 50% gray, we want to become white. We can show this as a graph, where the x-axis represents the input and the y-axis represents the output. The normal range, 0 to 100% in both dimensions, is the heavy rectangle. The blue line on the graph represents the transformation from input level to output level.
In this diagram:
In our sample requirement, py1=50%, px2=50%, px1<0, and py2>100%. We could use either -level or +level. But -level needs px1 and px2 as parameters, and +level needs py1 and py2.
The problem is general. We might know any two of the values and want to calculate the other two.
From similar triangles:
py1 100 100-py1 py2-py1 py2-100 py2 ----- = ------- = ------- = ------- = ------- = ------- = tan(-θ) -px1 px2-px1 px2 100 100-px2 100-px1
... where θ is the angle of the line to the horizontal, clockwise is positive.
(In this example, px1<0 so -px1>0.)
From those equalities, we get three equations for each of the four parameters:
py1*px2 100*py1 10000 - py2*px2 px1 = ------- = ------- = --------------- py1-100 py1-py2 100 - py2 px1*(py1-100) 100*(py1-100) 100*(100-px1) + px1*py2 px2 = ------------- = ------------- = ----------------------- py1 py1-py2 py2 100*px1 px1*py2 10000 - px2*py2 py1 = ------- = ------- = --------------- px1-px2 px1-100 100 - px2 py1*(px1-100) 100*(px1-100) 100*(100-py1) + py1*px2 py2 = ------------- = ------------- = ----------------------- px1 px1-px2 px2
In every equation we can interchange "x" and "y" and the equation remains valid.
A script levels4.bat is provided to calculate any two parameters from the other two, by using the appropriate equations from the 12 above. The script does not check for degenerate cases of horizontal or vertical lines.
Reading values from the diagram, with an accuracy of about 1%:
px1 = 50% px2 = 132.5% py1 = -59.5% py2 = 60%
We can check the script with these parameters. As we know two out of four values, there are six cases to test.
call %PICTBAT%levels4 50 132.5 ? ? call %PICTBAT%levels4 50 ? -59.5 ? call %PICTBAT%levels4 50 ? ? 60 call %PICTBAT%levels4 ? 132.5 -59.5 ? call %PICTBAT%levels4 ? 132.5 ? 60 call %PICTBAT%levels4 ? ? -59.5 60
50 132.5 -60.6060606060606 60.6060606060606 50 134.033613445378 -59.5 59.5 50 133.333333333333 -60 60 49.4278996865204 132.5 -59.5 60.877358490566 51.25 132.5 -63.0769230769231 60 49.7907949790795 133.47280334728 -59.5 60
Yes, the script works.
As a further check, we mirror-image the diagram left-right about the x=50 axis, so the line slopes from top-left to bottom-right, giving these values:
px1 = 50% px2 = -32.5% py1 = 60% py2 = -59.5%
Testing these six cases:
call %PICTBAT%levels4 50 -32.5 ? ? call %PICTBAT%levels4 50 ? 60 ? call %PICTBAT%levels4 50 ? ? -59.5 call %PICTBAT%levels4 ? -32.5 60 ? call %PICTBAT%levels4 ? -32.5 ? -59.5 call %PICTBAT%levels4 ? ? 60 -59.5
50 -32.5 60.6060606060606 -60.6060606060606 50 -33.3333333333333 60 -60 50 -34.0336134453782 59.5 -59.5 48.75 -32.5 60 -63.0769230769231 50.5721003134796 -32.5 60.877358490566 -59.5 50.2092050209205 -33.4728033472803 60 -59.5
Again, this is correct.
We use the first pair of numbers as parameters to -level, or the second pair with +level.
%IMG7%magick ^ -size 200x200 ^ gradient: ^ ( +clone ^ -level 50,133.33333%% ^ +write lv_grad1.png ^ +delete ^ ) ^ ( +clone ^ +level -60,60%% ^ +write lv_grad2.png ^ +delete ^ ) ^ NULL: |
The top-most diagram on this page has px2 = py1 = 50%. So:
call %PICTBAT%levels4 ? 50 50 ?
-50 50 50 150
%IMG7%magick ^ -size 200x200 ^ gradient: ^ -bordercolor #000 -border 1 ^ ( +clone ^ -level -50,50%% ^ +write lv_grad3.png ^ +delete ^ ) ^ ( +clone ^ +level 50,150%% ^ +write lv_grad4.png ^ +delete ^ ) ^ NULL: |
As required, black has become mid-gray, and anything that was at or above mid-gray has become white.
I would expect these two commands to give the same results. (-channel RGB is so v7 doesn't level the alpha channel.)
%IM7DEV%magick -size 1x5 gradient: -channel RGB -level 0,50%% +channel txt:
# ImageMagick pixel enumeration: 1,5,4294967295,srgb 0,0: (4.2949673e+09,4.2949673e+09,4.2949673e+09) #FFFFFFFFFFFFFFFFFFFFFFFF white 0,1: (4.2949673e+09,4.2949673e+09,4.2949673e+09) #FFFFFFFFFFFFFFFFFFFFFFFF white 0,2: (4.2949673e+09,4.2949673e+09,4.2949673e+09) #FFFFFFFFFFFFFFFFFFFFFFFF white 0,3: (2.1474836e+09,2.1474836e+09,2.1474836e+09) #800000008000000080000000 srgb(50%,50%,50%) 0,4: (0,0,0) #000000000000000000000000 black
%IM7DEV%magick -size 1x5 gradient: -channel RGB +level 0,200%% +channel txt:
# ImageMagick pixel enumeration: 1,5,4294967295,srgb 0,0: (8.5899346e+09,8.5899346e+09,8.5899346e+09) #FFFFFFFFFFFFFFFFFFFFFFFF srgb(200%,200%,200%) 0,1: (6.4424509e+09,6.4424509e+09,6.4424509e+09) #FFFFFFFFFFFFFFFFFFFFFFFF srgb(150%,150%,150%) 0,2: (4.2949673e+09,4.2949673e+09,4.2949673e+09) #FFFFFFFFFFFFFFFFFFFFFFFF srgb(255,255,255) 0,3: (2.1474836e+09,2.1474836e+09,2.1474836e+09) #800000008000000080000000 srgb(50%,50%,50%) 0,4: (0,0,0) #000000000000000000000000 black
They don't match because the -level version clips.
I think that when using HDRI, neither -level nor +level should clip values. Currently, -level does clip values. I think this is a bug.
The version of %IMDEV% is:
%IM7DEV%magick -version
Version: ImageMagick 7.1.0-20 Q32-HDRI x86_64 2021-12-29 https://imagemagick.org Copyright: (C) 1999-2021 ImageMagick Studio LLC License: https://imagemagick.org/script/license.php Features: Cipher DPC HDRI Modules OpenMP(4.5) Delegates (built-in): bzlib cairo fontconfig fpx freetype jbig jng jpeg lcms ltdl lzma pangocairo png raqm rsvg tiff webp wmf x xml zip zlib Compiler: gcc (11.2)
See the IM forum thread Process HDRI without clamping.
Instead of specifying the line by two points, we can describe it with polar coordinates (r,θ), where r is the distance of the line from a certain point and θ is the angle of the line to the horizontal. For convenience, we take the "certain point" to be (50%,50%). So θ has the range [-90°,+90°], with both extremes being vertical. r is always positive and, if the line is within the box, the maximum value of r is between 50% and sqrt(2)*50%.
Let (px3,py3) be the coordinates of the point on the line that is closest to (50%,50%), so the perpendicular at (px3,py3) passes through (50%,50%), and makes an angle of θ with the vertical, and the distance of (px3,py3) to (50%,50%) is r.
50-px3 = r.sin(θ) py3-50 = r.cos(θ)
Hence we know px3 and py3.
py3-py1 ------- = tan(θ) px3
Hence:
py1 = py3 - px3.tan(θ)
For θ near +90° or -90° we can use:
px3 ------- = tan(90°-θ) py3-py1
Hence:
py1 = py3 - px3 / tan(90°-θ) [[That's no help.]]
Nope. Above is garbage. For a given (r,θ) with the above definition, two lines satisfy the conditions. We can change the definition: θ is the direction (from 0 to 360°) of the perpendicular line from, say, due north (or east or any direction). Positive clockwise seems more natural. So first diagram has θ = -45°. Second example has θ = +135°.
px3 = 50 + dx py3 = 50 + dy dx = r.sin(θ) dy = r.cos(θ)
Hence:
dx px3-50 -- = ------ = tan(θ) dy py3-50
I would normally make this type of diagram with Inkscape, or my own system that generates SVG from a higher-level language. But it is simple enough to code in IM:
%IMG7%magick ^ -size 500x500 xc:#eef ^ -fill none ^ -stroke #000 ^ -strokewidth 1 ^ -draw "line 0,150 499,150" ^ -draw "line 0,349 499,349" ^ -draw "line 150,0 150,499" ^ -draw "line 349,0 349,499" ^ -strokewidth 3 ^ -draw "rectangle 150,150 349,349" ^ -fill #000 ^ -stroke none ^ -pointsize 15 ^ -draw "text 480,345 x" ^ -draw "text 135,15 y" ^ -draw "text 120,370 '0,0'" ^ -draw "text 355,370 '100,0'" ^ -draw "text 105,140 '0,100'" ^ lv_base.png %IMG7%magick ^ lv_base.png ^ -strokewidth 3 ^ -stroke #00f ^ -draw "line 0,400 400,0" ^ -strokewidth 1 ^ -draw "line 50,340 50,360" ^ -draw "line 250,140 250,160" ^ -draw "line 140,250 160,250" ^ -draw "line 340,50 360,50" ^ -stroke none ^ -fill #00f ^ -pointsize 20 ^ -draw "text 50,375 px1" ^ -draw "text 220,135 px2" ^ -draw "text 165,255 py1" ^ -draw "text 365,55 py2" ^ -bordercolor #fff -border 20 ^ lv_50g.png %IMG7%magick ^ lv_base.png ^ -strokewidth 3 ^ -stroke #00f ^ -draw "line 125,499 499,50" ^ -strokewidth 1 ^ -draw "line 250,340 250,360" ^ -draw "line 415,140 415,160" ^ -draw "line 140,469 160,469" ^ -draw "line 340,230 360,230" ^ -stroke none ^ -fill #00f ^ -pointsize 20 ^ -draw "text 250,375 px1" ^ -draw "text 385,135 px2" ^ -draw "text 165,474 py1" ^ -draw "text 365,235 py2" ^ -bordercolor #fff -border 20 ^ +write lv_plus.png ^ -resize 50%% ^ lv_teaser.png
For convenience, .bat scripts are also available in a single zip file. See Zipped BAT files.
The script does not check for degenerate cases.
@rem Given 4 level parameters, @rem px1, px2, py1, py2, @rem two of which are question marks, @rem calculates the missing two. @rem The script does _not_ check for degenerate cases. @rem @rem Updated: @rem 5-August-2022 for IM v7. @rem @if "%4"=="" findstr /B "rem @rem" %~f0 & exit /B 1 @setlocal @call echoOffSave set PX1=%1 set PX2=%2 set PY1=%3 set PY2=%4 set FLAGS=0 if "%PX1%"=="?" set /A FLAGS=1 if "%PX2%"=="?" set /A FLAGS ^|=2 if "%PY1%"=="?" set /A FLAGS ^|=4 if "%PY2%"=="?" set /A FLAGS ^|=8 if %FLAGS%==3 ( for /F "usebackq" %%L in (`%IMG7%magick identify -precision 15 ^ -format "PX1=%%[fx:100*(%PY1%)/((%PY1%)-(%PY2%))]\nPX2=%%[fx:100*(%PY1%-100)/((%PY1%)-(%PY2%))]" ^ xc:`) do set %%L ) else if %FLAGS%==5 ( for /F "usebackq" %%L in (`%IMG7%magick identify -precision 15 ^ -format "PX1=%%[fx:(10000-(%PY2%)*(%PX2%))/(100-(%PY2%))]\nPY1=%%[fx:(10000-(%PY2%)*(%PX2%))/(100-(%PX2%))]" ^ xc:`) do set %%L ) else if %FLAGS%==9 ( for /F "usebackq" %%L in (`%IMG7%magick identify -precision 15 ^ -format "PX1=%%[fx:((%PY1%)*(%PX2%))/((%PY1%)-100)]\nPY2=%%[fx:(100*(100-(%PY1%))+(%PY1%)*(%PX2%))/(%PX2%)]" ^ xc:`) do set %%L ) else if %FLAGS%==6 ( for /F "usebackq" %%L in (`%IMG7%magick identify -precision 15 ^ -format "PX2=%%[fx:(100*(100-(%PX1%))+(%PX1%)*(%PY2%))/(%PY2%)]\nPY1=%%[fx:((%PX1%)*(%PY2%))/((%PX1%)-100)]" ^ xc:`) do set %%L ) else if %FLAGS%==10 ( for /F "usebackq" %%L in (`%IMG7%magick identify -precision 15 ^ -format "PX2=%%[fx:(%PX1%)*((%PY1%)-100)/(%PY1%)]\nPY2=%%[fx:(%PY1%)*((%PX1%)-100)/(%PX1%)]" ^ xc:`) do set %%L ) else if %FLAGS%==12 ( for /F "usebackq" %%L in (`%IMG7%magick identify -precision 15 ^ -format "PY1=%%[fx:100*(%PX1%)/((%PX1%)-(%PX2%))]\nPY2=%%[fx:100*((%PX1%)-100)/((%PX1%)-(%PX2%))]" ^ xc:`) do set %%L ) else ( echo Need two numbers and two "?" question marks. exit /B 1 ) echo %PX1% %PX2% %PY1% %PY2% call echoRestore @endlocal
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)
%IM7DEV%magick -version
Version: ImageMagick 7.1.0-20 Q32-HDRI x86_64 2021-12-29 https://imagemagick.org Copyright: (C) 1999-2021 ImageMagick Studio LLC License: https://imagemagick.org/script/license.php Features: Cipher DPC HDRI Modules OpenMP(4.5) Delegates (built-in): bzlib cairo fontconfig fpx freetype jbig jng jpeg lcms ltdl lzma pangocairo png raqm rsvg tiff webp wmf x xml zip zlib Compiler: gcc (11.2)
To improve internet download speeds, some images may have been automatically converted (by ImageMagick, of course) from PNG or TIFF or MIFF to JPG.
Source file for this web page is levels.h1. To re-create this web page, run "procH1 levels".
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 11-November-2014.
Page created 06-Aug-2022 15:11:55.
Copyright © 2022 Alan Gibson.