﻿

# Composite compositions

We can manipulate and combine compositions for new operations such as "equals" and "greater than".

Create two source files for demonstration:

 ```%IMG7%magick -size 100x100 gradient: ^ -write cc_testA.png ^ -rotate 90 ^ cc_testB.png```

## Comparisons

We can implement the six comparison operators. Pixels are white where the condition is true; black where it is false.

 Equals, A = B, A ≡ B ```%IMG7%magick ^ cc_testA.png cc_testB.png ^ -compose Difference -composite ^ -fill White +opaque Black ^ -negate ^ cc_eq.png``` Not equals, A != B, A ≠ B ```%IMG7%magick ^ cc_testA.png cc_testB.png ^ -compose Difference -composite ^ -fill White +opaque Black ^ cc_neq.png``` Less than or equal, A <= B, A ≤ B ```%IMG7%magick ^ cc_testA.png cc_testB.png ^ -compose MinusSrc -composite ^ -fill White +opaque Black ^ cc_leq.png``` Greater than or equal, A >= B, A ≥ B ```%IMG7%magick ^ cc_testA.png cc_testB.png ^ -compose MinusDst -composite ^ -fill White +opaque Black ^ cc_geq.png``` Greater than, A > B ```%IMG7%magick ^ cc_testA.png cc_testB.png ^ -compose MinusSrc -composite ^ -fill White +opaque Black ^ -negate ^ cc_gtr.png``` Less than, A < B ```%IMG7%magick ^ cc_testA.png cc_testB.png ^ -compose MinusDst -composite ^ -fill White +opaque Black ^ -negate ^ cc_lss.png``` A < B ? black : A A ≥ B ? black : B ```%IMG7%magick ^ cc_testA.png ^ cc_testB.png ^ ( -clone 0-1 ^ -compose MinusDst -composite ^ -fill White +opaque Black ^ -write mpr:MASK ^ -negate ^ -clone 0 ^ -compose Darken -composite ^ -write cc_qa.png ^ +delete ^ ) ^ -delete 0 ^ mpr:MASK ^ -compose Darken -composite ^ cc_qb.png``` A < B ? black : A A ≥ B ? black : B

## Min and max

We can use these as masks to implement minimum and maximum.

 Minimum, min (A, B) ```%IMG7%magick ^ cc_testA.png cc_testB.png ^ ( -clone 0-1 ^ -compose MinusSrc -composite ^ -fill White +opaque Black ^ ) ^ -compose Over -composite ^ cc_min.png``` Maximum, max (A, B) ```%IMG7%magick ^ cc_testA.png cc_testB.png ^ ( -clone 0-1 ^ -compose MinusDst -composite ^ -fill White +opaque Black ^ ) ^ -compose Over -composite ^ cc_max.png```

However, it is generally more convenient and faster to use "-evaluate-sequence":

 Minimum, min (A, B) ```%IMG7%magick ^ cc_testA.png cc_testB.png ^ -evaluate-sequence Min ^ cc_emin.png``` Maximum, max (A, B) ```%IMG7%magick ^ cc_testA.png cc_testB.png ^ -evaluate-sequence Max ^ cc_emax.png```

## Fuzzy comparisons

These all use the idiom "-fill White +opaque Black", which turns all non-black pixls to white. This is a threshold. By replacing this with an explicit "-threshold", we implement some fuzzy comparisons.

 Fuzzy equals, A ≈ B, within plus or minus 20%. ```%IMG7%magick ^ cc_testA.png cc_testB.png ^ -compose Difference -composite ^ -threshold 20%% ^ -negate ^ cc_fzeq.png``` Fuzzy not equals, A != B, A ≠ B ```%IMG7%magick ^ cc_testA.png cc_testB.png ^ -compose Difference -composite ^ -threshold 20%% ^ cc_fzneq.png``` Fuzzy greater than, A > B ```%IMG7%magick ^ cc_testA.png cc_testB.png ^ -compose MinusSrc -composite ^ -threshold 20%% ^ -negate ^ cc_fzgtr.png``` Fuzzy less than, A < B ```%IMG7%magick ^ cc_testA.png cc_testB.png ^ -compose MinusDst -composite ^ -threshold 20%% ^ -negate ^ cc_fzlss.png```

