snibgo's ImageMagick pages

New FX tests

Tests for a proposed faster "-fx".

This page shows black-box tests performed on a new version of "-fx", comparing results to an old version of "-fx".

The tests were performed on my Windows 10 laptop. The CPU (Intel Core i7-4700MQ @2.40GHz) has 4 cores, and can run 8 threads. The IM version was v7.1.0-20, Q32, with HDRI and OpenMP. Two builds were created, for the old and new "-fx", and installed in the directories "%FXOLD%" and "%FXNEW%".

These tests are not exhaustive. I welcome any additions.

This is a companion page to New FX.

Performance

The script testPerf.bat runs a magick command for the old fx, and repeats it for the new fx. The command runs a "-fx" operation for each of the input strings in testPerf.txt. There are about 35 input strings.

Input size, pixels Old "-fx" New "-fx2"
10x10 2 second 2 seconds
100x10 4 seconds 2 seconds
100x100 14 seconds 2 seconds
1000x100 99 seconds 4 seconds
1000x1000 ???? seconds 9 seconds

Compatibility and accuracy

To test compatiblity, we run the same "-fx" operation using the old and new fx code, and find the RMSE distortion between the two results. The distortion is on a nominal scale of zero to one. If it is very close to zero, we consider the test has passed.

For accuracy, the results were compared to hand-calculations.

On my computer, the tests are run with the new fx installed at %FXNEW%:

set FXNEW=%IM7DEVFX2%
set FXNEW=%IM7DEV2%

set FXNEW 
FXNEW=C:\cygwin64\home\Alan\imdevins711_10\bin\

As of 31-March-2023, I patch the file fx.c that is in directory %IM7DEV%.

set FXNEW=%IM7DEV%

set FXNEW 
FXNEW=C:\cygwin64\home\Alan\imdevins7114\bin\
call %IM7SRCFX%\snibgo\goodFxSyntax.bat

Tests with a single input image:

-fx input string distortion
'0.000023M' 0
'10 - 2 + 3' 0
'10 * 2 + 3' 0
'4 ^ 3 ^ 2' 0
'0.3 ^ 1.5 ^ 2' 0
'xx=1? xx:4' 0
'-epsilon' 0
'3-1 ? 4+5 : 6 ? 7 :2*3' 0
'yy = 3-1 ? 4+5 : 6 ? 7 :2*3' 0
'aa=56; yy = 3-3 ? 4+5 : 6 ? 2+aa+3 :2*3' 0
'23+(+1)' 0
'23+(-1)' 0
'xx=23;-xx' 0
'xx=23;-(xx+1)' 0
'xx=23;-xx+1' 0
'xx=23;xx-=2;xx' 0
'xx=23; yy=34; xx+yy' 0
'xx = yy = 2+3' 0
'if (1,2,3)' 0
'if (1,2,)' 0
'if (1,,3)' 0
'u<0.01 ? 1: u>0.99 ? 0: u' 0
'if (u<0.01, 1, if (u>0.99, 0, u))' 0
'mean' 0
'mean.r' 0
'u.mean' 0
'u.mean.r' 0
'#abd' 0
'hsl(10%,20%,30%)' 0
'u.saturation' 0
'10 < i < 12' 0
'(0.10 < i) && (i < 0.12)' 0
'gray47' 0
'r - gray47' 0
'1/2' 0
'(1.0/(1.0+exp(10.0*(0.5-u)))-0.006693)*1.0092503' 0
'Xi=i-w/2; Yj=j-h/2; 1.2*(0.5-hypot(Xi,Yj)/70.0)+0.5' 0
'for (prime=2, prime < 30, composite=0; for (nn=2, nn < (prime/2+1), if ((prime % nn) == 0, composite++, ); nn++); if (composite <= 0, debug(prime), ); prime++)' 0
'channel ()' 0
'channel (0.1, 0.2)' 0
'i==10&&j==10?3:2; u' 0
'(u>0.5 ? 0.123 : 0.456)' 0
'if (u>0.5, 0.123, 0.456)' 0
'1.01 - (u>0.5?0.123:0.456)' 0
'1.01 - if (u>0.5, 0.123, 0.456)' 0
'1.01 - (u>0.5?0.123:0.456) - 0.01' 0
'1.01 - if (u>0.5, 0.123, 0.456) - 0.01' 0

Tests with two input images:

-fx input string distortion
'u' 0
'v' 0
's' 0
'u[1]' 0
'u.g' 0
'v.g' 0
's.g' 0
'u[1].g' 0
'mean' 0
'u.mean' 0
'v.mean' 0
's.mean' 0
'u[1].mean' 0
'mean.g' 0
'v.mean.g' 0
'u[1]mean.g' 0
'u.p{2,3}' 0
'u.p[2,3]' 0
'u.p{2,3}.g' 0
'u.p[2,3].g' 0
'v.p{2,3}' 0
'v.p[2,3]' 0
'v.p{2,3}.g' 0
'v.p[2,3].g' 0
'u[1].p{2,3}' 0
'u[1].p[2,3]' 0
'u[1].p{2,3}.g' 0
'u[1].p[2,3].g' 0
's.p{2,3}' 0
's.p[2,3]' 0
's.p{2,3}.g' 0
's.p[2,3].g' 0
'u.saturation' 0
'v.saturation' 0
's.saturation' 0
'u[1].saturation' 0
'u.p[1,2].saturation' 0
'v.p[1,2].saturation' 0
's.p[1,2].saturation' 0
'u[1].p[1,2].saturation' 0
'u-v+0.5' 0

Tests that fail (they show significant differences):

-fx input string distortion
'2300m' 0
'23 ;' 0
'23+(++1)' 0
'23+(--1)' 0
'xx=23;--xx' 0
'xx=23;--xx+1' 0
'xx=3; yy=xx++; yy' 0
'iso=32; rone=rand(); rtwo=rand(); myn=sqrt(-2*ln(rone))*cos(2*Pi*rtwo); myntwo=sqrt(-2*ln(rtwo))* cos(2*Pi*rone); pnoise=sqrt(p)*myn*sqrt(iso)* channel(4.28,3.86,6.68,0)/255; max(0,p+pnoise)' 0.131195

"2300m" has an SI prefix meaning "multiplied by 10-3", and the negative power doesn't work in the old "-fx".

"23 ;" shows different processing of the blank statement after the semi-colon. The old "-fx" returns 0 from blank statements. In the new "-fx", blank statements have no effect, so the return is whatever the previous statement returned.

Expressions with "rand()" give different results each time.

The old "-fx" has a problem with multiple unary prefixes:

%FXOLD%magick xc: -fx "23+(--2.5)" txt: 
%FXNEW%magick xc: -fx "23+(--2.5)" txt: 
# ImageMagick pixel enumeration: 1,1,0,4294967295,srgb
0,0: (109521666023,109521666023,109521666023)  #FFFFFFFFFFFFFFFFFFFFFFFF  srgb(2550%,2550%,2550%)
# ImageMagick pixel enumeration: 1,1,0,4294967295,srgb
0,0: (109521666023,109521666023,109521666023)  #FFFFFFFFFFFFFFFFFFFFFFFF  srgb(2550%,2550%,2550%)

I regard the new result to be correct.

For "xx=3; yy=xx++; yy", the old "-fx" returns zero. This is because it doesn't return a value from "xx++", but doesn't report a syntax problem and instead returns zero, which is used to set yy.

Sample formats

The script fmtTst.bat tests "-format %[string]" for the strings shown, for the old and new "-fx". There are two images, so two values are shown for each of the old and the new. It shows "same" when the outputs are identical, or "DIFFERENT" when they are different.

call %IM7SRCFX%\snibgo\fmtTst.bat

"-format %[fx:string]" using pixel operands without channel qualifiers are different because, as explained at New FX, the old "%[fx:u]" etc give an intensity, and the new versions give the red channel.

format string old result new result same/different
"fx:u-1.23e-4" 0.666544 0.666544 0.666544 0.666544 same
"fx:u-1.23e-4*2" 0.666421 0.666421 0.666421 0.666421 same
"fx:u-gray47" 0.196078 0.196078 0.196078 0.196078 same
"fx:u-gray47*2" -0.27451 -0.27451 -0.27451 -0.27451 same
"fx:u" 0.666667 0.666667 0.666667 0.666667 same
"fx:v" 0.4 0.4 0.4 0.4 same
"fx:s" 0.666667 0.4 0.666667 0.4 same
"fx:u[0]" 0.666667 0.666667 0.666667 0.666667 same
"fx:u[1-1]" 0.666667 0.666667 0.666667 0.666667 same
"fx:u[1]" 0.4 0.4 0.4 0.4 same
"fx:u.p[0,0]" 0.666667 0.666667 0.666667 0.666667 same
"fx:v.p[0,0]" 0.4 0.4 0.4 0.4 same
"fx:s.p[0,0]" 0.666667 0.4 0.666667 0.4 same
"fx:u[0].p[0,0]" 0.666667 0.666667 0.666667 0.666667 same
"fx:u[1-1].p[0,0]" 0.666667 0.666667 0.666667 0.666667 same
"fx:u[1].p[0,0]" 0.4 0.4 0.4 0.4 same

