﻿﻿

# Setting the mean

We can tweak an image to match the brightness (and colour) of another.

## Sample inputs

As example input images, we use:

 `set SRCA=dpt_lvs_sm.jpg` `set SRCB=toes_holed.png` This image is mostly transparent. ## The methods

We want to change an image so the means of the colour channels become the desired results. In the general case we have three desired means, one per channel, so we will apply a process to each channel. There are many possible methods for this, but four simple methods are:

1. Multiply values in each channel by a desired gain.
```vOut = vIn * gain
where gain = new_mean / old_mean```
Zero values will remain at zero, with the greatest changes occuring at high values.

If this process increases the mean of an image, its contrast will also increase.

2. Add a desired bias to values in each channel.
```vOut = vIn + bias
where bias = new_mean - old_mean```

If this process increases the mean of an image, highlights and shadows will increase by the same amount.

3. Raise values to a desired power.
```vOut = vIn ^ pow
where pow = log(new_mean) / log(old_mean)```

Zero values will remain at zero, 100% will remain at 100%, with the greatest changes occuring at middle values.

If this process increases the mean of an image, its shadows will increase in contrast while highlights will decrease.

4. Apply a process I call negative gain. Like gain, this changes the slope of the transfer curve, but instead of rotating it around 0%, the rotation is at 100%. We could implement this by negating the image, multiplying by the gain, then negating again. However, the script uses "-function polynomial" to achieve the same result.
`vOut = 1 - [ (1-vIn) * gain ]`

... or ...

```vOut = vIn * gain + bias
where gain = (new_mean - 1.0) / (old_mean - 1.0)
and bias = 1.0 - gain```

100% values will remain at 100%, with the greatest changes occuring at low values.

If this process increases the mean of an image, its contrast will decrease.

The scripts setMeanGain.bat, setMeanBias.bat, setMeanPow.bat and setMeanNegGain.bat implement these four methods. They take parameters:

• input file
• output file
• either one number (the new mean for all three channels) or three numbers (the new means for each channel), typically in the range 0.0 to 1.0.

Note that the gain, bias and negative gain methods can create values beyond 0 to 100%. If HDRI is not used, these will be clipped to the limits. Clipping may cause the new means not to be as predicted.

The scripts can fail (gracefully) if the mean of any channel is zero or 100%, from division by zero. I use the scripts for ordinary photos where this doesn't occur. The scripts could be modified to handle graphics images that do average zero or 100%, at the expense of complexity and performance.

We change channels independently, so hues will generally shift. Indeed, these techniques can be considered as methods to change colour balance.

The gain, bias and negative gain methods are all simplifications of the Gain and bias method.

## Common standard

We might set any number of images to a common standard.

For example, we use the three methods to set the leaves image to a mean of 0.5 in all three channels.

 ```call %PICTBAT%setMeanGain ^ %SRCA% sm_cs1.png 0.5``` ```call %PICTBAT%setMeanBias ^ %SRCA% sm_cs2.png 0.5``` ```call %PICTBAT%setMeanPow ^ %SRCA% sm_cs3.png 0.5``` ```call %PICTBAT%setMeanNegGain ^ %SRCA% sm_cs4.png 0.5``` For the aesthetics of ordinary photographs we usually want shadows to stay dark, which happens in the gain and power methods. The bias and negative gain methods tend to brighten shadows so are less useful.

## Reference image

The script setMeanImg.bat finds the mean values of a reference image (%2), and calls one of the above scripts with those values. Hence, it transforms an image to look more like another image.

For example, we change the leaves image to look more like the grass.

 ```call %PICTBAT%setMeanImg ^ %SRCA% %SRCB% sm_ri1.png gain``` ```call %PICTBAT%setMeanImg ^ %SRCA% %SRCB% sm_ri2.png bias``` ```call %PICTBAT%setMeanImg ^ %SRCA% %SRCB% sm_ri3.png pow``` ```call %PICTBAT%setMeanImg ^ %SRCA% %SRCB% sm_ri4.png neggain``` ## Future

