snibgo's ImageMagick pages

Adding zing to photographs

Sometimes a photograph needs more "zing", more "pop". Aesthetic decisions are difficult to quantify and harder to automate, but here are some tools aimed at large images derived from raw camera files.

The following operations are performed on the full-size image, approx 5000x7500 pixels. For presentation on this web page, three images are shown:

  1. The full-size result massively shrunk.
  2. A crop from near the centre of the result. This shows part of the woman's arm, donkey fur, leather harness and distant out-of-focus grass.
  3. A histogram of the full result.

Set up initial conditions:

set ZP_SRC=AGA_1434_gms.tiff

set RESIZE=-resize 500x500
set CROP=-crop 400x500+1930+2300 +repage 

An ImageMagick bug in version 6.9.1-6 causes "histogram:" images to be of MIFF format, even though they are named ".PNG". See bug report Histogram images. So I use an older version of IM for that task:

set IMH=%IMG691%

GMS: set gamma for maximum standard deviation

The script for doing this is not (yet) published, so this cookbook starts with an image that already has a "correct" gamma.

%IMG7%magick ^
  %ZP_SRC% ^
  ( +clone ^
    %RESIZE% ^
    -write zp_src_sm.jpg ^
    +delete ) ^
  %CROP% zp_src_sm_cr.jpg

%IMG7%magick ^
  AGA_1434_gms.tiff ^
  -define histogram:unique-colors=false ^
  histogram:AGA_1434_hist.png

call %PICTBAT%stats %ZP_SRC% 
min=0    max=1    mean=0.370564    SD=0.0949998
satMin=0 satMax=0.27509 satMean=0.0367565 satSD=0.021548
propWhite=0
zp_src_sm.jpg zp_src_sm_cr.jpg AGA_1434_hist.png

Pixel values are present from zero to one, so auto-level will do nothing. But the image has very few dark or light pixels, also evidenced from the low standard deviation (0.095) and the histogram is mostly scrunched up in the middle.

Quick and easy

We can apply quick and simple processing: linear-stretch, auto-gamma, increase saturation; unsharp:

%IMG7%magick ^
  %ZP_SRC% ^
  -linear-stretch 0.01x0.01%% ^
  -auto-gamma ^
  -modulate 100,250,100 ^
  -unsharp 0x1 ^
  -write zp_satun.tiff ^
  ( +clone ^
    %RESIZE% ^
    -write zp_satun.jpg ^
    +delete ) ^
  %CROP% zp_satun_cr.jpg

%IMG7%magick ^
  zp_satun.tiff ^
  -define histogram:unique-colors=false ^
  histogram:zp_satun_hist.png

call %PICTBAT%stats zp_satun.tiff 
min=0    max=1    mean=0.485442    SD=0.185361
satMin=0 satMax=1 satMean=0.162045 satSD=0.0849977
propWhite=2.8572e-05
zp_satun.jpg zp_satun_cr.jpg zp_satun_hist.png

The histogram shows that some pixels have clipped, at both ends.

SSD: Set standard deviation

The GMS image above has a low standard deviation (low contrast), and we can increase it to any value we want. The script sigSetSd.bat iterates to find a solution, so it isn't fast. The script can be run on a small version of the image with little loss of accuracy.

(A much faster process module is now available for this. See Process modules: set mean and standard deviation, and the Set mean and stddev page.)

call %PICTBAT%sigSetSd %ZP_SRC% MEAN

echo %sssOPTION%
-sigmoidal-contrast 7.10773,37.0563969%
%IMG7%magick ^
  %ZP_SRC% ^
  %sssOPTION% ^
  -write zp_ssd.tiff ^
  ( +clone ^
    %RESIZE% ^
    -write zp_ssd.jpg ^
    +delete ) ^
  %CROP% zp_ssd_cr.jpg

%IMG7%magick ^
  zp_ssd.tiff ^
  -define histogram:unique-colors=false ^
  histogram:zp_ssd_hist.png

Use this as the source for following operations.

set ZP_SRC2=zp_ssd.tiff

call %PICTBAT%stats %ZP_SRC% 
min=0    max=1    mean=0.370564    SD=0.0949998
satMin=0 satMax=0.27509 satMean=0.0367565 satSD=0.021548
propWhite=0
zp_ssd.jpg zp_ssd_cr.jpg zp_ssd_hist.png

Sigmoidal-contrast of 7.09 is a fairly heavy increase. The steepest increase is at tone=37%. The SD has increased to the required value. The mean has also increased, towards 0.5.

