snibgo's ImageMagick pages

Adaptive auto level and gamma

We can auto-level and auto-gamma as appropriate for different parts of an image, blending between these parts.

Auto level and gamma adjusts an image so it spans the range of possible values, and the mid-tone is mid-grey. Instead, we can divide the image into tiles (or regular crops), calculate the levelling parameters for each tile, then make one full image levelled with these parameters. Finally we blend these variations together.

Put it another way: "-auto-level" will transform a particular colour to the same output colour, irrespective of where it occurs in the image. This adaptive technique will make different output colours for the same, depending on the context around the input.

For the blending step of this process, see Blending tiles, which is a building block for this page.

Sample source file

For a sample source file, we will use a crop of a Nikon NEF file, converted by dcraw at gamma=1, but with metadata that declares it to be sRGB. The image looks dark, but consistently dark for all viewers and browsers. (The lens was 85mm, at f/1.8.)

All operations are performed on the full-size image, but smaller versions are made for the web.

set CROP=-crop 5684x4864+1100+60 +repage
set WEB_SIZE=-resize 600x600

set SRC=algt_src.tiff

if not exist %SRC% %IMG7%magick ^
  %PICTLIB%20141203\AGA_2159_raw.tiff ^
  %CROP% ^

%IMG7%magick ^
  %SRC% ^
  %WEB_SIZE% ^
call %PICTBAT%stats %SRC% 
min=0    max=1    mean=0.122594    SD=0.214132
satMin=0 satMax=0.692668 satMean=0.0119722 satSD=0.0181975

The image already spans the range 0.0 to 1.0, so "-auto-level" will do nothing. But "-auto-gamma" will improve visibility.

%IMG7%magick ^
  %SRC% ^
  -auto-gamma ^
  +write algt_agam.tiff ^
  %WEB_SIZE% ^
call %PICTBAT%stats algt_agam.tiff 
min=0    max=1    mean=0.387406    SD=0.21388
satMin=0 satMax=0.692699 satMean=0.0234596 satSD=0.0226683

This has increased the mean to closer to 0.5. The standard deviation has hardly changed. The saturation has doubled.

Two-by-two blend

The script algQtr.bat finds the minimum, maximum and mean values for each of the four quarters. It applies four "-levels" to four versions of the image, and blends them such that only the four corners are unblended.

call %PICTBAT%algQtr %SRC%

%IMG7%magick ^
  %algqOUTFILE% ^
  %WEB_SIZE% ^
call %PICTBAT%stats %algqOUTFILE% 
min=0    max=1    mean=0.42925    SD=0.189871
satMin=0 satMax=0.726421 satMean=0.0228923 satSD=0.0217019

This has brightened the figures, while darkening the pavement bottom-right.

Larger numbers