In cases where we don't have pixel operands without channel qualifiers, "-format %[fx:string]" give identical results.

format string old result new result same/different
"fx:1.23e-4" 0.000123 0.000123 0.000123 0.000123 same
"fx:1.23e-4*2" 0.000246 0.000246 0.000246 0.000246 same
"fx:gray47" 0.470588 0.470588 0.470588 0.470588 same
"fx:gray47*2" 0.941176 0.941176 0.941176 0.941176 same
"fx:u-1.23e-4" 0.666544 0.666544 0.666544 0.666544 same
"fx:u-1.23e-4*2" 0.666421 0.666421 0.666421 0.666421 same
"fx:u-gray47" 0.196078 0.196078 0.196078 0.196078 same
"fx:u-gray47*2" -0.27451 -0.27451 -0.27451 -0.27451 same
"fx:u" 0.666667 0.666667 0.666667 0.666667 same
"fx:v" 0.4 0.4 0.4 0.4 same
"fx:s" 0.666667 0.4 0.666667 0.4 same
"fx:u[0]" 0.666667 0.666667 0.666667 0.666667 same
"fx:u[1-1]" 0.666667 0.666667 0.666667 0.666667 same
"fx:u[1]" 0.4 0.4 0.4 0.4 same
"fx:u.p[0,0]" 0.666667 0.666667 0.666667 0.666667 same
"fx:v.p[0,0]" 0.4 0.4 0.4 0.4 same
"fx:s.p[0,0]" 0.666667 0.4 0.666667 0.4 same
"fx:u[0].p[0,0]" 0.666667 0.666667 0.666667 0.666667 same
"fx:u[1-1].p[0,0]" 0.666667 0.666667 0.666667 0.666667 same
"fx:u[1].p[0,0]" 0.4 0.4 0.4 0.4 same
"fx:u.g" 0.733333 0.733333 0.733333 0.733333 same
"fx:v.g" 0.333333 0.333333 0.333333 0.333333 same
"fx:s.g" 0.733333 0.333333 0.733333 0.333333 same
"fx:u[0].g" 0.733333 0.733333 0.733333 0.733333 same
"fx:u[1-1].g" 0.733333 0.733333 0.733333 0.733333 same
"fx:u[1].g" 0.333333 0.333333 0.333333 0.333333 same
"fx:u.p[0,0].g" 0.733333 0.733333 0.733333 0.733333 same
"fx:v.p[0,0].g" 0.333333 0.333333 0.333333 0.333333 same
"fx:s.p[0,0].g" 0.733333 0.333333 0.733333 0.333333 same
"fx:u[0].p[0,0].g" 0.733333 0.733333 0.733333 0.733333 same
"fx:u[1-1].p[0,0].g" 0.733333 0.733333 0.733333 0.733333 same
"fx:u[1].p[0,0].g" 0.333333 0.333333 0.333333 0.333333 same
"fx:u.saturation" 0.428571 0.428571 0.428571 0.428571 same
"fx:v.saturation" 0.2 0.2 0.2 0.2 same
"fx:s.saturation" 0.428571 0.2 0.428571 0.2 same
"fx:u[0].saturation" 0.428571 0.428571 0.428571 0.428571 same
"fx:u[1-1].saturation" 0.428571 0.428571 0.428571 0.428571 same
"fx:u[1].saturation" 0.2 0.2 0.2 0.2 same
"fx:u.p[0,0].saturation" 0.428571 0.428571 0.428571 0.428571 same
"fx:v.p[0,0].saturation" 0.2 0.2 0.2 0.2 same
"fx:s.p[0,0].saturation" 0.428571 0.2 0.428571 0.2 same
"fx:u[0].p[0,0].saturation" 0.428571 0.428571 0.428571 0.428571 same
"fx:u[1-1].p[0,0].saturation" 0.428571 0.428571 0.428571 0.428571 same
"fx:u[1].p[0,0].saturation" 0.2 0.2 0.2 0.2 same
"fx:u.median" 0.755556 0.755556 0.755556 0.755556 same
"fx:v.median" 0.333333 0.333333 0.333333 0.333333 same
"fx:s.median" 0.755556 0.333333 0.755556 0.333333 same
"fx:u[0].median" 0.755556 0.755556 0.755556 0.755556 same
"fx:u[1-1].median" 0.755556 0.755556 0.755556 0.755556 same
"fx:u[1].median" 0.333333 0.333333 0.333333 0.333333 same
"fx:u.median.r" 0.666667 0.666667 0.666667 0.666667 same
"fx:v.median.r" 0.4 0.4 0.4 0.4 same
"fx:s.median.r" 0.666667 0.4 0.666667 0.4 same
"fx:u[0].median.r" 0.666667 0.666667 0.666667 0.666667 same
"fx:u[1-1].median.r" 0.666667 0.666667 0.666667 0.666667 same
"fx:u[1].median.r" 0.4 0.4 0.4 0.4 same
"fx:u.median.g" 0.733333 0.733333 0.733333 0.733333 same
"fx:v.median.g" 0.333333 0.333333 0.333333 0.333333 same
"fx:s.median.g" 0.733333 0.333333 0.733333 0.333333 same
"fx:u[0].median.g" 0.733333 0.733333 0.733333 0.733333 same
"fx:u[1-1].median.g" 0.733333 0.733333 0.733333 0.733333 same
"fx:u[1].median.g" 0.333333 0.333333 0.333333 0.333333 same
"fx:u.median.b" 0.866667 0.866667 0.866667 0.866667 same
"fx:v.median.b" 0.266667 0.266667 0.266667 0.266667 same
"fx:s.median.b" 0.866667 0.266667 0.866667 0.266667 same
"fx:u[0].median.b" 0.866667 0.866667 0.866667 0.866667 same
"fx:u[1-1].median.b" 0.866667 0.866667 0.866667 0.866667 same
"fx:u[1].median.b" 0.266667 0.266667 0.266667 0.266667 same

"-format %[hex:string]" are entirely the same.