Where this increases contrast, it also increases saturation. If we want to avoid this, we can perform sigmoidal-contrast on just the lightness.

%IMG7%magick ^
  %ZP_SRC% ^
  -colorspace Lab -channel R ^
  %sssOPTION% ^
  +channel -colorspace sRGB ^
  -write zp_ssdl.tiff ^
  ( +clone ^
    %RESIZE% ^
    -write zp_ssdl.jpg ^
    +delete ) ^
  %CROP% zp_ssdl_cr.jpg

%IMG7%magick ^
  zp_ssdl.tiff ^
  -define histogram:unique-colors=false ^
  histogram:zp_ssdl_hist.png

call %PICTBAT%stats zp_ssdl.tiff 
min=0    max=1    mean=0.494226    SD=0.171846
satMin=0 satMax=0.276844 satMean=0.0388487 satSD=0.0230612
propWhite=0
zp_ssdl.jpg zp_ssdl_cr.jpg zp_ssdl_hist.png

Equalising the histogram

Equalising the histogram can be useful to see detail in highlights and shadows. The result has a mean of about 0.5, and a standard-deviation of about 0.2889 (which is usually a big increase, for photographs). By itself, the result is usually unattractive.

%IMG7%magick ^
  %ZP_SRC% ^
  -equalize ^
  -write zp_equ.tiff ^
  ( +clone ^
    %RESIZE% ^
    -write zp_equ.jpg ^
    +delete ) ^
  %CROP% zp_equ_cr.jpg

%IMG7%magick ^
  zp_equ.tiff ^
  -define histogram:unique-colors=false ^
  histogram:zp_equ_hist.png

call %PICTBAT%stats zp_equ.tiff 
min=0    max=1    mean=0.482264    SD=0.282403
satMin=0 satMax=0.87747 satMean=0.135663 satSD=0.104407
propWhite=7.48707e-06
zp_equ.jpg zp_equ_cr.jpg zp_equ_hist.png

As with SSD, where the contrast is increased, so is the saturation.

The simple equalisation has clipped both ends. We can reduce or eliminate this.

set eqshDEBUG=1
call %PICTBAT%equSlope %ZP_SRC% 20 55 8 90 zp_equs.tiff
set eqshDEBUG=

%IMG7%magick ^
  zp_equs.tiff ^
  ( +clone ^
    %RESIZE% ^
    -write zp_equs.jpg ^
    +delete ) ^
  %CROP% zp_equs_cr.jpg

%IMG7%magick ^
  zp_equs.tiff ^
  -define histogram:unique-colors=false ^
  histogram:zp_equs_hist.png

call %PICTBAT%stats zp_equs.tiff 
min=0    max=1    mean=0.487966    SD=0.240535
satMin=0 satMax=0.747921 satMean=0.115586 satSD=0.0889999
propWhite=0
zp_equs.jpg zp_equs_cr.jpg zp_equs_hist.png

Blending an equalisation with the source is more attractive. A 50% blend is often good.

%IMG7%magick ^
  %ZP_SRC% ^
  ( +clone -equalize ) ^
  -compose blend -define compose:args=50 -composite ^
  -write zp_equb.tiff ^
  ( +clone ^
    %RESIZE% ^
    -write zp_equb.jpg ^
    +delete ) ^
  %CROP% zp_equb_cr.jpg

%IMG7%magick ^
  zp_equb.tiff ^
  -define histogram:unique-colors=false ^
  histogram:zp_equb_hist.png

call %PICTBAT%stats zp_equb.tiff 
min=0    max=1    mean=0.426414    SD=0.187682
satMin=0 satMax=0.570703 satMean=0.0862097 satSD=0.0609256
propWhite=0
zp_equb.jpg zp_equb_cr.jpg zp_equb_hist.png

An alternative to blending is the limit the contrast-shift caused by the equalisation, using the script eqLimit.bat from my page Process modules: equalisation. Here, I limit the contrast-shift to (mean + 1*SD).

call %PICTBAT%eqLimit %ZP_SRC% 1 . . zp_equc.tiff

%IMG7%magick ^
  zp_equc.tiff ^
  ( +clone ^
    %RESIZE% ^
    -write zp_equc.jpg ^
    +delete ) ^
  %CROP% zp_equc_cr.jpg

%IMG7%magick ^
  zp_equc.tiff ^
  -define histogram:unique-colors=false ^
  histogram:zp_equc_hist.png

