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.
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% ^ %SRC% %IMG7%magick ^ %SRC% ^ %WEB_SIZE% ^ algt_src_sm.jpg |
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 propWhite=0
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% ^ algt_agam_sm.jpg |
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 propWhite=0
This has increased the mean to closer to 0.5. The standard deviation has hardly changed. The saturation has doubled.
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% ^ algt_2x2_sm.jpg |
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 propWhite=0
This has brightened the figures, while darkening the pavement bottom-right.
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 tilescall %PICTBAT%algTile %SRC% 2 2 algt_tile_2x2.tiff if ERRORLEVEL 1 goto error %IMG7%magick ^ %algtOUTFILE% ^ %WEB_SIZE% ^ algt_tile_2x2_sm.jpg |
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 propWhite=0
4x4 tilescall %PICTBAT%algTile %SRC% 4 4 algt_tile_4x4.tiff if ERRORLEVEL 1 goto error %IMG7%magick ^ %algtOUTFILE% ^ %WEB_SIZE% ^ algt_tile_4x4_sm.jpg |
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 propWhite=0
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.
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% ^ algt_sat_sm.jpg |
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 propWhite=0
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 @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 @rem See also algTile.bat @rem @rem Updated: @rem 27-July-2022 for IM v7. @rem @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 ^ %OUTFILE% 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 @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 @rem Updated: @rem 27-July-2022 for IM v7. @rem @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 ) set PART_COM= 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 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 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.