format string old result new result same/different
"hex:1.23e-4" 00080F9900080F9900080F99 00080F9900080F9900080F99 00080F9900080F9900080F99 00080F9900080F9900080F99 same
"hex:1.23e-4*2" 00101F3200101F3200101F32 00101F3200101F3200101F32 00101F3200101F3200101F32 00101F3200101F3200101F32 same
"hex:gray47" 787878787878787878787878 787878787878787878787878 787878787878787878787878 787878787878787878787878 same
"hex:gray47*2" F0F0F0F0F0F0F0F0F0F0F0F0 F0F0F0F0F0F0F0F0F0F0F0F0 F0F0F0F0F0F0F0F0F0F0F0F0 F0F0F0F0F0F0F0F0F0F0F0F0 same
"hex:u-1.23e-4" AAA29B11BBB3AC22DDD5CE44 AAA29B11BBB3AC22DDD5CE44 AAA29B11BBB3AC22DDD5CE44 AAA29B11BBB3AC22DDD5CE44 same
"hex:u-1.23e-4*2" AA9A8B78BBAB9C89DDCDBEAB AA9A8B78BBAB9C89DDCDBEAB AA9A8B78BBAB9C89DDCDBEAB AA9A8B78BBAB9C89DDCDBEAB same
"hex:u-gray47" 323232324343434365656565 323232324343434365656565 323232324343434365656565 323232324343434365656565 same
"hex:u-gray47*2" 000000000000000000000000 000000000000000000000000 000000000000000000000000 000000000000000000000000 same
"hex:u" AAAAAAAABBBBBBBBDDDDDDDD AAAAAAAABBBBBBBBDDDDDDDD AAAAAAAABBBBBBBBDDDDDDDD AAAAAAAABBBBBBBBDDDDDDDD same
"hex:v" 666666665555555544444444 666666665555555544444444 666666665555555544444444 666666665555555544444444 same
"hex:s" AAAAAAAABBBBBBBBDDDDDDDD 666666665555555544444444 AAAAAAAABBBBBBBBDDDDDDDD 666666665555555544444444 same
"hex:u[0]" AAAAAAAABBBBBBBBDDDDDDDD AAAAAAAABBBBBBBBDDDDDDDD AAAAAAAABBBBBBBBDDDDDDDD AAAAAAAABBBBBBBBDDDDDDDD same
"hex:u[1-1]" AAAAAAAABBBBBBBBDDDDDDDD AAAAAAAABBBBBBBBDDDDDDDD AAAAAAAABBBBBBBBDDDDDDDD AAAAAAAABBBBBBBBDDDDDDDD same
"hex:u[1]" 666666665555555544444444 666666665555555544444444 666666665555555544444444 666666665555555544444444 same
"hex:u.p[0,0]" AAAAAAAABBBBBBBBDDDDDDDD AAAAAAAABBBBBBBBDDDDDDDD AAAAAAAABBBBBBBBDDDDDDDD AAAAAAAABBBBBBBBDDDDDDDD same
"hex:v.p[0,0]" 666666665555555544444444 666666665555555544444444 666666665555555544444444 666666665555555544444444 same
"hex:s.p[0,0]" AAAAAAAABBBBBBBBDDDDDDDD 666666665555555544444444 AAAAAAAABBBBBBBBDDDDDDDD 666666665555555544444444 same
"hex:u[0].p[0,0]" AAAAAAAABBBBBBBBDDDDDDDD AAAAAAAABBBBBBBBDDDDDDDD AAAAAAAABBBBBBBBDDDDDDDD AAAAAAAABBBBBBBBDDDDDDDD same
"hex:u[1-1].p[0,0]" AAAAAAAABBBBBBBBDDDDDDDD AAAAAAAABBBBBBBBDDDDDDDD AAAAAAAABBBBBBBBDDDDDDDD AAAAAAAABBBBBBBBDDDDDDDD same
"hex:u[1].p[0,0]" 666666665555555544444444 666666665555555544444444 666666665555555544444444 666666665555555544444444 same
"hex:u.g" BBBBBBBBBBBBBBBBBBBBBBBB BBBBBBBBBBBBBBBBBBBBBBBB BBBBBBBBBBBBBBBBBBBBBBBB BBBBBBBBBBBBBBBBBBBBBBBB same
"hex:v.g" 555555555555555555555555 555555555555555555555555 555555555555555555555555 555555555555555555555555 same
"hex:s.g" BBBBBBBBBBBBBBBBBBBBBBBB 555555555555555555555555 BBBBBBBBBBBBBBBBBBBBBBBB 555555555555555555555555 same
"hex:u[0].g" BBBBBBBBBBBBBBBBBBBBBBBB BBBBBBBBBBBBBBBBBBBBBBBB BBBBBBBBBBBBBBBBBBBBBBBB BBBBBBBBBBBBBBBBBBBBBBBB same
"hex:u[1-1].g" BBBBBBBBBBBBBBBBBBBBBBBB BBBBBBBBBBBBBBBBBBBBBBBB BBBBBBBBBBBBBBBBBBBBBBBB BBBBBBBBBBBBBBBBBBBBBBBB same
"hex:u[1].g" 555555555555555555555555 555555555555555555555555 555555555555555555555555 555555555555555555555555 same
"hex:u.p[0,0].g" BBBBBBBBBBBBBBBBBBBBBBBB BBBBBBBBBBBBBBBBBBBBBBBB BBBBBBBBBBBBBBBBBBBBBBBB BBBBBBBBBBBBBBBBBBBBBBBB same
"hex:v.p[0,0].g" 555555555555555555555555 555555555555555555555555 555555555555555555555555 555555555555555555555555 same
"hex:s.p[0,0].g" BBBBBBBBBBBBBBBBBBBBBBBB 555555555555555555555555 BBBBBBBBBBBBBBBBBBBBBBBB 555555555555555555555555 same
"hex:u[0].p[0,0].g" BBBBBBBBBBBBBBBBBBBBBBBB BBBBBBBBBBBBBBBBBBBBBBBB BBBBBBBBBBBBBBBBBBBBBBBB BBBBBBBBBBBBBBBBBBBBBBBB same
"hex:u[1-1].p[0,0].g" BBBBBBBBBBBBBBBBBBBBBBBB BBBBBBBBBBBBBBBBBBBBBBBB BBBBBBBBBBBBBBBBBBBBBBBB BBBBBBBBBBBBBBBBBBBBBBBB same
"hex:u[1].p[0,0].g" 555555555555555555555555 555555555555555555555555 555555555555555555555555 555555555555555555555555 same
"hex:u.saturation" 6DB6DB6D6DB6DB6D6DB6DB6D 6DB6DB6D6DB6DB6D6DB6DB6D 6DB6DB6D6DB6DB6D6DB6DB6D 6DB6DB6D6DB6DB6D6DB6DB6D same
"hex:v.saturation" 333333333333333333333333 333333333333333333333333 333333333333333333333333 333333333333333333333333 same
"hex:s.saturation" 6DB6DB6D6DB6DB6D6DB6DB6D 333333333333333333333333 6DB6DB6D6DB6DB6D6DB6DB6D 333333333333333333333333 same
"hex:u[0].saturation" 6DB6DB6D6DB6DB6D6DB6DB6D 6DB6DB6D6DB6DB6D6DB6DB6D 6DB6DB6D6DB6DB6D6DB6DB6D 6DB6DB6D6DB6DB6D6DB6DB6D same
"hex:u[1-1].saturation" 6DB6DB6D6DB6DB6D6DB6DB6D 6DB6DB6D6DB6DB6D6DB6DB6D 6DB6DB6D6DB6DB6D6DB6DB6D 6DB6DB6D6DB6DB6D6DB6DB6D same
"hex:u[1].saturation" 333333333333333333333333 333333333333333333333333 333333333333333333333333 333333333333333333333333 same
"hex:u.p[0,0].saturation" 6DB6DB6D6DB6DB6D6DB6DB6D 6DB6DB6D6DB6DB6D6DB6DB6D 6DB6DB6D6DB6DB6D6DB6DB6D 6DB6DB6D6DB6DB6D6DB6DB6D same
"hex:v.p[0,0].saturation" 333333333333333333333333 333333333333333333333333 333333333333333333333333 333333333333333333333333 same
"hex:s.p[0,0].saturation" 6DB6DB6D6DB6DB6D6DB6DB6D 333333333333333333333333 6DB6DB6D6DB6DB6D6DB6DB6D 333333333333333333333333 same
"hex:u[0].p[0,0].saturation" 6DB6DB6D6DB6DB6D6DB6DB6D 6DB6DB6D6DB6DB6D6DB6DB6D 6DB6DB6D6DB6DB6D6DB6DB6D 6DB6DB6D6DB6DB6D6DB6DB6D same
"hex:u[1-1].p[0,0].saturation" 6DB6DB6D6DB6DB6D6DB6DB6D 6DB6DB6D6DB6DB6D6DB6DB6D 6DB6DB6D6DB6DB6D6DB6DB6D 6DB6DB6D6DB6DB6D6DB6DB6D same
"hex:u[1].p[0,0].saturation" 333333333333333333333333 333333333333333333333333 333333333333333333333333 333333333333333333333333 same
"hex:u.median" C16C16C1C16C16C1C16C16C1 C16C16C1C16C16C1C16C16C1 C16C16C1C16C16C1C16C16C1 C16C16C1C16C16C1C16C16C1 same
"hex:v.median" 555555555555555555555555 555555555555555555555555 555555555555555555555555 555555555555555555555555 same
"hex:s.median" C16C16C1C16C16C1C16C16C1 555555555555555555555555 C16C16C1C16C16C1C16C16C1 555555555555555555555555 same
"hex:u[0].median" C16C16C1C16C16C1C16C16C1 C16C16C1C16C16C1C16C16C1 C16C16C1C16C16C1C16C16C1 C16C16C1C16C16C1C16C16C1 same
"hex:u[1-1].median" C16C16C1C16C16C1C16C16C1 C16C16C1C16C16C1C16C16C1 C16C16C1C16C16C1C16C16C1 C16C16C1C16C16C1C16C16C1 same
"hex:u[1].median" 555555555555555555555555 555555555555555555555555 555555555555555555555555 555555555555555555555555 same
"hex:u.median.r" AAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAA same
"hex:v.median.r" 666666666666666666666666 666666666666666666666666 666666666666666666666666 666666666666666666666666 same
"hex:s.median.r" AAAAAAAAAAAAAAAAAAAAAAAA 666666666666666666666666 AAAAAAAAAAAAAAAAAAAAAAAA 666666666666666666666666 same
"hex:u[0].median.r" AAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAA same
"hex:u[1-1].median.r" AAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAA same
"hex:u[1].median.r" 666666666666666666666666 666666666666666666666666 666666666666666666666666 666666666666666666666666 same
"hex:u.median.g" BBBBBBBBBBBBBBBBBBBBBBBB BBBBBBBBBBBBBBBBBBBBBBBB BBBBBBBBBBBBBBBBBBBBBBBB BBBBBBBBBBBBBBBBBBBBBBBB same
"hex:v.median.g" 555555555555555555555555 555555555555555555555555 555555555555555555555555 555555555555555555555555 same
"hex:s.median.g" BBBBBBBBBBBBBBBBBBBBBBBB 555555555555555555555555 BBBBBBBBBBBBBBBBBBBBBBBB 555555555555555555555555 same
"hex:u[0].median.g" BBBBBBBBBBBBBBBBBBBBBBBB BBBBBBBBBBBBBBBBBBBBBBBB BBBBBBBBBBBBBBBBBBBBBBBB BBBBBBBBBBBBBBBBBBBBBBBB same
"hex:u[1-1].median.g" BBBBBBBBBBBBBBBBBBBBBBBB BBBBBBBBBBBBBBBBBBBBBBBB BBBBBBBBBBBBBBBBBBBBBBBB BBBBBBBBBBBBBBBBBBBBBBBB same
"hex:u[1].median.g" 555555555555555555555555 555555555555555555555555 555555555555555555555555 555555555555555555555555 same
"hex:u.median.b" DDDDDDDDDDDDDDDDDDDDDDDD DDDDDDDDDDDDDDDDDDDDDDDD DDDDDDDDDDDDDDDDDDDDDDDD DDDDDDDDDDDDDDDDDDDDDDDD same
"hex:v.median.b" 444444444444444444444444 444444444444444444444444 444444444444444444444444 444444444444444444444444 same
"hex:s.median.b" DDDDDDDDDDDDDDDDDDDDDDDD 444444444444444444444444 DDDDDDDDDDDDDDDDDDDDDDDD 444444444444444444444444 same
"hex:u[0].median.b" DDDDDDDDDDDDDDDDDDDDDDDD DDDDDDDDDDDDDDDDDDDDDDDD DDDDDDDDDDDDDDDDDDDDDDDD DDDDDDDDDDDDDDDDDDDDDDDD same
"hex:u[1-1].median.b" DDDDDDDDDDDDDDDDDDDDDDDD DDDDDDDDDDDDDDDDDDDDDDDD DDDDDDDDDDDDDDDDDDDDDDDD DDDDDDDDDDDDDDDDDDDDDDDD same
"hex:u[1].median.b" 444444444444444444444444 444444444444444444444444 444444444444444444444444 444444444444444444444444 same