call %PICTBAT%stats zp_equc.tiff 
min=0    max=1    mean=0.437378    SD=0.258051
satMin=0 satMax=0.792721 satMean=0.105077 satSD=0.0687336
propWhite=1.75065e-05
zp_equc.jpg zp_equc_cr.jpg zp_equc_hist.png

Blending 50% of the equalisation with the source limits the shift of all the pixels equally. By contrast, eqLimit.bat limits the shift to a certain maximum, while increasing the shift of other pixels. So eqLimit.bat results in a wider spreading of the overall histogram, while retaining the peaks. The result is less flat, more lively, than the 50% blend of equalisation and source.

EHB: Extrapolate from heavy blur

The script exHvyBlr.bat is like unsharp masking, but with much heavier blur.

call %PICTBAT%exHvyBlr %ZP_SRC2% 0.5 150

%IMG7%magick ^
  zp_ssd_ehb.tiff ^
  ( +clone ^
    %RESIZE% ^
    -write zp_ehb.jpg ^
    +delete ) ^
  %CROP% zp_ehb_cr.jpg

%IMG7%magick ^
  zp_ssd_ehb.tiff ^
  -define histogram:unique-colors=false ^
  histogram:zp_ssd_ehb_hist.png

call %PICTBAT%stats zp_ssd_ehb.tiff 
min=0    max=1    mean=0.468946    SD=0.192001
satMin=0 satMax=0.7075 satMean=0.0728775 satSD=0.0470818
propWhite=0.00217447
zp_ehb.jpg zp_ehb_cr.jpg zp_ssd_ehb_hist.png

Highlights have clipped badly. Local contrast has increased. Unfortunately, this has made the background people more distracting.

LBDM: light blur difference mask

The script lbdMask.bat shows where there is detail. The mask is white where there is detail and black where there isn't. This approximates to what is in focus, but skin tone that has little detail shows as black.

call %PICTBAT%lbdMask %ZP_SRC2%

%IMG7%magick ^
  zp_ssd_lbdm.tiff ^
  -write zp_lbdm.tiff ^
  ( +clone ^
    %RESIZE% ^
    -write zp_lbdm.jpg ^
    +delete ) ^
  %CROP% zp_lbdm_cr.jpg
zp_lbdm.jpg zp_lbdm_cr.jpg

(See Details, details for alternative methods of identifying detail.)

We can use the LBD mask to sharpen the sharp objects and further blur the unsharp objects, by composing the EHB sharp version over a blurred version.

%IMG7%magick ^
  %ZP_SRC2% ^
  -blur 0x20 ^
  zp_ssd_ehb.tiff ^
  zp_ssd_lbdm.tiff ^
  -composite ^
  -write zp_sus.tiff ^
  ( +clone ^
    %RESIZE% ^
    -write zp_sus.jpg ^
    +delete ) ^
  %CROP% zp_sus_cr.jpg

%IMG7%magick ^
  zp_sus.tiff ^
  -define histogram:unique-colors=false ^
  histogram:zp_sus_hist.png

call %PICTBAT%stats zp_sus.tiff 
min=0    max=1    mean=0.466803    SD=0.173657
satMin=0 satMax=0.7075 satMean=0.0672174 satSD=0.0423363
propWhite=0.000151448
zp_sus.jpg zp_sus_cr.jpg zp_sus_hist.png

Distant grass and people are blurred. Foreground skin is also blurred, removing texture but also removing the boundary between skin and unfocused grass (top-right of crop). Most donkey fur is sharpened, but individual strands top-right are blurred away. We could reduce these side-effects by enlarging the white areas of the mask with morphology dilate, or another method.

The effect is similar to both IM operations "-adaptive-sharpen" and "adaptive-blur". My method is slower, but I have total control over the "adaptive" part.

The histogram shows that some pixels have clipped, at both ends.

The operations have increased saturation, but we could boost it more:

%IMG7%magick ^
  zp_sus.tiff ^
  -modulate 100,200,100 ^
  -write zp_sus_sat.tiff ^
  ( +clone ^
    %RESIZE% ^
    -write zp_sus_sat.jpg ^
    +delete ) ^
  %CROP% zp_sus_sat_cr.jpg

%IMG7%magick ^
  zp_sus.tiff ^
  -define histogram:unique-colors=false ^
  histogram:zp_sus_sat_hist.png

call %PICTBAT%stats zp_sus_sat.tiff 
min=0    max=1    mean=0.469168    SD=0.182104
satMin=0 satMax=1 satMean=0.13441 satSD=0.0845872
propWhite=0.000151448
zp_sus_sat.jpg zp_sus_sat_cr.jpg zp_sus_sat_hist.png