As noted above, some of these methods can cause values to go beyond 0 to 100%, and these will be clipped. The methods could use minima and maxima values from the image to determine the transformation closest to the desired mean that doesn't quite cause clipping.

The scripts setMean*.bat take statistics from an image, then modify that same image. Hence, they can't be used directly to adjust an entire image so that only a portion becomes the desired mean.

## Scripts

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

### setMeanGain.bat

```rem From image %1, creates output %2 setting the mean by the "gain" method
rem (multiplying channel values).
@rem
@rem New mean is either %3 only (all 3 channels)
@rem or %3, %4 and %5 (each channel separately).
@rem New means should generally be in range 0.0 to 1.0.
@rem
@rem If any channel has mean of zero, this will fail with division by zero.
@rem
@rem Updated:
@rem   4-August-2022 for IM v7.
@rem

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

@setlocal enabledelayedexpansion

@call echoOffSave

call %PICTBAT%setInOut %1 smg

set OUTF=%2
if "%OUTF%"=="." set OUTF=
if "%OUTF%"=="" set OUTF=%OUTFILE%
set OUTFILE=%OUTF%

set new_mn_R=%3
set new_mn_G=%4
set new_mn_B=%5

if "%new_mn_R%"=="." set new_mn_R=0.5
if "%new_mn_G%"=="." set new_mn_G=0.5
if "%new_mn_B%"=="." set new_mn_B=0.5

if "%new_mn_R%"=="" set new_mn_R=0.5
if "%new_mn_G%"=="" set new_mn_G=%new_mn_R%
if "%new_mn_B%"=="" set new_mn_B=%new_mn_R%

echo new_mn_R=%new_mn_R% new_mn_G=%new_mn_G% new_mn_B=%new_mn_B%

set sFMT=^
gn_R=%%[fx:%new_mn_R%/p{0.0}.r]\n^
gn_G=%%[fx:%new_mn_G%/p{0.0}.g]\n^
gn_B=%%[fx:%new_mn_B%/p{0.0}.b]

set gn_R=
set gn_G=
set gn_B=

for /F "usebackq" %%L in (`%IMG7%magick ^
%INFILE% ^
-precision 19 ^
-scale "1x1^!" ^
-format "%sFMT%" ^
info:`) do set %%L

if "%gn_R%"=="" exit /B 1
if "%gn_G%"=="" exit /B 1
if "%gn_B%"=="" exit /B 1

%IMG7%magick ^
%INFILE% ^
-channel R -evaluate Multiply %gn_R% ^
-channel G -evaluate Multiply %gn_G% ^
-channel B -evaluate Multiply %gn_B% ^
+channel ^
%OUTFILE%

if ERRORLEVEL 1 exit /B 1

call echoRestore

endlocal & set smgOUTFILE=%OUTFILE%
```

### setMeanBias.bat

```rem From image %1, creates output %2 setting the mean by the "bias" method
@rem
@rem New mean is either %3 only (all 3 channels)
@rem or %3, %4 and %5 (each channel separately).
@rem New means should generally be in range 0.0 to 1.0.
@rem
@rem Updated:
@rem   4-August-2022 for IM v7.
@rem

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

@setlocal enabledelayedexpansion

@call echoOffSave

call %PICTBAT%setInOut %1 sma

set OUTF=%2
if "%OUTF%"=="." set OUTF=
if "%OUTF%"=="" set OUTF=%OUTFILE%
set OUTFILE=%OUTF%

set new_mn_R=%3
set new_mn_G=%4
set new_mn_B=%5

if "%new_mn_R%"=="." set new_mn_R=0.5
if "%new_mn_G%"=="." set new_mn_G=0.5
if "%new_mn_B%"=="." set new_mn_B=0.5

if "%new_mn_R%"=="" set new_mn_R=0.5
if "%new_mn_G%"=="" set new_mn_G=%new_mn_R%
if "%new_mn_B%"=="" set new_mn_B=%new_mn_R%

echo new_mn_R=%new_mn_R% new_mn_G=%new_mn_G% new_mn_B=%new_mn_B%

set sFMT=^
bias_R=%%[fx:100*(%new_mn_R%-p{0.0}.r)]\n^
bias_G=%%[fx:100*(%new_mn_G%-p{0.0}.g)]\n^
bias_B=%%[fx:100*(%new_mn_B%-p{0.0}.b)]

for /F "usebackq" %%L in (`%IMG7%magick ^
%INFILE% ^
-precision 19 ^
-scale "1x1^!" ^
-format "%sFMT%" ^
info:`) do set %%L

%IMG7%magick ^
%INFILE% ^
-channel R -evaluate Add %bias_R%%% ^
-channel G -evaluate Add %bias_G%%% ^
-channel B -evaluate Add %bias_B%%% ^
+channel ^
%OUTFILE%

call echoRestore

endlocal & set smaOUTFILE=%OUTFILE%```