## Biased subtract

Each source varies from zero to quantum. Hence a simple subtraction varies from -quantum to +quantum, which is awkward. Instead, we can divide by 2, subtract, and add 0.5 of quantum. This gives mid grey where the inputs are equal, white where Dst-Src is the maximum possible, and black where Dst-Src is the minimum possible.

If the four arguments are A, B, C and D then the result is A*Src*Dst + B*Src + C*Dst + D

 Biased subtract: result = (testA - testB) / 2 + 0.5 ```%IMG7%magick ^ cc_testA.png cc_testB.png ^ -compose Mathematics -define compose:args=0,-0.5,0.5,0.5 -composite ^ cc_math1.png``` Biased subtract: result = (testB - testA) / 2 + 0.5 ```%IMG7%magick ^ cc_testA.png cc_testB.png ^ -compose Mathematics -define compose:args=0,0.5,-0.5,0.5 -composite ^ cc_math2.png``` Square of difference: result = (testA - testB)2 ```%IMG7%magick ^ cc_testA.png cc_testB.png ^ -compose Difference -composite ^ -evaluate Pow 2 ^ cc_sqdiff.png```

Biased divide by 2.

 Biased divide by 2: result = testA/2 - 0.5/2 + 0.5 = testA/2 + 0.25 ```%IMG7%magick ^ cc_testA.png ^ -evaluate Divide 2 ^ -evaluate AddModulus 25%% ^ cc_div2.png``` More directly, using polynomial: ```%IMG7%magick ^ cc_testA.png ^ -function polynomial 0.5,0.25 ^ cc_div2b.png```

## Fan compose

For the Islands page, I wanted a compose that operated on gray images with the following result:

1. Where the destination (first input) is white the result must be white.
2. Where the source (second input) is black the result must be black.
3. The result is sensibly grey elsewhere; in particular the result should be 50% where source = 1-dest.
4. The diagonal where src=dest must be linear, so result=src=dest.
5. Lines parallel to this must also be linear between black and white.

The requirements contradict each other at pixels where dest=white and source=black. Should the resulting pixels be black, white or 50% grey? I don't really care.

This formula satisfies the conditions:

 result = src / (src-dest+1)

We can check this complies with the conditions:

1. At dest=1, result = src/src = 1. Correct.
2. At src=0, result = 0/(1-dest) = 0. Correct.
3. At src = (1-dest), result = src / (2*src) = 0.5. Correct.
4. At src=dest, result = src/1 = src. Correct.

At dest=1 and src=0, result = 0/0. Fair enough.

 ```%IMG7%magick ^ cc_testA.png ^ cc_testB.png ^ ( -clone 1 -evaluate Divide 2 ) ^ ( -clone 0-1 ^ -compose Mathematics ^ -define compose:args=0,0.5,-0.5,0.5 ^ -composite ^ ) ^ -delete 0-1 ^ -compose DivideSrc -composite ^ cc_fancomp4.png ```

This is implemented as a script: fanComp.bat. At dest=1 and src=0, the result is black.

Instead of the operations shown in the script, (one evaluate and two composites) we can use this:

`-fx "v/(v-u+1)"`

A variation of this mirrors the top-left half in the bottom-right. We might call this a "dual-axis fan" composition. The inputs must be square for "-transverse".

 ```%IMG7%magick ^ cc_fancomp4.png ^ ( +clone -transverse ) ^ cc_geq.png ^ -compose Over -composite ^ cc_fancomp5.png ```