Compare results

Compare the quick-and-easy (zp_satun.jpg) and more complicated version (zp_sus_sat.jpg) side by side:

zp_satun.jpg zp_sus_sat.jpg

The second version is, to my eyes, vastly superior. Compared to the first image, the second:

Further development

See the pages Adaptive auto level and gamma and [Adaptive] Contrast-limited equalisation.

Cleanup

We don't need to keep all those huge tiff files, so delete them.

del zp_lbdm.tiff
del zp_satun.tiff
del zp_ssd.tiff
del zp_ssd_ehb.tiff
del zp_ssd_lbdm.tiff
del zp_ssdl.tiff
del zp_equ.tiff
del zp_equb.tiff
del zp_equc.tiff

rem del zp_sus.tiff  ... keep this one

Scripts

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

stats.bat

@rem Updated:
@rem   26-July-2022 for IM 7.

@for /F "usebackq" %%L in (`%IMG7%magick ^
  %* ^
  -format "statMIN=%%[fx:minima]\nstatMAX=%%[fx:maxima]\nstatMEAN=%%[fx:mean]\nstatSD=%%[fx:standard_deviation]\n" ^
  -write info: ^
  ^( +clone ^
    -colorspace HCL ^
    -format "statSatMin=%%[fx:minima.g]\nstatSatMax=%%[fx:maxima.g]\nstatSatMean=%%[fx:mean.g]\nstatSatSD=%%[fx:standard_deviation.g]\n" ^
    -write info: ^
    +delete ^
  ^) ^
  -fill #000 +opaque #fff -format "statPropWhite=%%[fx:mean]" ^
  info:`) do @set %%L

@echo min=%statMIN%    max=%statMAX%    mean=%statMEAN%    SD=%statSD%
@echo satMin=%statSatMin% satMax=%statSatMax% satMean=%statSatMean% satSD=%statSatSd%
@echo propWhite=%statPropWhite%

sigSetSd.bat

This finds the contrast parameter of the "sigmoidal-contrast" operation, and the "+" or "-" sign, that gives a required standard deviation (SD). It doesn't try to maximise the SD, because increasing contrast always increases SD, until we have a black-and-white image.

A required input to this script is the mid-point of the sigmoid. (This is the steepest part of a "-" curve, or the shallowest part of a "+" curve.) In practice, setting the mid-point to the mean of the image often works well, so this is an input option.

The default goal SD is 0.166667. Initial guesses for the contrast are 0.0000001 and 30.

From two guesses, the script finds the SD that results from each contrast setting. Then it starts looping. In each loop, it finds the geometric mean of the two guessed contrasts, and the SD from that contrast. Now we have calculated three SDs. The required SD is between either the first two or the last two. We discard the worst result, leaving us two guesses for the contrast.

Continue looping until we have converged: two contrasts are equal, or two SDs are equal, within the desired precision.

rem For image %1, finds the +/-sigmoidal-contrast that makes the standard deviation a given goal.
rem %2 is required mid-point for sigmoidal-contrast, as percentage, eg "50".
rem   Or "mean" to use the mean of the image.
@rem
@rem Will use:
@rem   sssGOAL_SD default 0.166667
@rem   sssPRECISION default "-precision 6"
@rem
@rem   sssDIRECTION "incOnly", "decOnly" or "both".
@rem     For "incOnly", has goal of only increasing SD. If current SD >= goal, does nothing (and returns sssALREADY_CLOSE=1).
@rem     For "decOnly", has goal of only decreasing SD. If current SD <= goal, does nothing (and returns sssALREADY_CLOSE=1).
@rem     For "both", has goal of increasing or decreasing SD as required.
@rem
@rem Returns:
@rem    sssFALSE_RESULT = 0 (okay) or 1 (algorithm failed).
@rem    sssALREADY_CLOSE = 0 or 1
@rem    sssOPTION = "+sigmoidal-contrast %sssCONTRAST%,%sssMID_PT%" or
@rem                "-sigmoidal-contrast %sssCONTRAST%,%sssMID_PT%" or
@rem                "" (when already close)
@rem    sssCONTRAST, sssMID_PT
@rem
@rem When decreasing contrast, the algorithm can fail when the current SD is high;
@rem the image is mostly black and white,
@rem with few mid-tones to be shifted by "+sigmoidal-contrast".
@rem
@rem Updated:
@rem   26-July-2022 for IM v7.
@rem