"-format %[pixel:string]" are entirely the same.

format string old result new result same/different
"pixel:1.23e-4" srgb(0.0123%,0.0123%,0.0123%) srgb(0.0123%,0.0123%,0.0123%) srgb(0.0123%,0.0123%,0.0123%) srgb(0.0123%,0.0123%,0.0123%) same
"pixel:1.23e-4*2" srgb(0.0246%,0.0246%,0.0246%) srgb(0.0246%,0.0246%,0.0246%) srgb(0.0246%,0.0246%,0.0246%) srgb(0.0246%,0.0246%,0.0246%) same
"pixel:gray47" srgb(120,120,120) srgb(120,120,120) srgb(120,120,120) srgb(120,120,120) same
"pixel:gray47*2" srgb(240,240,240) srgb(240,240,240) srgb(240,240,240) srgb(240,240,240) same
"pixel:u-1.23e-4" srgb(66.6544%,73.321%,86.6544%) srgb(66.6544%,73.321%,86.6544%) srgb(66.6544%,73.321%,86.6544%) srgb(66.6544%,73.321%,86.6544%) same
"pixel:u-1.23e-4*2" srgb(66.6421%,73.3087%,86.6421%) srgb(66.6421%,73.3087%,86.6421%) srgb(66.6421%,73.3087%,86.6421%) srgb(66.6421%,73.3087%,86.6421%) same
"pixel:u-gray47" srgb(50,67,101) srgb(50,67,101) srgb(50,67,101) srgb(50,67,101) same
"pixel:u-gray47*2" srgb(-27.451%,-20.7843%,-7.45098%) srgb(-27.451%,-20.7843%,-7.45098%) srgb(-27.451%,-20.7843%,-7.45098%) srgb(-27.451%,-20.7843%,-7.45098%) same
"pixel:u" srgb(170,187,221) srgb(170,187,221) srgb(170,187,221) srgb(170,187,221) same
"pixel:v" srgb(102,85,68) srgb(102,85,68) srgb(102,85,68) srgb(102,85,68) same
"pixel:s" srgb(170,187,221) srgb(102,85,68) srgb(170,187,221) srgb(102,85,68) same
"pixel:u[0]" srgb(170,187,221) srgb(170,187,221) srgb(170,187,221) srgb(170,187,221) same
"pixel:u[1-1]" srgb(170,187,221) srgb(170,187,221) srgb(170,187,221) srgb(170,187,221) same
"pixel:u[1]" srgb(102,85,68) srgb(102,85,68) srgb(102,85,68) srgb(102,85,68) same
"pixel:u.p[0,0]" srgb(170,187,221) srgb(170,187,221) srgb(170,187,221) srgb(170,187,221) same
"pixel:v.p[0,0]" srgb(102,85,68) srgb(102,85,68) srgb(102,85,68) srgb(102,85,68) same
"pixel:s.p[0,0]" srgb(170,187,221) srgb(102,85,68) srgb(170,187,221) srgb(102,85,68) same
"pixel:u[0].p[0,0]" srgb(170,187,221) srgb(170,187,221) srgb(170,187,221) srgb(170,187,221) same
"pixel:u[1-1].p[0,0]" srgb(170,187,221) srgb(170,187,221) srgb(170,187,221) srgb(170,187,221) same
"pixel:u[1].p[0,0]" srgb(102,85,68) srgb(102,85,68) srgb(102,85,68) srgb(102,85,68) same
"pixel:u.g" srgb(187,187,187) srgb(187,187,187) srgb(187,187,187) srgb(187,187,187) same
"pixel:v.g" srgb(85,85,85) srgb(85,85,85) srgb(85,85,85) srgb(85,85,85) same
"pixel:s.g" srgb(187,187,187) srgb(85,85,85) srgb(187,187,187) srgb(85,85,85) same
"pixel:u[0].g" srgb(187,187,187) srgb(187,187,187) srgb(187,187,187) srgb(187,187,187) same
"pixel:u[1-1].g" srgb(187,187,187) srgb(187,187,187) srgb(187,187,187) srgb(187,187,187) same
"pixel:u[1].g" srgb(85,85,85) srgb(85,85,85) srgb(85,85,85) srgb(85,85,85) same
"pixel:u.p[0,0].g" srgb(187,187,187) srgb(187,187,187) srgb(187,187,187) srgb(187,187,187) same
"pixel:v.p[0,0].g" srgb(85,85,85) srgb(85,85,85) srgb(85,85,85) srgb(85,85,85) same
"pixel:s.p[0,0].g" srgb(187,187,187) srgb(85,85,85) srgb(187,187,187) srgb(85,85,85) same
"pixel:u[0].p[0,0].g" srgb(187,187,187) srgb(187,187,187) srgb(187,187,187) srgb(187,187,187) same
"pixel:u[1-1].p[0,0].g" srgb(187,187,187) srgb(187,187,187) srgb(187,187,187) srgb(187,187,187) same
"pixel:u[1].p[0,0].g" srgb(85,85,85) srgb(85,85,85) srgb(85,85,85) srgb(85,85,85) same
"pixel:u.saturation" srgb(42.8571%,42.8571%,42.8571%) srgb(42.8571%,42.8571%,42.8571%) srgb(42.8571%,42.8571%,42.8571%) srgb(42.8571%,42.8571%,42.8571%) same
"pixel:v.saturation" srgb(51,51,51) srgb(51,51,51) srgb(51,51,51) srgb(51,51,51) same
"pixel:s.saturation" srgb(42.8571%,42.8571%,42.8571%) srgb(51,51,51) srgb(42.8571%,42.8571%,42.8571%) srgb(51,51,51) same
"pixel:u[0].saturation" srgb(42.8571%,42.8571%,42.8571%) srgb(42.8571%,42.8571%,42.8571%) srgb(42.8571%,42.8571%,42.8571%) srgb(42.8571%,42.8571%,42.8571%) same
"pixel:u[1-1].saturation" srgb(42.8571%,42.8571%,42.8571%) srgb(42.8571%,42.8571%,42.8571%) srgb(42.8571%,42.8571%,42.8571%) srgb(42.8571%,42.8571%,42.8571%) same
"pixel:u[1].saturation" srgb(51,51,51) srgb(51,51,51) srgb(51,51,51) srgb(51,51,51) same
"pixel:u.p[0,0].saturation" srgb(42.8571%,42.8571%,42.8571%) srgb(42.8571%,42.8571%,42.8571%) srgb(42.8571%,42.8571%,42.8571%) srgb(42.8571%,42.8571%,42.8571%) same
"pixel:v.p[0,0].saturation" srgb(51,51,51) srgb(51,51,51) srgb(51,51,51) srgb(51,51,51) same
"pixel:s.p[0,0].saturation" srgb(42.8571%,42.8571%,42.8571%) srgb(51,51,51) srgb(42.8571%,42.8571%,42.8571%) srgb(51,51,51) same
"pixel:u[0].p[0,0].saturation" srgb(42.8571%,42.8571%,42.8571%) srgb(42.8571%,42.8571%,42.8571%) srgb(42.8571%,42.8571%,42.8571%) srgb(42.8571%,42.8571%,42.8571%) same
"pixel:u[1-1].p[0,0].saturation" srgb(42.8571%,42.8571%,42.8571%) srgb(42.8571%,42.8571%,42.8571%) srgb(42.8571%,42.8571%,42.8571%) srgb(42.8571%,42.8571%,42.8571%) same
"pixel:u[1].p[0,0].saturation" srgb(51,51,51) srgb(51,51,51) srgb(51,51,51) srgb(51,51,51) same
"pixel:u.median" srgb(75.5556%,75.5556%,75.5556%) srgb(75.5556%,75.5556%,75.5556%) srgb(75.5556%,75.5556%,75.5556%) srgb(75.5556%,75.5556%,75.5556%) same
"pixel:v.median" srgb(85,85,85) srgb(85,85,85) srgb(85,85,85) srgb(85,85,85) same
"pixel:s.median" srgb(75.5556%,75.5556%,75.5556%) srgb(85,85,85) srgb(75.5556%,75.5556%,75.5556%) srgb(85,85,85) same
"pixel:u[0].median" srgb(75.5556%,75.5556%,75.5556%) srgb(75.5556%,75.5556%,75.5556%) srgb(75.5556%,75.5556%,75.5556%) srgb(75.5556%,75.5556%,75.5556%) same
"pixel:u[1-1].median" srgb(75.5556%,75.5556%,75.5556%) srgb(75.5556%,75.5556%,75.5556%) srgb(75.5556%,75.5556%,75.5556%) srgb(75.5556%,75.5556%,75.5556%) same
"pixel:u[1].median" srgb(85,85,85) srgb(85,85,85) srgb(85,85,85) srgb(85,85,85) same
"pixel:u.median.r" srgb(170,170,170) srgb(170,170,170) srgb(170,170,170) srgb(170,170,170) same
"pixel:v.median.r" srgb(102,102,102) srgb(102,102,102) srgb(102,102,102) srgb(102,102,102) same
"pixel:s.median.r" srgb(170,170,170) srgb(102,102,102) srgb(170,170,170) srgb(102,102,102) same
"pixel:u[0].median.r" srgb(170,170,170) srgb(170,170,170) srgb(170,170,170) srgb(170,170,170) same
"pixel:u[1-1].median.r" srgb(170,170,170) srgb(170,170,170) srgb(170,170,170) srgb(170,170,170) same
"pixel:u[1].median.r" srgb(102,102,102) srgb(102,102,102) srgb(102,102,102) srgb(102,102,102) same
"pixel:u.median.g" srgb(187,187,187) srgb(187,187,187) srgb(187,187,187) srgb(187,187,187) same
"pixel:v.median.g" srgb(85,85,85) srgb(85,85,85) srgb(85,85,85) srgb(85,85,85) same
"pixel:s.median.g" srgb(187,187,187) srgb(85,85,85) srgb(187,187,187) srgb(85,85,85) same
"pixel:u[0].median.g" srgb(187,187,187) srgb(187,187,187) srgb(187,187,187) srgb(187,187,187) same
"pixel:u[1-1].median.g" srgb(187,187,187) srgb(187,187,187) srgb(187,187,187) srgb(187,187,187) same
"pixel:u[1].median.g" srgb(85,85,85) srgb(85,85,85) srgb(85,85,85) srgb(85,85,85) same
"pixel:u.median.b" srgb(221,221,221) srgb(221,221,221) srgb(221,221,221) srgb(221,221,221) same
"pixel:v.median.b" srgb(68,68,68) srgb(68,68,68) srgb(68,68,68) srgb(68,68,68) same
"pixel:s.median.b" srgb(221,221,221) srgb(68,68,68) srgb(221,221,221) srgb(68,68,68) same
"pixel:u[0].median.b" srgb(221,221,221) srgb(221,221,221) srgb(221,221,221) srgb(221,221,221) same
"pixel:u[1-1].median.b" srgb(221,221,221) srgb(221,221,221) srgb(221,221,221) srgb(221,221,221) same
"pixel:u[1].median.b" srgb(68,68,68) srgb(68,68,68) srgb(68,68,68) srgb(68,68,68) same