Writing this out, from the basic gradients:

 ```%IMG7%magick ^ cc_testA.png ^ cc_testB.png ^ ( -clone 1 -evaluate Divide 2 ) ^ ( -clone 0-1 ^ -compose Mathematics ^ -define compose:args=0,0.5,-0.5,0.5 ^ -composite ^ ) ^ ( -clone 0-1 ^ -compose MinusDst -composite ^ -fill White +opaque Black ^ ) ^ -delete 0-1 ^ ( -clone 0-1 ^ -compose DivideSrc -composite ^ ) ^ -delete 0-1 ^ ( -clone 1 -transverse ) ^ -swap 0,1 ^ -swap 1,2 ^ -compose Over -composite ^ cc_fancomp6.png ```

The script mkFanComp2A.bat makes a dual-axis fan graduated image that is black where x==0 or y==0, and white where x=100% or y=100%.

 `call %PICTBAT%mkFanComp2A 300 200 cc_fc2a.png`

### ASIDE: a painfully long-winded method for the same result

A formula for a composition that satisfies the first three conditions (except where dest=white and source=black) is:

 Where src < (1-dest): result = 0.5*src/(1-dest) Otherwise: result = 1 - 0.5*(1-dest)/src

Implementing this formula takes nine operations.

At dest=white and source=black, the script fanCompX.bat makes the result white.

 ```call %PICTBAT%fanCompX ^ cc_testA.png ^ cc_testB.png ^ cc_fancomp.png ```

Then I realised I wanted more conditions: the diagonal, from bottom-left to top-right, should be linear. Better still, all lines parallel to this should be linear. Extract the pixels from the diagonal:

 ```%IMG7%magick ^ -size 1x256 gradient:white-black -rotate 90 ^ cc_wb.png call %PICTBAT%fanCompX ^ cc_wb.png ^ cc_wb.png ^ cc_fc_diag.png call %PICTBAT%graph1d cc_fc_diag.png``` The diagonal is not currently linear. The curve is close to a "sigmoidal-contrast 5.45806x50%". Try a line parallel to the diagonal: ```%IMG7%magick ^ -size 1x256 gradient:white-gray(80%%) -rotate 90 ^ cc_wb2.png %IMG7%magick ^ -size 1x256 gradient:gray(20%%)-black -rotate 90 ^ cc_bw2.png call %PICTBAT%fanCompX ^ cc_wb2.png ^ cc_bw2.png ^ cc_fc_diag2.png call %PICTBAT%graph1d cc_fc_diag2.png```

Are these two curves similar?

```%IMG7%magick compare -metric RMSE cc_fc_diag.png cc_fc_diag2.png NULL:

cmd /c exit /B 0```
`74.3983 (0.00113525)`

Yes, they are similar.

We can treat cc_fc_diag.png as a clut. Invert it, and apply to the composition.

 ```call %PICTBAT%invClut cc_fc_diag.png cc_fc_diag_icl.png call %PICTBAT%graph1d cc_fc_diag_icl.png %IMG7%magick ^ cc_fancomp.png ^ cc_fc_diag_icl.png ^ -clut ^ cc_fancomp2.png```

We incorporate this adjustment in fanCompX.bat, setting the fourth parameter to "2" to adjust by clut.

 ```call %PICTBAT%fanCompX ^ cc_testA.png ^ cc_testB.png ^ cc_fancomp2a.png ^ 2```

We can test the adjustment:

 ```%IMG7%magick ^ -size 1x256 gradient:white-black -rotate 90 ^ cc_wb.png call %PICTBAT%fanCompX ^ cc_wb.png ^ cc_wb.png ^ cc_fc_diagA.png ^ 2 call %PICTBAT%graph1d cc_fc_diagA.png``` The diagonal for this adjusted composite looks linear. Check it against a known linear gradient: ```%IMG7%magick compare -metric RMSE cc_wb.png cc_fc_diagA.png NULL: cmd /c exit /B 0``` `4083.45 (0.0623094)` Yes, the adjustment is good.

Compare this roundabout method with the direct one:

```%IMG7%magick compare -metric RMSE cc_fancomp4.png cc_fancomp2a.png NULL:

cmd /c exit /B 0```
`4114.38 (0.0627814)`