### setMeanPow.bat

```rem From image %1, creates output %2 setting the mean by the "power" method
rem (raising channel values be a power).
@rem
@rem New mean is either %3 only (all 3 channels)
@rem or %3, %4 and %5 (each channel separately).
@rem New means should generally be in range 0.0 to 1.0.
@rem
@rem If any channel has mean of zero, this will fail with division by zero.
@rem
@rem Updated:
@rem   4-August-2022 for IM v7.
@rem

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

@setlocal enabledelayedexpansion

@call echoOffSave

call %PICTBAT%setInOut %1 smp

set OUTF=%2
if "%OUTF%"=="." set OUTF=
if "%OUTF%"=="" set OUTF=%OUTFILE%
set OUTFILE=%OUTF%

set new_mn_R=%3
set new_mn_G=%4
set new_mn_B=%5

if "%new_mn_R%"=="." set new_mn_R=0.5
if "%new_mn_G%"=="." set new_mn_G=0.5
if "%new_mn_B%"=="." set new_mn_B=0.5

if "%new_mn_R%"=="" set new_mn_R=0.5
if "%new_mn_G%"=="" set new_mn_G=%new_mn_R%
if "%new_mn_B%"=="" set new_mn_B=%new_mn_R%

set sFMT=^
pow_R=%%[fx:log(%new_mn_R%)/log(p{0.0}.r)]\n^
pow_G=%%[fx:log(%new_mn_G%)/log(p{0.0}.g)]\n^
pow_B=%%[fx:log(%new_mn_B%)/log(p{0.0}.b)]

set pow_R=
set pow_G=
set pow_B=

for /F "usebackq" %%L in (`%IMG7%magick ^
%INFILE% ^
-precision 19 ^
-scale "1x1^!" ^
-format "%sFMT%" ^
info:`) do set %%L

if "%pow_R%"=="" exit /B 1
if "%pow_G%"=="" exit /B 1
if "%pow_B%"=="" exit /B 1

%IMG7%magick ^
%INFILE% ^
-channel R -evaluate Pow %pow_R% ^
-channel G -evaluate Pow %pow_G% ^
-channel B -evaluate Pow %pow_B% ^
+channel ^
%OUTFILE%

if ERRORLEVEL 1 exit /B 1

call echoRestore

endlocal & set smgOUTFILE=%OUTFILE%
```

### setMeanNegGain.bat