@if "%2"=="" findstr /B "rem @rem" %~f0 & exit /B 1

@setlocal enabledelayedexpansion

@call echoOffSave

set INFILE=%1

set MID_PT=%2
if "%MID_PT%"=="" set MID_PT=30

set GOAL_SD=%sssGOAL_SD%
if "%GOAL_SD%"=="" set GOAL_SD=0.166667

set CON0=0.0000001
set CON2=30

set PRECISION=%sssPRECISION%
if "%PRECISION%"=="" set PRECISION=-precision 6

set DIRECTION=%sssDIRECTION%
if "%DIRECTION%"=="" set DIRECTION=both

set MAX_PREC=9

set TMP_IN=\temp\sss.miff

if /I "%MID_PT%"=="mean" for /F "usebackq" %%L ^
in (`%IMG7%magick ^
  %INFILE% ^
  +write %TMP_IN% ^
  -precision %MAX_PREC% ^
  -format "MID_PT=%%[fx:100*mean]" info:`) ^
do set %%L

echo %0: MID_PT=%MID_PT%

rem Are we already close?
for /F "usebackq" %%L ^
in (`%IMG7%magick ^
  %TMP_IN% ^
  -precision %MAX_PREC% ^
  ^( +clone -format "SDcur=%%[fx:standard_deviation]\n" -write info: +delete ^) ^
  ^( +clone +sigmoidal-contrast %CON0%x%MID_PT%%% -format "SDdec=%%[fx:standard_deviation]\n" -write info: +delete ^) ^
  -sigmoidal-contrast %CON0%x%MID_PT%%% -format "SDinc=%%[fx:standard_deviation]\n" info:`) ^
do set %%L

for /F "usebackq" %%L ^
in (`%IMG7%magick identify ^
  -format "ALREADY_CLOSE=%%[fx:%SDdec%<=%GOAL_SD%&&%GOAL_SD%<=%SDinc%]\nNEED_DEC=%%[fx:%GOAL_SD%<%SDdec%]" ^
  xc:`) ^
do set %%L

if %NEED_DEC%==1 if /I %DIRECTION%==incOnly set ALREADY_CLOSE=1
if %NEED_DEC%==0 if /I %DIRECTION%==decOnly set ALREADY_CLOSE=1

if %ALREADY_CLOSE%==1 (
  set FINISHED=1
  set FALSE_RESULT=0
  set OPTION=
  goto finished
)

if %NEED_DEC%==1 (
  set SIG_SIGN=+
  set tmpCon=%CON0%
  set CON0=%CON2%
  set CON2=!tmpCon!
) else (
  set SIG_SIGN=-
)

echo %0: SIG_SIGN=%SIG_SIGN%

for /F "usebackq" %%L ^
in (`%IMG7%magick ^
  %TMP_IN% ^
  -precision %MAX_PREC% ^
  ^( +clone %SIG_SIGN%sigmoidal-contrast %CON0%x%MID_PT%%% -format "SD0=%%[fx:standard_deviation]\n" -write info: +delete ^) ^
  %SIG_SIGN%sigmoidal-contrast %CON2%x%MID_PT%%% ^
  -format "SD2=%%[fx:standard_deviation]" ^
  info:`) ^
do set %%L

for /F "usebackq" %%L ^
in (`%IMG7%magick identify ^
  -format "FALSE_RESULT=%%[fx:%SD0%>=%GOAL_SD%||%SD2%<=%GOAL_SD%]" ^
  xc:`) ^
do set %%L

echo %0: SD0=%SD0% SD2=%SD2% FALSE_RESULT=%FALSE_RESULT%

set FINISHED=%FALSE_RESULT%

if %FINISHED%==1 goto finished

set nIter=0

:loop

if %CON0%==%CON2% set FINISHED=1

for /F "usebackq" %%L ^
in (`%IMG7%magick identify ^
  %PRECISION% ^
  -format "CON1=%%[fx:sqrt(%CON0%*%CON2%)]" ^
  xc:`) ^
do set %%L

if %CON0%==%CON1% set FINISHED=1
if %CON1%==%CON2% set FINISHED=1

for /F "usebackq" %%L ^
in (`%IMG7%magick ^
  %TMP_IN% ^
  %PRECISION% ^
  %SIG_SIGN%sigmoidal-contrast %CON1%x%MID_PT%%% ^
  -format "SD1=%%[fx:standard_deviation]" ^
  info:`) ^
do set %%L