The script goodpc.bat tests examples of "%[...]" that were kindly supplied by GeeMack. (Many thanks, GeeMack!) For these tests, occurrences of "rand()" were changed to "0.23". This exposed two bugs in the new fx, now fixed, so all examples create identical outputs for the old and new fx code.

call %WebDisk%\web\im\geemack\goodpc.bat
format string old result new result same/different
"%[fx:var=ceil(0.23*120+60);alt(var)*var]" 88 88 88 88 same
"%[fx:t==0?0.23*32:t==1?0.23*32+16:0.23*32+32]" 7.36 23.36 7.36 23.36 same
"%[pixel:p{0.23*w,0.23*h}]" srgb(170,187,221) srgb(102,85,68) srgb(170,187,221) srgb(102,85,68) same
"%[pixel:p{s.w/2,s.h/2}]" srgb(170,187,221) srgb(102,85,68) srgb(170,187,221) srgb(102,85,68) same
"%[pixel:u[-1].p{0.23*w,0.23*h}]" srgb(102,85,68) srgb(102,85,68) srgb(102,85,68) srgb(102,85,68) same
"%[pixel:u[-1].p{t*2,0}]" srgb(102,85,68) srgb(102,85,68) srgb(102,85,68) srgb(102,85,68) same
"%[pixel:u[-1].p{u[-1].w-2-(t*2),0}]" srgb(102,85,68) srgb(102,85,68) srgb(102,85,68) srgb(102,85,68) same
"%[fx:(mean<0.5)*50]" 0 50 0 50 same
"%[fx:(page.width>page.height)*30]" 0 0 0 0 same
"%[fx:(t?80:60)-(mean*100)]" -15.5556 46.6667 -15.5556 46.6667 same
"%[fx:(w>h)*90]" 0 0 0 0 same
"%[fx:180/page.y]" 1.8e+14 1.8e+14 1.8e+14 1.8e+14 same
"%[fx:50-(mean*100)]" -25.5556 16.6667 -25.5556 16.6667 same
"%[fx:alt(int(0.23*100))*(0.23*30+15)]" -21.9 -21.9 -21.9 -21.9 same
"%[fx:alt(0.23*1000)*((0.23*h*0.025)+(h*0.025))]" 0.03075 0.03075 0.03075 0.03075 same
"%[fx:alt(round(0.23))*(0.23*120+120)]" 147.6 147.6 147.6 147.6 same
"%[fx:alt(t%2)*(round(0.23*5+4)*64)]" 320 -320 320 -320 same
"%[fx:alt(t)*(0.23*30+15)]" 21.9 -21.9 21.9 -21.9 same
"%[fx:alt(t)*w/2]" 0.5 -0.5 0.5 -0.5 same
"%[fx:ceil(h*tan(pi/%SLICES%))]" 1 1 1 1 same
"%[fx:ceil(h*tan(pi/page.x)*1.5)]" -0 -0 -0 -0 same
"%[fx:ceil(0.23*2)+1]" 2 2 2 2 same
"%[fx:ceil(0.23*3)]" 1 1 1 1 same
"%[fx:floor(n/2)-1]" 0 0 0 0 same
"%[fx:floor(0.23*12)+8]" 10 10 10 10 same
"%[fx:h-((t*h*0.05)+(h*0.1))]" 0.9 0.85 0.9 0.85 same
"%[fx:hypot(w,h)-h]" 0.414214 0.414214 0.414214 0.414214 same
"%[fx:int(0.23*2+2)]" 2 2 2 2 same
"%[fx:max(page.width,page.height)*1.1]" 1.1 1.1 1.1 1.1 same
"%[fx:max(u[-1].w,u[-1].h)*1.1]" 1.1 1.1 1.1 1.1 same
"%[fx:n%2?0:n]" 2 2 2 2 same
"%[fx:page.y+page.height]" 1 1 1 1 same
"%[fx:0.23*(h*0.5)+(h*0.5)]" 0.615 0.615 0.615 0.615 same
"%[fx:round(0.23)*180/page.x]" 0 0 0 0 same
"%[fx:t*((u[-1].h-u[0].h)/n)+u[0].h]" 1 1 1 1 same
"%[fx:t==0?0.23*32:t==1?0.23*32+16:0.23*32+32]" 7.36 23.36 7.36 23.36 same
"%[fx:var=ceil(0.23*120+60);alt(var)*var]" 88 88 88 88 same

Error checking and reporting

Here are some invalid input strings, with each resulting error message. This error checking is done at translation, and has no impact on run-time performance.

