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 |
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 |
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 |
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 |
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 %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 |
For the Islands page, I wanted a compose that operated on gray images with the following result:
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:
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 |
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.
|
|
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.
%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)
For convenience, .bat scripts are also available in a single zip file. See Zipped BAT files.
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
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 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% ( %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% ) set sADJ=%FAN_CLUT% -clut ) else ( set sADJ= ) %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 ^ %sADJ% ^ %3 @call echoRestore @endlocal
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 License: https://imagemagick.org/script/license.php 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.
Copyright © 2024 Alan Gibson.