for /F "usebackq" %%L ^
in (`%IMG7%magick identify ^
  -format "signDiff=%%[fx:%SD1%<%GOAL_SD%?-1:%SD1%>%GOAL_SD%]" ^
  xc:`) ^
do set %%L

echo %0: CON0=%CON0% CON1=%CON1% CON2=%CON2%
echo %0: SD0=%SD0% SD1=%SD1% SD2=%SD2%

rem echo %0: signDiff=%signDiff%

if %signDiff%==0 (
  set FINISHED=1
) else if %SD0%==%SD1% (
  set FINISHED=1
) else if %SD1%==%SD2% (
  set FINISHED=1
) else if %signDiff%==-1 (
  set CON0=%CON1%
  set SD0=%SD1%
) else (
  set CON2=%CON1%
  set SD2=%SD1%
)

@set /A nIter+=1

if %FINISHED%==0 goto loop

echo %0: nIter=%nIter%

if %ALREADY_CLOSE%==0 (
  set OPTION=%SIG_SIGN%sigmoidal-contrast %CON1%,%MID_PT%%%
)

:finished

call echoRestore

endlocal & ^
set sssFALSE_RESULT=%FALSE_RESULT%& ^
set sssCONTRAST=%CON1%& ^
set sssMID_PT=%MID_PT%& ^
set sssALREADY_CLOSE=%ALREADY_CLOSE%& ^
set sssOPTION=%OPTION%

set sss

exit /B 0

eqLimit.bat

rem From image %1,
rem make contrast-limited histogram-equalised (with iterative redistribution) version.
@rem
@rem Optional parameters:
@rem   %2 is limiting factor SD_FACT, so limit = mean + SD_FACT * standard_deviation.
@rem        Default 1.
@rem   %3 is percentage lift for shadows. Maximum < 100. Default 0, no lift.
@rem   %4 is percentage drop for highlights. Maximum < 100. Default 0, no drop.
@rem   %5 is output file, or null: for no output.
@rem
@rem Can also use:
@rem   eqlDEBUG if 1, also creates curve histograms.
@rem   eqlDIFF_LIMIT iteration stops when the count redistributed is less than this percentage.
@rem     Set to a value >= 100 to prevent iteration.
@rem     Default 1.0.
@rem   eqlSUPPRESS_OUT if 1, suppresses output.
@rem
@rem Updated:
@rem   26-July-2022 for IM v7.
@rem


@if "%1"=="" findstr /B "rem @rem" %~f0 & exit /B 1

@setlocal enabledelayedexpansion

@call echoOffSave

call %PICTBAT%setInOut %1 eql

set SD_FACT=%2
if "%SD_FACT%"=="." set SD_FACT=
if "%SD_FACT%"=="" set SD_FACT=1

set LIFT_SHADOW_PC=%3
if "%LIFT_SHADOW_PC%"=="." set LIFT_SHADOW_PC=
if "%LIFT_SHADOW_PC%"=="" set LIFT_SHADOW_PC=0

set DROP_HIGHLIGHT_PC=%4
if "%DROP_HIGHLIGHT_PC%"=="." set DROP_HIGHLIGHT_PC=
if "%DROP_HIGHLIGHT_PC%"=="" set DROP_HIGHLIGHT_PC=0

if not "%5"=="" set OUTFILE=%5

if "%5"=="" (
  set EQL_BASE=%~n1_%sioCODE%
) else (
  set EQL_BASE=%~n5_%sioCODE%
)

if "%eqlDIFF_LIMIT%"=="" set eqlDIFF_LIMIT=1

set TMPEXT=miff

for /F "usebackq" %%L in (`cygpath %TEMP%`) do set CYGTEMP=%%L

%IM7DEV%magick ^
  %INFILE% ^
  -colorspace Lab -channel R -separate -set colorspace sRGB ^
  -process 'mkhisto norm' ^
  %CYGTEMP%\%EQL_BASE%_gch.%TMPEXT%

if ERRORLEVEL 1 exit /B 1	

for /F "usebackq" %%L in (`%IMG7%magick ^
  %TEMP%\%EQL_BASE%_gch.%TMPEXT% ^
  -precision 15 ^
  -format "histcap=%%[fx:(mean+%SD_FACT%*standard_deviation)*100]" ^
  info:`) do set %%L

echo %0: histcap=%histcap% 


set nIter=0
:loop

%IM7DEV%magick ^
  %CYGTEMP%\%EQL_BASE%_gch.%TMPEXT% ^
  -channel RGB ^
  -evaluate Min %histcap%%% ^
  +channel ^
  %CYGTEMP%\%EQL_BASE%_gchc_cap.%TMPEXT%

