snibgo's ImageMagick pages

Composite compositions

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

Create two source files for demonstration:

%IM%convert -size 100x100 gradient: ^
  -write cc_testA.png ^
  -rotate 90 ^
  cc_testB.png
cc_testA.png 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

%IM%convert ^
  cc_testA.png cc_testB.png ^
  -compose Difference -composite ^
  -fill White +opaque Black ^
  -negate ^
  cc_eq.png
cc_eq.png

Not equals, A != B, A ≠ B

%IM%convert ^
  cc_testA.png cc_testB.png ^
  -compose Difference -composite ^
  -fill White +opaque Black ^
  cc_neq.png
cc_neq.png

Less than or equal, A <= B, A ≤ B

%IM%convert ^
  cc_testA.png cc_testB.png ^
  -compose MinusSrc -composite ^
  -fill White +opaque Black ^
  cc_leq.png
cc_leq.png

Greater than or equal, A >= B, A ≥ B

%IM%convert ^
  cc_testA.png cc_testB.png ^
  -compose MinusDst -composite ^
  -fill White +opaque Black ^
  cc_geq.png
cc_geq.png

Greater than, A > B

%IM%convert ^
  cc_testA.png cc_testB.png ^
  -compose MinusSrc -composite ^
  -fill White +opaque Black ^
  -negate ^
  cc_gtr.png
cc_gtr.png

Less than, A < B

%IM%convert ^
  cc_testA.png cc_testB.png ^
  -compose MinusDst -composite ^
  -fill White +opaque Black ^
  -negate ^
  cc_lss.png
cc_lss.png

A < B ? black : A

A ≥ B ? black : B

%IM%convert ^
  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

cc_qa.png

A ≥ B ? black : B

cc_qb.png

Min and max

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

Minimum, min (A, B)

%IM%convert ^
  cc_testA.png cc_testB.png ^
  ( -clone 0-1 ^
    -compose MinusSrc -composite ^
    -fill White +opaque Black ^
  ) ^
  -compose Over -composite ^
  cc_min.png
cc_min.png

Maximum, max (A, B)

%IM%convert ^
  cc_testA.png cc_testB.png ^
  ( -clone 0-1 ^
    -compose MinusDst -composite ^
    -fill White +opaque Black ^
  ) ^
  -compose Over -composite ^
  cc_max.png
cc_max.png

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

Minimum, min (A, B)

%IM%convert ^
  cc_testA.png cc_testB.png ^
  -evaluate-sequence Min ^
  cc_emin.png
cc_emin.png

Maximum, max (A, B)

%IM%convert ^
  cc_testA.png cc_testB.png ^
  -evaluate-sequence Max ^
  cc_emax.png
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%.

%IM%convert ^
  cc_testA.png cc_testB.png ^
  -compose Difference -composite ^
  -threshold 20%% ^
  -negate ^
  cc_fzeq.png
cc_fzeq.png

Fuzzy not equals, A != B, A ≠ B

%IM%convert ^
  cc_testA.png cc_testB.png ^
  -compose Difference -composite ^
  -threshold 20%% ^
  cc_fzneq.png
cc_fzneq.png

Fuzzy greater than, A > B

%IM%convert ^
  cc_testA.png cc_testB.png ^
  -compose MinusSrc -composite ^
  -threshold 20%% ^
  -negate ^
  cc_fzgtr.png
cc_fzgtr.png

Fuzzy less than, A < B

%IM%convert ^
  cc_testA.png cc_testB.png ^
  -compose MinusDst -composite ^
  -threshold 20%% ^
  -negate ^
  cc_fzlss.png
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

See Usage: compose mathematics.

Biased subtract: result = (testA - testB) / 2 + 0.5

%IM%convert ^
  cc_testA.png cc_testB.png ^
  -compose Mathematics -define compose:args=0,-0.5,0.5,0.5 -composite ^
  cc_math1.png
cc_math1.png

Biased subtract: result = (testB - testA) / 2 + 0.5

%IM%convert ^
  cc_testA.png cc_testB.png ^
  -compose Mathematics -define compose:args=0,0.5,-0.5,0.5 -composite ^
  cc_math2.png
cc_math2.png

Square of difference: result = (testA - testB)2

%IM%convert ^
  cc_testA.png cc_testB.png ^
  -compose Difference -composite ^
  -evaluate Pow 2 ^
  cc_sqdiff.png
cc_sqdiff.png

Biased divide by 2.

Biased divide by 2: result = testA/2 - 0.5/2 + 0.5 = testA/2 + 0.25

%IM%convert ^
  cc_testA.png ^
  -evaluate Divide 2 ^
  -evaluate AddModulus 25%% ^
  cc_div2.png
cc_div2.png

More directly, using polynomial:

%IM%convert ^
  cc_testA.png ^
  -function polynomial 0.5,0.25 ^
  cc_div2b.png
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.

%IM%convert ^
  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 
cc_fancomp4.png

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

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".

%IM%convert ^
  cc_fancomp4.png ^
  ( +clone -transverse ) ^
  cc_geq.png ^
  -compose Over -composite ^
  cc_fancomp5.png 
cc_fancomp5.png

Writing this out, from the basic gradients:

%IM%convert ^
  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 
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
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 
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:

%IM%convert ^
  -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%".

cc_fc_diag_g1d.png

Try a line parallel to the diagonal:

%IM%convert ^
  -size 1x256 gradient:white-gray(80%%) -rotate 90 ^
  cc_wb2.png

%IM%convert ^
  -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
cc_fc_diag2_g1d.png

Are these two curves similar?

%IM%compare -metric RMSE cc_fc_diag.png cc_fc_diag2.png NULL: 

cmd /c exit /B 0
74.3708 (0.00113483)

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

%IM%convert ^
  cc_fancomp.png ^
  cc_fc_diag_icl.png ^
  -clut ^
  cc_fancomp2.png
cc_fc_diag_icl_g1d.png 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
cc_fancomp2a.png

We can test the adjustment:

%IM%convert ^
  -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:

%IM%compare -metric RMSE cc_wb.png cc_fc_diagA.png NULL: 

cmd /c exit /B 0
4085.34 (0.0623383)

Yes, the adjustment is good.

cc_fc_diagA_g1d.png

Compare this roundabout method with the direct one:

%IM%compare -metric RMSE cc_fancomp4.png cc_fancomp2a.png NULL: 

cmd /c exit /B 0
4120.08 (0.0628684)

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)

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

@%IM%convert ^
  %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

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


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

@setlocal enabledelayedexpansion

rem @call echoOffSave


set ADJ=%4

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

if "%ADJ%"=="1" (
  set sADJ=+sigmoidal-contrast "5.45806x50%%"
) else if "%ADJ%"=="2" (

  if not exist %FAN_CLUT% (

    %IM%convert ^
      -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%
  )

  set sADJ=%FAN_CLUT% -clut
) else (
  set sADJ=
)

%IM%convert ^
  %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 ^
  %sADJ% ^
  %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.

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

%IM%convert ^
  -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:

%IM%identify -version
Version: ImageMagick 6.9.5-3 Q16 x86 2016-07-22 http://www.imagemagick.org
Copyright: Copyright (C) 1999-2015 ImageMagick Studio LLC
License: http://www.imagemagick.org/script/license.php
Visual C++: 180040629
Features: Cipher DPC Modules OpenMP 
Delegates (built-in): bzlib cairo flif freetype jng jp2 jpeg lcms lqr openexr pangocairo png ps rsvg tiff webp xml zlib

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 30-Sep-2016 00:58:12.

Copyright © 2016 Alan Gibson.