```rem From image %1, creates output %2 setting the mean by the "negative gain" method
@rem
@rem New mean is either %3 only (all 3 channels)
@rem or %3, %4 and %5 (each channel separately).
@rem New means should generally be in range 0.0 to 1.0.
@rem
@rem If any channel has mean of 100%, this will fail with division by zero.
@rem
@rem Updated:
@rem   4-August-2022 for IM v7.
@rem

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

@setlocal enabledelayedexpansion

@call echoOffSave

call %PICTBAT%setInOut %1 smng

set OUTF=%2
if "%OUTF%"=="." set OUTF=
if "%OUTF%"=="" set OUTF=%OUTFILE%
set OUTFILE=%OUTF%

set new_mn_R=%3
set new_mn_G=%4
set new_mn_B=%5

if "%new_mn_R%"=="." set new_mn_R=0.5
if "%new_mn_G%"=="." set new_mn_G=0.5
if "%new_mn_B%"=="." set new_mn_B=0.5

if "%new_mn_R%"=="" set new_mn_R=0.5
if "%new_mn_G%"=="" set new_mn_G=%new_mn_R%
if "%new_mn_B%"=="" set new_mn_B=%new_mn_R%

echo new_mn_R=%new_mn_R% new_mn_G=%new_mn_G% new_mn_B=%new_mn_B%

set sFMT=^
gn_R=%%[fx:(%new_mn_R%-1)/(p{0.0}.r-1)]\n^
gn_G=%%[fx:(%new_mn_G%-1)/(p{0.0}.g-1)]\n^
gn_B=%%[fx:(%new_mn_B%-1)/(p{0.0}.b-1)]

set gn_R=
set gn_G=
set gn_B=

for /F "usebackq" %%L in (`%IMG7%magick ^
%INFILE% ^
-precision 19 ^
-scale "1x1^!" ^
-format "%sFMT%" ^
info:`) do set %%L

if "%gn_R%"=="" exit /B 1
if "%gn_G%"=="" exit /B 1
if "%gn_B%"=="" exit /B 1

for /F "usebackq" %%L in (`%IMG7%magick identify ^
-precision 19 ^
-format "bs_R=%%[fx:1-%gn_R%]\nbs_G=%%[fx:1-%gn_G%]\nbs_B=%%[fx:1-%gn_B%]" ^
xc:`) do set %%L

%IMG7%magick ^
%INFILE% ^
-channel R ^
-function Polynomial %gn_R%,%bs_R% ^
-channel G ^
-function Polynomial %gn_G%,%bs_G% ^
-channel B ^
-function Polynomial %gn_B%,%bs_B% ^
+channel ^
%OUTFILE%

if ERRORLEVEL 1 exit /B 1

call echoRestore

endlocal & set smngOUTFILE=%OUTFILE%```

### setMeanImg.bat

```rem From image %1 and reference image %2.
rem changes channel means of %1 to match %2,
rem writing to output %3.
rem %4 is optional keyword for method: gain or bias or pow or neggain (and case).
@rem
@rem Updated:
@rem   4-August-2022 for IM v7.
@rem

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

@setlocal enabledelayedexpansion

@call echoOffSave

call %PICTBAT%setInOut %1 smi

set REF_IMG=%2

set OUTF=%3
if "%OUTF%"=="." set OUTF=
if "%OUTF%"=="" set OUTF=%OUTFILE%
set OUTFILE=%OUTF%

set METH=%4
if "%METH%"=="." set METH=
if "%METH%"=="" set METH=gain

set mn_R=
set sFMT=^
mn_R=%%[fx:p{0.0}.r]\n^
mn_G=%%[fx:p{0.0}.g]\n^
mn_B=%%[fx:p{0.0}.b]

for /F "usebackq" %%L in (`%IMG7%magick ^
%REF_IMG% ^
-precision 19 ^
-scale "1x1^!" ^
-format "%sFMT%" ^
info:`) do set %%L
if "%mn_R%"=="" exit /B 1

if /I "%METH%"=="gain" (
call %PICTBAT%setMeanGain %INFILE% %OUTFILE% %mn_R% %mn_G% %mn_B%
if ERRORLEVEL 1 exit /B 1
) else if /I "%METH%"=="bias" (
call %PICTBAT%setMeanBias %INFILE% %OUTFILE% %mn_R% %mn_G% %mn_B%
if ERRORLEVEL 1 exit /B 1
) else if /I "%METH%"=="pow" (
call %PICTBAT%setMeanPow %INFILE% %OUTFILE% %mn_R% %mn_G% %mn_B%
if ERRORLEVEL 1 exit /B 1
) else if /I "%METH%"=="neggain" (
call %PICTBAT%setMeanNegGain %INFILE% %OUTFILE% %mn_R% %mn_G% %mn_B%
if ERRORLEVEL 1 exit /B 1
) else (
exit /B 1
)

call echoRestore

endlocal & set smiOUTFILE=%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
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 setmean.h1. To re-create this web page, execute "procH1 setmean".

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.