for /F "usebackq" %%L in (`%IMG7%magick ^
  %TEMP%\%EQL_BASE%_gch.%TMPEXT% ^
  %TEMP%\%EQL_BASE%_gchc_cap.%TMPEXT% ^
  -compose MinusSrc -composite ^
  -precision 15 ^
  -format "MeanDiffPC=%%[fx:mean*100]" ^
  info:`) do set %%L

echo %0: MeanDiffPC=%MeanDiffPC%

%IM7DEV%magick ^
  %CYGTEMP%\%EQL_BASE%_gch.%TMPEXT% ^
  -channel RGB ^
  -evaluate Min %histcap%%% ^
  +channel ^
  -evaluate Add %MeanDiffPC%%% ^
  %CYGTEMP%\%EQL_BASE%_gch.%TMPEXT%

for /F "usebackq" %%L in (`%IMG7%magick identify ^
  -format "DO_LOOP=%%[fx:%MeanDiffPC%>%eqlDIFF_LIMIT%?1:0]" ^
  xc:`) do set %%L

rem If max(eql_gch) > histcap + epsilon, repeat.
rem OR
rem if %MeanDiffPC% > epsilon, repeat

set /A nIter+=1

if %DO_LOOP%==1 goto loop

echo %0: nIter=%nIter%


if %LIFT_SHADOW_PC%==0 if %DROP_HIGHLIGHT_PC%==0 %IM7DEV%magick ^
  %CYGTEMP%\%EQL_BASE%_gch.%TMPEXT% ^
  -auto-level ^
  %CYGTEMP%\%EQL_BASE%_gch.%TMPEXT%

if %LIFT_SHADOW_PC%==0 goto skipShad
set WX=0

rem In following, "-blur" should really be "decomb".
set DECOMB=-blur 0x1

for /F "usebackq tokens=2 delims=:, " %%A in (`%IM7DEV%magick ^
  %CYGTEMP%\%EQL_BASE%_gch.%TMPEXT% ^
  -auto-level ^
  +write %CYGTEMP%\%EQL_BASE%_gch.%TMPEXT% ^
  %DECOMB% ^
  -threshold %LIFT_SHADOW_PC%%% ^
  -process onewhite ^
  NULL: 2^>^&1`) do set WX=%%A

rem %0: echo WX=%WX%

if "%WX%"=="none" (
  %IMG7%magick ^
  %TEMP%\%EQL_BASE%_gch.%TMPEXT% ^
  -fill #fff -colorize 100 ^
  %TEMP%\%EQL_BASE%_gch.%TMPEXT%
) else if not %WX%==0 %IM7DEV%magick ^
  %CYGTEMP%\%EQL_BASE%_gch.%TMPEXT% ^
  -size %WX%x1 xc:gray(%LIFT_SHADOW_PC%%%) ^
  -gravity West -composite ^
  %CYGTEMP%\%EQL_BASE%_gch.%TMPEXT%

:skipShad


if %DROP_HIGHLIGHT_PC%==0 goto skipHigh
set WX=0

rem In following, "-blur" should really be "decomb".
set DECOMB=-blur 0x1

for /F "usebackq tokens=2 delims=:, " %%A in (`%IM7DEV%magick ^
  %CYGTEMP%\%EQL_BASE%_gch.%TMPEXT% ^
  -auto-level ^
  +write %CYGTEMP%\%EQL_BASE%_gch.%TMPEXT% ^
  %DECOMB% ^
  -threshold %DROP_HIGHLIGHT_PC%%% ^
  -flop ^
  -process onewhite ^
  NULL: 2^>^&1`) do set WX=%%A

rem %0: echo WX=%WX%

if "%WX%"=="none" (
  %IMG7%magick ^
  %TEMP%\%EQL_BASE%_gch.%TMPEXT% ^
  -fill #fff -colorize 100 ^
  %TEMP%\%EQL_BASE%_gch.%TMPEXT%
) else if not %WX%==0 %IM7DEV%magick ^
  %CYGTEMP%\%EQL_BASE%_gch.%TMPEXT% ^
  -size %WX%x1 xc:gray(%DROP_HIGHLIGHT_PC%%%) ^
  -gravity East -composite ^
  %CYGTEMP%\%EQL_BASE%_gch.%TMPEXT%

:skipHigh