call %IM7SRCFX%\snibgo\badFxSyntax fxnt_err.lis
-fx input string Error message
23 /*+- Expected operand at '*+-' @ error/fx.c/TranslateExpression/2653.
23>> Expected operand after operator at '' @ error/fx.c/TranslateExpression/2637.
23 / Expected operand after operator at '' @ error/fx.c/TranslateExpression/2637.
10--2 Attempted update to non-UserSymbol '' at '2' @ error/fx.c/TranslateExpression/2595.
3-1 ? 4+5 ? 6 : 7 :2*3 Already have '?' in sub-expression at '6 : 7 :2*3' @ error/fx.c/ProcessTernaryOpr/2376.
? Expected operand at '?' @ error/fx.c/GetOperand/2354.
: Expected operand at ':' @ error/fx.c/GetOperand/2354.
3-1 ? : 4+5 Expected operand at ': 4+5' @ error/fx.c/TranslateExpression/2653.
aa=1 ? 34:45:56 Need '?' in sub-expression at '56' @ error/fx.c/ProcessTernaryOpr/2396.
aa=1 ? 34:45?56 '?' with no corresponding ':' '' at '' @ error/fx.c/ResolveTernaryAddresses/2516.
abs = 2+3 Expected char '(' at '= 2+3' @ error/fx.c/ExpectChar/1279.
r = 2+3 Attempted assignment to non-UserSymbol 'r' at '2+3' @ error/fx.c/TranslateExpression/2588.
epsilon = 2+3 Attempted assignment to non-UserSymbol 'epsilon' at '2+3' @ error/fx.c/TranslateExpression/2588.
s[0,1,2,3,4,5,6,7,8,9] Not a real operator at '[0,1,2,3,4,5,6,7,8,9...' @ error/fx.c/GetOperator/2435.
max(5,11,3,6) For function 'max', arguments don't end with ')' at '3,6)' @ error/fx.c/GetFunction/1906.
abs Expected char '(' at '' @ error/fx.c/ExpectChar/1279.
page.bad Attribute 'page' needs qualifier at '' @ error/fx.c/GetImgAttrToken/1458.
resolution.width Attribute 'resolution' needs qualifier at '' @ error/fx.c/GetImgAttrToken/1458.
u.bad For function 'u', bad qualifier 'bad' at 'bad' @ error/fx.c/GetFunction/1969.
page Attribute 'page' needs qualifier at '' @ error/fx.c/GetImgAttrToken/1458.
() Empty expression in parentheses at '' @ error/fx.c/GetOperand/2089.
(xx=123 '(' but no ')' at '' @ error/fx.c/GetOperand/2096.
abs () For function 'abs' expected 1 arguments, found too few (0) at '' @ error/fx.c/GetFunction/1893.
(3+ Empty expression in parentheses at '' @ error/fx.c/GetOperand/2089.
abs(12,) For function 'abs', arguments don't end with ')' at ')' @ error/fx.c/GetFunction/1906.
abs(12,34,56) For function 'abs', arguments don't end with ')' at '34,56)' @ error/fx.c/GetFunction/1906.
max(5,11,3,6) For function 'max', arguments don't end with ')' at '3,6)' @ error/fx.c/GetFunction/1906.
for (,,) For function 'for' expected 3 arguments, found too few (0) at '' @ error/fx.c/GetFunction/1893.
for (1,2) For function 'for' expected 3 arguments, found too few (2) at '' @ error/fx.c/GetFunction/1893.
mean.bad Bad channel qualifier at 'bad' @ error/fx.c/GetOperand/2269.
v Symbol 'v' but fewer than two images at 'v' @ error/fx.c/GetOperand/2235.
3 4 Expected operator at '4' @ error/fx.c/GetOperator/2443.
3 + * Expected operand at '*' @ error/fx.c/TranslateExpression/2653.
3 * Expected operand after operator at '' @ error/fx.c/TranslateExpression/2637.
3 ] Not a real operator at ']' @ error/fx.c/GetOperator/2435.
3 + 4 ) Not a real operator at ')' @ error/fx.c/GetOperator/2435.
3 ? 4 ? 5 : 6 Already have '?' in sub-expression at '5 : 6' @ error/fx.c/ProcessTernaryOpr/2376.
3 ? 4 : 5 : 6 Need '?' in sub-expression at '6' @ error/fx.c/ProcessTernaryOpr/2396.
3 : 4 Need '?' in sub-expression at '4' @ error/fx.c/ProcessTernaryOpr/2396.
xx = 3 + yy NewUserSymbol 'yy' after non-assignment operator at '' @ error/fx.c/TranslateExpression/2661.
xx NewUserSymbol 'xx' needs assignment operator at '' @ error/fx.c/TranslateExpression/2677.
xx++ Expected assignment after new UserSymbol 'xx' at '' @ error/fx.c/TranslateExpression/2581.
#badhex Bad hex number at '#badhex' @ error/fx.c/GetOperand/2147.
#a Bad hex number at '#a' @ error/fx.c/GetOperand/2147.
#z Bad hex number at '#z' @ error/fx.c/GetOperand/2147.
#abcde Bad hex number at '#abcde' @ error/fx.c/GetOperand/2147.
xx+=2 Expected assignment after new UserSymbol 'xx' at '2' @ error/fx.c/TranslateExpression/2581.
2=3 Attempted assignment to non-UserSymbol '' at '3' @ error/fx.c/TranslateExpression/2588.
r=2 Attempted assignment to non-UserSymbol 'r' at '2' @ error/fx.c/TranslateExpression/2588.
abs=2 Expected char '(' at '=2' @ error/fx.c/ExpectChar/1279.
2+=3 Attempted update to non-UserSymbol '' at '3' @ error/fx.c/TranslateExpression/2595.
r+=2 Attempted update to non-UserSymbol 'r' at '2' @ error/fx.c/TranslateExpression/2595.
abs+=2 Expected char '(' at '+=2' @ error/fx.c/ExpectChar/1279.
xx=2; xx++ *3 '++' and '--' must be the final operators in an expression at '*3' @ error/fx.c/TranslateExpression/2645.
2+xx+3 NewUserSymbol 'xx' after non-assignment operator at '+3' @ error/fx.c/TranslateExpression/2661.
u[-1].p[3,4].mean For function 'p', bad qualifier 'mean' at 'mean' @ error/fx.c/GetFunction/1969.
12*(34-5)) Not a real operator at ')' @ error/fx.c/GetOperator/2435.
-xx After unary, NewUserSymbol at '' @ error/fx.c/GetOperand/2126.
--xx After unary, bad operand at '' @ error/fx.c/GetOperand/2118.
u.p[0,0].median For function 'p', bad qualifier 'median' at 'median' @ error/fx.c/GetFunction/1969.
v.p[0,0].median Symbol 'v' but fewer than two images at 'v.p[0,0].median' @ error/fx.c/GetOperand/2235.
s.p[0,0].median For function 'p', bad qualifier 'median' at 'median' @ error/fx.c/GetFunction/1969.
u[0].p[0,0].median For function 'p', bad qualifier 'median' at 'median' @ error/fx.c/GetFunction/1969.
u[1-1].p[0,0].median For function 'p', bad qualifier 'median' at 'median' @ error/fx.c/GetFunction/1969.
u[1].p[0,0].median For function 'p', bad qualifier 'median' at 'median' @ error/fx.c/GetFunction/1969.
HCL(113 constant color missing ')' at 'HCL(113' @ error/fx.c/GetConstantColour/1607.
%[hex:KA=gZK=AK=AK&gZK=e] Unknown property '%[hex:KA=gZK=AK=AK^&gZK=e]' at '%[hex:KA=gZK=AK=AK^&...' @ error/fx.c/GetProperty/1536.
KA=gZK=AK=AK&gZK=e Expected operand at '&gZK=e' @ error/fx.c/TranslateExpression/2653.
do(o,,,2) For function 'do' required argument is missing at ',2)' @ error/fx.c/GetFunction/1802.
channel (1,2,3,4,5,6) For function 'channel', arguments don't end with ')' at '6)' @ error/fx.c/GetFunction/1906.
skewness.intensity Bad channel qualifier at 'intensity' @ error/fx.c/GetOperand/2269.
mean.intensity Bad channel qualifier at 'intensity' @ error/fx.c/GetOperand/2269.
w.intensity Expected operator at '.intensity' @ error/fx.c/GetOperator/2443.
quality.r Expected operator at '.r' @ error/fx.c/GetOperator/2443.
u.mean.intensity Can't have statistical image attribute 'mean' with virtual channel qualifier 'intensity' at '' @ error/fx.c/GetFunction/2006.
u.w.intensity Can't have image attribute 'w' with channel qualifier 'intensity' at '' @ error/fx.c/GetFunction/1998.
u.quality.r Can't have image attribute 'quality' with channel qualifier 'r' at '' @ error/fx.c/GetFunction/1998.

A message that includes "ValStack underflow" suggests the translator has a bug. The count of "underflow" messages in the above is: 0 .

A small selection of Fred's ImageMagick scripts were executed in sequence for the old "-fx" and the new "-fx2". For convenience on my computer, they were first modified to use the "magick" command. The elapsed times were measured, and the outputs from the old and new "-fx" were compared with "-metric RMSE".

The scripts were: adaptivegamma, defisheye, disperse, pano2rect, sharpedge, statsfilt, tunnelize, warplog and xpand.

The "disperse" script uses randomness, creating a different result each time, so the RMSE difference from that script was ignored.

The table shows the worst RMSE of the scripts (excluding "disperse"), on a nominal scale of 0.0 to 1.0. An RMSE of less than 1e-02 will generally not be noticable.

Input file Size (pixels) Old "-fx" New "-fx2" Worst RMSE
toes.png 267x233 3096 seconds 124 seconds 1.13239e-05

Many small images

The new fx code was developed to increase performance of large images, by decreasing the processing required at each pixel, at the expense of increasing the processing required at each image, which includes more extensive syntax checking and translation to a run-time RPN language.

A consequence is that the total processing required for small images has increased. We want to ensure the reduced performance is not excessive.

We consider two cases: without image statistics, then with image statistics.

Without image statistics

%FXOLD%magick xc:white -duplicate 999 +noise random -brightness-contrast %%[fx:50-(0.34*100)] null:
%FXNEW%magick xc:white -duplicate 999 +noise random -brightness-contrast %%[fx:50-(0.34*100)] null:
0 00:00:07
0 00:00:06

The times are shown in the format "D HH:MM:SS".

%FXOLD%magick xc:white -duplicate 999 +noise random -fx (50-(0.34*100))/100 null:
%FXNEW%magick xc:white -duplicate 999 +noise random -fx (50-(0.34*100))/100 null:
0 00:00:01
0 00:00:00

Conclusion: when no image statistics are involved, there is very little difference in performance between the old and new code.

With image statistics

%FXOLD%magick xc:white -duplicate 999 +noise random -brightness-contrast %%[fx:50-(mean*100)] null:
%FXNEW%magick xc:white -duplicate 999 +noise random -brightness-contrast %%[fx:50-(mean*100)] null:
0 00:00:09
0 00:00:10

In the above "%[fx:...]" example, the mean of each image is required. The old and the new calculate the mean of each image (and all the other statistics of each image).

%FXOLD%magick xc:white -duplicate 999 +noise random -fx (50-(mean*100))/100 null:
%FXNEW%magick xc:white -duplicate 999 +noise random -fx (50-(mean*100))/100 null:
0 00:00:03
0 00:00:03

In the above "-fx" example, the mean of only the first image is required. The old fx calculates the mean of just that image. The new fx calculates the mean of every image in the list, so it takes longer.

Conclusion: the new fx code does decrease performance when we have many small images, but this is not excessive.

As is often the case, rearranging the command can increase performance. For example, repeating the above example but using an embedded "%[fx:mean]":

%FXNEW%magick xc:white -duplicate 999 +noise random -fx "(50-(%%[fx:mean]*100))/100" null:
0 00:00:01

Scripts

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

The scripts use some utility programs such as StopWatchcGrep and chStrs. I do not supply the source or binaries of these.

testPerf.bat

set IMG=%~1
if "%IMG%"=="" set IMG=-size 1000x1000 xc:#abd

set SCRFILE=tp.scr

echo off

(
  for /F "tokens=*" %%L in (%~dp0testPerf.txt) do (
    echo ^( -clone 0 -fx "%%L" +delete ^)
  )

  echo NULL:
) >%SCRFILE%

echo on

type %SCRFILE%


call StopWatch
%FXOLD%magick %IMG% -script %SCRFILE%
call StopWatch
%FXNEW%magick %IMG% -script %SCRFILE%
call StopWatch

goodFxSyntax.bat

set xc2=xc:#abd xc:#432

%FXOLD%magick %xc2% -append +repage gfs.png

set VPIX=-virtual-pixel Black
set PREC=-precision 6
set OUTSPEC=-define quantum:format=floating-point -depth 32


echo off
(
for /F "tokens=*" %%L in (%~dp0goodfxsyntax.txt) do (
  ( %FXNEW%magick %PREC% gfs.png %VPIX% -fx "%%L" %OUTSPEC% gfs_new.miff 2^>^&1 ) |tail -5

  %FXOLD%magick %PREC% gfs.png %VPIX% -fx "%%L" %OUTSPEC% gfs_old.miff

  set cleaned=%%L
  set "cleaned=!cleaned:<=&lt;!"
  set "cleaned=!cleaned:>=&gt;!"
  set "cleaned=!cleaned:&=++amp++!"
  set "cleaned=!cleaned:%%=__!"
  set "cleaned=!cleaned:++amp++=&!"

  %IMG7%magick %PREC% gfs_old.miff gfs_new.miff -metric RMSE -format "=start= '!cleaned!' distortion=%%[distortion] =end=\n" -compare info: 
)
) >fxn_gfs.lis 

echo on

cgrep /ifxn_gfs.lis /ofxn_gfs1a.lis /sdistortion= /f\s\r\n

chStrs /ifxn_gfs1a.lis /ofxn_gfs1a.lis /f"\(" /t^&lt;
chStrs /ifxn_gfs1a.lis /ofxn_gfs1a.lis /f"\)" /t^&gt;
chStrs /ifxn_gfs1a.lis /ofxn_gfs1a.lis /f"__" /t%%

chStrs /ifxn_gfs1a.lis /ofxn_gfs1b.lis /f"=start=" /t\(tr\)\(td\)
chStrs /ifxn_gfs1b.lis /ofxn_gfs1b.lis /f"distortion=" /t\(/td\)\(td\)
chStrs /ifxn_gfs1b.lis /ofxn_gfs1b.lis /f"=end=" /t\(/td\)\(/tr\)

cPrefix /ifxn_gfs1b.lis /X /t"\(tr\)\(th\)-fx input string\(/th\)\(th\)distortion\(/th\)\(/tr\)"
cPrefix /ifxn_gfs1b.lis /X /t"\(table\)" /b"\(/table\)"


echo off
(
for /F "tokens=*" %%L in (%~dp0goodfxsyntax2.txt) do (
  ( %FXNEW%magick %PREC% -size 4x4 %xc2% %VPIX% -fx "%%L" %OUTSPEC% gfs2_new.miff 2^>^&1 ) |tail -5

  %FXOLD%magick %PREC% -size 4x4 %xc2% %VPIX% -fx "%%L" %OUTSPEC% gfs2_old.miff

  set cleaned=%%L

  set cleaned=!cleaned:%%=__!

  %IMG7%magick %PREC% gfs2_old.miff gfs2_new.miff -metric RMSE -format "=start= '!cleaned!' distortion=%%[distortion] =end=\n" -compare info:
)
) >fxn_gfs2.lis

echo on

cgrep /ifxn_gfs2.lis /ofxn_gfs2a.lis /sdistortion= /f\s\r\n

chStrs /ifxn_gfs2a.lis /ofxn_gfs2b.lis /f"=start=" /t\(tr\)\(td\)
chStrs /ifxn_gfs2b.lis /ofxn_gfs2b.lis /f"distortion=" /t\(/td\)\(td\)
chStrs /ifxn_gfs2b.lis /ofxn_gfs2b.lis /f"=end=" /t\(/td\)\(/tr\)

cPrefix /ifxn_gfs2b.lis /X /t"\(tr\)\(th\)-fx input string\(/th\)\(th\)distortion\(/th\)\(/tr\)"
cPrefix /ifxn_gfs2b.lis /X /t"\(table\)" /b"\(/table\)"


echo off
(
for /F "tokens=*" %%L in (%~dp0goodBadFxSyntax.txt) do (
  ( %FXNEW%magick %PREC% -size 4x4 %xc2% %VPIX% -fx "%%L" %OUTSPEC% gfs3_new.miff 2^>^&1 ) |tail -5

  %FXOLD%magick %PREC% -size 4x4 %xc2% %VPIX% -fx "%%L" %OUTSPEC% gfs3_old.miff

  set cleaned=%%L

  set cleaned=!cleaned:%%=__!

  %IMG7%magick %PREC% gfs3_old.miff gfs3_new.miff -metric RMSE -format "=start= '!cleaned!' distortion=%%[distortion] =end=\n" -compare info:
)
) >fxn_gfs3.lis

echo on

cgrep /ifxn_gfs3.lis /ofxn_gfs3a.lis /sdistortion= /f\s\r\n

chStrs /ifxn_gfs3a.lis /ofxn_gfs3b.lis /f"=start=" /t\(tr\)\(td\)
chStrs /ifxn_gfs3b.lis /ofxn_gfs3b.lis /f"distortion=" /t\(/td\)\(td\)
chStrs /ifxn_gfs3b.lis /ofxn_gfs3b.lis /f"=end=" /t\(/td\)\(/tr\)

cPrefix /ifxn_gfs3b.lis /X /t"\(tr\)\(th\)-fx input string\(/th\)\(th\)distortion\(/th\)\(/tr\)"
cPrefix /ifxn_gfs3b.lis /X /t"\(table\)" /b"\(/table\)"

goodpc.bat

set IMGS=xc:#abd xc:#654

set SLICES=5

set PREC=-precision 6

chStrs /i%~dp0goodpc.txt /ogoodpc_norand.txt /frand() /t0.23

%FXNEW%magick %IMGS% -define fx:debug=true -format "%%[fx:%~1]\n" info:

echo off
(
  for /F "tokens=*" %%L in (goodpc_norand.txt) do (
    for /F "usebackq tokens=*" %%B in (`%FXOLD%magick %PREC% %IMGS% -format "<td>%%L</td>" info:`) do set SOLD=%%B

    for /F "usebackq tokens=*" %%B in (`%FXNEW%magick %PREC% %IMGS% -format "<td>%%L</td>" info:`) do set SNEW=%%B

    set goodBad=same

    if !SOLD! NEQ !SNEW! set goodBad=DIFFERENT

    set NoLtGt=%%L
    set "NoLtGt=!NoLtGt:<=&lt;!"
    set "NoLtGt=!NoLtGt:>=&gt;!"

    echo =start= "!NoLtGt!" =mid= !SOLD! !SNEW! =mid2= !goodBad! =end=
  )
) >fxn_ft.lis

cGrep /ifxn_ft.lis /ofxn_ftb.lis /srand() /x

chStrs /ifxn_ftb.lis /ofxn_ftb.lis /f"=start=" /t"\(tr\)\(td\)"
chStrs /ifxn_ftb.lis /ofxn_ftb.lis /f"=mid=" /t"\(/td\)"
chStrs /ifxn_ftb.lis /ofxn_ftb.lis /f"=mid2=" /t"\(td\)"
chStrs /ifxn_ftb.lis /ofxn_ftb.lis /f"=end=" /t"\(/td\)\(/tr\)"

cPrefix /ifxn_ftb.lis /X /t"\(tr\)\(th\)format string\(/th\)\(th cspan\)old result\(/th\)\(th cspan\)new result\(/th\)\(th\)same/different\(/th\)\(/tr\)"
cPrefix /ifxn_ftb.lis /X /t"\(table\)" /b"\(/table\)"

chStrs /ifxn_ftb.lis /ofxn_ft_gm.lis /fcspan /tcolspan=\q2\q

echo on

badFxSyntax.bat

set LIS_FILE=%1

if "%LIS_FILE%"=="" set LIS_FILE=fxn_err.lis

echo off

(
echo ^<table^>
echo ^<tr^>^<th^>-fx input string^</th^>^<th^>Error message^</th^>^</tr^>

for /F "tokens=*" %%A in (%~dp0badfxsyntax.txt) do (
  for /F "usebackq tokens=*" %%B in (`%FXNEW%magick xc:#8bd -fx "%%A" NULL: 2^>^&1`) do set msg=%%B

  set cleaned=!msg!
  set "cleaned=!cleaned:<=&lt;!"
  set "cleaned=!cleaned:>=&gt;!"
  set "cleaned=!cleaned:&=++amp++!"
  set "cleaned=!cleaned:%%=__!"
  set "cleaned=!cleaned:++amp++=&!"

  set msg = !cleaned!

  set msg2=!msg:magick: =!

  if !msg2! NEQ !msg! (
    set msg3=!msg2:fatal/fx2.inc/=!
    echo ^<tr^>^<td^>%%A^</td^>^<td^>!msg3!^</td^>^</tr^>
  )
)
echo ^</table^>
) >%LIS_FILE%

echo on

echo created %LIS_FILE%

fmtTst.bat

set IMGS=xc:#abd xc:#654

set PREC=-precision 6

%FXNEW%magick %IMGS% -define fx:debug=true -format "%%[fx:%~1]\n" info:

%FXOLD%magick %IMGS% -format "%%[fx:%~1]\n" info:


for %%A in (fx) do (

  echo %%A

  echo off
  (
    for /F "tokens=*" %%L in (%~dp0fmtTstDiff.txt) do (
      for /F "usebackq tokens=*" %%B in (`%FXOLD%magick %PREC% %IMGS% -format "<td>%%[%%A:%%L]</td>" info:`) do set SOLD=%%B

      for /F "usebackq tokens=*" %%B in (`%FXNEW%magick %PREC% %IMGS% -format "<td>%%[%%A:%%L]</td>" info:`) do set SNEW=%%B

      set goodBad=same

      if !SOLD! NEQ !SNEW! set goodBad=DIFFERENT

      echo =start= "%%A:%%L" =mid= !SOLD! !SNEW! =mid2= !goodBad! =end=
    )
  ) >fxn_ft.lis
  echo on

  chStrs /ifxn_ft.lis /ofxn_ftb.lis /f"=start=" /t"\(tr\)\(td\)"
  chStrs /ifxn_ftb.lis /ofxn_ftb.lis /f"=mid=" /t"\(/td\)"
  chStrs /ifxn_ftb.lis /ofxn_ftb.lis /f"=mid2=" /t"\(td\)"
  chStrs /ifxn_ftb.lis /ofxn_ftb.lis /f"=end=" /t"\(/td\)\(/tr\)"

  cPrefix /ifxn_ftb.lis /X /t"\(tr\)\(th\)format string\(/th\)\(th cspan\)old result\(/th\)\(th cspan\)new result\(/th\)\(th\)same/different\(/th\)\(/tr\)"
  cPrefix /ifxn_ftb.lis /X /t"\(table\)" /b"\(/table\)"

  chStrs /ifxn_ftb.lis /ofxn_ft_diff_%%A.lis /fcspan /tcolspan=\q2\q
)

for %%A in (fx pixel hex) do (

  echo %%A

  echo off
  (
    for /F "tokens=*" %%L in (%~dp0fmtTst.txt) do (
      for /F "usebackq tokens=*" %%B in (`%FXOLD%magick %PREC% %IMGS% -format "<td>%%[%%A:%%L]</td>" info:`) do set SOLD=%%B

      for /F "usebackq tokens=*" %%B in (`%FXNEW%magick %PREC% %IMGS% -format "<td>%%[%%A:%%L]</td>" info:`) do set SNEW=%%B

      set goodBad=same

      if !SOLD! NEQ !SNEW! set goodBad=DIFFERENT

      echo =start= "%%A:%%L" =mid= !SOLD! !SNEW! =mid2= !goodBad! =end=
    )
  ) >fxn_ft.lis
  echo on

  chStrs /ifxn_ft.lis /ofxn_ftb.lis /f"=start=" /t"\(tr\)\(td\)"
  chStrs /ifxn_ftb.lis /ofxn_ftb.lis /f"=mid=" /t"\(/td\)"
  chStrs /ifxn_ftb.lis /ofxn_ftb.lis /f"=mid2=" /t"\(td\)"
  chStrs /ifxn_ftb.lis /ofxn_ftb.lis /f"=end=" /t"\(/td\)\(/tr\)"

  cPrefix /ifxn_ftb.lis /X /t"\(tr\)\(th\)format string\(/th\)\(th cspan\)old result\(/th\)\(th cspan\)new result\(/th\)\(th\)same/different\(/th\)\(/tr\)"
  cPrefix /ifxn_ftb.lis /X /t"\(table\)" /b"\(/table\)"

  chStrs /ifxn_ftb.lis /ofxn_ft_%%A.lis /fcspan /tcolspan=\q2\q
)

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

%FXOLD%magick identify -version
Version: ImageMagick 7.1.1-5 (Beta) Q32-HDRI x86_64 852a723f1:20230319 https://imagemagick.org
Copyright: (C) 1999 ImageMagick Studio LLC
License: https://imagemagick.org/script/license.php
Features: Cipher DPC HDRI Modules OpenMP(4.5) 
Delegates (built-in): bzlib cairo fftw fontconfig freetype heic jbig jng jpeg lcms ltdl lzma pangocairo png raqm raw rsvg tiff webp wmf x xml zip zlib
Compiler: gcc (11.3)
%FXNEW%magick identify -version
Version: ImageMagick 7.1.1-5 (Beta) Q32-HDRI x86_64 852a723f1:20230319 https://imagemagick.org
Copyright: (C) 1999 ImageMagick Studio LLC
License: https://imagemagick.org/script/license.php
Features: Cipher DPC HDRI Modules OpenMP(4.5) 
Delegates (built-in): bzlib cairo fftw fontconfig freetype heic jbig jng jpeg lcms ltdl lzma pangocairo png raqm raw rsvg tiff webp wmf x xml zip zlib
Compiler: gcc (11.3)

To improve internet download speeds, some images may have been automatically converted (by ImageMagick, of course) from PNG or TIFF or MIFF to JPG.

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


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 8-January-2022.

Page created 04-Jul-2023 19:39:48.

Copyright © 2023 Alan Gibson.