## Scripts

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

### fanComp.bat

```rem A compose like a fan.
@rem
@rem result = src / (src-dest+1)
@rem
@rem Updated:
@rem   13-August-2022 for IM v7.
@rem

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

@%IMG7%magick ^
%1 ^
%2 ^
( -clone 1 -evaluate Divide 2 ) ^
( -clone 0-1 ^
-compose Mathematics ^
-define compose:args=0,0.5,-0.5,0.5 ^
-composite ^
) ^
-delete 0-1 ^
-compose DivideSrc -composite ^
%3

if ERRORLEVEL 1 exit /B 1

exit /B 0```

### fanCompX.bat

```rem A compose like a fan.
@rem
@rem Bottom-left: 0.5*y/(1-x)
@rem Top-right: 1 - 0.5*(1-x)/y
@rem Result is black where %2 is black;
@rem white where %1 is white.
@rem Optional %3 is:
@rem   0: no adjustment
@rem   1: adjust by sigmoidal-contrat
@rem   2: adjust by clut
@rem
@rem Updated:
@rem   21-August-2022 Upgraded for IM v7.
@rem

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

@setlocal enabledelayedexpansion

rem @call echoOffSave

set FAN_DIAG=%TEMP%\fanDiag.miff
set FAN_CLUT=%TEMP%\fanDiag_icl.miff
set fcTEMP=%TEMP%\fc_wb.miff

) else if "%ADJ%"=="2" (

if not exist %FAN_CLUT% (

%IMG7%magick ^
-size 1x10000 gradient:white-black -rotate 90 ^
%fcTEMP%

call %PICTBAT%fanComp ^
%fcTEMP% ^
%fcTEMP% ^
%FAN_DIAG%

del %fcTEMP%

call %PICTBAT%invClut %FAN_DIAG% %FAN_CLUT%

del %FAN_DIAG%
)

) else (
)

%IMG7%magick ^
%1 ^
%2 ^
( -clone 0-1 ^
( -clone 0 -negate ) ^
( -clone 1 -evaluate Divide 2 ) ^
-delete 0-1 ^
-compose DivideDst -composite ^
) ^
( -clone 0-1 ^
( -clone 0 -negate -evaluate Divide 2 ) ^
-delete 0 ^
-compose DivideDst -composite ^
-negate ^
) ^
-delete 0-1 ^
( -clone 1 -threshold 50%% ) ^
-compose Over -composite ^
%3

@call echoRestore

@endlocal```

### mkFanComp2A.bat

```rem Make a 2-axis fan composition image.
rem %1,%2 Required width and height.
rem %3 output filename.
@rem
@rem Updated:
@rem   21-August-2022 Upgraded for IM v7.
@rem

set mkfDim=%1
if %mkfDim% LSS %2 set mkfDim=%2

%IMG7%magick ^
-size %mkfDim%x%mkfDim% ^
( gradient: -flip ) ^
( gradient: -rotate 90 ) ^
( -clone 1 -evaluate Divide 2 ) ^
( -clone 0-1 ^
-compose Mathematics ^
-define compose:args=0,0.5,-0.5,0.5 ^
-composite ^
) ^
( -clone 0-1 ^
-compose MinusDst -composite ^
-fill White +opaque Black ^
) ^
-delete 0-1 ^
( -clone 0-1 ^
-compose DivideSrc -composite ^
) ^
-delete 0-1 ^
( -clone 1 -transpose ) ^
-swap 0,1 ^
-swap 1,2 ^
-compose Over -composite ^
-resize "%1x%2^!" ^
%3```

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

`%IMG7%magick -version`
```Version: ImageMagick 7.1.1-20 Q16-HDRI x86 98bb1d4:20231008 https://imagemagick.org
Copyright: (C) 1999 ImageMagick Studio LLC
Features: Cipher DPC HDRI OpenCL OpenMP(2.0)
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 (193532217)```

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

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.1 12-June-2014.

Page created 01-Mar-2024 15:19:21.