%IM7DEV%magick ^
  %CYGTEMP%\%EQL_BASE%_gch.%TMPEXT% ^
  -process 'cumulhisto norm' ^
  %CYGTEMP%\%EQL_BASE%_gchc_clhe_red.%TMPEXT%



if /I "%OUTFILE%" NEQ "null:" if not "%eqlSUPPRESS_OUT%"=="1" %IMG7%magick ^
  %INFILE% ^
  %TEMP%\%EQL_BASE%_gchc_clhe_red.%TMPEXT% ^
  -clut ^
  %OUTFILE%


if "%eqlDEBUG%"=="1" (
  call %PICTBAT%graphLineCol %TEMP%\%EQL_BASE%_gch.%TMPEXT% . . 0 %EQL_BASE%_gch.png
  call %PICTBAT%graphLineCol %TEMP%\%EQL_BASE%_gchc_cap.%TMPEXT% . . 0 %EQL_BASE%_gchc_cap.png
  call %PICTBAT%graphLineCol %TEMP%\%EQL_BASE%_gchc_clhe_red.%TMPEXT% . . 0 %EQL_BASE%_gchc_clhe_red.png
)


call echoRestore

@endlocal & set eqlOUTFILE=%OUTFILE% &set eqlCLUT=%TEMP%\%EQL_BASE%_gchc_clhe_red.%TMPEXT%

exHvyBlr.bat

rem Extrapolate away from heavy blur. Increases local contrast.
rem A bit like tone-mapping, HDR, High Dynamic Range.
@rem
@rem %2 is resize amount (inverse of blur radius) as percentage of image size.
@rem   Small amounts (0.1-5) spread impact over large area. [0.5]
@rem   Large amounts (25-100) give unsharp mask.
@rem %3 is extrapolation as percentage.
@rem   0 is blur; 100 is no effect (unchanged image), 150 is subtle, 15000 is massive.
@rem %4, if given, is output filename.
@rem
@rem See also hvyBlrDiff.bat
@rem
@rem Updated:
@rem   26-July-2022 for IM v7.
@rem

@if "%1"=="" findstr /B "rem @rem" %~f0 & exit /B 1

@setlocal

@call echoOffSave

call %PICTBAT%setInOut %1 ehb

set BLUR_PC=%2
if "%BLUR_PC%"=="." set BLUR_PC=
if "%BLUR_PC%"=="" set BLUR_PC=0.5

set BLEND_PC=%3
if "%BLEND_PC%"=="." set BLEND_PC=
if "%BLEND_PC%"=="" set BLEND_PC=200

if not "%4"=="" set OUTFILE=%4


FOR /F "usebackq" %%L IN (`%IMG7%magick identify -format "WW=%%w\nHH=%%h" %INFILE%`) DO set %%L

%IMG7%magick ^
  %INFILE% ^
  ^( +clone ^
     -resize %BLUR_PC%%% -resize "%WW%x%HH%^!" ^
  ^) ^
  +swap ^
  -compose Blend -define compose:args=%BLEND_PC% -composite ^
  %OUTFILE%

call echoRestore

@endlocal & set ehbOUTFILE=%OUTFILE%

lbdMask.bat

rem Makes a mask from LBD, difference from light blur resulting in monochrome RMS.
rem White or light where there is detail; black or dark where there isn't.
rem
rem %2 is blur sigma in pixels.
@rem
@rem See also ltBlrDiff.
@rem
@rem Updated:
@rem   26-July-2022 for IM v7.
@rem

@if "%1"=="" findstr /B "rem @rem" %~f0 & exit /B 1

@setlocal

@call echoOffSave

call %PICTBAT%setInOut %1 lbdm

set BLUR_SIG=%2
if "%BLUR_SIG%"=="" set BLUR_SIG=4

%IMG7%magick ^
  %INFILE% ^
  ^( +clone -blur 0x%BLUR_SIG% ^) ^
  -compose Difference -composite ^
  -grayscale RMS ^
  -auto-level ^
  -auto-gamma ^
  -blur 0x20 ^
  -contrast-stretch 25x25%% ^
  %OUTFILE%

call echoRestore

@endlocal

All images on this page were created by the commands shown, using:

%IMG7%magick -version
Version: ImageMagick 7.1.0-49 Q16-HDRI x64 7a3f3f1:20220924 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 (193331630)

Source file for this web page is zingph.h1. To re-create this web page, execute "procH1 zingph".


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 4-Mar-2014.

Page created 26-Nov-2022 00:25:36.

Copyright © 2022 Alan Gibson.