The script algTile.bat finds the minimum, maximum and mean values for each of a number of tiles (using IM's "-crop NxM@"), and applies a "-level" to the same number of copies of the image. It then blends the results, interpolating between the centre of the tiles. Hence each output pixel takes its value from only one image, or from a blend of two images, or from a blend of four images.

Versions of IM up to and including v6.9.0-0 had a bug, where some combinations of image size and NxM parameters made the wrong number of crops. See Forum: Crop @ problem. For example, if the image is 267x233 pixels and we "-crop 10x11@", IM should make 110 crops but makes only 11. When this happens, the script will error with a message and ERRORLEVEL > 0.

This implements the interpolation method shown in Wikipedia: Adaptive histogram equalization: Efficient computation by interpolation.

2x2 tiles

call %PICTBAT%algTile %SRC% 2 2 algt_tile_2x2.tiff
if ERRORLEVEL 1 goto error

%IMG7%magick ^
  %algtOUTFILE% ^
  %WEB_SIZE% ^
call %PICTBAT%stats %algtOUTFILE% 
min=0    max=1    mean=0.433093    SD=0.193705
satMin=0 satMax=0.750347 satMean=0.0232949 satSD=0.022087

4x4 tiles

call %PICTBAT%algTile %SRC% 4 4 algt_tile_4x4.tiff
if ERRORLEVEL 1 goto error

%IMG7%magick ^
  %algtOUTFILE% ^
  %WEB_SIZE% ^
call %PICTBAT%stats %algtOUTFILE% 
min=0    max=1    mean=0.402968    SD=0.19882
satMin=0 satMax=0.771649 satMean=0.0262081 satSD=0.0248092

The smaller tiles have resulted in a greater contrast within each tile. Sadly, the transitions around the top-left tiles are rather obvious.

The time taken is proportional to the image size multipled by the number of tiles. The entire image is processed for each of the tiles. This could be optimised so processing is limited to a crop the size of a tile, plus half a tile on the four sides.

Spice up the colour

The numbers above show that the saturation (in HCL space) spans 0.0 to about 0.7, and the mean saturation is very low. Just for fun, we boost the higher saturations with "-equalize", then reduce all but the higher saturations.

%IMG7%magick ^
  algt_tile_4x4.tiff ^
  -colorspace HCL ^
  -channel G ^
  -equalize -evaluate Pow 10 ^
  +channel ^
  -colorspace sRGB ^
  +write algt_sat.tiff ^
  %WEB_SIZE% ^
call %PICTBAT%stats algt_sat.tiff 
min=0    max=1    mean=0.404742    SD=0.216277
satMin=0 satMax=0.999527 satMean=0.0892531 satSD=0.19358


We don't need to keep these large TIFF files, so delete them.

del algt_*_*.tiff
del algt_agam.tiff
del algt_sat.tiff


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


rem Applies auto level and auto gamma as appropriate for four tiles,
rem   blending between the four quarters.
rem %2 is optional output filename.
@rem Can also use:
@rem   algqMASK_CURVE eg -sigmoidal-contrast 5x50%
@rem   algqLINEAR if 1, composes in linear colourspace.
@rem   algqDO_MIN if 0, ignores min values so doesn't auto-level shadows.
@rem   algqDO_MAX if 0, ignores max values so doesn't auto-level highlights.
@rem   algqDO_GAM if 0, ignores mean values so doesn't auto-gamma.
@rem   algqNEW_MEAN default 0.5
@rem See also algTile.bat
@rem Updated:
@rem   27-July-2022 for IM v7.

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

@setlocal enabledelayedexpansion

@call echoOffSave

call %PICTBAT%setInOut %1 algq

if not "%2"=="" set OUTFILE=%2

if "%algqNEW_MEAN%"=="" set algqNEW_MEAN=0.5

set N=0

for /F "tokens=1-3 usebackq" %%A in (`%IMG7%magick ^
  %INFILE% ^
  -crop 2x2@ +repage ^
  -precision 15 ^
  -format "%%[fx:minima*100] %%[fx:maxima*100] %%[fx:ln(mean)/ln(minima+%algqNEW_MEAN%*(maxima-minima))]\n" ^
  info:`) do (
  set eqqMin.!N!=%%A
  set eqqMax.!N!=%%B
  set eqqGam.!N!=%%C

  rem gamma was ln(mean)/ln(0.5)
  rem gamma was ln(mean)/ln((minima+maxima)/2)

  if "%algqDO_MIN%"=="0" set eqqMin.!N!=0
  if "%algqDO_MAX%"=="0" set eqqMax.!N!=100
  if "%algqDO_GAM%"=="0" set eqqGam.!N!=1

  set /A N+=1

:: set eqq

%IMG7%magick ^
  %INFILE% ^
  -alpha off ^
  ( -clone 0 -level %eqqMin.0%%%,%eqqMax.0%%%,%eqqGam.0% %algqMASK_CURVE% ) ^
  ( -clone 0 -level %eqqMin.1%%%,%eqqMax.1%%%,%eqqGam.1% %algqMASK_CURVE% ) ^
  ( -clone 0 -level %eqqMin.2%%%,%eqqMax.2%%%,%eqqGam.2% %algqMASK_CURVE% ) ^
  ( -clone 0 -level %eqqMin.3%%%,%eqqMax.3%%%,%eqqGam.3% %algqMASK_CURVE% ) ^
  ( -clone 0 -sparse-color Bilinear 0,0,#000,%%[fx:w-1],0,#fff ) ^
  -delete 0 ^
  ( -clone 0,1,4 -composite ) ^
  ( -clone 2,3,4 -composite ) ^
  -delete 0-4 ^
  ( -clone 0 -sparse-color Bilinear 0,0,#000,0,%%[fx:h-1],#fff ) ^
  -composite ^
  +depth ^

call echoRestore

@endlocal & set algqOUTFILE=%OUTFILE%


rem Given image %1, effectively divided into %2 x %3 tiles,
rem applies auto level and auto gamma as appropriate for each tile,
rem and merges the results.
rem %4 is optional output filename.
@rem Can also use:
@rem   algtMASK_CURVE eg -sigmoidal-contrast 5x50%
@rem   algtLINEAR if 1, composes in linear colourspace.
@rem   algtCLEAN if 0, does not delete temporary files.
@rem   algtDO_MIN if 0, ignores min values so doesn't auto-level shadows.
@rem   algtDO_MAX if 0, ignores max values so doesn't auto-level highlights.
@rem   algtDO_GAM if 0, ignores mean values so doesn't auto-gamma.
@rem Updated:
@rem   27-July-2022 for IM v7.

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

@setlocal enabledelayedexpansion

@call echoOffSave

call %PICTBAT%setInOut %1 algt

set VSN_LIST=%TEMP%\algtVsn.lis
del %VSN_LIST% 2>nul

set N_COL=%2
set N_ROW=%3
if not "%4"=="" set OUTFILE=%4

for /F "usebackq" %%L in (`%IMG7%magick ^
  %INFILE% ^
  -crop "%N_COL%x%N_ROW%@" +repage ^
  -precision 15 ^
  -format "MIN.%%p=%%[fx:100*minima]\nMAX.%%p=%%[fx:100*maxima]\nMEAN.%%p=%%[fx:100*mean]\nGAMM.%%p=%%[fx:ln(mean)/ln((minima+maxima)/2)]\nMAX_N=%%p\n" ^
  INFO:`) do @set %%L

rem set MIN
rem set MAX
rem set MEAN

set /A COL_X_ROWm1=%N_COL%*%N_ROW%-1

if not %MAX_N%==%COL_X_ROWm1% (
  echo %0: Bug: %MAX_N% neq %COL_X_ROWm1%
  exit /B 1

for /L %%i in (0,1,%MAX_N%) do (
  if "%algtDO_MIN%"=="0" set MIN.%%i=0
  if "%algtDO_MAX%"=="0" set MAX.%%i=100
  if "%algtDO_GAM%"=="0" set GAMM.%%i=1

  set VSN_FILE=algtVsn_%%i.png
  set PART_COM=!PART_COM! ^( +clone -level !MIN.%%i!%%,!MAX.%%i!%%,!GAMM.%%i! %algtMASK_CURVE% +write !VSN_FILE! +delete ^)
  echo !VSN_FILE!>>%VSN_LIST%
rem echo %PART_COM%

%IMG7%magick %INFILE% +depth %PART_COM% NULL:

rem type %VSN_LIST%

set btLINEAR=%algtLINEAR%

call %PICTBAT%blendTile %VSN_LIST% %N_COL% %N_ROW% %OUTFILE%
if ERRORLEVEL 1 exit /B 1

if not "algtCLEAN"=="0" (
  del %VSN_LIST%

  for /L %%i in (0,1,%MAX_N%) do (
    set VSN_FILE=algtVsn_%%i.png
    del !VSN_FILE!

call echoRestore

@endlocal & set algtOUTFILE=%OUTFILE%

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
Copyright: (C) 1999 ImageMagick Studio LLC
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 algtile.h1. To re-create this web page, execute "procH1 algtile".

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 14-Dec-2014.

Page created 27-Jul-2022 20:47:51.

Copyright © 2022 Alan Gibson.