snibgo's ImageMagick pages

Process modules

We can customise ImageMagick to add new functionality, for example with -process using MagickCore, like plug-ins. I show some examples.

(This page used to be titled "Customising ImageMagick".)

See the official page ImageMagick Architecture: Custom Image Filters.

This can accelerate operations that would otherwise require a looping script. For example, making a histogram image from a 20 megapixel input takes about 0.003 seconds. Compared with the equivalent Windows BAT script, taking about 20 seconds, we improve the speed by a factor of 6000.

I assume that:

Those three pages are building blocks for this one.

On this computer, my username is Alan, and I have installed my own Q32 HDRI compilation of ImageMagick to the Cygwin directory ~/iminst32f. You will need to adjust directory names for your own system.

It is probably a good idea to put ~/iminst32f/bin (or, rather, the Windows equivalent) on the system search path. While experimenting, I prefer not to do this.

This page is based on a download of ImageMagick on 30 July 2014, v6.8.9-6, with Cygwin bash, on a 64-bit Windows 8.1 computer.

On this page, I prefix bash commands with a dollar, "$". This is the usual bash prompt. Don't type it. Any commands not prefixed with a dollar are for Windows cmd.

Modules on this page are aimed at images that have channels representing red, green, blue and possibly alpha. If channels represent CMY, CMYK, Lab etc then results will be weird. In particular, I ignore the K channel of CMYK(A). The K channel needs special processing. My work doesn't need CMYK, so I haven't bothered with it.

The -process mechanism works with image lists, not with magick wands. Hence it interfaces at the low-level MagickCore rather than the higher-level MagickWand. My primary reference for the MagickCore interface is the source code itself. This is quite well commented, and there seems to be no other significant documentation.

My reference for C library functions is The GNU C Library manual. My code may not compile on non-GNU systems, though I expect that any toolset that can build IM will also build my modules. If I don't comply with some standard or other, and simple changes will make it comply, I might make those changes. I won't splatter my code with #ifdefs to comply with different standards/OSs/quirks.

CAUTION: I am not a great C programmer, nor am I expert at using MagickCore or MagickWand, the internals of ImageMagick. My code probably isn't "best practice". My error-handling is poor. I know nothing about parallel processing. I haven't checked my code for memory leakage. My programming style (especially the layout of code) is different to that of the ImageMagick developers. I have tested the C code only on a Q32 HDRI build, and tests are limited to commands shown on this page.

Create development environment

I develop code in a separate directory to the "as downloaded" official distribution. You might prefer to directly modify the official distribution. Adjust the following to suit.

Create new directories for development source code, and installation of the built development version:

bash
$ cd ~
$ mkdir imdevsrc
$ mkdir imdevins

Copy the unpacked IM distribution to the development source directory:

$ cp -R ImageMagick-6.8.9-6/* imdevsrc

Build this development version, to verify the build works before we start hacking:

$ cd imdevsrc
$ ./configure --prefix=/home/Alan/imdevins --with-quantum-depth=32 --enable-hdri --with-rsvg=yes --with-windows-font-dir=/cygdrive/c/Windows/Fonts --disable-docs --with-modules=yes
$ make
$ make install

For convenience, I set the Windows environment variable IMDEV to this installation directory:

echo %IMDEV% 
C:\cygwin64\home\Alan\imdevins7114\bin\ 

I have a parallel environment for v7 development. Source code lives in %IM7SRC%. Binary files live in %IM7DEV%.

echo %IM7SRC% 
c:\cygwin64\home\Alan\imagemagick-7.1.1-4B\ImageMagick-Windows-7\ImageMagick 
echo %IM7DEV% 
C:\cygwin64\home\Alan\imdevins7114\bin\ 

Source code for my modules resides in %IMSRC%\filters and %IM7SRC%\filters. The files in those directories are identical, and could be symbolic links. There are also two header files in the parents of filters, ie in %IMSRC% and %IM7SRC%.

Verify the installation:

$ ~/imdevins/bin/convert -version
Version: ImageMagick 6.X.X-X Q32 x86_64 2014-08-06 http://www.imagemagick.org
Copyright: Copyright (C) 1999-2014 ImageMagick Studio LLC
Features: DPC HDRI OpenMP
Delegates: bzlib fontconfig freetype fpx jbig jng jpeg lcms lzma png tiff x zlib

Build "-process" modules

IM has a -process command. This takes a module name as an argument, optionally followed by arguments to the module. If the module has arguments, the module name and its arguments must be bracketed in quotes. (For this, Windows is happy with either single or double quotes. I use single quotes.)

The process module is a C function. IM gives it an image list, and any arguments the user has supplied in the command line. The function can do anything it wants, such as modifying the images or replacing the image list with another list.

Source code for the modules, a makefile and other files are in a filters directory from the development directory, eg ~/imdevsrc/filters.

ASIDE: This filters directory should not be confused with IM's -filter setting for resizing and other operations. They are not related.

Custom filters and coders are collectively known as "modules". If IM has been built with --with-modules=yes, modules are compiled into separate libraries and can be listed with IM's -list module command.

Verify the installed version contains the "analyze" module:

$ ~/imdevins/bin/convert rose: -process analyze r.png

$ ~/imdevins/bin/identify -verbose r.png|grep filter
 filter:brightness:kurtosis: -1.38096
    filter:brightness:mean: 2.56068e+09
    filter:brightness:skewness: -0.0077617
    filter:brightness:standard-deviation: 1.15205e+09
    filter:saturation:kurtosis: -1.42743
    filter:saturation:mean: 1.8842e+09
    filter:saturation:skewness: 0.00504635
    filter:saturation:standard-deviation: 1.12284e+09

The analyze module successfully runs.

Edit ~/imdevsrc/filters/analyze.c so the first line with FormatLocaleString is:

 (void) FormatLocaleString(text,MaxTextExtent,"%g (Hello world)",brightness_mean);

Like me, the compiler etc doesn't care what characters mark line ends, so Windows editors that write CR-LF can be used.

Remake, install, and test:

$ make
$ make install
$ ~/imdevins/bin/convert rose: -process analyze r.png
$ ~/imdevins/bin/identify -verbose r.png |grep filter
 filter:brightness:kurtosis: -1.38096
    filter:brightness:mean: 2.56068e+09 (Hello world)
    filter:brightness:skewness: -0.0077617
    filter:brightness:standard-deviation: 1.15205e+09
    filter:saturation:kurtosis: -1.42743
    filter:saturation:mean: 1.8842e+09
    filter:saturation:skewness: 0.00504635
    filter:saturation:standard-deviation: 1.12284e+09

Yup. Our edit to analyze.c has taken effect.

Copy filters/analyze.c to filters/sortlines.c. Edit sortlines.c so the function is named sortlinesImage, and edit the Format line.

Edit imdevsrc/filters/Makefile.am, replicating references to analyze. My current version of Makefile.am is shown below.

Edit magick/module.c and magick/static.c. In these source files, search for analyze, and add code for any other modules. This is only needed if we link the modules into a single library or binary. If we configure it with --with-modules=yes, we don't need to change these source files (I think).

However, magick/module.c contains functions GetModuleList() and ListModuleInfo(), and these don't need editing. I think analyze is used as an example to find the path to modules, and they are all in the same place so the other modules don't need to be listed.

--with-modules=yes: modules are dynamically linked. That is, they are not known to source code within convert.exe etc. The alternative requires that we patch static.c and module.c. Look for occurrences of "analyze". Static build means that whenever a module is edited, all the utilities will be re-linked. Static build doesn't list modules (either coders or filters) with "-list module".

The normal build process is: configure, make, make install. When we add new filters, we need to precede these with automake (which uses filters/automake.am) and autoconf. So the sequence is:

$ automake
$ autoconf
$ ./configure --prefix=/home/Alan/imdevins --with-quantum-depth=32 --enable-hdri --with-rsvg=yes --with-windows-font-dir=/cygdrive/c/Windows/Fonts --disable-docs --with-modules=yes
$ make
$ make install

automake and autoconf are bash scripts, part of (I think) the GNU development toolset. ./configure is a bash script in the development directory. make is a binary program.

(The configure option --disable-docs prevents documentation from being installed. This is a time-consuming part of make install, and we don't need it.)

After we edit the source of any module, such as sortlines.c, we need to run make and make install again. The first three steps (automake, autoconf and ./configure) are needed only if we add new modules.

ASIDE: Back in the old days, I programmed in binary machine code. That was fun but error-prone and tedious. Assembly code was easier, and an assembler converted this to binary. Even easier was C or C++. A compiler converts that to assembly code, and an assembler converts that to binary, and binary files are linked with a linker. To ensure binaries are updated whenever source code changes, we use make. But the switches for compilation and assembly depend on our Q number, our operating system, and so on. So we need to specify our configuration, for which we use ./configure. But the configuration is complex, so we need to build that, with autoconf. And we need to build our makefile, with automake. I'm going crazy.

And this is before the complexities of licensing, back-end code management and code repositories and versioning and subversioning, regression testing, build environment validation, destination system validation, and front-end package distribution and dependencies.

Maintenance is another layer of complexity.

Of course, this is all useful. It means that I can write code that you can integrate into ImageMagick, whatever your Q-number or operating system or anything else. But I am nostalgic for the old days of binary machine code. They were simpler, somehow.

The structure of functions

A function for -process is given a list of images. To aid possible future integration into ImageMagick code, at the MagickCore and MagickWand levels, this main function calls a function that processes a single image, and is modelled on those in either magick/transform.c or magick/enhance.c.

Within my .c file, the top-level function handles the list-processing.

I haven't provided "Accelerate" versions of the functions, which would go in accelerate.c and use OpenCL. This is an extra layer of difficulty that I can't get into right now.

Nor have I included support for the progress monitor.

A function for -process is given a pointer to ExceptionInfo. I don't (yet) understand this, and have probably got it wrong. Should errors be returned through this, or image->exception?

I probably need ThrowImageException(...).

If errors are encountered within a module, it will return with a non-success flag that makes the calling IM program fail with a message like:

convert: image filter signature mismatch `fillholes': ffffffffffffffff != 220
  @ error/module.c/InvokeDynamicImageFilter/1030.

There will usually be a message before this that explains the problem encountered within the module.

Most modules don't (currently) issue warnings. They either work, or fail.

If an invalid argument is given to a module, it will print a slightly helpful usage message then fail as above.

Hints and tips

printf and fprintf (stderr, ...) work fine inside filter modules. Modules should work quietly, but this is handy for debugging. Some of my modules have a verbose option; these fprintf to stderr, in the usual ImageMagick way. (They should probably use MagickCore function FormatLocaleFile().) I send usage information to stdout.

./configure --help lists the available options.

make, alone, is never needed. make install does the job of make, if needed. But this tip doesn't save time.

make install seems to also do any make required. make is an external command, so it works from either bash or Windows cmd.

The first make after a ./configure re-builds everything so takes a few minutes.

The primary structure at the MagickCore level is an Image. This contains pixel values and metadata about the image, and also data about command settings that have been set so far. Being a structure, an image is usually referenced by a pointer. When the value of that pointer may change, such as by a process function, we naturally have a pointer to a pointer.

What I call an "image list", but the official documentation sometimes calls a queue or stack, is implemented as a doubly-linked list with conventional next and previous pointers. Thus each image can appear in only one list. Functions are provided in list.c to manipulate lists. I expect it is bad practice to use the next and previous pointers and we should always use the provided functions. A reference to an image is also an indirect reference to all the other images in the list and to the list as a whole.

The value passed to a process function always seems to point to the start of the list. When a process function replaces the first image of the list, that pointer will then point to unused memory, so it must be updated. I currently do this (crudely) whenever any image is replaced.

The list probably shouldn't be left empty.

Useful C statements for checking the integrity of a list, assuming Image **images:

assert((*images)->signature == MagickSignature);
printf ("List length %i\n", (int)GetImageListLength(*images));
assert((*images)->previous == (Image *) NULL);

Better, #include chklist.h, then sprinkle calls like chklist("end geodistImage: images",images) and chklist("after GeoDImage: &new_image",&new_image) in the code. Use chklist when pointing to the first entry in the list, or chkentry when pointing to an arbitrary entry (this doesn't check for a NULL previous).

To take advantage of multi-threading, don't use GetVirtualPixels(), GetAuthenticPixels(), QueueAuthenticPixels() and SyncAuthenticPixels(). Instead, use the CacheView versions such as GetCacheViewAuthenticPixels().

Authentic or Virtual? Use Authentic if you want to update pixels, and follow this by SyncCacheViewAuthenticPixels() to ensure they get written. Use Virtual if you only want to read pixels (including pixels outside the image).

Get or Queue? If you are reading and writing pixels, use Get. If you are only writing, the Queue version is (apparently) more efficient.

Testing for the presence of a module

For convenience, I set an environment variable to the directory of the new magick.exe.

echo %IM7DEV% 
C:\cygwin64\home\Alan\imdevins7114\bin\ 

If magick can't find a module, it returns with a non-zero exit code (and writes a message to stderr).

%IM7DEV%magick xc: -process analyze NULL:
echo %ERRORLEVEL% 
0 
%IM7DEV%magick xc: -process nonesuch NULL:
echo %ERRORLEVEL% 
cmd /c exit /B 0
1 

Here are some sample process modules. The first few are skeletons or toys, to show how the MagickCore mechanisms work. Later modules have practical uses. The source files are shown at the bottom of this page. They are also available in a single zip file; see Zipped BAT files.

This is a partial list of which process modules are used in which of my BAT scripts. For a list of scripts and the pages in which they are described, see Zipped BAT files.

Process module BAT files
( iiGauss.bat
@rem oogPower.bat whatLevelP.bat maxLocCont.bat whatLevel.bat
allwhite traceLines.bat histoPeaks.bat fillPix.bat srchImgAg.bat
arctan2 setLabLch.bat slopeXYdirn.bat lab2lch.bat
barymap sqshxyY.bat
cols2mat what3x3.bat grayPoly.bat 24cardSelfGray.bat
cumulhisto eqLimit.bat sh2shPolar.bat sh2shLinear.bat integral.bat invDiffGrad.bat histoStats.bat mkThetaMap.bat r2shPol.bat equSlopeH.bat histo2Img.bat equSlope.bat
darkestpath tileDp.bat rectDp.bat mebcOne.bat
darkestpntpnt lns2ptsX.bat minDp.bat lns2pts.bat shapeDp.bat traceLn.bat traceLines.bat
deintegim lightColCh.bat genMean.bat minMnkSd.bat iiGauss2d.bat srndMinMnkSd.bat deOutlier.bat iiWinSd.bat iiMeanSd.bat iiGaussOne.bat
fillholespri deEdgeFft.bat extLineEnds.bat
for iiGaussK.bat iiGaussMult.bat
if 24cardRot.bat
img2knl img2knl4f.bat img2knl4.bat gaussSlpMag.bat
integim minMnkSd.bat iiGauss2d.bat iiMeanSd.bat iiGaussOne.bat srndMinMnkSd.bat lightColCh.bat iiWinSd.bat iiGaussMult.bat iiGauss.bat deOutlier.bat genMean.bat
invclut histo2Img.bat sh2shPolar.bat r2shPol.bat invLogPolar.bat invHRDM.bat
midlightest find4cornEdgeBlr.bat loHiCols.bat traceLines.bat membrane.bat
mkhisto mkOvClutEnds.bat angramGr.bat mkThetaMap.bat analGrids.bat rawDev.bat mkLapPyr.bat mkHistoImg.bat mkGausPyr.bat matchHistoPyr.bat matchHistoLong.bat matchHisto.bat matchGauss.bat invLogPolar.bat hueChart.bat histoStats.bat histoPeaks.bat histoGraph.bat matchLapHisto.bat histo2Img.bat forceClip.bat equSlope.bat mkOvClut.bat eqLimit.bat entropy.bat demoNoise.bat angramRGB.bat
nearestwhite TjuncLineEnds.bat lns2pts.bat traceLines.bat nearCoast2.bat lns2ptsX.bat traceContour.bat
onelightest findVertLine.bat closestMap.bat tileDp.bat lgstConnCompB.bat lgstConnComp.bat histoPeaks.bat
onewhite dp2Grad.bat minDp.bat clc2pts.bat nearCoast.bat nLightest.bat clc2lpbrk.bat midWhite.bat line2Grad.bat histToeInt.bat followLine.bat find4cornPolDist.bat shapeDp.bat eqLimit.bat
oogbox maxLocCont.bat maxLocCol.bat compressL.bat rawDev.bat dehaze.bat
plotrg nefGamuts.bat xyyHorse.bat rawGamut.bat
rhotheta chb2rgybb.bat rgybb2chb.bat chb2srgb.bat srgb2chb.bat sqshxyY.bat
rmsealpha whatScale.bat mkQuiltLike.bat mkQuilt.bat
sortpixelsblue lns2ptsX.bat
sphaldcl smSpHald.bat
srchimg subSrchPnts.bat srchImgCring.bat salMapCrop.bat chainSrch.bat 24cardXX.bat 24cardX2.bat
srt3d rotCubeAnim.bat

Update for v7.0.7-28

These process modules have used IM code in thread-private.h. This breaks in IM version 7.0.7-28 due to a macro changing name from magick_threads to magick_number_threads. My cure is to create my own macro named MAGICK_THREADS in vsn_defines.h, and use that instead.

Many other changes to the modules have been made in response to changes in IM between v7.0.1-0 and v7.0.7-28, and to fix module bugs.

Commands and scripts on this page are v6, but have also been mechanically converted from v6 to v7 using this table, and then tested:

v6 v7
%IM%convert %IMG7%magick
%IM%identify %IMG7%magick identify
%IM%compare %IMG7%magick compare
%IML%convert %IMG7%magick convert
%IMDEV%convert %IM7DEV%magick
%IM32f%convert %IM7DEV%magick
%PICTBAT% %PICTBATv7%
-mask -read-mask
+mask +read-mask
-radial-blur -rotational-blur

Skeleton module: hello world

hellow.c is a minimal "hello world" example. It takes no arguments. It has no effect on the image list. It just writes text to stdout.

%IM7DEV%magick xc: -precision 20 -process hellow NULL: 
Greetings to wizards of the ImageMagick world!

MAGICKCORE_QUANTUM_DEPTH 32
QuantumRange 4294967295
  V/V-1 0
QuantumScale 2.3283064370807973753e-10
MagickEpsilon 9.9999999999999997989e-13
Quantum Quantum
sizeof:
  MagickRealType 8
  MagickSizeType 8
  int 4
  long int 8
  long long int 8
  float 4
  double 8
  long double 16
  _Float128 16
  Image 13512
  ImageInfo 13024
  Quantum 8
MagickSizeType [MagickSizeType]
MagickSizeFormat [llu]
QuantumFormat: [%g]
MaxMap 65535
MaxTextExtent 4096
MagickPathExtent 4096
Includes HDRI
MAGICKCORE_WINDOWS_SUPPORT not defined
WIN32 not defined
WIN64 not defined
MAGICKCORE_OPENMP_SUPPORT defined
MAGICKCORE_HAVE_CL_CL_H defined
MAGICKCORE_HAVE_OPENCL_CL_H not defined
MAGICKCORE_OPENCL_SUPPORT defined
Written ImageMagickOpenCL.log
GetOpenCLEnabled() = true
Written ImageMagickOpenCL.log
mcle->number_devices = 0

The overhead per image is surprisingly large, but not usually a problem. Just for fun I created a long list with a command like ...

convert xc: xc: xc: ... xc: +append x.png

... with 100,000 xc images. This worked fine, though I suppose it took 1.3 GB of memory.

Skeleton module: echo stuff

echostuff.c echos its arguments, and basic data on each image in the list, to stdout. It has no effect on the image list.

We can call it with no arguments. It lists all the images in the current list. In the following example, there is only one image.

%IM7DEV%magick xc:red -process echostuff NULL: 
echostuff: no arguments
mc=black
echostuff: Input image [0] [red]  depth 32  size 1x1
  Virtual pixel method 0 [(null)]
  mattecolor: (null)
  fuzz 0
  alpha is on: no
  channels 3 meta_channels 0
  ( MaxPixelChannels = 64 )
  GetPixelChannels() = 3
  Number of channels with update trait = 3
Image channels:
  i, channel, name, traits 
   0, 0, R, traits: 2=(update )
   1, 1, G, traits: 2=(update )
   2, 2, B, traits: 2=(update )
channel_map:
  i, channel, offset, traits
  0, 0, 0  traits: 2=(update )
  1, 1, 1  traits: 2=(update )
  2, 2, 2  traits: 2=(update )
  3, 0, 0  traits: 0=(undefined )
  4, 0, 0  traits: 0=(undefined )
  5, 0, 0  traits: 0=(undefined )
  6, 0, 0  traits: 0=(undefined )
  7, 0, 0  traits: 0=(undefined )
  8, 0, 0  traits: 0=(undefined )
  9, 0, 0  traits: 0=(undefined )
  10, 0, 0  traits: 0=(undefined )
  11, 0, 0  traits: 0=(undefined )
  12, 0, 0  traits: 0=(undefined )
  13, 0, 0  traits: 0=(undefined )
  14, 0, 0  traits: 0=(undefined )
  15, 0, 0  traits: 0=(undefined )
  16, 0, 0  traits: 0=(undefined )
  17, 0, 0  traits: 0=(undefined )
  18, 0, 0  traits: 0=(undefined )
  19, 0, 0  traits: 0=(undefined )
  20, 0, 0  traits: 0=(undefined )
  21, 0, 0  traits: 0=(undefined )
  22, 0, 0  traits: 0=(undefined )
  23, 0, 0  traits: 0=(undefined )
  24, 0, 0  traits: 0=(undefined )
  25, 0, 0  traits: 0=(undefined )
  26, 0, 0  traits: 0=(undefined )
  27, 0, 0  traits: 0=(undefined )
  28, 0, 0  traits: 0=(undefined )
  29, 0, 0  traits: 0=(undefined )
  30, 0, 0  traits: 0=(undefined )
  31, 0, 0  traits: 0=(undefined )
  32, 0, 0  traits: 0=(undefined )
  33, 0, 0  traits: 0=(undefined )
  34, 0, 0  traits: 0=(undefined )
  35, 0, 0  traits: 0=(undefined )
  36, 0, 0  traits: 0=(undefined )
  37, 0, 0  traits: 0=(undefined )
  38, 0, 0  traits: 0=(undefined )
  39, 0, 0  traits: 0=(undefined )
  40, 0, 0  traits: 0=(undefined )
  41, 0, 0  traits: 0=(undefined )
  42, 0, 0  traits: 0=(undefined )
  43, 0, 0  traits: 0=(undefined )
  44, 0, 0  traits: 0=(undefined )
  45, 0, 0  traits: 0=(undefined )
  46, 0, 0  traits: 0=(undefined )
  47, 0, 0  traits: 0=(undefined )
  48, 0, 0  traits: 0=(undefined )
  49, 0, 0  traits: 0=(undefined )
  50, 0, 0  traits: 0=(undefined )
  51, 0, 0  traits: 0=(undefined )
  52, 0, 0  traits: 0=(undefined )
  53, 0, 0  traits: 0=(undefined )
  54, 0, 0  traits: 0=(undefined )
  55, 0, 0  traits: 0=(undefined )
  56, 0, 0  traits: 0=(undefined )
  57, 0, 0  traits: 0=(undefined )
  58, 0, 0  traits: 0=(undefined )
  59, 0, 0  traits: 0=(undefined )
  60, 0, 0  traits: 0=(undefined )
  61, 0, 0  traits: 0=(undefined )
  62, 0, 0  traits: 0=(undefined )
  63, 0, 0  traits: 0=(undefined )

 

When we have arguments, we need to bracket the module name and arguments with quotes.

%IM7DEV%magick ^
  xc:red ^
  -size 1x10 gradient:blue-green ^
  -process "echostuff hello world" ^
  NULL: 
echostuff: 2 arguments: [hello] [world]
mc=black
echostuff: Input image [0] [red]  depth 32  size 1x1
  Virtual pixel method 0 [(null)]
  mattecolor: (null)
  fuzz 0
  alpha is on: no
  channels 3 meta_channels 0
  ( MaxPixelChannels = 64 )
  GetPixelChannels() = 3
  Number of channels with update trait = 3
Image channels:
  i, channel, name, traits 
   0, 0, R, traits: 2=(update )
   1, 1, G, traits: 2=(update )
   2, 2, B, traits: 2=(update )
channel_map:
  i, channel, offset, traits
  0, 0, 0  traits: 2=(update )
  1, 1, 1  traits: 2=(update )
  2, 2, 2  traits: 2=(update )
  3, 0, 0  traits: 0=(undefined )
  4, 0, 0  traits: 0=(undefined )
  5, 0, 0  traits: 0=(undefined )
  6, 0, 0  traits: 0=(undefined )
  7, 0, 0  traits: 0=(undefined )
  8, 0, 0  traits: 0=(undefined )
  9, 0, 0  traits: 0=(undefined )
  10, 0, 0  traits: 0=(undefined )
  11, 0, 0  traits: 0=(undefined )
  12, 0, 0  traits: 0=(undefined )
  13, 0, 0  traits: 0=(undefined )
  14, 0, 0  traits: 0=(undefined )
  15, 0, 0  traits: 0=(undefined )
  16, 0, 0  traits: 0=(undefined )
  17, 0, 0  traits: 0=(undefined )
  18, 0, 0  traits: 0=(undefined )
  19, 0, 0  traits: 0=(undefined )
  20, 0, 0  traits: 0=(undefined )
  21, 0, 0  traits: 0=(undefined )
  22, 0, 0  traits: 0=(undefined )
  23, 0, 0  traits: 0=(undefined )
  24, 0, 0  traits: 0=(undefined )
  25, 0, 0  traits: 0=(undefined )
  26, 0, 0  traits: 0=(undefined )
  27, 0, 0  traits: 0=(undefined )
  28, 0, 0  traits: 0=(undefined )
  29, 0, 0  traits: 0=(undefined )
  30, 0, 0  traits: 0=(undefined )
  31, 0, 0  traits: 0=(undefined )
  32, 0, 0  traits: 0=(undefined )
  33, 0, 0  traits: 0=(undefined )
  34, 0, 0  traits: 0=(undefined )
  35, 0, 0  traits: 0=(undefined )
  36, 0, 0  traits: 0=(undefined )
  37, 0, 0  traits: 0=(undefined )
  38, 0, 0  traits: 0=(undefined )
  39, 0, 0  traits: 0=(undefined )
  40, 0, 0  traits: 0=(undefined )
  41, 0, 0  traits: 0=(undefined )
  42, 0, 0  traits: 0=(undefined )
  43, 0, 0  traits: 0=(undefined )
  44, 0, 0  traits: 0=(undefined )
  45, 0, 0  traits: 0=(undefined )
  46, 0, 0  traits: 0=(undefined )
  47, 0, 0  traits: 0=(undefined )
  48, 0, 0  traits: 0=(undefined )
  49, 0, 0  traits: 0=(undefined )
  50, 0, 0  traits: 0=(undefined )
  51, 0, 0  traits: 0=(undefined )
  52, 0, 0  traits: 0=(undefined )
  53, 0, 0  traits: 0=(undefined )
  54, 0, 0  traits: 0=(undefined )
  55, 0, 0  traits: 0=(undefined )
  56, 0, 0  traits: 0=(undefined )
  57, 0, 0  traits: 0=(undefined )
  58, 0, 0  traits: 0=(undefined )
  59, 0, 0  traits: 0=(undefined )
  60, 0, 0  traits: 0=(undefined )
  61, 0, 0  traits: 0=(undefined )
  62, 0, 0  traits: 0=(undefined )
  63, 0, 0  traits: 0=(undefined )
mc=black
echostuff: Input image [1] [blue]  depth 32  size 1x10
  Virtual pixel method 0 [(null)]
  mattecolor: (null)
  fuzz 0
  alpha is on: no
  channels 3 meta_channels 0
  ( MaxPixelChannels = 64 )
  GetPixelChannels() = 3
  Number of channels with update trait = 3
Image channels:
  i, channel, name, traits 
   0, 0, R, traits: 2=(update )
   1, 1, G, traits: 2=(update )
   2, 2, B, traits: 2=(update )
channel_map:
  i, channel, offset, traits
  0, 0, 0  traits: 2=(update )
  1, 1, 1  traits: 2=(update )
  2, 2, 2  traits: 2=(update )
  3, 0, 0  traits: 0=(undefined )
  4, 0, 0  traits: 0=(undefined )
  5, 0, 0  traits: 0=(undefined )
  6, 0, 0  traits: 0=(undefined )
  7, 0, 0  traits: 0=(undefined )
  8, 0, 0  traits: 0=(undefined )
  9, 0, 0  traits: 0=(undefined )
  10, 0, 0  traits: 0=(undefined )
  11, 0, 0  traits: 0=(undefined )
  12, 0, 0  traits: 0=(undefined )
  13, 0, 0  traits: 0=(undefined )
  14, 0, 0  traits: 0=(undefined )
  15, 0, 0  traits: 0=(undefined )
  16, 0, 0  traits: 0=(undefined )
  17, 0, 0  traits: 0=(undefined )
  18, 0, 0  traits: 0=(undefined )
  19, 0, 0  traits: 0=(undefined )
  20, 0, 0  traits: 0=(undefined )
  21, 0, 0  traits: 0=(undefined )
  22, 0, 0  traits: 0=(undefined )
  23, 0, 0  traits: 0=(undefined )
  24, 0, 0  traits: 0=(undefined )
  25, 0, 0  traits: 0=(undefined )
  26, 0, 0  traits: 0=(undefined )
  27, 0, 0  traits: 0=(undefined )
  28, 0, 0  traits: 0=(undefined )
  29, 0, 0  traits: 0=(undefined )
  30, 0, 0  traits: 0=(undefined )
  31, 0, 0  traits: 0=(undefined )
  32, 0, 0  traits: 0=(undefined )
  33, 0, 0  traits: 0=(undefined )
  34, 0, 0  traits: 0=(undefined )
  35, 0, 0  traits: 0=(undefined )
  36, 0, 0  traits: 0=(undefined )
  37, 0, 0  traits: 0=(undefined )
  38, 0, 0  traits: 0=(undefined )
  39, 0, 0  traits: 0=(undefined )
  40, 0, 0  traits: 0=(undefined )
  41, 0, 0  traits: 0=(undefined )
  42, 0, 0  traits: 0=(undefined )
  43, 0, 0  traits: 0=(undefined )
  44, 0, 0  traits: 0=(undefined )
  45, 0, 0  traits: 0=(undefined )
  46, 0, 0  traits: 0=(undefined )
  47, 0, 0  traits: 0=(undefined )
  48, 0, 0  traits: 0=(undefined )
  49, 0, 0  traits: 0=(undefined )
  50, 0, 0  traits: 0=(undefined )
  51, 0, 0  traits: 0=(undefined )
  52, 0, 0  traits: 0=(undefined )
  53, 0, 0  traits: 0=(undefined )
  54, 0, 0  traits: 0=(undefined )
  55, 0, 0  traits: 0=(undefined )
  56, 0, 0  traits: 0=(undefined )
  57, 0, 0  traits: 0=(undefined )
  58, 0, 0  traits: 0=(undefined )
  59, 0, 0  traits: 0=(undefined )
  60, 0, 0  traits: 0=(undefined )
  61, 0, 0  traits: 0=(undefined )
  62, 0, 0  traits: 0=(undefined )
  63, 0, 0  traits: 0=(undefined )

 

Single quotes also work. The "wizard" image occurs after -process, so echostuff can't see it.

%IM7DEV%magick ^
  xc:red ^
  -size 1x10 gradient:blue-green ^
  rose: ^
  -process 'echostuff hello world' ^
  wizard: ^
  NULL: 
echostuff: 2 arguments: [hello] [world]
mc=black
echostuff: Input image [0] [red]  depth 32  size 1x1
  Virtual pixel method 0 [(null)]
  mattecolor: (null)
  fuzz 0
  alpha is on: no
  channels 3 meta_channels 0
  ( MaxPixelChannels = 64 )
  GetPixelChannels() = 3
  Number of channels with update trait = 3
Image channels:
  i, channel, name, traits 
   0, 0, R, traits: 2=(update )
   1, 1, G, traits: 2=(update )
   2, 2, B, traits: 2=(update )
channel_map:
  i, channel, offset, traits
  0, 0, 0  traits: 2=(update )
  1, 1, 1  traits: 2=(update )
  2, 2, 2  traits: 2=(update )
  3, 0, 0  traits: 0=(undefined )
  4, 0, 0  traits: 0=(undefined )
  5, 0, 0  traits: 0=(undefined )
  6, 0, 0  traits: 0=(undefined )
  7, 0, 0  traits: 0=(undefined )
  8, 0, 0  traits: 0=(undefined )
  9, 0, 0  traits: 0=(undefined )
  10, 0, 0  traits: 0=(undefined )
  11, 0, 0  traits: 0=(undefined )
  12, 0, 0  traits: 0=(undefined )
  13, 0, 0  traits: 0=(undefined )
  14, 0, 0  traits: 0=(undefined )
  15, 0, 0  traits: 0=(undefined )
  16, 0, 0  traits: 0=(undefined )
  17, 0, 0  traits: 0=(undefined )
  18, 0, 0  traits: 0=(undefined )
  19, 0, 0  traits: 0=(undefined )
  20, 0, 0  traits: 0=(undefined )
  21, 0, 0  traits: 0=(undefined )
  22, 0, 0  traits: 0=(undefined )
  23, 0, 0  traits: 0=(undefined )
  24, 0, 0  traits: 0=(undefined )
  25, 0, 0  traits: 0=(undefined )
  26, 0, 0  traits: 0=(undefined )
  27, 0, 0  traits: 0=(undefined )
  28, 0, 0  traits: 0=(undefined )
  29, 0, 0  traits: 0=(undefined )
  30, 0, 0  traits: 0=(undefined )
  31, 0, 0  traits: 0=(undefined )
  32, 0, 0  traits: 0=(undefined )
  33, 0, 0  traits: 0=(undefined )
  34, 0, 0  traits: 0=(undefined )
  35, 0, 0  traits: 0=(undefined )
  36, 0, 0  traits: 0=(undefined )
  37, 0, 0  traits: 0=(undefined )
  38, 0, 0  traits: 0=(undefined )
  39, 0, 0  traits: 0=(undefined )
  40, 0, 0  traits: 0=(undefined )
  41, 0, 0  traits: 0=(undefined )
  42, 0, 0  traits: 0=(undefined )
  43, 0, 0  traits: 0=(undefined )
  44, 0, 0  traits: 0=(undefined )
  45, 0, 0  traits: 0=(undefined )
  46, 0, 0  traits: 0=(undefined )
  47, 0, 0  traits: 0=(undefined )
  48, 0, 0  traits: 0=(undefined )
  49, 0, 0  traits: 0=(undefined )
  50, 0, 0  traits: 0=(undefined )
  51, 0, 0  traits: 0=(undefined )
  52, 0, 0  traits: 0=(undefined )
  53, 0, 0  traits: 0=(undefined )
  54, 0, 0  traits: 0=(undefined )
  55, 0, 0  traits: 0=(undefined )
  56, 0, 0  traits: 0=(undefined )
  57, 0, 0  traits: 0=(undefined )
  58, 0, 0  traits: 0=(undefined )
  59, 0, 0  traits: 0=(undefined )
  60, 0, 0  traits: 0=(undefined )
  61, 0, 0  traits: 0=(undefined )
  62, 0, 0  traits: 0=(undefined )
  63, 0, 0  traits: 0=(undefined )
mc=black
echostuff: Input image [1] [blue]  depth 32  size 1x10
  Virtual pixel method 0 [(null)]
  mattecolor: (null)
  fuzz 0
  alpha is on: no
  channels 3 meta_channels 0
  ( MaxPixelChannels = 64 )
  GetPixelChannels() = 3
  Number of channels with update trait = 3
Image channels:
  i, channel, name, traits 
   0, 0, R, traits: 2=(update )
   1, 1, G, traits: 2=(update )
   2, 2, B, traits: 2=(update )
channel_map:
  i, channel, offset, traits
  0, 0, 0  traits: 2=(update )
  1, 1, 1  traits: 2=(update )
  2, 2, 2  traits: 2=(update )
  3, 0, 0  traits: 0=(undefined )
  4, 0, 0  traits: 0=(undefined )
  5, 0, 0  traits: 0=(undefined )
  6, 0, 0  traits: 0=(undefined )
  7, 0, 0  traits: 0=(undefined )
  8, 0, 0  traits: 0=(undefined )
  9, 0, 0  traits: 0=(undefined )
  10, 0, 0  traits: 0=(undefined )
  11, 0, 0  traits: 0=(undefined )
  12, 0, 0  traits: 0=(undefined )
  13, 0, 0  traits: 0=(undefined )
  14, 0, 0  traits: 0=(undefined )
  15, 0, 0  traits: 0=(undefined )
  16, 0, 0  traits: 0=(undefined )
  17, 0, 0  traits: 0=(undefined )
  18, 0, 0  traits: 0=(undefined )
  19, 0, 0  traits: 0=(undefined )
  20, 0, 0  traits: 0=(undefined )
  21, 0, 0  traits: 0=(undefined )
  22, 0, 0  traits: 0=(undefined )
  23, 0, 0  traits: 0=(undefined )
  24, 0, 0  traits: 0=(undefined )
  25, 0, 0  traits: 0=(undefined )
  26, 0, 0  traits: 0=(undefined )
  27, 0, 0  traits: 0=(undefined )
  28, 0, 0  traits: 0=(undefined )
  29, 0, 0  traits: 0=(undefined )
  30, 0, 0  traits: 0=(undefined )
  31, 0, 0  traits: 0=(undefined )
  32, 0, 0  traits: 0=(undefined )
  33, 0, 0  traits: 0=(undefined )
  34, 0, 0  traits: 0=(undefined )
  35, 0, 0  traits: 0=(undefined )
  36, 0, 0  traits: 0=(undefined )
  37, 0, 0  traits: 0=(undefined )
  38, 0, 0  traits: 0=(undefined )
  39, 0, 0  traits: 0=(undefined )
  40, 0, 0  traits: 0=(undefined )
  41, 0, 0  traits: 0=(undefined )
  42, 0, 0  traits: 0=(undefined )
  43, 0, 0  traits: 0=(undefined )
  44, 0, 0  traits: 0=(undefined )
  45, 0, 0  traits: 0=(undefined )
  46, 0, 0  traits: 0=(undefined )
  47, 0, 0  traits: 0=(undefined )
  48, 0, 0  traits: 0=(undefined )
  49, 0, 0  traits: 0=(undefined )
  50, 0, 0  traits: 0=(undefined )
  51, 0, 0  traits: 0=(undefined )
  52, 0, 0  traits: 0=(undefined )
  53, 0, 0  traits: 0=(undefined )
  54, 0, 0  traits: 0=(undefined )
  55, 0, 0  traits: 0=(undefined )
  56, 0, 0  traits: 0=(undefined )
  57, 0, 0  traits: 0=(undefined )
  58, 0, 0  traits: 0=(undefined )
  59, 0, 0  traits: 0=(undefined )
  60, 0, 0  traits: 0=(undefined )
  61, 0, 0  traits: 0=(undefined )
  62, 0, 0  traits: 0=(undefined )
  63, 0, 0  traits: 0=(undefined )
mc=black
echostuff: Input image [2] [ROSE]  depth 8  size 70x46
  Virtual pixel method 0 [(null)]
  mattecolor: (null)
  fuzz 0
  alpha is on: no
  channels 3 meta_channels 0
  ( MaxPixelChannels = 64 )
  GetPixelChannels() = 3
  Number of channels with update trait = 3
Image channels:
  i, channel, name, traits 
   0, 0, R, traits: 2=(update )
   1, 1, G, traits: 2=(update )
   2, 2, B, traits: 2=(update )
channel_map:
  i, channel, offset, traits
  0, 0, 0  traits: 2=(update )
  1, 1, 1  traits: 2=(update )
  2, 2, 2  traits: 2=(update )
  3, 0, 0  traits: 0=(undefined )
  4, 0, 0  traits: 0=(undefined )
  5, 0, 0  traits: 0=(undefined )
  6, 0, 0  traits: 0=(undefined )
  7, 0, 0  traits: 0=(undefined )
  8, 0, 0  traits: 0=(undefined )
  9, 0, 0  traits: 0=(undefined )
  10, 0, 0  traits: 0=(undefined )
  11, 0, 0  traits: 0=(undefined )
  12, 0, 0  traits: 0=(undefined )
  13, 0, 0  traits: 0=(undefined )
  14, 0, 0  traits: 0=(undefined )
  15, 0, 0  traits: 0=(undefined )
  16, 0, 0  traits: 0=(undefined )
  17, 0, 0  traits: 0=(undefined )
  18, 0, 0  traits: 0=(undefined )
  19, 0, 0  traits: 0=(undefined )
  20, 0, 0  traits: 0=(undefined )
  21, 0, 0  traits: 0=(undefined )
  22, 0, 0  traits: 0=(undefined )
  23, 0, 0  traits: 0=(undefined )
  24, 0, 0  traits: 0=(undefined )
  25, 0, 0  traits: 0=(undefined )
  26, 0, 0  traits: 0=(undefined )
  27, 0, 0  traits: 0=(undefined )
  28, 0, 0  traits: 0=(undefined )
  29, 0, 0  traits: 0=(undefined )
  30, 0, 0  traits: 0=(undefined )
  31, 0, 0  traits: 0=(undefined )
  32, 0, 0  traits: 0=(undefined )
  33, 0, 0  traits: 0=(undefined )
  34, 0, 0  traits: 0=(undefined )
  35, 0, 0  traits: 0=(undefined )
  36, 0, 0  traits: 0=(undefined )
  37, 0, 0  traits: 0=(undefined )
  38, 0, 0  traits: 0=(undefined )
  39, 0, 0  traits: 0=(undefined )
  40, 0, 0  traits: 0=(undefined )
  41, 0, 0  traits: 0=(undefined )
  42, 0, 0  traits: 0=(undefined )
  43, 0, 0  traits: 0=(undefined )
  44, 0, 0  traits: 0=(undefined )
  45, 0, 0  traits: 0=(undefined )
  46, 0, 0  traits: 0=(undefined )
  47, 0, 0  traits: 0=(undefined )
  48, 0, 0  traits: 0=(undefined )
  49, 0, 0  traits: 0=(undefined )
  50, 0, 0  traits: 0=(undefined )
  51, 0, 0  traits: 0=(undefined )
  52, 0, 0  traits: 0=(undefined )
  53, 0, 0  traits: 0=(undefined )
  54, 0, 0  traits: 0=(undefined )
  55, 0, 0  traits: 0=(undefined )
  56, 0, 0  traits: 0=(undefined )
  57, 0, 0  traits: 0=(undefined )
  58, 0, 0  traits: 0=(undefined )
  59, 0, 0  traits: 0=(undefined )
  60, 0, 0  traits: 0=(undefined )
  61, 0, 0  traits: 0=(undefined )
  62, 0, 0  traits: 0=(undefined )
  63, 0, 0  traits: 0=(undefined )

 

The process is within parentheses, so the only image is the clone.

%IM7DEV%magick ^
  xc:red ^
  -size 1x10 gradient:blue-green ^
  rose: ^
  ( -clone 0 ^
    -process 'echostuff hello world' ^
  ) ^
  wizard: ^
  NULL: 
echostuff: 2 arguments: [hello] [world]
mc=black
echostuff: Input image [0] [red]  depth 32  size 1x1
  Virtual pixel method 0 [(null)]
  mattecolor: (null)
  fuzz 0
  alpha is on: no
  channels 3 meta_channels 0
  ( MaxPixelChannels = 64 )
  GetPixelChannels() = 3
  Number of channels with update trait = 3
Image channels:
  i, channel, name, traits 
   0, 0, R, traits: 2=(update )
   1, 1, G, traits: 2=(update )
   2, 2, B, traits: 2=(update )
channel_map:
  i, channel, offset, traits
  0, 0, 0  traits: 2=(update )
  1, 1, 1  traits: 2=(update )
  2, 2, 2  traits: 2=(update )
  3, 0, 0  traits: 0=(undefined )
  4, 0, 0  traits: 0=(undefined )
  5, 0, 0  traits: 0=(undefined )
  6, 0, 0  traits: 0=(undefined )
  7, 0, 0  traits: 0=(undefined )
  8, 0, 0  traits: 0=(undefined )
  9, 0, 0  traits: 0=(undefined )
  10, 0, 0  traits: 0=(undefined )
  11, 0, 0  traits: 0=(undefined )
  12, 0, 0  traits: 0=(undefined )
  13, 0, 0  traits: 0=(undefined )
  14, 0, 0  traits: 0=(undefined )
  15, 0, 0  traits: 0=(undefined )
  16, 0, 0  traits: 0=(undefined )
  17, 0, 0  traits: 0=(undefined )
  18, 0, 0  traits: 0=(undefined )
  19, 0, 0  traits: 0=(undefined )
  20, 0, 0  traits: 0=(undefined )
  21, 0, 0  traits: 0=(undefined )
  22, 0, 0  traits: 0=(undefined )
  23, 0, 0  traits: 0=(undefined )
  24, 0, 0  traits: 0=(undefined )
  25, 0, 0  traits: 0=(undefined )
  26, 0, 0  traits: 0=(undefined )
  27, 0, 0  traits: 0=(undefined )
  28, 0, 0  traits: 0=(undefined )
  29, 0, 0  traits: 0=(undefined )
  30, 0, 0  traits: 0=(undefined )
  31, 0, 0  traits: 0=(undefined )
  32, 0, 0  traits: 0=(undefined )
  33, 0, 0  traits: 0=(undefined )
  34, 0, 0  traits: 0=(undefined )
  35, 0, 0  traits: 0=(undefined )
  36, 0, 0  traits: 0=(undefined )
  37, 0, 0  traits: 0=(undefined )
  38, 0, 0  traits: 0=(undefined )
  39, 0, 0  traits: 0=(undefined )
  40, 0, 0  traits: 0=(undefined )
  41, 0, 0  traits: 0=(undefined )
  42, 0, 0  traits: 0=(undefined )
  43, 0, 0  traits: 0=(undefined )
  44, 0, 0  traits: 0=(undefined )
  45, 0, 0  traits: 0=(undefined )
  46, 0, 0  traits: 0=(undefined )
  47, 0, 0  traits: 0=(undefined )
  48, 0, 0  traits: 0=(undefined )
  49, 0, 0  traits: 0=(undefined )
  50, 0, 0  traits: 0=(undefined )
  51, 0, 0  traits: 0=(undefined )
  52, 0, 0  traits: 0=(undefined )
  53, 0, 0  traits: 0=(undefined )
  54, 0, 0  traits: 0=(undefined )
  55, 0, 0  traits: 0=(undefined )
  56, 0, 0  traits: 0=(undefined )
  57, 0, 0  traits: 0=(undefined )
  58, 0, 0  traits: 0=(undefined )
  59, 0, 0  traits: 0=(undefined )
  60, 0, 0  traits: 0=(undefined )
  61, 0, 0  traits: 0=(undefined )
  62, 0, 0  traits: 0=(undefined )
  63, 0, 0  traits: 0=(undefined )

 

What happens if we don't clone?

%IM7DEV%magick ^
  xc:red ^
  -size 1x10 gradient:blue-green ^
  rose: ^
  ( ^
    -process 'echostuff hello world' ^
  ) ^
  wizard: ^
  NULL: 

cmd /c exit /B 0
magick: no images found for operation `-process' at CLI arg 7 @ error/operation.c/CLIOption/5479.

IM v7 correctly reports an error: we start a new list with "(", and then try to "-process" an image, but that are no images in the list.

From v6, we get a weird result. The process module is given a list of four images: the three that come before it, and also the one that comes after it. This may be a legacy from old syntax when arguments were given before the images. Open parenthesis should normally be immediately followed by an image (not an operation) so I would regard the visibility of wizard to the module as an "undocumented feature".

Skeleton module: dump image

dumpimage.c dumps some data about each image in the list to stdout. It is sensitive to the "-precision" setting. It has no effect on the image list.

%IM7DEV%magick ^
  -size 1x10 gradient:red-blue ^
  -precision 15 ^
  -process dumpimage ^
  NULL: 
Input image [0] [red]  depth 32  size 1x10
channels=3
0,0: 4294967295,0,0,4294967295
0,1: 3817748706.66667,0,477218588.333333,4294967295
0,2: 3340530118.33333,0,954437176.666667,4294967295
0,3: 2863311530,0,1431655765,4294967295
0,4: 2386092941.66667,0,1908874353.33333,4294967295
0,5: 1908874353.33333,0,2386092941.66667,4294967295
0,6: 1431655765,0,2863311530,4294967295
0,7: 954437176.666667,0,3340530118.33333,4294967295
0,8: 477218588.333334,0,3817748706.66667,4294967295
0,9: 0,0,4294967295,4294967295

Skeleton module: add end

addend.c adds an image to the end of the list. The new image is a clone of the last image, the same size, but entirely black.

-process addend is equivalent to ( -repect-parentheses +clone -fill black -colorize 100 ).

%IM7DEV%magick ^
  toes.png ^
  -process addend ^
  -delete 0 ^
  cu_addend.png
cu_addend.png

We can use echostuff to see what is happening. This is similar to using -write info:. We create two images, so addend adds a third.

%IM7DEV%magick ^
  toes.png ^
  ( +clone -fill khaki -colorize 50 -resize 75%% ) ^
  -process addend ^
  -process echostuff ^
  -write info: ^
  +append +repage ^
  cu_addend2.png 
echostuff: no arguments
mc=black
echostuff: Input image [0] [toes.png]  depth 16  size 267x233
  Virtual pixel method 0 [(null)]
  mattecolor: (null)
  fuzz 0
  alpha is on: no
  channels 3 meta_channels 0
  ( MaxPixelChannels = 64 )
  GetPixelChannels() = 3
  Number of channels with update trait = 3
Image channels:
  i, channel, name, traits 
   0, 0, R, traits: 2=(update )
   1, 1, G, traits: 2=(update )
   2, 2, B, traits: 2=(update )
channel_map:
  i, channel, offset, traits
  0, 0, 0  traits: 2=(update )
  1, 1, 1  traits: 2=(update )
  2, 2, 2  traits: 2=(update )
  3, 0, 0  traits: 0=(undefined )
  4, 0, 0  traits: 0=(undefined )
  5, 0, 0  traits: 0=(undefined )
  6, 0, 0  traits: 0=(undefined )
  7, 0, 0  traits: 0=(undefined )
  8, 0, 0  traits: 0=(undefined )
  9, 0, 0  traits: 0=(undefined )
  10, 0, 0  traits: 0=(undefined )
  11, 0, 0  traits: 0=(undefined )
  12, 0, 0  traits: 0=(undefined )
  13, 0, 0  traits: 0=(undefined )
  14, 0, 0  traits: 0=(undefined )
  15, 0, 0  traits: 0=(undefined )
  16, 0, 0  traits: 0=(undefined )
  17, 0, 0  traits: 0=(undefined )
  18, 0, 0  traits: 0=(undefined )
  19, 0, 0  traits: 0=(undefined )
  20, 0, 0  traits: 0=(undefined )
  21, 0, 0  traits: 0=(undefined )
  22, 0, 0  traits: 0=(undefined )
  23, 0, 0  traits: 0=(undefined )
  24, 0, 0  traits: 0=(undefined )
  25, 0, 0  traits: 0=(undefined )
  26, 0, 0  traits: 0=(undefined )
  27, 0, 0  traits: 0=(undefined )
  28, 0, 0  traits: 0=(undefined )
  29, 0, 0  traits: 0=(undefined )
  30, 0, 0  traits: 0=(undefined )
  31, 0, 0  traits: 0=(undefined )
  32, 0, 0  traits: 0=(undefined )
  33, 0, 0  traits: 0=(undefined )
  34, 0, 0  traits: 0=(undefined )
  35, 0, 0  traits: 0=(undefined )
  36, 0, 0  traits: 0=(undefined )
  37, 0, 0  traits: 0=(undefined )
  38, 0, 0  traits: 0=(undefined )
  39, 0, 0  traits: 0=(undefined )
  40, 0, 0  traits: 0=(undefined )
  41, 0, 0  traits: 0=(undefined )
  42, 0, 0  traits: 0=(undefined )
  43, 0, 0  traits: 0=(undefined )
  44, 0, 0  traits: 0=(undefined )
  45, 0, 0  traits: 0=(undefined )
  46, 0, 0  traits: 0=(undefined )
  47, 0, 0  traits: 0=(undefined )
  48, 0, 0  traits: 0=(undefined )
  49, 0, 0  traits: 0=(undefined )
  50, 0, 0  traits: 0=(undefined )
  51, 0, 0  traits: 0=(undefined )
  52, 0, 0  traits: 0=(undefined )
  53, 0, 0  traits: 0=(undefined )
  54, 0, 0  traits: 0=(undefined )
  55, 0, 0  traits: 0=(undefined )
  56, 0, 0  traits: 0=(undefined )
  57, 0, 0  traits: 0=(undefined )
  58, 0, 0  traits: 0=(undefined )
  59, 0, 0  traits: 0=(undefined )
  60, 0, 0  traits: 0=(undefined )
  61, 0, 0  traits: 0=(undefined )
  62, 0, 0  traits: 0=(undefined )
  63, 0, 0  traits: 0=(undefined )
mc=black
echostuff: Input image [1] [toes.png]  depth 16  size 200x175
  Virtual pixel method 0 [(null)]
  mattecolor: (null)
  fuzz 0
  alpha is on: no
  channels 3 meta_channels 0
  ( MaxPixelChannels = 64 )
  GetPixelChannels() = 3
  Number of channels with update trait = 3
Image channels:
  i, channel, name, traits 
   0, 0, R, traits: 2=(update )
   1, 1, G, traits: 2=(update )
   2, 2, B, traits: 2=(update )
channel_map:
  i, channel, offset, traits
  0, 0, 0  traits: 2=(update )
  1, 1, 1  traits: 2=(update )
  2, 2, 2  traits: 2=(update )
  3, 0, 0  traits: 0=(undefined )
  4, 0, 0  traits: 0=(undefined )
  5, 0, 0  traits: 0=(undefined )
  6, 0, 0  traits: 0=(undefined )
  7, 0, 0  traits: 0=(undefined )
  8, 0, 0  traits: 0=(undefined )
  9, 0, 0  traits: 0=(undefined )
  10, 0, 0  traits: 0=(undefined )
  11, 0, 0  traits: 0=(undefined )
  12, 0, 0  traits: 0=(undefined )
  13, 0, 0  traits: 0=(undefined )
  14, 0, 0  traits: 0=(undefined )
  15, 0, 0  traits: 0=(undefined )
  16, 0, 0  traits: 0=(undefined )
  17, 0, 0  traits: 0=(undefined )
  18, 0, 0  traits: 0=(undefined )
  19, 0, 0  traits: 0=(undefined )
  20, 0, 0  traits: 0=(undefined )
  21, 0, 0  traits: 0=(undefined )
  22, 0, 0  traits: 0=(undefined )
  23, 0, 0  traits: 0=(undefined )
  24, 0, 0  traits: 0=(undefined )
  25, 0, 0  traits: 0=(undefined )
  26, 0, 0  traits: 0=(undefined )
  27, 0, 0  traits: 0=(undefined )
  28, 0, 0  traits: 0=(undefined )
  29, 0, 0  traits: 0=(undefined )
  30, 0, 0  traits: 0=(undefined )
  31, 0, 0  traits: 0=(undefined )
  32, 0, 0  traits: 0=(undefined )
  33, 0, 0  traits: 0=(undefined )
  34, 0, 0  traits: 0=(undefined )
  35, 0, 0  traits: 0=(undefined )
  36, 0, 0  traits: 0=(undefined )
  37, 0, 0  traits: 0=(undefined )
  38, 0, 0  traits: 0=(undefined )
  39, 0, 0  traits: 0=(undefined )
  40, 0, 0  traits: 0=(undefined )
  41, 0, 0  traits: 0=(undefined )
  42, 0, 0  traits: 0=(undefined )
  43, 0, 0  traits: 0=(undefined )
  44, 0, 0  traits: 0=(undefined )
  45, 0, 0  traits: 0=(undefined )
  46, 0, 0  traits: 0=(undefined )
  47, 0, 0  traits: 0=(undefined )
  48, 0, 0  traits: 0=(undefined )
  49, 0, 0  traits: 0=(undefined )
  50, 0, 0  traits: 0=(undefined )
  51, 0, 0  traits: 0=(undefined )
  52, 0, 0  traits: 0=(undefined )
  53, 0, 0  traits: 0=(undefined )
  54, 0, 0  traits: 0=(undefined )
  55, 0, 0  traits: 0=(undefined )
  56, 0, 0  traits: 0=(undefined )
  57, 0, 0  traits: 0=(undefined )
  58, 0, 0  traits: 0=(undefined )
  59, 0, 0  traits: 0=(undefined )
  60, 0, 0  traits: 0=(undefined )
  61, 0, 0  traits: 0=(undefined )
  62, 0, 0  traits: 0=(undefined )
  63, 0, 0  traits: 0=(undefined )
mc=black
echostuff: Input image [2] [toes.png]  depth 16  size 200x175
  Virtual pixel method 0 [(null)]
  mattecolor: (null)
  fuzz 0
  alpha is on: no
  channels 3 meta_channels 0
  ( MaxPixelChannels = 64 )
  GetPixelChannels() = 3
  Number of channels with update trait = 3
Image channels:
  i, channel, name, traits 
   0, 0, R, traits: 2=(update )
   1, 1, G, traits: 2=(update )
   2, 2, B, traits: 2=(update )
channel_map:
  i, channel, offset, traits
  0, 0, 0  traits: 2=(update )
  1, 1, 1  traits: 2=(update )
  2, 2, 2  traits: 2=(update )
  3, 0, 0  traits: 0=(undefined )
  4, 0, 0  traits: 0=(undefined )
  5, 0, 0  traits: 0=(undefined )
  6, 0, 0  traits: 0=(undefined )
  7, 0, 0  traits: 0=(undefined )
  8, 0, 0  traits: 0=(undefined )
  9, 0, 0  traits: 0=(undefined )
  10, 0, 0  traits: 0=(undefined )
  11, 0, 0  traits: 0=(undefined )
  12, 0, 0  traits: 0=(undefined )
  13, 0, 0  traits: 0=(undefined )
  14, 0, 0  traits: 0=(undefined )
  15, 0, 0  traits: 0=(undefined )
  16, 0, 0  traits: 0=(undefined )
  17, 0, 0  traits: 0=(undefined )
  18, 0, 0  traits: 0=(undefined )
  19, 0, 0  traits: 0=(undefined )
  20, 0, 0  traits: 0=(undefined )
  21, 0, 0  traits: 0=(undefined )
  22, 0, 0  traits: 0=(undefined )
  23, 0, 0  traits: 0=(undefined )
  24, 0, 0  traits: 0=(undefined )
  25, 0, 0  traits: 0=(undefined )
  26, 0, 0  traits: 0=(undefined )
  27, 0, 0  traits: 0=(undefined )
  28, 0, 0  traits: 0=(undefined )
  29, 0, 0  traits: 0=(undefined )
  30, 0, 0  traits: 0=(undefined )
  31, 0, 0  traits: 0=(undefined )
  32, 0, 0  traits: 0=(undefined )
  33, 0, 0  traits: 0=(undefined )
  34, 0, 0  traits: 0=(undefined )
  35, 0, 0  traits: 0=(undefined )
  36, 0, 0  traits: 0=(undefined )
  37, 0, 0  traits: 0=(undefined )
  38, 0, 0  traits: 0=(undefined )
  39, 0, 0  traits: 0=(undefined )
  40, 0, 0  traits: 0=(undefined )
  41, 0, 0  traits: 0=(undefined )
  42, 0, 0  traits: 0=(undefined )
  43, 0, 0  traits: 0=(undefined )
  44, 0, 0  traits: 0=(undefined )
  45, 0, 0  traits: 0=(undefined )
  46, 0, 0  traits: 0=(undefined )
  47, 0, 0  traits: 0=(undefined )
  48, 0, 0  traits: 0=(undefined )
  49, 0, 0  traits: 0=(undefined )
  50, 0, 0  traits: 0=(undefined )
  51, 0, 0  traits: 0=(undefined )
  52, 0, 0  traits: 0=(undefined )
  53, 0, 0  traits: 0=(undefined )
  54, 0, 0  traits: 0=(undefined )
  55, 0, 0  traits: 0=(undefined )
  56, 0, 0  traits: 0=(undefined )
  57, 0, 0  traits: 0=(undefined )
  58, 0, 0  traits: 0=(undefined )
  59, 0, 0  traits: 0=(undefined )
  60, 0, 0  traits: 0=(undefined )
  61, 0, 0  traits: 0=(undefined )
  62, 0, 0  traits: 0=(undefined )
  63, 0, 0  traits: 0=(undefined )
toes.png[0] PNG 267x233 267x233+0+0 16-bit TrueColor sRGB 320268B 0.094u 0:00.112
toes.png[1] PNG 200x175 200x175+0+0 16-bit sRGB 320268B 0.094u 0:00.112
toes.png[2] PNG 200x175 200x175+0+0 16-bit sRGB 320268B 0.094u 0:00.112
cu_addend2.pngjpg

Skeleton module: replace last

replacelast.c replaces the last image in the list with a green image.

-process replacelast is equivalent to ( -repect-parentheses +clone -fill green -colorize 100 ) -delete -2.

%IM7DEV%magick ^
  toes.png ^
  ( +clone -fill khaki -colorize 50 -resize 75%% ) ^
  -process replacelast ^
  -process echostuff ^
  -write info: ^
  +append +repage ^
  cu_repl.png 
echostuff: no arguments
mc=black
echostuff: Input image [0] [toes.png]  depth 16  size 267x233
  Virtual pixel method 0 [(null)]
  mattecolor: (null)
  fuzz 0
  alpha is on: no
  channels 3 meta_channels 0
  ( MaxPixelChannels = 64 )
  GetPixelChannels() = 3
  Number of channels with update trait = 3
Image channels:
  i, channel, name, traits 
   0, 0, R, traits: 2=(update )
   1, 1, G, traits: 2=(update )
   2, 2, B, traits: 2=(update )
channel_map:
  i, channel, offset, traits
  0, 0, 0  traits: 2=(update )
  1, 1, 1  traits: 2=(update )
  2, 2, 2  traits: 2=(update )
  3, 0, 0  traits: 0=(undefined )
  4, 0, 0  traits: 0=(undefined )
  5, 0, 0  traits: 0=(undefined )
  6, 0, 0  traits: 0=(undefined )
  7, 0, 0  traits: 0=(undefined )
  8, 0, 0  traits: 0=(undefined )
  9, 0, 0  traits: 0=(undefined )
  10, 0, 0  traits: 0=(undefined )
  11, 0, 0  traits: 0=(undefined )
  12, 0, 0  traits: 0=(undefined )
  13, 0, 0  traits: 0=(undefined )
  14, 0, 0  traits: 0=(undefined )
  15, 0, 0  traits: 0=(undefined )
  16, 0, 0  traits: 0=(undefined )
  17, 0, 0  traits: 0=(undefined )
  18, 0, 0  traits: 0=(undefined )
  19, 0, 0  traits: 0=(undefined )
  20, 0, 0  traits: 0=(undefined )
  21, 0, 0  traits: 0=(undefined )
  22, 0, 0  traits: 0=(undefined )
  23, 0, 0  traits: 0=(undefined )
  24, 0, 0  traits: 0=(undefined )
  25, 0, 0  traits: 0=(undefined )
  26, 0, 0  traits: 0=(undefined )
  27, 0, 0  traits: 0=(undefined )
  28, 0, 0  traits: 0=(undefined )
  29, 0, 0  traits: 0=(undefined )
  30, 0, 0  traits: 0=(undefined )
  31, 0, 0  traits: 0=(undefined )
  32, 0, 0  traits: 0=(undefined )
  33, 0, 0  traits: 0=(undefined )
  34, 0, 0  traits: 0=(undefined )
  35, 0, 0  traits: 0=(undefined )
  36, 0, 0  traits: 0=(undefined )
  37, 0, 0  traits: 0=(undefined )
  38, 0, 0  traits: 0=(undefined )
  39, 0, 0  traits: 0=(undefined )
  40, 0, 0  traits: 0=(undefined )
  41, 0, 0  traits: 0=(undefined )
  42, 0, 0  traits: 0=(undefined )
  43, 0, 0  traits: 0=(undefined )
  44, 0, 0  traits: 0=(undefined )
  45, 0, 0  traits: 0=(undefined )
  46, 0, 0  traits: 0=(undefined )
  47, 0, 0  traits: 0=(undefined )
  48, 0, 0  traits: 0=(undefined )
  49, 0, 0  traits: 0=(undefined )
  50, 0, 0  traits: 0=(undefined )
  51, 0, 0  traits: 0=(undefined )
  52, 0, 0  traits: 0=(undefined )
  53, 0, 0  traits: 0=(undefined )
  54, 0, 0  traits: 0=(undefined )
  55, 0, 0  traits: 0=(undefined )
  56, 0, 0  traits: 0=(undefined )
  57, 0, 0  traits: 0=(undefined )
  58, 0, 0  traits: 0=(undefined )
  59, 0, 0  traits: 0=(undefined )
  60, 0, 0  traits: 0=(undefined )
  61, 0, 0  traits: 0=(undefined )
  62, 0, 0  traits: 0=(undefined )
  63, 0, 0  traits: 0=(undefined )
mc=black
echostuff: Input image [1] [toes.png]  depth 8  size 200x175
  Virtual pixel method 0 [(null)]
  mattecolor: (null)
  fuzz 0
  alpha is on: no
  channels 3 meta_channels 0
  ( MaxPixelChannels = 64 )
  GetPixelChannels() = 3
  Number of channels with update trait = 3
Image channels:
  i, channel, name, traits 
   0, 0, R, traits: 2=(update )
   1, 1, G, traits: 2=(update )
   2, 2, B, traits: 2=(update )
channel_map:
  i, channel, offset, traits
  0, 0, 0  traits: 2=(update )
  1, 1, 1  traits: 2=(update )
  2, 2, 2  traits: 2=(update )
  3, 0, 0  traits: 0=(undefined )
  4, 0, 0  traits: 0=(undefined )
  5, 0, 0  traits: 0=(undefined )
  6, 0, 0  traits: 0=(undefined )
  7, 0, 0  traits: 0=(undefined )
  8, 0, 0  traits: 0=(undefined )
  9, 0, 0  traits: 0=(undefined )
  10, 0, 0  traits: 0=(undefined )
  11, 0, 0  traits: 0=(undefined )
  12, 0, 0  traits: 0=(undefined )
  13, 0, 0  traits: 0=(undefined )
  14, 0, 0  traits: 0=(undefined )
  15, 0, 0  traits: 0=(undefined )
  16, 0, 0  traits: 0=(undefined )
  17, 0, 0  traits: 0=(undefined )
  18, 0, 0  traits: 0=(undefined )
  19, 0, 0  traits: 0=(undefined )
  20, 0, 0  traits: 0=(undefined )
  21, 0, 0  traits: 0=(undefined )
  22, 0, 0  traits: 0=(undefined )
  23, 0, 0  traits: 0=(undefined )
  24, 0, 0  traits: 0=(undefined )
  25, 0, 0  traits: 0=(undefined )
  26, 0, 0  traits: 0=(undefined )
  27, 0, 0  traits: 0=(undefined )
  28, 0, 0  traits: 0=(undefined )
  29, 0, 0  traits: 0=(undefined )
  30, 0, 0  traits: 0=(undefined )
  31, 0, 0  traits: 0=(undefined )
  32, 0, 0  traits: 0=(undefined )
  33, 0, 0  traits: 0=(undefined )
  34, 0, 0  traits: 0=(undefined )
  35, 0, 0  traits: 0=(undefined )
  36, 0, 0  traits: 0=(undefined )
  37, 0, 0  traits: 0=(undefined )
  38, 0, 0  traits: 0=(undefined )
  39, 0, 0  traits: 0=(undefined )
  40, 0, 0  traits: 0=(undefined )
  41, 0, 0  traits: 0=(undefined )
  42, 0, 0  traits: 0=(undefined )
  43, 0, 0  traits: 0=(undefined )
  44, 0, 0  traits: 0=(undefined )
  45, 0, 0  traits: 0=(undefined )
  46, 0, 0  traits: 0=(undefined )
  47, 0, 0  traits: 0=(undefined )
  48, 0, 0  traits: 0=(undefined )
  49, 0, 0  traits: 0=(undefined )
  50, 0, 0  traits: 0=(undefined )
  51, 0, 0  traits: 0=(undefined )
  52, 0, 0  traits: 0=(undefined )
  53, 0, 0  traits: 0=(undefined )
  54, 0, 0  traits: 0=(undefined )
  55, 0, 0  traits: 0=(undefined )
  56, 0, 0  traits: 0=(undefined )
  57, 0, 0  traits: 0=(undefined )
  58, 0, 0  traits: 0=(undefined )
  59, 0, 0  traits: 0=(undefined )
  60, 0, 0  traits: 0=(undefined )
  61, 0, 0  traits: 0=(undefined )
  62, 0, 0  traits: 0=(undefined )
  63, 0, 0  traits: 0=(undefined )
toes.png[0] PNG 267x233 267x233+0+0 16-bit TrueColor sRGB 320268B 0.093u 0:00.092
toes.png[1] PNG 200x175 200x175+0+0 8-bit sRGB 320268B 0.093u 0:00.092
cu_repl.pngjpg

Skeleton module: replace first

replacefirst.c replaces the first image in the list with a red image the same size as the last image.

-process replacefirst is equivalent to ( -repect-parentheses +clone -fill red -colorize 100 ) -swap 0,-1 +delete.

%IM7DEV%magick ^
  toes.png ^
  ( +clone -fill khaki -colorize 50 -resize 75%% ) ^
  -process replacefirst ^
  -process echostuff ^
  -write info: ^
  +append +repage ^
  cu_repf.png 
echostuff: no arguments
mc=black
echostuff: Input image [0] [toes.png]  depth 8  size 200x175
  Virtual pixel method 0 [(null)]
  mattecolor: (null)
  fuzz 0
  alpha is on: no
  channels 3 meta_channels 0
  ( MaxPixelChannels = 64 )
  GetPixelChannels() = 3
  Number of channels with update trait = 3
Image channels:
  i, channel, name, traits 
   0, 0, R, traits: 2=(update )
   1, 1, G, traits: 2=(update )
   2, 2, B, traits: 2=(update )
channel_map:
  i, channel, offset, traits
  0, 0, 0  traits: 2=(update )
  1, 1, 1  traits: 2=(update )
  2, 2, 2  traits: 2=(update )
  3, 0, 0  traits: 0=(undefined )
  4, 0, 0  traits: 0=(undefined )
  5, 0, 0  traits: 0=(undefined )
  6, 0, 0  traits: 0=(undefined )
  7, 0, 0  traits: 0=(undefined )
  8, 0, 0  traits: 0=(undefined )
  9, 0, 0  traits: 0=(undefined )
  10, 0, 0  traits: 0=(undefined )
  11, 0, 0  traits: 0=(undefined )
  12, 0, 0  traits: 0=(undefined )
  13, 0, 0  traits: 0=(undefined )
  14, 0, 0  traits: 0=(undefined )
  15, 0, 0  traits: 0=(undefined )
  16, 0, 0  traits: 0=(undefined )
  17, 0, 0  traits: 0=(undefined )
  18, 0, 0  traits: 0=(undefined )
  19, 0, 0  traits: 0=(undefined )
  20, 0, 0  traits: 0=(undefined )
  21, 0, 0  traits: 0=(undefined )
  22, 0, 0  traits: 0=(undefined )
  23, 0, 0  traits: 0=(undefined )
  24, 0, 0  traits: 0=(undefined )
  25, 0, 0  traits: 0=(undefined )
  26, 0, 0  traits: 0=(undefined )
  27, 0, 0  traits: 0=(undefined )
  28, 0, 0  traits: 0=(undefined )
  29, 0, 0  traits: 0=(undefined )
  30, 0, 0  traits: 0=(undefined )
  31, 0, 0  traits: 0=(undefined )
  32, 0, 0  traits: 0=(undefined )
  33, 0, 0  traits: 0=(undefined )
  34, 0, 0  traits: 0=(undefined )
  35, 0, 0  traits: 0=(undefined )
  36, 0, 0  traits: 0=(undefined )
  37, 0, 0  traits: 0=(undefined )
  38, 0, 0  traits: 0=(undefined )
  39, 0, 0  traits: 0=(undefined )
  40, 0, 0  traits: 0=(undefined )
  41, 0, 0  traits: 0=(undefined )
  42, 0, 0  traits: 0=(undefined )
  43, 0, 0  traits: 0=(undefined )
  44, 0, 0  traits: 0=(undefined )
  45, 0, 0  traits: 0=(undefined )
  46, 0, 0  traits: 0=(undefined )
  47, 0, 0  traits: 0=(undefined )
  48, 0, 0  traits: 0=(undefined )
  49, 0, 0  traits: 0=(undefined )
  50, 0, 0  traits: 0=(undefined )
  51, 0, 0  traits: 0=(undefined )
  52, 0, 0  traits: 0=(undefined )
  53, 0, 0  traits: 0=(undefined )
  54, 0, 0  traits: 0=(undefined )
  55, 0, 0  traits: 0=(undefined )
  56, 0, 0  traits: 0=(undefined )
  57, 0, 0  traits: 0=(undefined )
  58, 0, 0  traits: 0=(undefined )
  59, 0, 0  traits: 0=(undefined )
  60, 0, 0  traits: 0=(undefined )
  61, 0, 0  traits: 0=(undefined )
  62, 0, 0  traits: 0=(undefined )
  63, 0, 0  traits: 0=(undefined )
mc=black
echostuff: Input image [1] [toes.png]  depth 16  size 200x175
  Virtual pixel method 0 [(null)]
  mattecolor: (null)
  fuzz 0
  alpha is on: no
  channels 3 meta_channels 0
  ( MaxPixelChannels = 64 )
  GetPixelChannels() = 3
  Number of channels with update trait = 3
Image channels:
  i, channel, name, traits 
   0, 0, R, traits: 2=(update )
   1, 1, G, traits: 2=(update )
   2, 2, B, traits: 2=(update )
channel_map:
  i, channel, offset, traits
  0, 0, 0  traits: 2=(update )
  1, 1, 1  traits: 2=(update )
  2, 2, 2  traits: 2=(update )
  3, 0, 0  traits: 0=(undefined )
  4, 0, 0  traits: 0=(undefined )
  5, 0, 0  traits: 0=(undefined )
  6, 0, 0  traits: 0=(undefined )
  7, 0, 0  traits: 0=(undefined )
  8, 0, 0  traits: 0=(undefined )
  9, 0, 0  traits: 0=(undefined )
  10, 0, 0  traits: 0=(undefined )
  11, 0, 0  traits: 0=(undefined )
  12, 0, 0  traits: 0=(undefined )
  13, 0, 0  traits: 0=(undefined )
  14, 0, 0  traits: 0=(undefined )
  15, 0, 0  traits: 0=(undefined )
  16, 0, 0  traits: 0=(undefined )
  17, 0, 0  traits: 0=(undefined )
  18, 0, 0  traits: 0=(undefined )
  19, 0, 0  traits: 0=(undefined )
  20, 0, 0  traits: 0=(undefined )
  21, 0, 0  traits: 0=(undefined )
  22, 0, 0  traits: 0=(undefined )
  23, 0, 0  traits: 0=(undefined )
  24, 0, 0  traits: 0=(undefined )
  25, 0, 0  traits: 0=(undefined )
  26, 0, 0  traits: 0=(undefined )
  27, 0, 0  traits: 0=(undefined )
  28, 0, 0  traits: 0=(undefined )
  29, 0, 0  traits: 0=(undefined )
  30, 0, 0  traits: 0=(undefined )
  31, 0, 0  traits: 0=(undefined )
  32, 0, 0  traits: 0=(undefined )
  33, 0, 0  traits: 0=(undefined )
  34, 0, 0  traits: 0=(undefined )
  35, 0, 0  traits: 0=(undefined )
  36, 0, 0  traits: 0=(undefined )
  37, 0, 0  traits: 0=(undefined )
  38, 0, 0  traits: 0=(undefined )
  39, 0, 0  traits: 0=(undefined )
  40, 0, 0  traits: 0=(undefined )
  41, 0, 0  traits: 0=(undefined )
  42, 0, 0  traits: 0=(undefined )
  43, 0, 0  traits: 0=(undefined )
  44, 0, 0  traits: 0=(undefined )
  45, 0, 0  traits: 0=(undefined )
  46, 0, 0  traits: 0=(undefined )
  47, 0, 0  traits: 0=(undefined )
  48, 0, 0  traits: 0=(undefined )
  49, 0, 0  traits: 0=(undefined )
  50, 0, 0  traits: 0=(undefined )
  51, 0, 0  traits: 0=(undefined )
  52, 0, 0  traits: 0=(undefined )
  53, 0, 0  traits: 0=(undefined )
  54, 0, 0  traits: 0=(undefined )
  55, 0, 0  traits: 0=(undefined )
  56, 0, 0  traits: 0=(undefined )
  57, 0, 0  traits: 0=(undefined )
  58, 0, 0  traits: 0=(undefined )
  59, 0, 0  traits: 0=(undefined )
  60, 0, 0  traits: 0=(undefined )
  61, 0, 0  traits: 0=(undefined )
  62, 0, 0  traits: 0=(undefined )
  63, 0, 0  traits: 0=(undefined )
toes.png[0] PNG 200x175 200x175+0+0 8-bit sRGB 320268B 0.093u 0:00.096
toes.png[1] PNG 200x175 200x175+0+0 16-bit sRGB 320268B 0.093u 0:00.096
cu_repf.png

Skeleton module: replace all

replaceall.c replaces the all images in the list with a single blue image the same size as the last image.

-process replacefirst is equivalent to ( -repect-parentheses +clone -fill blue -colorize 100 ) -delete 0--2.

%IM7DEV%magick ^
  toes.png ^
  ( +clone -fill khaki -colorize 50 -resize 75%% ) ^
  -process replaceall ^
  -process echostuff ^
  -write info: ^
  +append +repage ^
  cu_repa.png 
echostuff: no arguments
mc=black
echostuff: Input image [0] [toes.png]  depth 8  size 200x175
  Virtual pixel method 0 [(null)]
  mattecolor: (null)
  fuzz 0
  alpha is on: no
  channels 3 meta_channels 0
  ( MaxPixelChannels = 64 )
  GetPixelChannels() = 3
  Number of channels with update trait = 3
Image channels:
  i, channel, name, traits 
   0, 0, R, traits: 2=(update )
   1, 1, G, traits: 2=(update )
   2, 2, B, traits: 2=(update )
channel_map:
  i, channel, offset, traits
  0, 0, 0  traits: 2=(update )
  1, 1, 1  traits: 2=(update )
  2, 2, 2  traits: 2=(update )
  3, 0, 0  traits: 0=(undefined )
  4, 0, 0  traits: 0=(undefined )
  5, 0, 0  traits: 0=(undefined )
  6, 0, 0  traits: 0=(undefined )
  7, 0, 0  traits: 0=(undefined )
  8, 0, 0  traits: 0=(undefined )
  9, 0, 0  traits: 0=(undefined )
  10, 0, 0  traits: 0=(undefined )
  11, 0, 0  traits: 0=(undefined )
  12, 0, 0  traits: 0=(undefined )
  13, 0, 0  traits: 0=(undefined )
  14, 0, 0  traits: 0=(undefined )
  15, 0, 0  traits: 0=(undefined )
  16, 0, 0  traits: 0=(undefined )
  17, 0, 0  traits: 0=(undefined )
  18, 0, 0  traits: 0=(undefined )
  19, 0, 0  traits: 0=(undefined )
  20, 0, 0  traits: 0=(undefined )
  21, 0, 0  traits: 0=(undefined )
  22, 0, 0  traits: 0=(undefined )
  23, 0, 0  traits: 0=(undefined )
  24, 0, 0  traits: 0=(undefined )
  25, 0, 0  traits: 0=(undefined )
  26, 0, 0  traits: 0=(undefined )
  27, 0, 0  traits: 0=(undefined )
  28, 0, 0  traits: 0=(undefined )
  29, 0, 0  traits: 0=(undefined )
  30, 0, 0  traits: 0=(undefined )
  31, 0, 0  traits: 0=(undefined )
  32, 0, 0  traits: 0=(undefined )
  33, 0, 0  traits: 0=(undefined )
  34, 0, 0  traits: 0=(undefined )
  35, 0, 0  traits: 0=(undefined )
  36, 0, 0  traits: 0=(undefined )
  37, 0, 0  traits: 0=(undefined )
  38, 0, 0  traits: 0=(undefined )
  39, 0, 0  traits: 0=(undefined )
  40, 0, 0  traits: 0=(undefined )
  41, 0, 0  traits: 0=(undefined )
  42, 0, 0  traits: 0=(undefined )
  43, 0, 0  traits: 0=(undefined )
  44, 0, 0  traits: 0=(undefined )
  45, 0, 0  traits: 0=(undefined )
  46, 0, 0  traits: 0=(undefined )
  47, 0, 0  traits: 0=(undefined )
  48, 0, 0  traits: 0=(undefined )
  49, 0, 0  traits: 0=(undefined )
  50, 0, 0  traits: 0=(undefined )
  51, 0, 0  traits: 0=(undefined )
  52, 0, 0  traits: 0=(undefined )
  53, 0, 0  traits: 0=(undefined )
  54, 0, 0  traits: 0=(undefined )
  55, 0, 0  traits: 0=(undefined )
  56, 0, 0  traits: 0=(undefined )
  57, 0, 0  traits: 0=(undefined )
  58, 0, 0  traits: 0=(undefined )
  59, 0, 0  traits: 0=(undefined )
  60, 0, 0  traits: 0=(undefined )
  61, 0, 0  traits: 0=(undefined )
  62, 0, 0  traits: 0=(undefined )
  63, 0, 0  traits: 0=(undefined )
toes.png PNG 200x175 200x175+0+0 8-bit sRGB 320268B 0.094u 0:00.092
cu_repa.png

Skeleton module: replace each

replaceeach.c replaces each of the images in the list with a magenta image the same size as the corresponding input image.

-process replaceeach is equivalent to -fill magenta -colorize 100.

%IM7DEV%magick ^
  toes.png ^
  ( +clone -fill khaki -colorize 50 -resize 75%% ) ^
  -process replaceeach ^
  -process echostuff ^
  -write info: ^
  +append +repage ^
  cu_repe.png 
echostuff: no arguments
mc=black
echostuff: Input image [0] [toes.png]  depth 8  size 267x233
  Virtual pixel method 0 [(null)]
  mattecolor: (null)
  fuzz 0
  alpha is on: no
  channels 3 meta_channels 0
  ( MaxPixelChannels = 64 )
  GetPixelChannels() = 3
  Number of channels with update trait = 3
Image channels:
  i, channel, name, traits 
   0, 0, R, traits: 2=(update )
   1, 1, G, traits: 2=(update )
   2, 2, B, traits: 2=(update )
channel_map:
  i, channel, offset, traits
  0, 0, 0  traits: 2=(update )
  1, 1, 1  traits: 2=(update )
  2, 2, 2  traits: 2=(update )
  3, 0, 0  traits: 0=(undefined )
  4, 0, 0  traits: 0=(undefined )
  5, 0, 0  traits: 0=(undefined )
  6, 0, 0  traits: 0=(undefined )
  7, 0, 0  traits: 0=(undefined )
  8, 0, 0  traits: 0=(undefined )
  9, 0, 0  traits: 0=(undefined )
  10, 0, 0  traits: 0=(undefined )
  11, 0, 0  traits: 0=(undefined )
  12, 0, 0  traits: 0=(undefined )
  13, 0, 0  traits: 0=(undefined )
  14, 0, 0  traits: 0=(undefined )
  15, 0, 0  traits: 0=(undefined )
  16, 0, 0  traits: 0=(undefined )
  17, 0, 0  traits: 0=(undefined )
  18, 0, 0  traits: 0=(undefined )
  19, 0, 0  traits: 0=(undefined )
  20, 0, 0  traits: 0=(undefined )
  21, 0, 0  traits: 0=(undefined )
  22, 0, 0  traits: 0=(undefined )
  23, 0, 0  traits: 0=(undefined )
  24, 0, 0  traits: 0=(undefined )
  25, 0, 0  traits: 0=(undefined )
  26, 0, 0  traits: 0=(undefined )
  27, 0, 0  traits: 0=(undefined )
  28, 0, 0  traits: 0=(undefined )
  29, 0, 0  traits: 0=(undefined )
  30, 0, 0  traits: 0=(undefined )
  31, 0, 0  traits: 0=(undefined )
  32, 0, 0  traits: 0=(undefined )
  33, 0, 0  traits: 0=(undefined )
  34, 0, 0  traits: 0=(undefined )
  35, 0, 0  traits: 0=(undefined )
  36, 0, 0  traits: 0=(undefined )
  37, 0, 0  traits: 0=(undefined )
  38, 0, 0  traits: 0=(undefined )
  39, 0, 0  traits: 0=(undefined )
  40, 0, 0  traits: 0=(undefined )
  41, 0, 0  traits: 0=(undefined )
  42, 0, 0  traits: 0=(undefined )
  43, 0, 0  traits: 0=(undefined )
  44, 0, 0  traits: 0=(undefined )
  45, 0, 0  traits: 0=(undefined )
  46, 0, 0  traits: 0=(undefined )
  47, 0, 0  traits: 0=(undefined )
  48, 0, 0  traits: 0=(undefined )
  49, 0, 0  traits: 0=(undefined )
  50, 0, 0  traits: 0=(undefined )
  51, 0, 0  traits: 0=(undefined )
  52, 0, 0  traits: 0=(undefined )
  53, 0, 0  traits: 0=(undefined )
  54, 0, 0  traits: 0=(undefined )
  55, 0, 0  traits: 0=(undefined )
  56, 0, 0  traits: 0=(undefined )
  57, 0, 0  traits: 0=(undefined )
  58, 0, 0  traits: 0=(undefined )
  59, 0, 0  traits: 0=(undefined )
  60, 0, 0  traits: 0=(undefined )
  61, 0, 0  traits: 0=(undefined )
  62, 0, 0  traits: 0=(undefined )
  63, 0, 0  traits: 0=(undefined )
mc=black
echostuff: Input image [1] [toes.png]  depth 8  size 200x175
  Virtual pixel method 0 [(null)]
  mattecolor: (null)
  fuzz 0
  alpha is on: no
  channels 3 meta_channels 0
  ( MaxPixelChannels = 64 )
  GetPixelChannels() = 3
  Number of channels with update trait = 3
Image channels:
  i, channel, name, traits 
   0, 0, R, traits: 2=(update )
   1, 1, G, traits: 2=(update )
   2, 2, B, traits: 2=(update )
channel_map:
  i, channel, offset, traits
  0, 0, 0  traits: 2=(update )
  1, 1, 1  traits: 2=(update )
  2, 2, 2  traits: 2=(update )
  3, 0, 0  traits: 0=(undefined )
  4, 0, 0  traits: 0=(undefined )
  5, 0, 0  traits: 0=(undefined )
  6, 0, 0  traits: 0=(undefined )
  7, 0, 0  traits: 0=(undefined )
  8, 0, 0  traits: 0=(undefined )
  9, 0, 0  traits: 0=(undefined )
  10, 0, 0  traits: 0=(undefined )
  11, 0, 0  traits: 0=(undefined )
  12, 0, 0  traits: 0=(undefined )
  13, 0, 0  traits: 0=(undefined )
  14, 0, 0  traits: 0=(undefined )
  15, 0, 0  traits: 0=(undefined )
  16, 0, 0  traits: 0=(undefined )
  17, 0, 0  traits: 0=(undefined )
  18, 0, 0  traits: 0=(undefined )
  19, 0, 0  traits: 0=(undefined )
  20, 0, 0  traits: 0=(undefined )
  21, 0, 0  traits: 0=(undefined )
  22, 0, 0  traits: 0=(undefined )
  23, 0, 0  traits: 0=(undefined )
  24, 0, 0  traits: 0=(undefined )
  25, 0, 0  traits: 0=(undefined )
  26, 0, 0  traits: 0=(undefined )
  27, 0, 0  traits: 0=(undefined )
  28, 0, 0  traits: 0=(undefined )
  29, 0, 0  traits: 0=(undefined )
  30, 0, 0  traits: 0=(undefined )
  31, 0, 0  traits: 0=(undefined )
  32, 0, 0  traits: 0=(undefined )
  33, 0, 0  traits: 0=(undefined )
  34, 0, 0  traits: 0=(undefined )
  35, 0, 0  traits: 0=(undefined )
  36, 0, 0  traits: 0=(undefined )
  37, 0, 0  traits: 0=(undefined )
  38, 0, 0  traits: 0=(undefined )
  39, 0, 0  traits: 0=(undefined )
  40, 0, 0  traits: 0=(undefined )
  41, 0, 0  traits: 0=(undefined )
  42, 0, 0  traits: 0=(undefined )
  43, 0, 0  traits: 0=(undefined )
  44, 0, 0  traits: 0=(undefined )
  45, 0, 0  traits: 0=(undefined )
  46, 0, 0  traits: 0=(undefined )
  47, 0, 0  traits: 0=(undefined )
  48, 0, 0  traits: 0=(undefined )
  49, 0, 0  traits: 0=(undefined )
  50, 0, 0  traits: 0=(undefined )
  51, 0, 0  traits: 0=(undefined )
  52, 0, 0  traits: 0=(undefined )
  53, 0, 0  traits: 0=(undefined )
  54, 0, 0  traits: 0=(undefined )
  55, 0, 0  traits: 0=(undefined )
  56, 0, 0  traits: 0=(undefined )
  57, 0, 0  traits: 0=(undefined )
  58, 0, 0  traits: 0=(undefined )
  59, 0, 0  traits: 0=(undefined )
  60, 0, 0  traits: 0=(undefined )
  61, 0, 0  traits: 0=(undefined )
  62, 0, 0  traits: 0=(undefined )
  63, 0, 0  traits: 0=(undefined )
toes.png[0] PNG 267x233 267x233+0+0 8-bit sRGB 320268B 0.078u 0:00.092
toes.png[1] PNG 200x175 200x175+0+0 8-bit sRGB 320268B 0.078u 0:00.092
cu_repe.png

Skeleton module: replace spec

replacespec.c replaces each of the images in the list with an image of the given size and colour.

The size defaults to the same as the corresponding input image. If size is given, all outputs will be the same size. The colour defaults to brown.

Option Description
Short
form
Long form
s WxH size WxH Size width and height.
If W is omitted, the default is taken from the image's width.
If H is omitted, the default is taken from the image's height.
c string color string Any IM colour, eg White or #123. Default: brown.
v verbose Write some text output to stderr.

For example:

%IM7DEV%magick ^
  toes.png ^
  ( +clone -fill khaki -colorize 50 -resize 75%% ) ^
  -process replacespec ^
  +append +repage ^
  cu_repspec1.png
cu_repspec1.png
%IM7DEV%magick ^
  toes.png ^
  ( +clone -fill khaki -colorize 50 -resize 75%% ) ^
  -process 'replacespec  color red size 20x20' ^
  +append +repage ^
  cu_repspec2.png
cu_repspec2.png
%IM7DEV%magick ^
  toes.png ^
  ( +clone -fill khaki -colorize 50 -resize 75%% ) ^
  -process 'replacespec  color green size x20' ^
  +append +repage ^
  cu_repspec3.png
cu_repspec3.png
%IM7DEV%magick ^
  toes.png ^
  ( +clone -fill khaki -colorize 50 -resize 75%% ) ^
  -process 'replacespec  color blue size 20x' ^
  +append +repage ^
  cu_repspec4.png
cu_repspec4.png

My convention for modules is that verbose may give some information to stderr, including a list of the options seen by the module.

%IM7DEV%magick ^
  toes.png ^
  ( +clone -fill khaki -colorize 50 -resize 75%% ) ^
  -process 'replacespec verbose' ^
  +append +repage ^
  NULL: 
replacespec options:  verbose
  input image size 267x233
  input image size 200x175

Invalid options such as help give usage information to stdout, and cause IM to fail.

%IM7DEV%magick ^
  toes.png ^
  ( +clone -fill khaki -colorize 50 -resize 75%% ) ^
  -process 'replacespec help' ^
  +append +repage ^
  dump.png 1>cu_rs_help.lis 2>&1

cmd /c exit /B 0
replacespec: ERROR: unknown option [help]
magick: image filter signature mismatch 'replacespec': ffffffffffffffff !=      a20 @ error/module.c/InvokeDynamicImageFilter/1035.
Usage: -process 'replacespec [OPTION]...'
Replace each image.

  s, size WxH         size of new image
  c, color string     make new image this color

Skeleton module: grad2

grad2.c replaces each image in the list with a two-way gradient the same size as the corresponding input image. The function visits every pixel in each output image, setting the pixel values according to a simple formula.

-process grad2 is equivalent to ??.

%IM7DEV%magick ^
  toes.png ^
  ( +clone -fill khaki -colorize 50 -resize 75%% ) ^
  -process grad2 ^
  +append +repage ^
  cu_grad2.png
cu_grad2.png

Skeleton module: draw circle

drawcirc.c modifies each image in the list, drawing a blue circle on each one.

-process grad2 is equivalent to -draw "fill Blue circle CX,CY CX,CY+RAD" where (CX,CY) is the centre and RAD is min(CX,CY).

%IM7DEV%magick ^
  toes.png ^
  ( +clone -fill khaki -colorize 50 -resize 75%% ) ^
  -process drawcirc ^
  +append +repage ^
  cu_drawcirc.png
cu_drawcirc.pngjpg

Skeleton module: geometric distortion

geodist.c applies a geometric distortion to each image in the list.

This respects the -virtual-pixel and -interpolate settings. It uses InterpolateMagickPixelPacket() to find the colour at an arbitrary non-integer coordinate.

If any options are given, the filter name and its options must be quoted.

Option Description
Short
form
Long form
s N style N Distortion style: 0, 1 or 2. See samples below.

For example:

Style 0

Shift the image up and left.

%IM7DEV%magick toes.png ^
  -virtual-pixel Mirror ^
  -process geodist ^
  cu_gd0.png

Virtual pixels fill-in bottom and right.

cu_gd0.pngjpg

Style 1

Shift horizontally according to saturation,
and vertically according to brightness.

%IM7DEV%magick toes.png ^
  -virtual-pixel Mirror ^
  -process 'geodist style 1' ^
  cu_gd1.png
cu_gd1.pngjpg

Style 2

Shift at an angle determined by hue,
at a distance according to brightness.

%IM7DEV%magick toes.png ^
  -virtual-pixel Mirror ^
  -process 'geodist style 2' ^
  cu_gd2.png
cu_gd2.pngjpg

Practical module: fisheye

rect2eqfish.c converts from a rectilinear image (eg from an ordinary camera) to an equidistant fisheye. There are many fisheye projections. The "equidistant" fisheye is also known as "linear". This module respects the -virtual-pixel and -interpolate settings.

This module is a direct translation (I hope) of part of Fred Weinhaus's fisheye script, with his permission. See Fred's fisheye page.

The main difference between my code and Fred's are:

Option Description
Short
form
Long form
i N ifov N input field of view from corner to opposite corner in degrees, default 120.
o N ofov N output field of view from corner to opposite corner in degrees, default 180.
f string format string fullframe or circular.
r N radius N Output radius (overrides format).
v verbose Write some text output to stderr.

Examples:

%IM7DEV%magick ^
  toes.png ^
  -process rect2eqfish ^
  cu_fish1.jpg
cu_fish1.jpg
%IM7DEV%magick ^
  toes.png ^
  -virtual-pixel Black ^
  -process 'rect2eqfish format circular' ^
  cu_fish2.jpg
cu_fish2.jpg
%IM7DEV%magick ^
  toes.png ^
  -virtual-pixel Mirror ^
  -process 'rect2eqfish format circular' ^
  cu_fish3.jpg
cu_fish3.jpg
call StopWatch

%IM7DEV%magick ^
  toes.png ^
  -virtual-pixel Black ^
  -process 'rect2eqfish format circular o 120 i 120 v' ^
  cu_fish4.jpg 2>cu_fish4.lis

call StopWatch 
cu_fish4.jpg

rect2eqfish has a verbose option that gives the equivalent -fx command. The output from the last command above is:

rect2eqfish options:  verbose  ifov 120  ofov 120  format circular
CentX=133  CentY=116  dim=233  ifoc=67.2613  ofocinv=0.00898882  (1/111.249)

FX equivalent:
  -fx "xd=i-133;yd=j-116;rd=hypot(xd,yd);phiang=0.00898882*rd;rr=67.2613*tan(phiang);xs=(rd?rr/rd:0)*xd+133;ys=(rd?rr/rd:0)*yd+116;u.p{xs,ys}"

It took this long (to the nearest second):

0 00:00:00

We can extract the -fx component and use it in a separate command:

for /F "usebackq tokens=*" %%F ^
in (`findstr fx cu_fish4.lis`) ^
do set FX=%%F

call StopWatch

%IMG7%magick ^
  toes.png ^
  -virtual-pixel Black ^
  %FX% ^
  cu_fishfx.jpg

call StopWatch 
cu_fishfx.jpg

The -fx version took this long:

0 00:00:00

I can't see any good reason for doing this, apart from finding out how slow -fx is.

Practical module: onewhite

onewhite.c writes exactly one line to stderr for each image in the list. The line starts with "onewhite: ", then has either "none" or the integer x,y coordinates of the first white pixel. "White" is defined here as each of the red, green and blue channels being greater than or equal to QuantumRange minus the -fuzz setting. This can allow for -auto-level sometimes not making genuine white in HDRI.

It is not sensitive to -channels. Each image search stops as soon as the first white pixel is found.

For example:

%IM7DEV%magick ^
  xc:black ^
  xc:white ^
  -process onewhite ^
  NULL: 
onewhite: none
onewhite: 0,0

There are two images, so there are two lines of text.

%IM7DEV%magick ^
  xc:black ^
  -bordercolor White -border 1 ^
  ( +clone ) ^
  -process onewhite ^
  NULL: 
onewhite: 0,0
onewhite: 0,0

BEWARE: Even for grayscale images, -auto-level does not always make exactly white pixels. (I haven't investigated when this happens.) Consider using "-fuzz", or use onelightest instead.

If the module finds a white pixel, it sets the image property filter:onewhite to the coordinates.

For example uses, see Details, details, [Adaptive] Contrast-limited equalisation, Islands, Simple alignment by matching areas, Adding zing to photographs and Standalone programs.

Practical module: nearestwhite

nearestwhite.c writes exactly one line to stderr for each image in the list. The line starts with "nearestwhite: ", then has either "none" or the integer x,y coordinates of the white pixel that is nearest to the centre. "White" is defined here as each of the red, green and blue channels being greater than or equal to QuantumRange minus the -fuzz setting. This can allow for -auto-level sometimes not making genuine white in HDRI.

If the module finds a white pixel, it sets the image property filter:nearestwhite to the coordinates of the nearest.

Option Description
Short
form
Long form
cx N cx N Central x-coordinate (pixels or % of width-1).
Default: 50%.
cy N cy N Central y-coordinate (pixels or % of height-1).
Default: 50%.
v verbose Write some text output.

Each number can be suffixed by % or c, both meaning a percentage of the image dimension minus one, or p meaning a proportion of the image dimension minus one.

For example:

%IM7DEV%magick ^
  xc:black ^
  xc:white ^
  -process nearestwhite ^
  NULL: 
nearestwhite: none
nearestwhite: 0,0

There are two images, so there are two lines of text.

%IM7DEV%magick ^
  xc:black ^
  -bordercolor White -border 1 ^
  ( +clone ) ^
  -process nearestwhite ^
  NULL: 
nearestwhite: 1,0
nearestwhite: 1,0

FUTURE: We may know in advance that the nearest pixel will be within a certain radius of (cx,cy). So we might have an option to limit the search to that area.

Practical module: allwhite

allwhite.c lists all the white pixels in all the images in the list. The listing for each image starts with the line "allwhite:". Then it lists the coordinates of each white pixel (if any), one per line. If none are found, it does not write "none".

"White" is defined as for onewhite above. It is not sensitive to -fuzz or -channels.

For example:

%IM7DEV%magick ^
  xc:black ^
  ( +clone ) ^
  -process allwhite ^
  NULL: 
allwhite:
allwhite:
%IM7DEV%magick ^
  xc:black ^
  -bordercolor White -border 1 ^
  ( +clone ) ^
  -process allwhite ^
  NULL: 
allwhite:
0,0
1,0
2,0
0,1
2,1
0,2
1,2
2,2
allwhite:
0,0
1,0
2,0
0,1
2,1
0,2
1,2
2,2

Practical module: onelightest

onelightest.c writes exactly one line to stderr for each image in the list, containing the x,y coordinate of the lightest pixel, as defined by the current -intensity setting. If a number of pixels are equal-lightest, only the first one is found.

%IM7DEV%magick ^
  -size 10x10 ^
  xc:black ^
  xc:white ^
  -process onelightest ^
  NULL: 
0,0
0,0

Note that every image has a lightest pixel.

For example uses, see Histogram peaks and troughs.

Practical module: midlightest

midlightest.c is similar to onelightest.c. However, if a number of pixels are equal-lightest, this module finds which of them is closest to the centroid of the set.

%IM7DEV%magick ^
  -size 10x10 ^
  xc:black ^
  xc:white ^
  -process midlightest ^
  NULL: 
4,4
4,4

For an example use, see Membranes.

Practical module: sort pixels

sortpixels.c modifies each image in the list, rearranging all the pixels in each horizontal line so they are sorted in ascending order of intensity. I wrote it for data files that have height=1, but it also creates an interesting pictorial effect.

As from 1 October 2016, it respects the -intensity setting.

Previously, for v6, it used PixelPacketIntensity from pixel-accessor.h which uses the formula intensity=0.212656*red + 0.715158*green + 0.072186*blue.

It is not sensitive to a -channels setting.

My code uses the library function qsort, which is extensively used within ImageMagick so I guess build environments generally support it.

toes.png:

toes.png

Sort each line, with darkest pixels on the left.

%IM7DEV%magick ^
  toes.png ^
  -process sortpixels ^
  cu_sl.png
cu_sl.pngjpg

By rotating, we sort columns with darkest pixels at the bottom.

%IM7DEV%magick ^
  toes.png ^
  -rotate 90 ^
  -process sortpixels ^
  -rotate -90 ^
  cu_sl2.png
cu_sl2.pngjpg

We can sort all of the pixels by rearranging them into a single line,
sorting, and reassembling to the original dimensions.

for /F "usebackq" %%L in (`%IMG7%magick identify ^
  -format "WW=%%w" ^
  toes.png`) do set %%L

%IM7DEV%magick ^
  toes.png ^
  -crop x1 +repage ^
  +append +repage ^
  -intensity Rec709Luminance ^
  -process sortpixels ^
  -flop ^
  -crop %WW%x1 ^
  -append +repage ^
  cu_sl3.png
cu_sl3.pngjpg

The module can be used to find the median value, the value at which 50% of the pixels are darker and 50% are lighter, as defined by the -intensity setting:

%IM7DEV%magick ^
  toes.png ^
  -crop x1 +append +repage ^
  -process sortpixels ^
  -gravity Center -crop 1x1+0+0 +repage ^
  txt: 
# ImageMagick pixel enumeration: 1,1,0,65535,srgb
0,0: (34168,30512,32580)  #857877307F44  srgb(52.137023%,46.558326%,49.713895%)
%IM7DEV%magick ^
  toes.png ^
  -colorspace Gray ^
  -crop x1 +append +repage ^
  -process sortpixels ^
  -gravity Center -crop 1x1+0+0 +repage ^
  txt: 
# ImageMagick pixel enumeration: 1,1,0,65535,gray
0,0: (31439)  #7ACF7ACF7ACF  gray(47.972459%)

This returns the centre value if there is an odd number of pixels. For an even number of pixels, it returns the next pixel.

For an alternative method to find the median, probably faster, see invclut.

Update 22 January 2021: See also Command line options: sort-pixels. This seems to be a recent addition to ImageMagick. Perhaps it does the same as my "-process sortpixels".

Practical module: sort pixels blue

sortpixelsblue.c is exactly like sort pixels above, except that the sort compares values in the blue channel only. Like sort pixels, it moves entire pixels, and it operates on each row independently of other rows.

set SRC=toes.png

%IM7DEV%magick ^
  %SRC% ^
  -process sortpixelsblue ^
  cu_spb.png
cu_spb.pngjpg

By populating the red, green and blue channels with specific data, we can use the module to find the coordinates of the ten lightest pixels in toes.png.

for /F "usebackq" %%L in (`%IM7DEV%magick identify ^
  -precision 19 ^
  -format "WW=%%w\nWm1=%%[fx:w-1]\nHm1=%%[fx:h-1]" ^
  %SRC%`) do set %%L

set IDENT=^
0,0,#008,^
%%[fx:w-1],0,#f08,^
0,%%[fx:h-1],#0f8,^
%%[fx:w-1],%%[fx:h-1],#ff8

%IM7DEV%magick ^
  %SRC% ^
  ( -clone 0 ^
    -sparse-color bilinear %IDENT% ^
    -channel RG -separate +channel ^
  ) ^
  ( -clone 0 ^
    -grayscale Rec709Luminance ^
  ) ^
  -delete 0 ^
  -combine ^
  -crop x1 +append +repage ^
  -process sortpixelsblue ^
  -flop ^
  +depth ^
  +write cu_spb_map.miff ^
  -crop 10x1+0+0 +repage ^
  -crop 1x1 +repage ^
  -format "[%%p]=%%[fx:int(%Wm1%*r+0.5)],%%[fx:int(%Hm1%*u.g+0.5)],%%[fx:u.b]\n" ^
  info: 
[0]=255,217,0.8337736
[1]=255,217,0.8337736
[2]=225,217,0.8337736
[3]=225,217,0.8337736
[4]=256,217,0.8337736
[5]=255,217,0.8337736
[6]=254,217,0.8337736
[7]=266,217,0.8337736
[8]=266,217,0.8337736
[9]=256,217,0.8337736

The command:

  1. sets the blue channel to the source intensity, and the red and green channels to the x- and y-coordinates;
  2. crops the result into lines;
  3. appends these lines sideways, into a single line;
  4. sorts all the pixels on the line by the blue channel;
  5. flops to put the highest intensity at the left;
  6. crops the ten left-most pixels, then crops them into separate images, one pixel per image;
  7. writes information about the only pixel in each image.

If you want all the pixels, omit the -crop 10x1+0+0 line.

The red and green channels are initialised to an identity absolute distortion map. After the sort, those channels in each pixel give the source coordinates for that pixel. So we can use them to distort the source image into the sorted order. The map cu_spb_map.miff is a single row, so first we need to chop it into lines that we reassemble vertically.

%IM7DEV%magick ^
  %SRC% ^
  ( cu_spb_map.miff ^
    -crop %WW%x +repage -append +repage ^
  ) ^
  -compose Distort ^
  -set option:compose:args 100%%x100%% ^
  -composite ^
  cu_spb_srt.png
cu_spb_srt.pngjpg

This should be the same as the previous sort:

%IM7DEV%magick compare -metric RMSE cu_sl3.png cu_spb_srt.png NULL: 
0 (0)

It is the same.

If the blue channel of pixels is set to different random numbers before the sort, the result will be a shuffle of the pixels.

Sorting moves each pixel to only one destination, and moves no other pixels to that destination. Hence the displacement map is 1:1, hence it is reversible without leaving any gaps. (More technically, it is bijective, and a permutation.) See below, Practical module: invert displacement map.

FUTURE: For use in making displacement maps, maybe we should have a tie-breaker when two pixels have equal values in the blue channels.

Practical module: shadow sort pixels

shadowsortpixels.c needs a list of exactly two images, of the same dimensions. The first image will be sorted in the same way as sortpixels above, so each row will be sorted in ascending order of intensity. Pixels in the second image will undergo exactly the same swaps as in the first image.

It respects the -intensity setting. It is not sensitive to a -channels setting.

An alternative name might be "guided sort pixels" or "proxy sort pixels".

The module was written for applications where we want to sort a Nx1 clut, and apply the same swaps to another Nx1 clut. See Heatmaps.

When the first image is an ordinary photo, the second image could be an identity displacement map. The swapped displacement map will not have gaps.

%IM7DEV%magick ^
  toes.png ^
  ( +clone ^
    -sparse-color Bilinear ^
0,0,#000,^
%%[fx:w-1],0,#f00,^
0,%%[fx:h-1],#0f0,^
%%[fx:w-1],%%[fx:h-1],#ff0 ^
  ) ^
  -process shadowsortpixels ^
  cu_ssp_%%d.png
cu_ssp_0.pngjpg cu_ssp_1.pngjpg

We have displaced pixels from toes.png by sorting them, and displaced pixels from the identity displacement map in the same way. Hence the second result, cu_ssp_1.png, is the displacement map that has the same effect as the sort.

If we displace cu_ssp_0.png by the inverse of cu_ssp_1.png, the result should be the same as toes.png:

%IM7DEV%magick ^
  cu_ssp_0.png ^
  ( cu_ssp_1.png ^
    -process invdispmap ^
  ) ^
  -compose Distort -composite ^
  cu_ssp_t.png
cu_ssp_t.pngjpg
%IM7DEV%magick compare -metric RMSE cu_ssp_t.png toes.png NULL: 
cmd /c exit /B 0
0 (0)

This confirms the round-trip.

Practical module: fill holes

fillholes.c modifies each image in the list, changing any fully-transparent pixels (alpha=0) to be fully opaque (alpha=1), setting their colours from elsewhere in the image. The technique is also known as "infilling" or "inpainting".

Caution: this is not fast. By default, the module runs a custom-built "subimage-search" across the entire image for every single non-transparent pixel. Various options will increase performance. These usually (but not always) decrease quality.

Option Description
Short
form
Long form
wr N window_radius N Radius of window for searches, >= 1.
Window will be D*D square where D = radius*2+1.
The minimum radius is one.
Default 1.
lsr N limit_search_radius N Limit radius from transparent pixel to search for source, >= 0.
Default = 0 = no limit
als X auto_limit_search X Automatically limit the search radius.
X is on or off.
Default = on
s X search X Mode for searching, where X is entire or random or skip.
Default = entire.
rs N rand_searches N For random searches, the number to make per attempted match.
Default = 0 = minimum of search field width and height
sn N skip_num N For skip searches, the number of positions to skip in x and y directions.
Default = 0 = window radius
hc N hom_chk N Homogeneity check.
N is either a number, typically 0 < N < 0.5, or off to remove the check.
Default = 0.1
e X search_to_edges X Whether to search for matches to image edges, so the result will be influenced by virtual pixels.
Set to on or off.
Default = on
st N similarity_threshold N Stop searching for a window when RMSE <= N, when a match is "good enough".
Typically use 0.0 <= N <= 1.0, eg 0.01.
Default = 0 = keep going to find the best match.
dt N dissimilarity_threshold N When a best match is found, if score > N, reject it, leaving pixels unfilled.
Typically use 0.0 <= N <= 1.0, eg 0.05.
If zero, only perfect matches will be filled.
Default = no threshold, so no rejections from bad scores.
cp X copy X Mode for copying, where X is either onepixel or window.
Default = onepixel.
cr N copy_radius N Radius of pixels to be copied from a matching window, >= 1.
Window will be D*D square where D = radius*2+1.
Default: the value of window_radius.
c favour_close Favour search results close to destination pixel. EXPERIMENTAL.
w filename write filename Write the input image, then one frame per pass.
Example filename: frame_%06d.png
w2 filename write2 filename Write the input image, then one frame per changed pixel.
v verbose Write some text output to stderr.

For example:

Make a test image, with a hole.

%IMG7%magick ^
  -size 100x100 xc:lime ^
  -stroke Red -draw "line 0,0 99,99" ^
  -stroke Blue -strokewidth 3 -draw "line 0,20 69,99" ^
  ( +clone -fill White -colorize 100 ^
    -fill Black -draw "Rectangle 30,20 79,69" ^
    -threshold 50%% ^
  ) ^
  -alpha off ^
  -compose CopyOpacity -composite ^
  cu_fh_src1.png
cu_fh_src1.png

Fill any holes.

%IM7DEV%magick ^
  cu_fh_src1.png ^
  -process 'fillholes wr 2' ^
  cu_fh_src1_fh.png
cu_fh_src1_fh.png

See the page Filling holes for more details.

Practical module: fill holes with priority

fillholespri.c performs the same hole-filling ("infilling", "inpainting") as fillholes above, but pixels are filled in priority order. This module gives higher quality results when holes span areas of mixed amounts of detail.

This module takes the same options as fill holes above.

See the page Filling holes in priority order for more details.

Practical module: img2knl, from an image, write a kernel string

img2knl.c writes an image as text to stdout, in a format suitable for a morphology kernel. The output text is:

WxH:n,n,...n

where W is the image width, H is the image height, and each n is the intensity of a pixel. When HDRI is used, intensities can be negative.

The output text contains no spaces, quotes or newlines.

The module respects the -precision and -intensity settings.

For example:

%IM7DEV%magick -size 1x5 gradient: -process img2knl NULL:
1x5:1,0.75,0.5,0.25,0

The result can be written to an environment variable ...

for /F "usebackq" %%L in (`%IM7DEV%magick ^
  -size 1x5 gradient: -process img2knl NULL:`) do set KNL=%%L

echo %KNL% 
1x5:1,0.75,0.5,0.25,0 

... and then used as a kernel:

%IMG7%magick ^
  xc: ^
  -define morphology:showkernel=1 -morphology convolve %KNL% ^
  NULL: 
Kernel "User Defined" of size 1x5+0+2 with values from 0 to 1
Forming a output range from 0 to 2.5 (Sum 2.5)
 0:         1
 1:      0.75
 2:       0.5
 3:      0.25
 4:         0

At my request, IM can now read a kernel from a file. (See IM forum thread Kernels from text files.) If the text output from img2knl is directed to a file, IM can read that into a kernel.

Future: I may provide an option for a kernel offset, eg +2+3 and other stuff that can come before the colon ('@', '<' and '>'). Perhaps offsets would come from image attributes or canvas offsets. (However, canvas offsets default to 0,0 where kernel offsets default to w/2,h/2.) I may also provide the option to split the output into more readable lines.

This module might be better as a coder, so we can output to (say) KNL:myknl.txt. An input coder could also be written, so we could input from KNL:myknl.txt, adding an image to the current list. If the kernel contained a rotation symbol, would this add multiple images to the list? Perhaps this is best handled with image attributes.

The script knl2img.bat creates a kernel image from a kernel string.

call %PICTBAT%knl2img "%KNL%" cu_knl.png
cu_knl.png
call %PICTBAT%blockPix cu_knl.png
cu_knl_bp.png

See also the scripts img2knl4.bat and img2knl4f.bat that write kernel strings to environment variable and text files respectively.

Practical module: interppix, interpolate pixel values

interppix.c returns the interpolated pixel values found at given coordinates. It provides a command-line inerface to IM's InterpolateMagickPixelPacket() function.

The module takes an even number of input arguments, the x- and y-coordinates of the desired points. These are floating-point values. Coordinates can be given as conventional pixel coordinates, or as percentages of image width-1 and height-1 by using a suffixed "%" or "c", or as proportions of image width-1 and height-1 by using a suffixed "p".

"c" is a synonym for "%". It avoids some complicated escaping in scripts and programs.

The suffixes only apply to the given coordinate. If they are both intended as percentages, they both must be suffixed.

Why are percentages and proportions based on image dimensions minus one? Because then:

If an image is 301x201 pixels, the following coordinate pairs refer to the same position:

234 123
78% 61.5%
78c 61.5c
0.78p 0.615p
78c 0.615p

The module can be called multiple times, once per coordinate-pair. But it is much faster to call it just once, listing all the coordinate pairs. Input arguments must be separated by spaces.

The module outputs one line with "interppix:" and 13 numbers to stderr:

The five channel values are RGBAK, CMYAK, or whatever comes out of InterpolateMagickPixelPacket(). However, for IM v6 the code subtracts the "opacity" value from QuantumRange.

set COORDS=-0.5 0  0.0 0  0.5 0  1.0 0  1.25 0  1.5 0  ^
  2.0 0  2.5 0  3.0 0  3.5 0  4.0 0  4.5 0

%IM7DEV%magick ^
  -size 2x1 ^
  xc:Black xc:White +append +repage ^
  -alpha Opaque ^
  -channel A -evaluate Multiply 0.25 +channel ^
  +write txt: ^
  +write cu_interppix.miff ^
  -background Blue -virtual-pixel Background ^
  -process 'interppix %COORDS%' ^
  NULL:  1> cu_interppix.lis 2>&1
interppix: 0: @-0.5,0 (0,0,3.4359738e+09,2.6843546e+09,0) (0%,0%,80%,62.5%,0%)
interppix: 0: @0,0 (0,0,0,1.0737418e+09,0) (0%,0%,0%,25%,0%)
interppix: 0: @0.5,0 (0,0,0,1.0737418e+09,0) (0%,0%,0%,25%,0%)
interppix: 0: @1,0 (0,0,0,1.0737418e+09,0) (0%,0%,0%,25%,0%)
interppix: 0: @1.25,0 (1.0737418e+09,1.0737418e+09,1.0737418e+09,1.0737418e+09,0) (25%,25%,25%,25%,0%)
interppix: 0: @1.5,0 (2.1474836e+09,2.1474836e+09,2.1474836e+09,1.0737418e+09,0) (50%,50%,50%,25%,0%)
interppix: 0: @2,0 (4.2949673e+09,4.2949673e+09,4.2949673e+09,1.0737418e+09,0) (100%,100%,100%,25%,0%)
interppix: 0: @2.5,0 (4.2949673e+09,4.2949673e+09,4.2949673e+09,1.0737418e+09,0) (100%,100%,100%,25%,0%)
interppix: 0: @3,0 (4.2949673e+09,4.2949673e+09,4.2949673e+09,1.0737418e+09,0) (100%,100%,100%,25%,0%)
interppix: 0: @3.5,0 (8.5899346e+08,8.5899346e+08,4.2949673e+09,2.6843546e+09,0) (20%,20%,100%,62.5%,0%)
interppix: 0: @4,0 (0,0,4.2949673e+09,4.2949673e+09,0) (0%,0%,100%,100%,0%)
interppix: 0: @4.5,0 (0,0,4.2949673e+09,4.2949673e+09,0) (0%,0%,100%,100%,0%)
# ImageMagick pixel enumeration: 4,1,0,4294967295,srgba
0,0: (0,0,0,1073741824)  #00000000000000000000000040000000  srgba(0,0,0,0.25)
1,0: (0,0,0,1073741824)  #00000000000000000000000040000000  srgba(0,0,0,0.25)
2,0: (4294967295,4294967295,4294967295,1073741824)  #FFFFFFFFFFFFFFFFFFFFFFFF40000000  srgba(255,255,255,0.25)
3,0: (4294967295,4294967295,4294967295,1073741824)  #FFFFFFFFFFFFFFFFFFFFFFFF40000000  srgba(255,255,255,0.25)

The image has four pixels, with conventional x-coordinates 0, 1, 2 and 3. The module shows normal images values when it is given integer coordinates. At non-integer coordinates, it interpolates between image pixels. Beyond the coordinate width-1, it interpolates between the last pixel and the virtual colour, which is blue.

Repeat the test, but with spline interpolation:

%IM7DEV%magick ^
  cu_interppix.miff ^
  -background Blue -virtual-pixel Background ^
  -interpolate Spline ^
  -process 'interppix %COORDS%' ^
  NULL:  1> cu_interppix2.lis 2>&1
interppix: 0: @-0.5,0 (0,0,2.8633115e+09,3.2212255e+09,0) (0%,0%,66.666667%,75%,0%)
interppix: 0: @0,0 (0,0,1.9088744e+09,2.5053976e+09,0) (0%,0%,44.444444%,58.333333%,0%)
interppix: 0: @0.5,0 (14913081,14913081,1.5062212e+09,2.1922229e+09,0) (0.34722222%,0.34722222%,35.069444%,51.041667%,0%)
interppix: 0: @1,0 (1.1930465e+08,1.1930465e+08,1.5509604e+09,2.1474836e+09,0) (2.7777778%,2.7777778%,36.111111%,50%,0%)
interppix: 0: @1.25,0 (2.2742448e+08,2.2742448e+08,1.6590802e+09,2.1474836e+09,0) (5.2951389%,5.2951389%,38.628472%,50%,0%)
interppix: 0: @1.5,0 (3.5791394e+08,3.5791394e+08,1.7895697e+09,2.1474836e+09,0) (8.3333333%,8.3333333%,41.666667%,50%,0%)
interppix: 0: @2,0 (5.9652324e+08,5.9652324e+08,2.028179e+09,2.1474836e+09,0) (13.888889%,13.888889%,47.222222%,50%,0%)
interppix: 0: @2.5,0 (6.8600172e+08,6.8600172e+08,2.1773098e+09,2.1922229e+09,0) (15.972222%,15.972222%,50.694444%,51.041667%,0%)
interppix: 0: @3,0 (5.9652324e+08,5.9652324e+08,2.5053976e+09,2.5053976e+09,0) (13.888889%,13.888889%,58.333333%,58.333333%,0%)
interppix: 0: @3.5,0 (3.5791394e+08,3.5791394e+08,3.2212255e+09,3.2212255e+09,0) (8.3333333%,8.3333333%,75%,75%,0%)
interppix: 0: @4,0 (1.1930465e+08,1.1930465e+08,3.9370534e+09,3.9370534e+09,0) (2.7777778%,2.7777778%,91.666667%,91.666667%,0%)
interppix: 0: @4.5,0 (14913081,14913081,4.2502281e+09,4.2502281e+09,0) (0.34722222%,0.34722222%,98.958333%,98.958333%,0%)

With spline interpolation, the red and green channels don't reach 100% before falling down to the virtual colour.

The module always outputs a value for opacity ("alpha"). If alpha is currently off, the output value should be ignored.

Like other process modules, this processes all the images in the current list.

The module respects -virtual-pixel, -interpolate and -precision. It does not respect -channel settings.

(Can this currently be done with a command? Scrolling with distort SRT?)

%IM7DEV%magick ^
  cu_interppix.miff ^
  -background Blue -virtual-pixel Background ^
  -interpolate Spline ^
  -distort SRT 3.5,0,1,0,0,0 -crop 1x1+0+0 +repage ^
  txt: 
# ImageMagick pixel enumeration: 1,1,0,4294967295,srgba
0,0: (829902453,829902453,4294967295,2718884104)  #31774E7531774E75FFFFFFFFA20EE108  srgba(19.322672%,19.322672%,100%,0.63303953)

Future: the module could create an image from the found values.

Practical module: mkgauss, make Gaussian clut

mkgauss.c adds a new image size Nx1 with the RGB channels set to a Gaussian curve, also known as General Normal Distribution, or bell curve. When cumul is specified, makes Cumulative Distribution Function (CDF). See Wikipedia: Normal distribution.

Like other process modules, this one requires that the image list already has at least one entry. However, the image itself is irrelevant. For the examples here, I create an image with xc: and delete it immediately after the process.

Option Description
Short
form
Long form
w N width N Output width (pixels).
Default: 256.
m N mean N The x-coordinate of the mean (pixels or % of width).
Default: 50%.
sd N standarddeviation N The standard deviation x-interval (pixels or % of width).
Default: 10%.
k N skew N Skew to place the peak away from the specified mean
(pixels or % of width).
Default: 0.
sm N skewmethod N Method of skewing: 1 or 2.
1: smooth transition left and right of peak.
2: different linear shift left and right of peak.
Default: 1.
z zeroize Subtract the minimum calculated value so output has a zero.
c cumul Cumulate the values.
n norm Normalise the output so the maximum is Quantum.
v verbose Write some text output to stderr.

But doesn't sigmoidal-contrast already do this? No. The sigmoidal-contrast curve is the cumulative of the logistic curve. See Wikipedia: Logistic distribution. De-cumulating the sigmoidal-contrast curve yields a similar result to the gaussian curve, but they are not identical.

Each value for mean, standard deviation and skew is in units of pixels unless it is suffixed with a percent sign (%) or 'c', which makes it a percentage of the width. In Windows BAT files, we need to double the percent sign: %%.

The module implements the usual formula for the distribution:

y = 1 / (sd * sqrt(2*pi)) * exp (-(x-mean)*(x-mean)/(2*sd*sd))

However, if a skew is given, the x-value is first modified with a power function. A power function shifts middle values, without changing values at 0 or 100%.

Zeroizing occurs before cumulation.

For the basic (unskewed, unzeroized) curve, about 68.3% of values will be within (mean ± sd); 95.4% will be within (mean ± 2*sd); 99.7% will be within (mean ± 3*sd);

A non-default mean will shift the entire graph to the left or right. A non-default skew will shift the peak to the left or right without shifting the ends of the graph; it will also change the mean and SD.

If not normalised, the mean pixel value of the entire function (from -infinity to +infinity) will be 1.0 * QuantumValue. Provided IM is compiled with HDRI, the module does not clamp or clip values to Quantum. For example:

%IM7DEV%magick ^
  xc: ^
  -process 'mkgauss v sd 1%%' ^
  -format "mean=%%[fx:mean]  sd=%%[fx:standard_deviation]  max=%%[fx:maxima]\n" ^
  info: 
 
mean=1  sd=0  max=1
mean=0.99609375  sd=5.216645  max=39.134651

The first image is white so the maximum value is 1 * Quantum. The mkgauss image has a much larger maximum value.

The calculated mean value of the curve should be 1.0 (by definition of the function), but will be less as the graph does not extend to infinity in either direction.

To verify the module has made the desired standard deviation, we make a cumulative function and examine the value at the mean minus one standard deviation. As about 68.3% of values should be within (mean ± sd), (100-68.3)/2 = 15.85% of values should be below (mean - sd).

%IM7DEV%magick ^
  xc: ^
  -process 'mkgauss width 100000 v sd 1%% cumul norm' ^
  -delete 0 ^
  -crop 2x1+48999+0 ^
  -format "mean=%%[fx:mean]\n" ^
  info: 
 
mean=0.15877387

Other examples, displaying the Nx1 image as a graph:

set IFFEXT=tiff

%IM7DEV%magick ^
  xc: ^
  -process 'mkgauss sd 1%%' ^
  -delete 0 ^
  cu_mg0.%IFFEXT%

call %PICTBAT%graphLineCol ^
  cu_mg0.%IFFEXT% . . 0 cu_mg0_glc.png
cu_mg0_glc.png
%IM7DEV%magick ^
  xc: ^
  -process 'mkgauss sd 30%%' ^
  -delete 0 ^
  cu_mg1.%IFFEXT%

call %PICTBAT%graphLineCol ^
  cu_mg1.%IFFEXT% . . 0 cu_mg1_glc.png
cu_mg1_glc.png
%IM7DEV%magick ^
  xc: ^
  -process 'mkgauss sd 30%% norm' ^
  -delete 0 ^
  cu_mg2.%IFFEXT%

call %PICTBAT%graphLineCol ^
  cu_mg2.%IFFEXT% . . 0 cu_mg2_glc.png
cu_mg2_glc.png
%IM7DEV%magick ^
  xc: ^
  -process 'mkgauss sd 30%% cumul norm' ^
  -delete 0 ^
  cu_mg3.%IFFEXT%

call %PICTBAT%graphLineCol ^
  cu_mg3.%IFFEXT% . . 0 cu_mg3_glc.png
cu_mg3_glc.png

Shift the mean to 20% of the width.

%IM7DEV%magick ^
  xc: ^
  -process 'mkgauss sd 30%% mean 20%% norm' ^
  -delete 0 ^
  cu_mg4.%IFFEXT%

call %PICTBAT%graphLineCol ^
  cu_mg4.%IFFEXT% . . 0 cu_mg4_glc.png

The curve remains symmetrical, so the start and end points are not equal.

cu_mg4_glc.png

Shift the peak 20% (of the width) left of the mean position.

%IM7DEV%magick ^
  xc: ^
  -process 'mkgauss sd 30%% skew -20%% norm' ^
  -delete 0 ^
  cu_mg5.%IFFEXT%

call %PICTBAT%graphLineCol ^
  cu_mg5.%IFFEXT% . . 0 cu_mg5_glc.png
cu_mg5_glc.png

Shift the peak 20% (of the width) right of the mean position.

%IM7DEV%magick ^
  xc: ^
  -process 'mkgauss sd 30%% skew 20%% norm' ^
  -delete 0 ^
  cu_mg6.%IFFEXT%

call %PICTBAT%graphLineCol ^
  cu_mg6.%IFFEXT% . . 0 cu_mg6_glc.png

This is a mirror image of -20%.

cu_mg6_glc.png

Shift the peak 20% (of the width) left of the mean position, and cumulate.

%IM7DEV%magick ^
  xc: ^
  -process 'mkgauss sd 30%% skew -20%% cumul norm' ^
  -delete 0 ^
  cu_mg7.%IFFEXT%

call %PICTBAT%graphLineCol ^
  cu_mg7.%IFFEXT% . . 0 cu_mg7_glc.png

The steepest part of the cumulative clut is at
the largest value of the non-cumulative.

cu_mg7_glc.png

Repeat the previous two examples, with zeroize.

Shift the peak 20% (of the width) right of the mean position.

%IM7DEV%magick ^
  xc: ^
  -process 'mkgauss sd 30%% skew 20%% zeroize norm' ^
  -delete 0 ^
  cu_mg6a.%IFFEXT%

call %PICTBAT%graphLineCol ^
  cu_mg6a.%IFFEXT% . . 0 cu_mg6a_glc.png

Doing both zeroise and normalise has the same effect as -autolevel.

cu_mg6a_glc.png
%IM7DEV%magick ^
  xc: ^
  -process 'mkgauss sd 30%% skew -20%% zeroize cumul norm' ^
  -delete 0 ^
  cu_mg7a.%IFFEXT%

call %PICTBAT%graphLineCol ^
  cu_mg7a.%IFFEXT% . . 0 cu_mg7a_glc.png
cu_mg7a_glc.png

There are two methods for skewing. They have the same overall effect: the peak is shifted to left or right, with suitable adjustment of values between the peak and ends.

No skew.

%IM7DEV%magick ^
  xc: ^
  -process 'mkgauss sd 30%% norm' ^
  -delete 0 ^
  cu_noskew.%IFFEXT%

call %PICTBAT%graphLineCol ^
  cu_noskew.%IFFEXT% . . 0 cu_noskew_glc.png
cu_noskew_glc.png

Skew left by 20% with method 1.

%IM7DEV%magick ^
  xc: ^
  -process 'mkgauss sd 30%% skew -20%% skewmethod 1 norm' ^
  -delete 0 ^
  cu_sm1.%IFFEXT%

call %PICTBAT%graphLineCol ^
  cu_sm1.%IFFEXT% . . 0 cu_sm1_glc.png

Method 1 varies the compression/expansion across the image.

cu_sm1_glc.png

Skew left by 20% with method 2.

%IM7DEV%magick ^
  xc: ^
  -process 'mkgauss sd 30%% skew -20%% skewmethod 2 norm' ^
  -delete 0 ^
  cu_sm2.%IFFEXT%

call %PICTBAT%graphLineCol ^
  cu_sm2.%IFFEXT% . . 0 cu_sm2_glc.png

Method 2 shrinks the left side evenly, and expands the right side evenly.
This makes the sides straighter and the peak more pointy than method 1.

cu_sm2_glc.png

For a skewed zeroized normalised Gaussian curve, find the values at x=0, 20%, .. 100%, as percentages of maximum.

(for /F "usebackq tokens=10 delims=:()@,%% " %%V in (`%IM7DEV%magick ^
  xc: ^
  -process 'mkgauss w 1000 sd 30%% skew -20%% zeroize norm' ^
  -delete 0 ^
  -process 'interppix 0%% 0  20%% 0  40%% 0  60%% 0  80%% 0  100%% 0' ^
  NULL: 2^>^&1`) do @echo %%V) >cu_gauss_vals.lis
0
92.21699
94.129833
62.169307
26.648452
0

mkgauss makes an image that represents a Gaussian curve with the given mean and SD. If we want an image with a Gaussian distribution of pixel values, we can follow mkgauss with invclut :

%IM7DEV%magick ^
  xc: ^
  -process 'mkgauss width 65536 mean 40%% sd 10%% cumul norm' ^
  -delete 0 ^
  -process 'invclut' ^
  +write cu_gauss_dist.png ^
  -format "mean=%%[fx:mean] SD=%%[fx:standard_deviation]" ^
  info: 
mean=0.40001599 SD=0.099998906

The result is not exact, especially for SD greater than 20% or so, because the curve does not extend to ±infinity.

The result, cu_gauss_dist.png, is 65536x1, which is difficult to see. We can crop it into rows and append them vertically:

Skew left by 20% with method 2.

%IMG7%magick ^
  cu_gauss_dist.png ^
  -crop 256x ^
  -append +repage ^
  cu_gauss_dist_sq.png
cu_gauss_dist_sq.png

For example uses, see the pages Camera blur (unpublished) and Contrast-limited equalisation.

Practical module: mkhisto, make histogram

mkhisto.c replaces each image in the list with a histogram size Nx1, channel by channel. Each output file contains three separate histograms, one for each of the input RBG channels.

The process is reversible: from a histogram, we can easily make an image for which this would be the histogram. See Application: de-histogram below.

If any options are given, the filter name and its options must be quoted.

Option Description
Short
form
Long form
b N capnumbuckets N Limit the number of buckets (pixel width) to N, where N>0.
Default: limit to 65536.
m N multiply N Multiply the count by N. Useful when output format has less precision than IM's Q-number.
Default: 1.
r regardalpha Multiply the count by alpha, so transparent pixels aren't counted.
l log Use logs of counts.
c cumul Cumulate the histogram values.
n norm Normalise the histogram so the maximum is Quantum. (The minimum may not be zero.)
v verbose Write some text output to stderr.

Each histogram pixel represents the number of input pixels with values within a certain range. The histogram pixels are "buckets". The histogram width, or number of buckets, is determined by the possible number of values per channel in the input image. (A histogram shouldn't be unnecessarily wide, or it will contain many entries that cannot hold values.)

If the input has depth 8, the histogram will normally be 256 pixels wide. For depth 16, it will be 65536. For depth 32, it would be 4 billion pixels wide. IM could handle this, but horribly slowly if this exceeds memory.

The setting capnumbuckets limits the histogram width. This needs an integer from 1 upwards. The default is 65536, so histograms are limited to a width of 65536. For inputs that have more than 16 bits/channel/pixel, it will be more precise with larger numbers, such as 1000000 (one million). A small number might be wanted for special purposes. A single bucket is fairly pointless as all the input pixels will be counted in it, but you can have just one if you want.

Small images may benefit from a small capnumbuckets setting. For example, if a 16-bit/channel image has only 30,000 pixels then probably only half the buckets will have an entry, and the range of counts will be zero to one, or at least very small. This may make further processing meaningless. Perhaps a rule-of-thumb is that the number of buckets should be less than the number of pixels divided by 256.

Every input pixel increases the count in one red bucket, one green bucket and one blue bucket. If no options are given, the bucket is incremented by one for each input pixel.

If the multiply option is given, this will be the basic increment.

If the regardalpha option is given, the basic increment is multiplied by the alpha channel value (on a scale of 0.0 to 1.0). Hence fully transparent pixels will not increment the contents of any bucket. An opaque pixel will add multiply to the count in the appropriate bucket. If non-HDRI, a pixel will either be counted or not, according to whether alpha is > 0.5. For HDRI, each count of multiply is multiplied by alpha, so the histogram may contain non-integer values.

An input image value V (which is one of VR, VG or VB) is counted in bucket number min(int(V/(Q/N)),N-1). For example if there are 20 buckets, numbered 0 to 19, bucket[0] will contain count of input image pixels 0 to 4.999%, bucket[1] will contain 5% to 9.999%, and so on to the final bucket, bucket[19], which will contain 95% to 100%.

If neither cumulative nor normalised, the pixel values in the output represent a true count of the number of pixels, subject to Q which may cause clipping. If normalised (option norm), the output is multiplied such that the largest count becomes Quantum. This makes the graphical output more useful.

Clipping can occur if either of the following are true:

  1. IM is compiled without HDRI, and a bucket count exceeds Quantum;
  2. the norm option is not selected, and the output is written to an integer-only file format (such as PNG),

As from 27 September 2015, code in mkhisto.c checks for the first condition, and issues a warning if this occurs. It cannot check for the second condition.

If cumul alone is given, the result is a true cumulative count of the number of pixels at or below the value, again subject to clipping due to Q. If a photograph contains a million pixels, a Q16 IM build is not sufficient to contain an accurate cumulative count. The build needs to be Q32.

If options cumul and norm are both specified, the result is a traditional cumulative histogram, with all channels roughly zero in the first bucket (pixel coordinate 0,0), and theoretically 100% of quantum in the last bucket.

Beware: If the calculation is performed by a Q32 program, and the input is small, and the results are saved to a Q16 file, all result pixels are likely to be zero. Losing 16 bits of precision is equivalent to dividing by 65536 and rounding to the nearest integer. Any counts less than 32768 will be rounded to zero. If the program is Q32 but the output is to be Q16, use -evaluate LeftShift 16 -depth 16. Left-shifting may clip values to quantum. Eg, if a count is 80,000, it will be clipped to 65535 in a Q16 file. (Put it another way: when we save Q32 data in a Q16 file, we can lose either the bottom 16 bits or the top 16 bits.) TIFF files can be saved as -depth 32.

Another way around this is to use the mult option.

Options can be given in the short form (single letters) or long form. Where an option takes an argument, separate them with a space (not an equals sign "="). Options can be given in any order. If an invalid option is given, such as help, usage text will be written and the filter will fail. (Currently, this will not cause the program to fail.)

CAUTION: So far, I have tested this only in a Q32 HDRI build.

In the following examples, I limit the number of buckets to 500 for web display. For normal use, I wouldn't use such a small number. The script graphLineCol.bat displays the graphs with a height of 256 pixels, from a theoretical height of 232-1 = 4 billion.

%IM7DEV%magick ^
  toes.png ^
  -process 'mkhisto capnumbuckets 500' ^
  -depth 32 ^
  cu_h1.%IFFEXT%

call %PICTBAT%graphLineCol ^
  cu_h1.%IFFEXT% . . 0 cu_h1_glc.png

The counts are too low to register on the graph.

cu_h1_glc.png

To make the counts visible, we normalise.

%IM7DEV%magick ^
  toes.png ^
  -process 'mkhisto capnumbuckets 500 norm' ^
  -depth 32 ^
  cu_hn.%IFFEXT%

call %PICTBAT%graphLineCol ^
  cu_hn.%IFFEXT% . . 0 cu_hn_glc.png

Normalising multiplies the three channels by the same factor,
so two channels may not reach the maximim.

cu_hn_glc.png

Compare this to the conventional IM histogram:

%IMG7%magick ^
  toes.png ^
  -density 256x256 ^
  -define histogram:unique-colors=false ^
  histogram:cu_toes_hist.png

(In v6.9.0-6 and other versions,
the output is actually created as a MIFF file.)

cu_toes_hist.png
%IM7DEV%magick ^
  toes.png ^
  -process 'mkhisto capnumbuckets 500 cumul' ^
  -depth 32 ^
  cu_hc.%IFFEXT%

call %PICTBAT%graphLineCol ^
  cu_hc.%IFFEXT% . . 0 cu_hc_glc.png

The counts are too low to register on the graph.

cu_hc_glc.png
%IM7DEV%magick ^
  toes.png ^
  -process 'mkhisto capnumbuckets 500 cumul norm' ^
  -depth 32 ^
  cu_hcn.%IFFEXT%

call %PICTBAT%graphLineCol ^
  cu_hcn.%IFFEXT% . . 0 cu_hcn_glc.png

Norm and cumul together will multiply the three channels by different factors,
so all three channels will reach the maximim.

cu_hcn_glc.png

We can sort a histogram (for no good reason that I can see):

%IM7DEV%magick ^
  toes.png ^
  -process 'mkhisto capnumbuckets 500 norm' ^
  -process sortpixels ^
  -depth 32 ^
  cu_hcns.%IFFEXT%

call %PICTBAT%graphLineCol ^
  cu_hcns.%IFFEXT% . . 0 cu_hcns_glc.png
cu_hcns_glc.png

If we greyscale the image first, the resulting sorted histogram is:

%IM7DEV%magick ^
  toes.png ^
  -modulate 100,0,100 ^
  -process 'mkhisto capnumbuckets 500 norm' ^
  -process sortpixels ^
  -depth 32 ^
  cu_hcngs.%IFFEXT%

call %PICTBAT%graphLineCol ^
  cu_hcngs.%IFFEXT% . . 0 cu_hcngs_glc.png
cu_hcngs_glc.png

PPM is a convenient format to extract numerical values. Use -compress None to get text output. (An IM option to get one PPM pixel per line would be useful, to aid post-processing. Likewise for sparse-color:.) For example, 4 pixels (buckets) near the centre of the normalised histogram:

%IM7DEV%magick cu_hn.%IFFEXT% -crop 4x1+250+0 -compress None ppm:
P3
#
4 1
4294967295
1575052672 2005243264 2060751616 1581991168 2053813120 2074628736 1602806912 2234215680 2032997504 1464035712 2206461440 2157891584 

From the plain, un-normalised histogram:

%IM7DEV%magick cu_h1.%IFFEXT% -crop 4x1+250+0 -compress None ppm:>cu_h1.ppm
P3
#
4 1
4294967295
227 289 297 228 296 299 231 322 293 211 318 311 

This tells us that from the original image, the red channel of 231 pixels fell into bucket number 250; the green channel of 287 pixels fell into bucket number 250; and the blue channel of 301 pixels fell into bucket number 250. These number are so low compared to quantum (300 compared to 4 billion) that they don't register on the graph.

By specifying a generous cap to the number of buckets, we try to ensure we get one value per bucket. We also give the verbose option to get some information, and -write info:.

%IM7DEV%magick ^
  toes.png ^
  -process 'mkhisto verbose capnumbuckets 1000000' ^
  -depth 32 ^
  -write info: ^
  cu_h1wide.%IFFEXT% 1> cu_h1wide.lis 2>&1
mkhisto options: capnumbuckets 1000000  multiply 1  verbose 
mkhisto: Input image [toes.png] depth is 16
  NumBuckets 65536  BucketSize 65536
  counts: min_value 1, max_value 49
  sum_red 62211, sum_green 62211, sum_blue 62211
toes.png PNG 65536x1 65536x1+0+0 32-bit sRGB 320268B 0.094u 0:00.093

This tells us the options we have specified. The input image has 16 bits/channel/pixel, so we have 65536 buckets. "BucketSize" is the range of input values, within the quantum of the program, per bucket. When IM reads 16-bit toes.png, it scales pixel values up to quantum, in this case 32 bits.

The other numbers are for debugging. toes.png has 62211 pixels. The lowest value in any histogram bucket that has any count is 1; the largest value is 49. The sum of the red, green and blue values is equal to the number of pixels. If the input image had any transparency, and the regardalpha option had been set, the sums would have been less.

As expected, the created histogram is 65536 pixels wide.

We can dump the contents of 4 buckets:

%IM7DEV%magick cu_h1wide.%IFFEXT% -crop 4x1+32768+0 -compress None ppm: >cu_h1wide.ppm
P3
#
4 1
4294967295
0 0 0 8 15 3 0 0 0 0 0 0 

This tells us that no toes.png pixels have any RGB channel of 32768 or 32769. 8 pixels have 32770 in the red channel, 15 pixels have 32770 in the green channel, and 3 pixels have 32770 in the blue channel.

If the process module mkhisto were to be incorporated into conventional IM code as an ordinary command, I suggest the following syntax:

The command is named -mkhisto, with no arguments.

The -mkhisto command respects options, if specified before the command:

Or any or all of these might be arguments to the -mkhisto command, or -define attributes.

The -mkhisto command would respect the general -verbose setting. It might also respect the -channel setting, and work correctly for CMY(K)(A) images.

Practical module: invert clut

invclut.c inverts a clut file, so the input becomes the output and the output becomes the input.

Like most IM operations, it operates on all the images in the list. Each image will be replaced by another of the same size. If an input image has more than one row, each is processed separately, as if it were a clut.

(I might add an option to process just the last image.)

A cumulative normalised histogram can be regarded as a clut file: for any input value, it defines an output value. Importantly, every input defines exactly one output. (Possibly, every output is defined by exactly one input.) A general clut file might have more than one input defining the same output. A graph of the clut may rise, fall or be level. However, a cumulative normalised histogram increases monotonically; it may rise or be level, but cannot fall. See my Clut cookbook: Cluts from cumulative histogram.

Invclut assumes each channel increases monotonically. If this is not true, the result will be garbage. (The module could be modified to produce a good result for monotonically decreasing inputs, and to raise errors on inputs that both rise and fall.)

If the input contains an alpha channel, it is copied unchanged to the output. (test??)

We will invert cu_hcn.%IFFEXT%, the cumulative normalised histogram created above.

We will invert this:

call %PICTBAT%graphLineCol ^
  cu_hcn.%IFFEXT% . . 0 cu_hcn_glc.png
cu_hcn_glc.png

We invert it in two ways. The first is by making a cumulative normalised histogram of cu_hcn.%IFFEXT%. The second uses the invclut method.

%IM7DEV%magick ^
  cu_hcn.%IFFEXT% ^
  -process 'mkhisto capnumbuckets 500 cumul norm' ^
  -depth 32 ^
  cu_hcn_i1.%IFFEXT%

call %PICTBAT%graphLineCol ^
  cu_hcn_i1.%IFFEXT% . . 0 cu_hcn_i1_glc.png
cu_hcn_i1_glc.png
%IM7DEV%magick ^
  cu_hcn.%IFFEXT% ^
  -process invclut ^
  -depth 32 ^
  cu_hcn_i2.%IFFEXT%

call %PICTBAT%graphLineCol ^
  cu_hcn_i2.%IFFEXT% . . 0 cu_hcn_i2_glc.png
cu_hcn_i2_glc.png

Visually, the curves appear identical, apart from the ends.

To check the result, we apply the same two processes to the results. An inverse of an inverse should take us back where we started.

%IM7DEV%magick ^
  cu_hcn_i1.%IFFEXT% ^
  -process 'mkhisto capnumbuckets 500 cumul norm' ^
  -depth 32 ^
  cu_hcn_i3.%IFFEXT%

call %PICTBAT%graphLineCol ^
  cu_hcn_i3.%IFFEXT% . . 0 cu_hcn_i3_glc.png
cu_hcn_i3_glc.png
%IM7DEV%magick ^
  cu_hcn_i2.%IFFEXT% ^
  -process invclut ^
  -depth 32 ^
  cu_hcn_i4.%IFFEXT%

call %PICTBAT%graphLineCol ^
  cu_hcn_i4.%IFFEXT% . . 0 cu_hcn_i4_glc.png
cu_hcn_i4_glc.png

Compare each with the original:

%IM7DEV%magick compare -metric RMSE cu_hcn.%IFFEXT% cu_hcn_i3.%IFFEXT% NULL: 
cmd /c exit /B 0
8014544.6 (0.0018660316)
%IM7DEV%magick compare -metric RMSE cu_hcn.%IFFEXT% cu_hcn_i4.%IFFEXT% NULL: 
cmd /c exit /B 0
8623753 (0.002007874)

Both results are close to the original. The histogram has 500 buckets, so we shouldn't expect an accuracy much better than 1/500 = 0.002.

Let's repeat the process, not limiting the number of buckets to 500. The first convert makes a cumulative normalised histogram of toes.png, saves it, and inverts it twice. The second convert takes the saved cumulative normalised histogram and inverts it twice, with the invclut module. Then we compare the two results with the saved cumulative normalised histogram.

%IM7DEV%magick ^
  toes.png ^
  -process 'mkhisto cumul norm' ^
  -write cu_hcn_wide.%IFFEXT% ^
  -process 'mkhisto cumul norm' ^
  -process 'mkhisto cumul norm' ^
  -depth 32 ^
  cu_hcn_wide1.%IFFEXT%

%IM7DEV%magick ^
  cu_hcn_wide.%IFFEXT% ^
  -process invclut ^
  -process invclut ^
  -depth 32 ^
  cu_hcn_wide2.%IFFEXT%

%IM7DEV%magick compare -metric RMSE cu_hcn_wide.%IFFEXT% cu_hcn_wide1.%IFFEXT% NULL: 
cmd /c exit /B 0
258629.3 (6.0216826e-05)
%IM7DEV%magick compare -metric RMSE cu_hcn_wide.%IFFEXT% cu_hcn_wide2.%IFFEXT% NULL: 
cmd /c exit /B 0
255069.6 (5.9388018e-05)

The results are better than capnumbuckets 500 by a factor of about a hundred. The histogram has 65536 buckets, so we shouldn't expect an accuracy much better than 1/65536 = 1.53e-05.

Only when writing these process modules and this page did I discover that inverting a clut is the same as taking a cumulative normalised histogram of the clut. This came as a great surprise to me, but I suppose it is documented in the literature, and may be a well-known fact. (It is related to the fact that clutting an image with its own cumulative histogram equalises the histogram.)

If a clut doesn't increase from black to white, but from a% to b%, then the inverse will have the first a% of buckets black, and the last (100-b%) buckets will be white.

For using the invclut module to displace one shape into another, see the (unpublished) page Shape to shape: displacement maps.

Inverting a normalised cumulative histogram by either method gives us the median, or any desired threshold:

%IM7DEV%magick ^
  toes.png ^
  -colorspace Gray ^
  -process 'mkhisto cumul norm' ^
  -process 'mkhisto cumul norm' ^
  -gravity Center -crop 1x1+0+0 +repage ^
  txt: 
# ImageMagick pixel enumeration: 1,1,0,65535,gray
0,0: (31439)  #7ACF7ACF7ACF  gray(47.972107%)

Currently, histograms are calculated for each channel independently. So using this method on a colour image would give the median red, median green and median blue, which would not be the same as any form of median intensity.

For an alternative method to find the median, probably slower, see sortpixels.

Practical module: cumulate histogram

cumulhisto.c makes cumulative histograms from non-cumulative histograms, or vice versa.

Option Description
Short
form
Long form
r regardalpha Multiply input RGB values by alpha; cumulate alpha.
d decumul De-cumulate the histogram values, instead of cumulating them.
n norm Normalise the output histogram so the maximum is Quantum. (The minimum may not be zero.)
v verbose Write some text output to stderr.

Cumulation is an integration of the input, defined as, for all y:

cumul[0,y] = in_image[0,y]
cumul[x,y] = in_image[x,y] + cumul[x-1,y] for x > 0.

De-cumulation is the opposite, a differentiation:

decumul[0,y] = in_image[0,y]
decumul[x,y] = in_image[x,y] - decumul[x-1,y] for x > 0.

The input is typically one image with a single row that represents a histogram. Multiple input images will result in corresponding multiple output images. If an image has multiple rows, each is treated as an independent histogram, and is cumulated into an output row.

Each output will be the same size as the corresponding input.

By default, the alpha channel is copied unchanged. If regardalpha is used, the cumulation process is modified so each colour channel value is multiplied by alpha (scaled to 0.0 to 1.0) before being cumulated, and the alpha channel is also cumulated.

Input histograms can be normalised or not. Normalisation effects the RGB channels only (not alpha); values are scaled so the maximum value is 100% of QuantumRange. Output histograms will be normalised if the norm option is used. If an input is normalised but the output isn't, cumulated results will be clipped.

CAUTION: If the input images aren't histograms, this module won't make histograms of any kind. The result may be quite pretty when normalised, or the un-normalised version can be useful, eg to make Integral images.

Examples of cumulation:

For the input, we use the non-cumulative histogram cu_hn.%IFFEXT% created above.

call %PICTBAT%graphLineCol ^
  cu_hn.%IFFEXT% . . 0 cu_hn_glc2.png
cu_hn_glc2.png

From that input, we make a cumulative histogram.

%IM7DEV%magick ^
  cu_hn.%IFFEXT% ^
  -process 'cumulhisto norm' ^
  -depth 32 ^
  cu_hn_c.%IFFEXT%

call %PICTBAT%graphLineCol ^
  cu_hn_c.%IFFEXT% . . 0 cu_hn_c_glc.png

Where the input is highest, the output is steepest.

Cumulation has smoothed the histogram's appearance because of the great difference in scale;
visual precision has been lost.

cu_hn_c_glc.png

We can make a normalised cumulative histogram of a Gaussian distribution,
and its inverse.

%IM7DEV%magick ^
  -size 2x1 xc: -bordercolor Black -border 1x0 ^
  -filter gaussian ^
  -resize "500x1^!" ^
  -process 'cumulhisto norm' ^
  -depth 32 ^
  -write cu_gauss_ch.%IFFEXT% ^
  -process invclut ^
  cu_gauss_ch_icl.%IFFEXT%

call %PICTBAT%graphLineCol ^
  cu_gauss_ch.%IFFEXT% . . 0 cu_gauss_ch_glc.png

call %PICTBAT%graphLineCol ^
  cu_gauss_ch_icl.%IFFEXT% . . 0 cu_gauss_ch_icl_glc.png
cu_gauss_ch_glc.png cu_gauss_ch_icl_glc.png

Make a clut that will transform each channel of toes.png to a Gaussian distribution.

We would normally create a greyscale clut to process the channels identically,
but we process them separately for fun.

%IMG7%magick ^
  cu_hn_c.%IFFEXT% ^
  cu_gauss_ch_icl.%IFFEXT% ^
  -clut ^
  cu_to_gauss.%IFFEXT%

call %PICTBAT%graphLineCol ^
  cu_to_gauss.%IFFEXT% . . 0 cu_to_gauss_glc.png
cu_to_gauss_glc.png

Apply the clut so each channel of toes.png becomes a Gaussian distribution.

%IMG7%magick ^
  toes.png ^
  cu_to_gauss.%IFFEXT% ^
  -clut ^
  cu_toes_gauss.png

%IM7DEV%magick ^
  cu_toes_gauss.png ^
  -process 'mkhisto capnumbuckets 500 norm' ^
  cu_toes_gauss_hn.png

call %PICTBAT%graphLineCol ^
  cu_toes_gauss_hn.png . . 0 cu_toes_gauss_hn_glc.png
cu_toes_gauss.pngjpg cu_toes_gauss_hn_glc.png

Just for fun, apply the cumulhisto process to toes.png.

%IM7DEV%magick ^
  toes.png ^
  -process 'cumulhisto norm' ^
  cu_toes_ch.png

The result is pretty, but junk.

cu_toes_ch.pngjpg

Examples of de-cumulation:

From cu_hn_c.%IFFEXT% created above, we make a de-cumulated histogram.

%IM7DEV%magick ^
  cu_hn_c.%IFFEXT% ^
  -process 'cumulhisto decumul norm' ^
  -depth 32 ^
  cu_hn_dc.%IFFEXT%

call %PICTBAT%graphLineCol ^
  cu_hn_dc.%IFFEXT% . . 0 cu_hn_dc_glc.png

We can verify the round-trip:

%IMG7%magick compare -metric RMSE cu_hn.%IFFEXT% cu_hn_dc.%IFFEXT% NULL: 
cmd /c exit /B 0
0.111195 (1.69673e-06)

The round-trip is accurate, within 32-bit integer quantum.

cu_hn_dc_glc.png

De-cumulate a sigmoidal curve.

%IM7DEV%magick ^
  -size 1x500 gradient: -rotate 90 ^
  -sigmoidal-contrast 10x50%% ^
  -process 'cumulhisto decumul norm' ^
  -depth 32 ^
  cu_sig_hn.%IFFEXT%

call %PICTBAT%graphLineCol ^
  cu_sig_hn.%IFFEXT% . . 0 cu_sig_hn_glc.png
cu_sig_hn_glc.png

De-cumulate a different sigmoidal curve.

%IM7DEV%magick ^
  -size 1x500 gradient: -rotate 90 ^
  -sigmoidal-contrast 10x20%% ^
  -process 'cumulhisto decumul norm' ^
  -depth 32 ^
  cu_sig2_hn.%IFFEXT%

call %PICTBAT%graphLineCol ^
  cu_sig2_hn.%IFFEXT% . . 0 cu_sig2_hn_glc.png
cu_sig2_hn_glc.png

For using these process modules to displace one shape into another, see the (unpublished) page Shape to shape: displacement maps.

ASIDE: After writing the above, IM has acquired an -integral operation. This has the same effect as "-process cumulhisto". We can also divide the result by the right-most pixel scaled to the full width to get the same effect as "-process 'cumulhisto norm'".

%IMG7%magick ^
  cu_hn.%IFFEXT% ^
  -set option:WW %%w ^
  -integral ^
  ( +clone ^
    -crop 1x1+%%[fx:w-1]+0 +repage ^
    -scale "%%[WW]x1^!" ^
  ) ^
  -compose DivideSrc -composite ^
  cu_integ.%IFFEXT%

call %PICTBAT%graphLineCol ^
  cu_integ.%IFFEXT% . . 0 cu_integ.png
cu_integ.png

Compare this to the result from "-process 'cumulhisto norm'":

%IMG7%magick compare ^
  -metric RMSE ^
  cu_hn_c.%IFFEXT% cu_integ.%IFFEXT% ^
  NULL: 
0.00931123 (1.4208e-07)

The result is practically identical.

Practical module: invert displacement map

invdispmap.c inverts displacement maps.

A displacement map will transform image A into image B. If we invert the displacement map, we can use the result to transform image B into image A.

Option Description
Short
form
Long form
t string type string Declare the map type.
string must be one of:
  Absolute (which uses -compose Distort) or
  Relative (which uses -compose Displace).
Default: Absolute.
v verbose Write some text output to stderr.

In general, displacements are not 1:1 mappings. Some pixels in A may displace to more than one pixel in B. The resulting inverse map will have only one pixel in B displaced from that pixel of A. The other corresponding pixels in the inverse map will become transparent black. This will be obvious where pixels in A are squashed together in B. If desired, the inverse absolute (or relative) map may be filled in by one of the techniques shown in Filling holes.

Alternatively, supersampling may be used: resize the input up, then resize the output down to the original size. This will usually give better results. However, a resizing may not eliminate all holes, so it may be necessary to resize the input up, invert, fill holes, and finally resize the output down.

Like other modules and IM processes, this operates on every image in the current list.

This module assumes the red channel represents the horizontal component of a displacement; the green channel represents the vertical component of a displacement; a value of zero represents the left or top edge; a value of quantum represents the right or bottom edge. Input pixels that are fully transparent are ignored.

The input blue channel is ignored. The output blue channel is set to zero.

Note that:

A displacement map is a "pull" map: the value at each location specifies where the pixel colour for that location should be pulled from. Inverting a displacement map converts it to a "push" map: the value at each location specifies where the pixel colour at that location should be pushed to.

For example usage, see Follow line: inverse process, Straightening horizons: inverse follow-line and Pin or push.

Future: I may add an option to fill holes using seamless-blend composite.

Practical module: find darkest path

darkestpath.c finds the darkest path from the top edge to the bottom edge. It replaces the input image with one the same size, with white pixels showing the path and black pixels elsewhere.

Option Description
Short
form
Long form
c cumerr Write cumulative error image instead of path image.
v verbose Write some text output to stderr.

The code is based on an algorithm in Image Quilting for Texture Synthesis and Transfer, Alexei A. Efros and William T. Freeman, 2001. The algorithm is short, elegant and fast. However, it defines "path" in a narrow sense.

A path from the top always moves towards the bottom, possibly also moving left or right by only one pixel at each row, like the way a pawn moves in chess. There is exactly one pixel in the path for every row in the image. The line will never be more shallow then 45° from the horizontal. The path may reach the left and right edges of the image.

All possible paths are the same length, which is the height of the input image.

The darkness of a path is the sum of the intensity of the pixels in the path.

In versions before 21-May-2016, the darkness of a path was the sum of the red channels of the pixels in the path.

The module ignores the alpha channel.

Like other modules and IM processes, this operates on every image in the current list.

The module accumulates intensities, storing results in a temporary image. Cumulative intensities will often exceed quantum, so this module should be compiled with HDRI.

If the input is the difference between two images, the output is a "minimum error boundary" between the two images, and can be used as a ragged cut line for photo-montage and image quilting.

Input: toes.png

toes.png

Darkest path between top and bottom.

%IM7DEV%magick ^
  toes.png ^
  -colorspace Gray ^
  -process darkestpath ^
  cu_dp1.png
cu_dp1.png

Check the opposite direction.

%IM7DEV%magick ^
  toes.png ^
  -colorspace Gray ^
  -flip ^
  -process darkestpath ^
  -flip ^
  cu_dp2.png
cu_dp2.png

Darkest path between left and right.

%IM7DEV%magick ^
  toes.png ^
  -colorspace Gray ^
  ( +clone ^
    -rotate 90 ^
    -process darkestpath ^
    -rotate -90 ^
    -colorspace sRGB ^
    -fill Yellow -opaque White ^
    -transparent Black ^
  ) ^
  -composite ^
  cu_dp3.png
cu_dp3.pngjpg

For further explanation and examples, see Dark paths.

Practical module: find darkest meandering path

darkestmeander.c finds the darkest meandering path from the top edge to the bottom edge. It replaces the input image with one the same size, with white pixels showing the path and black pixels elsewhere.

Option Description
Short
form
Long form
m N maxiter N Maximum number of iterations of each cycle.
0 = no limit.
Default = 10.
a autoiter Stop iterating when path becomes stable.
c cumulerr Return cumulative image instead of path image.
s string side string Which sides to search for the minimum cumulative.
String may have one or more of LBRlbr for left side, bottom or right side.
Default = b.
e X,Y end_at X,Y Find the path that ends at given X,Y coordinate.
v verbose Write some text output to stderr.
v2 verbose2 Write more text output to stderr.

Unlike darkestpath above, a meandering path can move in any direction: up, down or sideways. It will not cross itself.

Possible meandering paths are of different lengths. The meander returned will be the one with the least total sum of the pixel red channels. In general, this will be different to a path that has the smallest average value of the pixel red channels.

The darkestmeander of an image, from one side to the opposite side, may be the same as the darkestpath of the same image. For ordinary photographs, this is often the case.

For further explanation and examples, see Dark paths.

I'd like to add an option for starting the path at a given location, but can't see how to do this. A workaround is to use a super-white gate, or to use the next module below.

Practical module: find darkest path between two points

darkestpntpnt.c implements Dijkstra's algorithm (see Wikipedia) for the shortest distance between two points, where "distance" is implemented here as the sum of intensities along the path.

Option Description
Short
form
Long form
s X,Y start_at X,Y Start the path at this coordinate.
Default 0,0.
e X,Y end_at X,Y End the path at this coordinate.
Default 0,0.
t N threshold_visited N Pixels with an intensity greater than this will not be visited. Generally 0.0 < N <= 1.0
n no_end Don't stop processing at path end.
d data Write data image instead of path image.
p print Write each visited coordinate to stderr.
v verbose Write some text output to stderr.

The start and end coordinates both default to 0,0, which results in a very short and pointless path, so the start and end should normally both be specified. Each coordinate can be individually suffixed with one of % or c or p. % and c are synonyms, meaning percent of width or height minus 1. p is the proportion of width or height minus 1. If an image is 600x400 pixels, the expressions ...

... all refer to the bottom-right pixel.

The "threshold_visited N" option prevents a path from passing through pixels with an intensity greater than N. The normal range is 0.0 to 1.0, but when used with HDRI, pixels can be painted super-white eg gray(200%) and the threshold set at (for example) 1.5. The default is to search for a path that might pass through any pixel, even pixels with values greater than 100%.

The code stops when the shortest path to the end has been found, unless the no_end option is used. The no_end option will continue processing until paths have been found from the start to every pixel (within the threshold). no_end is generally used with data.

The data option provides the distance from the start to any pixel on the path. When used with no_end, it gives the distance from the start to all pixels in the image.

The print option writes the x,y coordinates of each visited node to stderr. This can be used to list the coordinates on pixels on a path, where those off the path are above the threshold.

Currently, all eight neighbours of each pixel may be visited. I may provide an option to visit only 4-connected neighbours.

The module contains code to record a list of coordinates in an automatically-expanding array. This isn't currently used in the process module, but is available for other software, which should call CreateCoordList() and DestroyCoordList(). The coordinates recorded are either the start pixel and all visited pixels in the order they are visited, or the path from start to end, according to whether wrData is true or false.

For further explanation and examples, see Dark paths.

Practical module: alpha-weighted RMSE search

rmsealpha.c performs weighted RMSE comparisons or subimage searches. It searches for the second image as a subimage within the first, and finds the best result. The subimage is assumed neither scaled nor rotated. The module compares image pairs, testing all possible window positions within the first image, and returns the position that has the best score, and the value of that score. The score will be between 0.0 and 1.0 inclusive. The returned coordinates are the best position for the top-left of the second image within the first.

Option Description
Short
form
Long form
ai avoid_identical Windows with all pixels identical to corresponding subimage pixels in each RGBA channel will score 1.0.
ddo dont_decrease_opacity Windows with lower opacity in any pixel than corresponding subimage pixel will score 1.0.
ms multi_scale Do a multi-scale search. This is faster, but potentially finds the wrong "best" match.
adj number adjustLC number Adjust lightness and contrast of windows to match the subimage before comparing.
Default: 0.0.
j just_score Write the score only, with no trailing \n.
so stdout Write data to stdout.
se stderr Write data to stderr. (Default.)
sis saveInpScales For testing only: run multiple times, saving resized input image.
sss saveSubScales For testing only: run multiple times, saving resized subimage.
z number sizeDiv number Size divider for multi_scale.
Default: 2.0.
md integer minDim integer Minimum dimension for multi_scale.
Default: 20.
v verbose Write some text output to stderr.

(The multi-scale and adjustLC options were added 22-August-2017.)

The module doesn't modify images or add new ones.

It operates on all images in the current list, in pairs. If the list has no images, or an odd number of images, it raises a fatal error. Within each pair, the first should be at least as large as the second, in both width and height.

It always operates on R,G and B channels, weighted by alpha. It does not repect similarity or dissimilarity thresholds (but this might change). If the process finds a perfect score of zero, it stops searching. Otherwise, it performs an exhaustive search. It ignores the -metric setting, always using RMSE. The score is given according to the -precision setting.

The weighting is such that a fully-transparent pixel (alpha=0) in one image will exactly match any colour of pixel in the other image, whatever its alpha value. This is like a "wildcard" search.

The usual ImageMagick comparisons pre-multiply colour values by alpha before subtracting them, so two pixels with the same RGB values but different alphas will have a distance greater than 0.0, unless they are both black. By contrast, this module subtracts RGB values then multiplies by the product of the two alphas, so two pixels with the same RGB values but different alphas will have a distance of exactly 0.0.

If the multi_scale option is used, and the smallest dimension of both images and their difference is at least minDim (default 20) pixels the module resizes both images by factor sizeDiv (default 2.0, ie resize 50%) and searches recursively. This always returns correct results. Reducing the number will increase performance, but if reduced too far will return incorrect results. For graphics images it may be possible to reduce this to 5, which increases performance by 60%.

By default, the module writes two lines of text per image-pair to stderr. The lines are of the format:

rmsealpha: n @ x,y 
rmsealphaCrop: WxH+x+y

... where n is the score, 0<=n<=1, and x,y are integer coordinates within the first image for the top-left corner of the second, and WxH is the size of the subimage.

However, when the just_score option is chosen, the only output will be the score, with no trailing new-line.

V6 and v7 should produce the same result. It seems v7 gives the same coordinates but a different score. I haven't yet investigated this.

When the images are the same size, the best (and only) match will be at 0,0, so the score is the conventional RMSE score between two equal-sized images, but taking transparency into account.

For large images, the default processing is unusably slow. The multi_scale option is very much faster, but it can return the wrong "best" result. This works as described on Searching an image, but the image is resized by 50% at each level.

The process module searches for one subimage within a larger, main image. We often want to search for many different subimages within a single main image, or for one subimage within many different main images. The C code has an option to improve performance: instead of creating a hierarchy of resizes for each search, they can be saved in memory and re-used for subsequent searches. The pyramid of either the main image (saveInpScales) or the subimage (saveSubScales) can be saved. They can both be saved, though this is unlikely to be useful.

For example:

%IM7DEV%magick ^
  toes.png ^
  rose: ^
  -process rmsealpha ^
  NULL: 
rmsealpha: 0.25165087 @ 107,166
rmsealphaCrop: 70x46+107+166
%IM7DEV%magick ^
  toes.png ^
  rose: ^
  -process 'rmsealpha multi_scale' ^
  NULL: 
rmsealpha: 0.25165087 @ 107,166
rmsealphaCrop: 70x46+107+166

For comparison:

%IMG7%magick compare ^
  -metric RMSE ^
  -subimage-search ^
  toes.png ^
  rose: ^
  NULL: 
16491.9 (0.251651) @ 107,166

One use for this module is to search for non-rectangular shapes within images. For example, to search for a circle of red pixels within rose:, create an image with a red circle on a transparent background.

%IM7DEV%magick ^
  rose: ^
  ( -size 11x11 xc:None -fill Red -draw "translate 5,5 circle 0,0 0,5" ) ^
  -process rmsealpha ^
  NULL: 
rmsealpha: 0.18442898 @ 32,9
rmsealphaCrop: 11x11+32+9

Another example, from the Multi-phase search page:

A main image to search.

ms_toes_frogs.png

ms_toes_frogs.pngjpg

A subimage to search for.

frog.png

This has transparency.

frog.png

Search for the subimage.

%IM7DEV%magick ^
  ms_toes_frogs.png ^
  frog.png ^
  -process 'rmsealpha' ^
  +repage ^
  NULL: 
rmsealpha: 0.054971276 @ 48,148
rmsealphaCrop: 45x45+48+148

[No image]

The adjustLC option adjusts the lightness and contrast of the candidate window of the main image to roughly match the lightness and contrast of the subimage, by a gain-and-bias method, before comparing the window to the subimage. This takes a number parameter, typically 0.0 to 1.0, that determines how far to make the adjustment. The default is 0.0, which is no adjustment. 1.0 is full adjustment. Any (non-zero) adjustment adds to the processing time required.

The module finds one offset that gives the best score. If more than one offset gives the same best score, the module won't find the others.

The process module searches for one subimage within a larger, main image. We often want to search for many different subimages within a single main image, or for one subimage within many different main images. The C code has a mechanism to improve performance: instead of creating a hierarchy of resizes for each search, they can be saved in memory (as two lists of images) and re-used for subsequent searches. The pyramid of either the main image (saveInpScales) or the subimage (saveSubScales) can be saved. They can both be saved, though this is unlikely to be useful. The module contains options saveInpScales and saveSubScales to test this mechanism. For example:

%IM7DEV%magick ^
  toes.png ^
  rose: ^
  -process 'rmsealpha ms sis sss' ^
  NULL: 
rmsealpha: 0.25165087 @ 107,166
rmsealphaCrop: 70x46+107+166
rmsealpha: 0.25165087 @ 107,166
rmsealphaCrop: 70x46+107+166
rmsealpha: 0.25165087 @ 107,166
rmsealphaCrop: 70x46+107+166

Possible variations for filling holes etc:

  1. Ignore windows that contain any transparency.
  2. Instead of processing images pair-wise, process them with the first as the large image to be searched, and all the other images as subimages.
  3. If score is better than user-defined threshold for success, stop early.
  4. Instead of starting search top-left and moving in row-column order, start at user-defined seed and spiral out.
  5. Searching in random order may be useful: user supplies max_iter, the maximum number of iterations, perhaps as percentage of the total possible number. Extent of search could be across full possible range, or like patchMatch.pdf 3.2.
  6. Likewise an option for "search skip", as in fill holes.

Practical module: multi-scale image search

srchimg.c is similar to the srchimg.bat script. It searches within the first image for the second image, assumed neither scaled nor rotated, finds the best result, and replaces the two input images with a crop of the first. The search is "multi-scale", ie both images are reduced in size for approximate searches. The RMSE score will be between 0.0 and 1.0 inclusive. The returned coordinates are the best position for the top-left of the second image within the first.

Option Description
Short
form
Long form
n noCrop Don't replace input images with crop.
z number sizeDiv number Size divider for multi_scale.
Default: 2.0.
md integer minDim integer Minimum dimension for multi_scale.
Default: 20.
ss integer superSample integer Factor for super-sampling.
Default: 1 (no super-sampling).
string file string Write verbose text to stderr or stdout.
Default: stderr.
v verbose Write some text output to stderr.
version Write version information to stderr.

This is similar to alpha-weighted RMSE search with the ms option.

The module needs exactly two input images. The second must be no larger, in either dimension, than the first.

By default, it replaces both input images with a cropped version of the first. (The crop has appropriate canvas settings; use +repage if these are not wanted.) The noCrop option prevents this, so the image list will not be changed. This saves some time when we want only the coordinates.

The module respects the -precision setting.

Make a subimage to search for.

%IM7DEV%magick ^
  toes.png ^
  -crop 100x80+50+60 ^
  +repage ^
  -attenuate 0.5 ^
  +noise Gaussian ^
  cu_srchsub.png
cu_srchsub.pngjpg

Search for the subimage.

%IM7DEV%magick ^
  toes.png ^
  cu_srchsub.png ^
  -precision 9 ^
  -process 'srchimg' ^
  +repage ^
  cu_srchi.png 
0.0390116315 @ 50,60
cu_srchi.pngjpg

Another example, from the Multi-phase search page:

A main image to search.

ms_toes_frogs.png

ms_toes_frogs.pngjpg

A subimage to search for.

frog.png

This has transparency.

frog.png

Search for the subimage.

%IM7DEV%magick ^
  ms_toes_frogs.png ^
  frog.png ^
  -process 'srchimg' ^
  +repage ^
  cu_srchi2.png 
0.054971276 @ 48,148
cu_srchi2.pngjpg

For more details, see Searching an image: process module.

Practical module: aggressive image search

aggrsrch.c searches aggressively for the second image (a subimage) within the first. Both images are scaled down: the sub-image to 1x1 pixel, and the main image by the same proportion. The 1x1 sub-image is then scaled up to the new size of the main image, and the difference between the two is found. We gray-scale the shrunken main image (with RMS method).

This difference would be black where the exact 1x1 sub-image is found.

Option Description
Short
form
Long form
a N aggression N Aggression factor.
More than 0.0, typically no more than 1.0.
Default = 1.0.
m makeMask Make a mask.
t N threshold N Threshold for the mask.
Typically 0.0 to 1.0.
0.0 will exclude all positions (which isn't useful). More than 1.0 will exclude no positions (which may be useful).
Default = 0.75.
v verbose Write some text output to stderr.
version Write version information.

By default, the output is the same size as the first input. The makeMask option crops the output to the sliding area of the second image within the first image, sets the red and green channels to zero, and sets the blue channel to 100% where it is above a given threshold.

A threshold greater than 1.0 (eg 1.1) will not eliminate any positions.

This module is useful when the subimage is significantly smaller than the main image. When this isn't true, the method may return the wrong result.

A main image to search.

ms_toes_frogs.png

ms_toes_frogs.pngjpg

A subimage to search for.

frog.png

This has transparency.

frog.png

Make a difference image.

%IM7DEV%magick ^
  ms_toes_frogs.png ^
  frog.png ^
  -process 'aggrsrch v' ^
  cu_aggr1.png 
aggrsrch options:  aggression 1  verbose
aggrSrch Diff: main 267x233  sub 45x45  newMain 6x5 threshold 0.75
aggrsrch Res: 0.316605 @ 4,4
cu_aggr1.pngjpg

Make the mask.

%IM7DEV%magick ^
  ms_toes_frogs.png ^
  frog.png ^
  -process 'aggrsrch makeMask v' ^
  cu_aggr2.png 
aggrsrch options:  aggression 1  makeMask  threshold 0.75  verbose
aggrSrch Diff: main 267x233  sub 45x45  newMain 6x5 threshold 0.75
aggrsrch Res: 0.316605 @ 4,4
LightenNonSinks: nIter 1
aggrSrch Mask: crop to 223x189+22+22
aggrSrch Mask: removed 88.1225%
cu_aggr2.pngjpg

Practical module: match pixels

pixmatch.c takes two input images and creates a displacement map that would transform the first to the second. It works by considering windows in the second image, and searching for each window in the first. This task is simple but doing it quickly is more difficult.

Option Description
Short
form
Long form
wr N window_radius N Radius of window for searches, >= 0.
Window will be D*D square where D = radius*2+1.
Zero is a valid radius, giving a 1x1 window.
Default = 1.
lsr N limit_search_radius N Limit radius to search for source, >= 0.
Default = 0 = no limit
st N similarity_threshold N Stop searching for a window when RMSE <= N, when a match is "good enough".
Typically use 0.0 <= N <= 1.0, eg 0.01.
Default = 0 = keep going to find the best match.
hc N hom_chk N Homogeneity check.
N is either a number, typically 0 < N < 0.5, or off to remove the check.
Default = 0.1
e X search_to_edges X Whether to search for matches to image edges, so the result will be influenced by virtual pixels.
Set to on or off.
Default = on
s X search X Mode for searching, where X is none or entire or random or skip.
Default = entire.
rs N rand_searches N For random searches, the number to make per attempted match.
Default = 0 = minimum of search field width and height
sn N skip_num N For skip searches, the number of positions to skip in x and y directions.
Default = 0 = window radius
ref X refine X Whether to refine random and skip searches.
Set to on or off.
Default = on
part X propagate_and_random_trial X Whether to seek matches by propagation and random trials (after search X phase).
Set to on or off.
Default = on
mpi N max_part_iter N Maximum number of PART iterations.
Set to 0 for no maximum. Default = 10
tanc X terminate_after_no_change X Whether to terminate PART iterations when no changes are made.
Set to on or off.
Default = on
swp X sweep X Whether to attempt to match any unmatched pixels by exhaustive (slow) search (after PART phase).
Set to on or off.
Default = off
dpt X displacement_type X Type of displacement map for input and output.
Set to absolute or relative.
Default = absolute
w filename write filename Write the an output image file after each PART iteration.
Example filename: frame_%06d.png
ac N auto_correct N After finding the best score for a pixel (using shortcut methods),
if the RMSE >= N, try again with no shortcuts.
Typically use 0.0 <= N < 1.0, eg 0.10.
Default = 0 = no auto correction.
v verbose Write some text output to stderr.

For details and example usage, see Pixel match.

Practical module: scale, rotate and translate in 3d

srt3d.c applies an arbitrary series of scales, rotations and translations in 3D, possibly with perspective.

Options:

Option Description
Short
form
Long form
t string transform string Input transformation string.
Default: no string (so array is required).
a filename array filename Input transformation array: a text file with four lines, each with four numbers.
Can be "-" for stdin.
Default: no array (so string is required).
A filename out-array filename Output filename for transformation array.
Can be "-" for stdout.
Default: no output array.
c filename coords filename Input filename for coordinate list.
Note: the file is read multiple times, so "-" can't be used for stdin.
Default: no input coordinates, so the image corners will be used.
C filename out-coords filename Output filename for coordinate list.
Can be "-" for stdout.
Default: no output coordinates.
n integer nOutCoords integer Number of coordinates to output with C option.
Use 2 for just the x and y coordinates if you want to use the list in an IM "distort" command.
Default: 3 (x, y and z coordinates).
f number focal-length number Focal length for perspective.
Default: 0, no perspective.
r hide-reverse Hide reversed polygons.
h help Write some help to stdout.
v verbose Write some text output to stderr.
version Write version text to stdout.

The transform string is a list of scale, rotate and translate operations in any order.

The module is sensitive to -precision, -virtual-pixel and -mattecolor.

For example:

%IM7DEV%magick ^
  toes.png ^
  -virtual-pixel Black -mattecolor Black ^
  -process 'srt3d transform ty-50c,tx-50c,rx30,ry60 f 600' ^
  cu_s3d.jpg
cu_s3d.jpg

For more details, see the Scale, rotate and translate in 3D page.

Practical module: arctan2

arctan2.c takes two same-size input images and replaces them with atan2(u,v) where u is the first input and v is the second. Inputs can be negative (which needs HDRI, of course). Output values are in the range 0 to 100%. With default parameters, it is the equivalent of "-fx atan2(u,v)/(2*pi)+0.5", but much faster.

Option Description
Short
form
Long form
s number scale number Gain factor for distance from WP.
Default: 0.5 / pi.
b number bias number Bias for distance from WP.
Default: 0.5.
v verbose Write some text output to stderr.

The module processes red, green and blue channels independently, and currently ignores the alpha channel.

output = QuantumRange * [atan2 (u, v) * scale + bias]

... where u is the first image, v is the second image, scale defaults to 1/2pi, and bias defaults to 0.5.

The function atan2() returns a value between -pi and +pi, so the default values of scale and bias normalises the output to the range 0 to QuantumRange.

Make two gradient images.

set DIM=300

%IMG7%magick ^
  -size %DIM%x%DIM% ^
  gradient: -rotate 180 ^
  ( +clone -rotate -90 ) ^
  +swap ^
  cu_2grad.png
cu_2grad-0.pngjpg cu_2grad-1.pngjpg

Find the arctangent.

%IM7DEV%magick ^
  cu_2grad-0.png cu_2grad-1.png ^
  -process arctan2 ^
  cu_atan2_1.png
cu_atan2_1.pngjpg

Subtract 50%, then find the arctangent.

%IM7DEV%magick ^
  cu_2grad-0.png cu_2grad-1.png ^
  -evaluate Subtract 50%% ^
  -process arctan2 ^
  cu_atan2_2.png
cu_atan2_2.pngjpg

Subtract 50%, then find the arctangent, with no bias.

%IM7DEV%magick ^
  cu_2grad-0.png cu_2grad-1.png ^
  -evaluate Subtract 50%% ^
  -process 'arctan2 b 0' ^
  -evaluate AddModulus 0 ^
  cu_atan2_3.png
cu_atan2_3.pngjpg

Subtract 50%, then find the arctangent, with 25% bias.

%IM7DEV%magick ^
  cu_2grad-0.png cu_2grad-1.png ^
  -evaluate Subtract 50%% ^
  -process 'arctan2 b 0' ^
  -evaluate AddModulus 25%% ^
  cu_atan2_4.png
cu_atan2_4.pngjpg

Use "-negate" to invert the direction.

%IM7DEV%magick ^
  cu_2grad-0.png cu_2grad-1.png ^
  -evaluate Subtract 50%% ^
  -process 'arctan2 b 0' ^
  -evaluate AddModulus 25%% ^
  -negate ^
  cu_atan2_5.png
cu_atan2_5.pngjpg

Practical module: rhotheta

rhotheta.c processes all images in the list, replacing the first two channels. By default it reads these channels as cartesian coordinates x,y and replaces these with polar coordinates rho and theta. Optionally it does the inverse.

Option Description
Short
form
Long form
s number scale number Scale factor for theta.
Default: 0.5 / pi.
b number bias number Bias for theta.
Default: 0.5.
o number,number offset number,number Offset for cartesian coordinates.
Default: 0,0.
inv inverse Inverse calculation (polar to cartesian).
v verbose Write some text output to stderr.

The module reads and writes the first two colour channels (red and green) only. The blue and alpha (if any) channels are untouched.

Channels values 0 to QuantumRange are normalised to 0.0 to 1.0 before the calculations, and denormalised afterwards.

offset numbers should typically be in the range 0.0 to 1.0.

Cartesian to polar:

dx = x - offsetX
dy = y - offsetY
rho = hypot (dx,dy)
theta = atan2 (dx, dy) * scale + bias

When 0 <= x,y <= 1, 0 <= rho <= sqrt(2)

Polar to cartesian:

t = (theta - bias) / scale
x = rho * sin (t) + offsetX
y = rho * cos (t) + offsetY

With default settings, at x=0 and y<0, theta is 0.0 or 100%.

Make a gradient image.

call %PICTBAT%identAbsDispMap 300 300 cu_tst_rt.png
cu_tst_rt.png

Find rho and theta, with defaults.

%IM7DEV%magick ^
  cu_tst_rt.png ^
  -process 'rhotheta' ^
  cu_out_rt_1.png
cu_out_rt_1.png

Subtract 50%, then find rho and theta.

%IM7DEV%magick ^
  cu_tst_rt.png ^
  -evaluate Subtract 50%% ^
  -process 'rhotheta' ^
  cu_out_rt_2.png
cu_out_rt_2.png

Subtract 50%, then find rho and theta, with no bias.

%IM7DEV%magick ^
  cu_tst_rt.png ^
  -evaluate Subtract 50%% ^
  -process 'rhotheta b 0' ^
  -evaluate AddModulus 0 ^
  cu_out_rt_3.png
cu_out_rt_3.png

Subtract 50%, then find rho and theta, with 25% bias.

%IM7DEV%magick ^
  cu_tst_rt.png ^
  -evaluate Subtract 50%% ^
  -process 'rhotheta b 0' ^
  -evaluate AddModulus 25%% ^
  cu_out_rt_4.png
cu_out_rt_4.png

As previous, but then negate green channel.

%IM7DEV%magick ^
  cu_tst_rt.png ^
  -evaluate Subtract 50%% ^
  -process 'rhotheta b 0' ^
  -evaluate AddModulus 25%% ^
  -channel G -negate +channel ^
  cu_out_rt_5.png
cu_out_rt_5.png

Examples of inverse:

Find x and y, with defaults.

%IM7DEV%magick ^
  cu_out_rt_1.png ^
  -process 'rhotheta inverse' ^
  cu_out_rt_1i.png
cu_out_rt_1i.png

Subtract 50%, then find x and y.

%IM7DEV%magick ^
  cu_out_rt_2.png ^
  -process 'rhotheta inverse' ^
  -evaluate Add 50%% ^
  cu_out_rt_2i.png
cu_out_rt_2i.png

This module is used to calculate Colourfulness.

Practical module: pause

pause.c writes a message to stdout and waits for the 'return' key to be pressed. It takes no arguments. It has no effect on the image list.

This pauses IM while it is running, so temporary files may still exist. This can be useful in debugging or investigating weirdness.

Even if there are multiple images in the current list, the pause is executed only once.

Practical module: shell

shell.c attempts to execute its arguments as a shell command. IM waits for the command to finish. It has no effect on the image list. It respects the current "-verbose" setting, sending some text to stderr.

Even if there are multiple images in the current list, the command is executed only once.

The module can be used to call operating system utilities that monitor resource usage, such as temporary files and memory usage.

For example:

%IM7DEV%magick xc: -process 'shell pwd' NULL: >cu_shell1.lis 2>&1
/cygdrive/c/prose/pictures
%IM7DEV%magick xc: -verbose -process 'shell pwd' +verbose NULL: >cu_shell2.lis 2>&1
shell: sCmd = [pwd]
/cygdrive/c/prose/pictures
shell: status 0.

This can get information about the process while it is running:

%IM7DEV%magick ^
  -size 1000x1000 xc:pink ^
  -alpha opaque ^
  -process 'shell tasklist ^|findstr magick' ^
  -scale "2000x1000^!" ^
  -process 'shell tasklist ^|findstr magick' ^
  NULL: >cu_shell3.lis 2>&1
magick.exe                    6608 Console                    1     42,248 K
magick.exe                    6608 Console                    1     73,564 K

Hence this version of IM needs 32 million bytes for 1 million pixels, so 8 bytes/channel/pixel.

Practical module: sparse hald clut

sphaldcl.c replaces the last image in the current list, which must have a height of 2 pixels, with a hald clut.

Option Description
Short
form
Long form
h N haldlevel N Level of hald, eg 8 for hald:8.
Default = 8.
m string method string Method for populating clut, one of:
    identity the "no-change" clut;
    shepards Shepard's interpolation;
    voronoi Voronoi interpolation;
Default = identity.
p N power N Power parameter for Shepard's. A positive number.
Default = 2.
n N nearest N Just use N nearest colours.
0 means use them all, no limit.
Default = 0.
v verbose Write some text output to stderr.

Even if there are multiple images in the current list, only the last image is processed and replaced. This is for compatability with IM's -hald-clut operation.

For details and examples, see the Sparse hald cluts page.

Practical module: set mean and standard deviation

setmnsd.c sets the mean and standard deviation of each image by applying a suitable "-sigmoidal-contrast" and "-evaluate pow".

The module corresponds to the script setmnsd.bat, and works in the same way, but it is much quicker as it doesn't write to disk at each iteration.

Option Description
Short
form
Long form
mn N meanGoal N Goal for the mean.
Proportion of quantum 0.0 to 1.0, or percentage suffixed by 'c' or '%', or pin.
Default: no goal for mean value.
sd N sdGoal N Goal for standard deviation.
Proportion of quantum 0.0 to 0.5, or percentage suffixed by 'c' or '%', or pin.
Default: no goal for SD.
t N tolerance N Tolerance for mean and standard deviation.
Proportion of quantum, or percentage suffixed by 'c' or '%'.
Default: 0.00001 (0.001%).
m N mid N Mid-point for sigmoidal-contrast, as percentage of quantum (0.0 to 100.0)
or mean to use the mean of the image for the mid-point.
Default: mean.
i0 N initCon0 N Lower initial guess for contrast.
Can be 0.0.
Default: 0.0000001.
i2 N initCon2 N Upper initial guess for contrast.
Default: 30.
d string direction string One of:
    incOnly to prevent the SD decreasing;
    decOnly to prevent the SD increasing;
or both for no restriction.
Default: both.
f string file string Write verbose text to stderr or stdout.
Default: stderr.
v verbose Write text information.
v2 verbose2 Write more text information.

The process first calculates whether contrast needs to increase or decrease. Then it uses two initial guesses for the contrast, and calculates the SDs. If either calculated SD is within tolerance of the required SD, the task is done. Otherwise, the SDs should bracket the required SD. A geometric or arithmetic mean of the two contrast guesses gives a third, and the SD from this is either within tolerance or is too high or two low. So the third guessed contrast replaces one of the others, and the process iterates.

An ordinary image has all pixels in the range 0 to 100%. The standard deviation of the image has a range of 0.0 to 0.5. When the image is a constant colour, the SD is 0.0 When the image contains 0 and 100% only, in equal amounts, the SD is 0.5. Provided "mid mean" is used, with Q32 HDRI, the module can find SDs close to the theoretical limits of 0.0 and 0.5.

When the module doesn't find a solution, it reports this to stderr and causes IM to fail.

%IM7DEV%magick ^
  toes.png ^
  -process 'setmnsd sd 0 t 0.01 i2 1000 mid mean' ^
  cu_sss0.png
cu_sss0.pngjpg
%IM7DEV%magick ^
  toes.png ^
  -process 'setmnsd sd 0.1 mid mean' ^
  cu_sss1.png
cu_sss1.pngjpg
%IM7DEV%magick ^
  toes.png ^
  -process 'setmnsd sd 0.2 mid mean' ^
  cu_sss2.png
cu_sss2.pngjpg
%IM7DEV%magick ^
  toes.png ^
  -process 'setmnsd sd 0.3 mid mean' ^
  cu_sss3.png
cu_sss3.pngjpg
%IM7DEV%magick ^
  toes.png ^
  -process 'setmnsd sd 0.4 mid mean' ^
  cu_sss4.png
cu_sss4.pngjpg
%IM7DEV%magick ^
  toes.png ^
  -process 'setmnsd sd 0.5 t 0.01 i2 1000 mid mean' ^
  cu_sss5.png
cu_sss5.pngjpg

For processing photographs, direction incOnly is useful, to ensure the SD is at least a specified amount.

For more details and examples, see the Set mean and stddev page.

Practical module: integral image

integim.c converts ordinary images into integral images (summed area tables).

Option Description
Short
form
Long form
pm string premult string Whether to pre-multiply RGB values by alpha.
One of:
    yes (do pre-multiply),
    no (don't pre-multiply), or
    auto (from current alpha setting).
Default: yes.
f string file string Write verbose text to stderr or stdout.
Default: stderr.
v verbose Write text information.

An integral of an image has pixel values of each coordinate set to the sum of the image's pixel values across a rectangle from the origin (0,0) to this coordinate. This is done for each channel. The integral image needs to be HDRI.

For opaque images, this creates in a single pass the same result we get from two orthogonal runs of cumulhisto.

Visually, integral images are mostly whiter than white. We can view them after "-auto-level":

%IM7DEV%magick ^
  toes.png ^
  -process 'integim' ^
  -depth 32 ^
  -define quantum:format=floating-point ^
  +write cu_ii1.miff ^
  -channel RGB -auto-level +channel ^
  cu_ii1_al.png
cu_ii1.miffjpg cu_ii1_al.pngjpg

For images with transparency, when premult is used, each colour value is pre-multiplied by the alpha at that pixel. The alpha also accumulates to values greater than 100%.

%IM7DEV%magick ^
  toes_holed.png ^
  -process 'integim' ^
  -depth 32 ^
  -define quantum:format=floating-point ^
  +write cu_th_ii1.miff ^
  -channel RGB -auto-level +channel ^
  cu_th_ii1_al.png
cu_th_ii1.miffjpg cu_th_ii1_al.pngjpg

CAUTION: If they are saved, integral images should be saved as high-precision HDRI. Most pixel values will be multiples of QuantumRange. An image with a million pixels will have values up to one million times QuantumRange. Applications use the difference between two such values, so if the image isn't sufficiently precise, the result will be totally wrong.

Of course, the integral image might be made, used and discarded in a single command.

For more details and examples, see the Integral images page.

Practical module: deintegral image

deintegim.c converts from integral images (summed area tables) into ordinary images.

Option Description
Short
form
Long form
w string window string The window size, two numbers separated by "x".
Each number may be suffixed with "%" or "c" or "p".
Default: 1x1.
pd string postdiv string Whether to post-divide RGB values by alpha.
One of:
    yes (do post-divide),
    no (don't post-divide), or
    auto (from current alpha setting).
Default: yes.
ox integer offsetX integer Offset for window centre in X-direction.
Default: 0.
oy integer offsetY integer Offset for window centre in Y-direction.
Default: 0.
ae adjedges Adjust divisor near image edges (boundaries).
dd dontdivide Don't divide colour channels by anything.
f string file string Write verbose text to stderr or stdout.
Default: stderr.
v verbose Write text information.

The windows width and height are integers from one upwards. When the window size is 1x1, this is the inverse process to integral image.

%IM7DEV%magick ^
  cu_ii1.miff ^
  -process 'deintegim' ^
  -define quantum:format=floating-point ^
  cu_ii1_di.png
cu_ii1_di.pngjpg
%IM7DEV%magick ^
  cu_th_ii1.miff ^
  -process 'deintegim' ^
  -define quantum:format=floating-point ^
  cu_th_ii1_di.png
cu_th_ii1_di.png

More usefully, a larger window size makes a mean (blurred) result:

%IM7DEV%magick ^
  cu_ii1.miff ^
  -process 'deintegim window 25x5' ^
  cu_ii1_bl.png
cu_ii1_bl.pngjpg
%IM7DEV%magick ^
  cu_th_ii1.miff ^
  -process 'deintegim window 25x5' ^
  cu_th_ii1_bl.png
cu_th_ii1_bl.png

We can use both processes in a single command:

%IM7DEV%magick ^
  toes.png ^
  -process 'integim' ^
  -process 'deintegim window 5x25' ^
  cu_id_bl.png
cu_id_bl.pngjpg

For more details and examples, see the Integral images page.

Practical module: deintegral image two

deintegim2.c converts from integral images (summed area tables) into ordinary images.

deintegim2 is similar to deintegim, and takes the same options.

deintegim2 takes exactly two input images: a Integral Image that has been created by integim, and a map image of the same size. The red channel of the map image, divided by QuantumRange, is used as a multiplier for the window width, and the green channel as a multiplier for the window height.

The calculated window width and height are rounded to the nearest integer. This is crude. A more accurate method would use non-integral window sizes. For each corner, it would read at least four pixels from the Integral Image, and interpolate.

When the map is white, deintegim2 gives the same result as deintegim with the same options, but deintegim will be faster.

When the map is black, the window size is 1x1, so the result is the same as deintegim with a 1x1 window, which is the input to integim.

For example, we use a map where the red channel increase across the image, and the green channel increases down the image. (This is the same as an identity absolute displacement map.) The result has increasing horizontal blur across the image, and increasing vertical blur down the image.

%IM7DEV%magick ^
  %SRC% ^
  -process integim ^
  ( +clone ^
    -sparse-color Bilinear ^
0,0,#000,^
%%[fx:w-1],0,#f00,^
0,%%[fx:h-1],#0f0,^
%%[fx:w-1],%%[fx:h-1],#ff0 ^
    -evaluate Pow 3 ^
  ) ^
  -process 'deintegim2 window 200x200' ^
  cu_deinteg2.png
cu_deinteg2.pngjpg

The effect is similar to that from a -compose Blur, although this also blurs with pixels beyond the image boundary, virtual pixels.

%IM7DEV%magick ^
  %SRC% ^
  -process integim ^
  ( +clone ^
    -sparse-color Bilinear ^
0,0,#000,^
%%[fx:w-1],0,#f00,^
0,%%[fx:h-1],#0f0,^
%%[fx:w-1],%%[fx:h-1],#ff0 ^
    -evaluate Pow 3 ^
  ) ^
  -compose Blur ^
  -set option:compose:args 80x80 -composite ^
  cu_deinteg2b.png
cu_deinteg2b.pngjpg

Practical module: k-clustering

kcluster.c implements clustering by k-means, fuzzy k-means and k-harmonic, with transparency.

Option Description
Short
form
Long form
m string method string Method for converging clusters, one of:
    Means "k-means" clustering;
    Fuzzy "fuzzy k-means" clustering;
    Harmonic "k-harmonic" clustering;
    Null no convergence;
Default = means.
k N
k N-M
kCols N
kCols N-M
Number of clusters to find.
Parameter is one integer, or two integers separated by a hyphen.
When two integers, finds k as the best value between N and M inclusive by the jump method.
For example: kCols 10, kCols 5-15.
Default = 10.
j N,M jump N,M DEPRECATED. Find k as the best value between N and M inclusive by the jump method.
For example: jump 1,10
s sdNorm Transform input to equalise channel SDs, and invert this at the end.
a regardAlpha Process opacity.
i string initialize string Initialization for clusters, one of:
    Colors IM's "-colors";
    Forgy chooses random pixels from the image;
    RandPart assigns image pixels to random partitions;
    SdLine uses colours from a short line in the colour cube;
    FromList uses unique colours from last image in list;
Default = colors.
y N jumpY N Y power parameter for "jump" iterations. A positive number.
Default = 0.5, 1.0 or 1.5.
r N fuzzyR N Power parameter r for fuzzy method. A positive number at least 1.0.
Default = 1.5.
p N harmonicP N Power parameter p for harmonic method. A positive number at least 2.0.
Default = 3.5.
t N tolerance N Stop iterating when worst channel change is less than or equal to this.
Default = 0.01.
x N maxIter N Stop after this many iterations.
Default = 100.
w filename write filename Write iterations as image files.
For example: write myfile_%06d.png
string file string Write verbose text to stderr or stdout.
Default: stderr.
v verbose Write some text output to stderr.
v2 verbose2 Write more text output to stderr.
d debug Write debugging text output to stderr.

For example:

%IM7DEV%magick ^
  toes.png ^
  -process 'kcluster' ^
  cu_kc.png
cu_kc.pngjpg

For more details and examples, see the K-clustering page.

Practical module: central small crop

centsmcrop.c takes two input images, and replaces each with a central crop of that image that has the smallest dimensions of the input images. Both outputs have zero canvases.

For example:

%IM7DEV%magick ^
  toes.png ^
  rose: ^
  -process 'centsmcrop' ^
  cu_csc_%%d.png
cu_csc_0.pngjpg cu_csc_1.pngjpg
%IM7DEV%magick ^
  toes.png ^
  ( +clone -rotate 90 ) ^
  -process 'centsmcrop' ^
  cu_csc2_%%d.png
cu_csc2_0.pngjpg cu_csc2_1.pngjpg

Practical module: find sinks and peaks

Option Description
Short
form
Long form
p findPeaks Find peaks instead of sinks.
v verbose Write some text output to stderr.
version Write version text to stdout.

findsinks.c finds local sinks or peaks. It makes non-sink pixels white (or non-peak pixels black). Colours of sink (or peak) pixels are unchanged. Alpha values are unchanged. For the purpose of deciding which pixels are sinks (or peaks), alpha is ignored.

Each pixel has up to eight neighbours. A pixel that is not neighboured by any darker (lighter) pixels is a sink (peak). In addition, when a pixel is part of a connected group of equal-value pixels, and that group is not neighboured by darker (lighter) pixels, the pixels in the group are all sinks (peaks).

With this definition, in an image of a single colour, all the pixels are both sinks and peaks.

White pixels can't be sinks (unless the image is entirely white), so we use it to denote non-sinks. Similarly for black pixels and peaks.

Lightness is defined by the "-intensity" setting. If we imagine the image represents an area of land, with lightness representing height above sea level, sinks are where water would collect after rainfall (forming lakes), and peaks are where rain would always flow away from. All other pixels represent the slopes of the hills, although connected pixels may have the same height so they are terraces or "shelves" in the hillside.

For ordinary photographs, which have no significant terraces, the process is fast.

For example:

%IM7DEV%magick ^
  toes.png ^
  -process 'findsinks' ^
  cu_fsink.png
cu_fsink.png
%IM7DEV%magick ^
  toes.png ^
  -process 'findsinks p' ^
  cu_fsinkp.png
cu_fsinkp.png

Ordinary photographs don't have sinks or peaks in connected groups. We can make an artificial example:

%IM7DEV%magick ^
  toes.png ^
  +dither -colors 20 ^
  cu_fsink_col.png
cu_fsink_col.png
%IM7DEV%magick ^
  cu_fsink_col.png ^
  -process 'findsinks' ^
  cu_fsink_col2.png
cu_fsink_col2.png
%IM7DEV%magick ^
  cu_fsink_col.png ^
  -process 'findsinks p' ^
  cu_fsinkp_col2.png
cu_fsinkp_col2.png

The code that does the work, function LightenNonSinks in findsinks.inc, was written for a specific purpose and generalised for the process module. The function identifies pixels that are not sinks. A pixel that has a darker neighbour is not a sink. In addition, a pixel that is next to a not-sink and has the same intensity as that pixel is not a sink. Non-sink pixels are changed to QuantumRange. The function operates on the blue channel only.

Practical module: average concentric rings

avgconcrings.c creates a representation of an image at multiple scales, invariant to rotation.

Option Description
Short
form
Long form
s number minScale number Minimum (lowest) scale in range.
Default: 0.5.
t number maxScale number Maximum (highest) scale in range.
Default: 1.5.
n integer nScales integer Number of scales.
Default: 100.
m string method string Processing method. One of:
    Slow
    Quick
Default: Quick.
v verbose Write some text output to stderr.
version Write version text to stdout.

There are nScales output rows. Each row represents the image at a single scale, with minScale at the top and maxScale at the bottom. The pixels within each row are the mean colours of a ring around the image centre. From the left, the first pixel is the image center, the second is the average of the ring at radius=1, the next at radius=2, and so on. Pixels at a radius beyond the image's inscribed circle are increasingly transparent.

The result width is the semi-diagonal of the input image. Where the scale at a row is greater than one, the row is truncated on the right.

At row number y, the scale is:

scale = minScale * (maxScale/minScale)y/(nScales-1)

Thus:

y/(nScales-1) = log (scale / minScale) / log (maxScale / minScale)

... where the log is to any base.

method Slow depolarises each resized image, and scales those to one dimension. By contrast, method Quick depolarizes and scales to one dimension only once, for the largest scale. For the smaller scales, it downsamples (with distort SRT) the 1D image.

For example, we try both methods on the same input, and show the time taken for each:

%IM7DEV%magick ^
  toes.png ^
  -process ^
    'avgconcrings' ^
  cu_acr.png
0 00:00:01
cu_acr.png
%IM7DEV%magick ^
  toes.png ^
  -process ^
    'avgconcrings method Slow' ^
  cu_acr2.png
0 00:00:03
cu_acr2.png

Check the results:

%IM7DEV%magick compare -metric RMSE cu_acr.png cu_acr2.png NULL: 
15354618 (0.0035750256)

The results are practically the same.

Currently, the rings are always centred on the centre of the image. I may provide a facility for a different centre, if this seems desirable.

Average concentric circles are used when searching for a subimage at a range of scales, invariant to rotation. See what scale below.

Practical module: what rotation

whatrot.c takes two images, replacing them with the rotation of the second image that best matches the first image, assuming there is no translation.

Option Description
Short
form
Long form
a approxOnly Use only the first (approximate) method.
t number tolerance number Plus-or-minus tolerance for second (golden section) method, in degrees.
Default: 0.01.
x noRotate Don't replace images with rotation.
string file string Write verbose text to stderr or stdout.
Default: stderr.
v verbose Write some text output to stderr.
v2 verbose2 Write more text output to stderr.
version Write version text to stdout.

The module uses two phases. The first is a radial filter: both images are unrolled and scaled to a single row. A WxH image is summarised as Nx1 pixels, where N is the semi-diagonal of WxH multiplied by pi (3.141...). This search is invariant to scale. The method is fairly fast, but approximate (low precision, and possibly slightly wrong).

The second phase starts at the result of the first and searches for a more precise result with successive approximations using the golden section method. This is slower, but precise to any required tolerance. (However, numerical instability may prevent high tolerance being achieved. In addition, low-resolution images cannot yield high-resolution transformations. A third problem is that the method is not invarient to rotation, so if rotation is present, the method will either fail or return the wrong answer.)

The module respects the -precision setting for the text output of floating-point results.

The module needs exactly two input images. By default, it replaces both input images with a rotated version of the second. (The output has appropriate canvas settings; use +repage if these are not wanted.) The noRotate option prevents this, so the image list will not be changed. This saves some time when we want only the text results.

Make two images, rotations of a common source.

%IM7DEV%magick ^
  toes.png ^
  -rotate 34.567 +repage ^
  toes.png ^
  -gravity Center ^
  -crop 100x100+0+0 +repage ^
  cu_wr_%%d.png
cu_wr_0.pngjpg cu_wr_1.pngjpg

Find the rotation that makes the second match the first.

%IM7DEV%magick ^
  cu_wr_0.png ^
  cu_wr_1.png ^
  -process 'whatrot' ^
  cu_wr_out1.png 
whatrot:  ang=34.859226 angPorm=0.0095968936 angScore=0.0083337867
cu_wr_out1.pngjpg

The RMSE score is from 0.0 (perfect match) to 1.0 (pixels are totally different). A score better than (less than) 0.01, ie 1%, for ordinary photographs, means there is probably no visible difference.

These images have a "radius" of about 70 pixels, so a one-pixel displacement at the corners represents a rotation of arctan(1/70) = 0.8°, so we can't expect precision much better than this.

The result is more or less correct. However, the true result is not within the angle stated plus or minus the stated error margin.

In practice, the second phase might be used to confirm the correctness of the result from the first phase. However, the first phase is invariant to scale, and the second isn't.

Find the rotation that makes the second, resized, match the first.

%IM7DEV%magick ^
  cu_wr_0.png ^
  ( cu_wr_1.png -resize 300%% ) ^
  -process 'whatrot' ^
  cu_wr_out2.png 
whatrot:  ang=33.957526 angPorm=0.040653094 angScore=0.12014631
cu_wr_out2.pngjpg

The score is poor because of the resize, but the found angle is correct, more or less. [[The gss message announces a failure in the golden section search. The found angle comes from the first phase, and it is correct, within the stated tolerance.]]

Make two images, rotations and scales of a common source.

%IM7DEV%magick ^
  toes.png ^
  -rotate 34.567 +repage ^
  -resize 120%% +repage ^
  toes.png ^
  -gravity Center ^
  -crop 100x100+0+0 +repage ^
  cu_wrws_%%d.png
cu_wrws_0.pngjpg cu_wrws_1.pngjpg

Find the rotation that makes the second match the first.

%IM7DEV%magick ^
  cu_wrws_0.png ^
  cu_wrws_1.png ^
  -process 'whatrot' ^
  cu_wrws_out1.png 
whatrot:  ang=32.223571 angPorm=0.0095968936 angScore=0.065099396
cu_wrws_out1.pngjpg

FUTURE: For performance, a facility to limit the range of searches would be useful, eg search only between -10° and +10°.

FUTURE: The module can waste time seeking a higher precision than small images can support. For performance (and accuracy of the plus-or-minus result), it should stop early.

Practical module: what scale

whatscale.c takes two images, replacing them with a scaled version of the second image that best matches the first image, assuming there is no translation. This is very similar to the what rotation module, but the first phase scales the unrolled image to a single column instead of a single row.

Option Description
Short
form
Long form
a approxOnly Use only the first (approximate) phase.
x noScale Don't replace images with rescaled.
s0 number minScale number Minimum scale.
Default: 0.5.
s1 number minScale number Maximum scale.
Default: 2.0.
n integer nScales integer Number of scales.
Default: 100.
string method string Method for creating concentric rings. One of:
    Slow
    Quick
Default: Quick.
string file string Write verbose text to stderr or stdout.
Default: stderr.
v verbose Write some text output to stderr.
v2 verbose2 Write more text output to stderr.
version Write version text to stdout.

The module uses two phases. The first is a circular filter: the images are unrolled and scaled to a single column. A WxH image is summarised as 1xN, where N is the semi-diagonal of WxH. This search is invariant to rotation. The method is fairly fast, but approximate (low precision, and possibly slightly wrong). It uses code that lies behind the average concentric rings module.

The second phase starts at the result of the first and searches for a more precise result with successive approximations using the golden section method. This is slower, but precise to any required tolerance. (However, numerical instability may prevent high tolerance being achieved. In addition, low-resolution images cannot yield high-resolution transformations.)

The module respects the -precision setting for the text output of floating-point results.

The module needs exactly two input images. By default, it replaces both input images with a resized version of the second, effectively using +distort SRT. (The output has appropriate canvas settings; use +repage if these are not wanted.) The noScale option prevents this, so the image list will not be changed. This saves some time when we want only the text results.

For example, we create a pair of images, where the first is the second enlarged by a factor of 1.35.

Make two images, scales of a common source.

%IM7DEV%magick ^
  toes.png ^
  -define distort:viewport=100x100+30+50 ^
  ( -clone 0 -distort SRT 80,100,1.35,0,80,100 ) ^
  ( -clone 0 -distort SRT 80,100,1.00,0,80,100 ) ^
  -delete 0 ^
  +repage ^
  cu_ws_%%d.png
cu_ws_0.pngjpg cu_ws_1.pngjpg

Find the scale that makes the second match the first.

%IM7DEV%magick ^
  cu_ws_0.png ^
  cu_ws_1.png ^
  -process 'whatscale' ^
  cu_ws_out1.png 
whatscale: scale=1.348578 scalePorm=0.0048481956 scaleScore=0.0010225431
cu_ws_out1.pngjpg

These images have a "radius" of about 70 pixels, so a scale that displaces by one pixel in the corners is a ratio of about 71/70=1.014, so we can't expect the scale to be much more accurate than about 1.4%.

FUTURE: It would be useful if the module worked at any scale, rather than having to specify the search limits.

Use two images created above, cu_wrws_0.png and cu_wrws_1.png:

cu_wrws_0.pngjpg cu_wrws_1.pngjpg

Find the scale that makes the second match the first.

%IM7DEV%magick ^
  cu_wrws_0.png ^
  cu_wrws_1.png ^
  -process 'whatscale' ^
  cu_wrws_out3.png 
whatscale: GoldSectScale failed
whatscale: scale=1.2909392 scalePorm=0.097711945 scaleScore=0.0086793421
cu_wrws_out3.pngjpg

Because there is both rotation and scaling, the golden section search failed. But the first (approximate) phase has found the correct solution, within the stated precision.

The approximate search explores within a range. For best accuracy and performance, provide a sensible range for the expected resulting scale. (The default range is 0.5 to 2.0.) If the found result is at exactly the top or bottom of the range, the module will assume the true result may be beyond that found result, and will repeat the search with a different range. It will do this up to ten times, which allows for a magnification or reduction up to 210 = 1024.

%IM7DEV%magick ^
  rose: -resize 23%% ^
  rose: ^
  -process 'whatscale noScale' ^
  NULL: 
whatscale: scale=0.22975392 scalePorm=0.0049849308 scaleScore=0.08625291
%IM7DEV%magick ^
  rose: -resize 567%% ^
  rose: ^
  -process 'whatscale noScale' ^
  NULL: 
whatscale: scale=5.6989377 scalePorm=0.004578015 scaleScore=0.020585027

What scale and rotation

The first phase of whatrot finds the approximate rotation that is invariant to scale, and the first phase of whatscale finds the approximate scale that is invariant to rotation. We can run both modules in a single command on the same pair of images to get approximations of both scale and rotation.

Use two images created above, cu_wrws_0.png and cu_wrws_1.png:

cu_wrws_0.pngjpg cu_wrws_1.pngjpg

Find the rotation and scale that makes the second match the first.

%IM7DEV%magick ^
  cu_wrws_0.png ^
  cu_wrws_1.png ^
  -process 'whatrot approxOnly noRotate' ^
  -process 'whatscale approxOnly noScale' ^
  NULL: 
whatrot:  ang=33.901345 angPorm=0.80717489 angScore=0.016217008
whatscale: scale=1.2001027 scalePorm=0.090836478 scaleScore=0.0068274963

[No image]

The score for the found scale is less than 0.01, suggesting it is correct, and more correct than the rotation which has a larger (worse) score.

The returned values can be used to rotate and scale the second image, and the modules used again to calculate a more accurate transformation.

for /F "usebackq tokens=1,2" %%A in (`%IM7DEV%magick ^
  cu_wrws_0.png ^
  cu_wrws_1.png ^
  -process 'whatrot approxOnly noRotate' ^
  -process 'whatscale approxOnly noScale' ^
  NULL: 2^>^&1`) do set %%B

set ang1=%ang%
set scale1=%scale%

echo ang1 is %ang1% 
echo scale1 is %scale1% 
ang1 is 33.901345 
scale1 is 1.2001027 
for /F "usebackq tokens=1,2" %%A in (`%IM7DEV%magick ^
  cu_wrws_0.png ^
  ^( cu_wrws_1.png ^
     -virtual-pixel None ^
     +distort SRT "%scale1%,%ang1%" +repage ^) ^
  -process 'whatrot noRotate' ^
  -process 'whatscale noScale' ^
  NULL: 2^>^&1`) do set %%B

echo ang is %ang% 
echo scale is %scale% 
ang is 0.99626821 
scale is 0.98549912 

Practical module: what rotation and scale

whatrotscale.c combines the functionality of what rotation and what scale. It takes two images, replacing them with a scaled and rotated version of the second image that best matches the first image, assuming there is no translation.

Use two images created above, cu_wrws_0.png and cu_wrws_1.png:

cu_wrws_0.pngjpg cu_wrws_1.pngjpg

Find the rotation and scale that makes the second match the first.

%IM7DEV%magick ^
  cu_wrws_0.png ^
  cu_wrws_1.png ^
  -process 'whatrotscale' ^
  cu_wrs_out.png 
WhatRotScale scale=1.1893139 angle=34.72907 score=0.01082364
cu_wrs_out.pngjpg

Practical module: colours to matrix

cols2mat.c takes two equal-sized images and makes a 6x6 colour matrix for use in the "-color-matrix" operation. It also replaces the input images with the first image transformed by the matrix. The matrix will contain up to 12 terms that depend on the inputs.

Option Description
Short
form
Long form
m string method string Method for calculating the matrix, one of:
    Cross include cross-channel multipliers (12 terms);
    NoCross exclude cross-channel multipliers (6 terms);
    NoCrossPoly polynomial without cross-channel (3*degreePoly+3 terms);
    GainOnly include only this-channel multipliers (3 terms).
Default = Cross.
d integer degreePoly integer For method NoCrossPoly, degree of polynomial.
For example: degree 3 gives v' = a*v3 + b*v2 + c*v + d.
Default: 2.
w number weightLast number Weight for last line of image.
For example: more than 1.0 (eg 10, 100) to give greater weight to last line,
between 0.0 and 1.0 to give less weight.
Default: 1.0.
wa weightAlpha Multiplies weight by product of the pixel alphas.
x noTrans Don't replace images with transformation.
string file string Write text data (the colour matrix) to stderr or stdout.
Default = stderr.
v verbose Write some text output to stderr.
version Write version information to stdout.

If a third input image is provided, this (instead of the first) image will be transformed.

When the calculated colour matrix is applied to an image, it may cause clipping.

The module respects the "-precision" setting.

For example, what colour matrix gives the closest approximation for the transformation from toes.png to toes_x.jpg?

%IM7DEV%magick ^
  toes.png ^
  toes_x.jpg ^
  -process 'cols2mat f stdout' ^
  cu_c2m.png 
cu_c2m.png
c2matrix=1.8136873,-0.41789439,0.37468949,0,0,-0.36273306,-0.086006068,1.721299,-0.069416049,0,0,-0.23137974,0.17349911,-0.19861703,1.695083,0,0,-0.37885636,0,0,0,1,0,0,0,0,0,0,1,0,0,0,0,0,0,1
Warning: may clip Red shadows
Warning: may clip Red highlights
Warning: may clip Green shadows
Warning: may clip Green highlights

How close is the result?

%IM7DEV%magick compare -metric RMSE toes_x.jpg cu_c2m.png NULL: 
2.1077115e+08 (0.049073983)

One application is in normalising (aka "grading") grayscale or colours in photographs or video using colour checker charts. More details on this module and example uses are on that page.

Practical module: putting OOG values back in the box

oogbox.c adjusts pixel values to put them in the range 0 to 100%.

Option Description
Short
form
Long form
m string method string Method for adjusting the shadows and highlights, one of:
    Linear
    Power
Default = Power.
fmn number forceMin number Force minimum value (override calculation).
Default: use calculated value.
fmx number forceMax number Force maximum value (override calculation).
Default: use calculated value.
l number loLimit number Low limit for shadow processing.
Default: 0.1.
h number hiLimit number High limit for highlight processing.
Default: 0.9.
lg number loGradient number Gradient for shadows.
Default: use calculated value.
hg number hiGradient number Gradient for highlights.
Default: use calculated value.
v verbose Write some text output to stderr.
version Write version information to stdout.

For more details, see Putting OOG back in the box.

Practical module: painting patches

paintpatches.c blah...

Option Description
Short
form
Long form
c string colfrom string Method blah, one of:
    Avg
    Cent
    Gradient
    Palette
    Sample
Default = ??.
ps string patchShape string Method blah, one of:
    Rect
    Ellipse
    Min
Default = Rect.
o number opacity number Blah.
0.0 to 1.0.
Default: 1.0.
ms number multSal number Blah.
0.0 to 1.0.
Default: 0.5.
wt number wrngThresh number Blah.
0.0 to 1.0.
Default: 0.1.
ac number adjLcComp number Adjust lightness and contrast for comparisons.
??
Default: 0.0.
ap number adjLcPat number Adjust lightness and contrast of patches.
??
Default: 0.0.
fe number feather number Feather patch edges (blur sigma).
??
Default: 0.0.
x integer maxIter integer Maximum iterations.
??
Default: ?1000.
m mebc Do minimum error boundary cuts.
rc rectCanv Set rectangle to canvas.
hs hotSpot Hotspot only (for ps Min).
w filename write filename Write each iteration to a file.
Eg frame_%06d.png
Default: iterations are not written to files.
d debug Write debugging information.
v verbose Write some text output to stderr.
v2 verbose2 Write more text output to stderr.
version Write version information to stdout.

For more details, see Painting with patches.

Practical module: plot red versus green

plotrg.c makes a plot of red versus green values. This is a two-dimensional histogram. The ouput is always fully opaque.

A conventional histogram (see mkhisto, make histogram) has a one-dimensional array of buckets, and each pixel is allocated to a bucket according to some criteria such as intensity or the value in a single channel. This module creates a two-dimensional array of buckets and allocates each pixel to a bucket according the values in its red and green channels. The blue channel of the input is ignored.

Option Description
Short
form
Long form
d integer dim integer Width and height of square output.
Default: 256.
ra regardAlpha Increment count by alpha, instead of one.
n norm Normalise the histogram so the maximum is QuantumRange. (The minimum may not be zero.)
ns normSum Normalise the histogram so the sum is QuantumRange.
co calcOnly Calculate only; don't replace the image.
string file string Write text data (the colour matrix) to stderr or stdout.
Default = stderr.
v verbose Write some text output to stderr.
version Write version information to stdout.

By default the output image contains counts of pixels on a scale of zero to QuantumRange. When we use Q32 these counts are too close to zero to be visible. The normalise option expands the range so the maximum count is 100%.

The verbose text output respects the current -precison setting.

For example:

%IM7DEV%magick ^
  toes.png ^
  -precision 15 ^
  -process ^
    'plotrg norm f stdout verbose' ^
  cu_prg.png 
cu_prg.png
totalCnt=62211
minR=0.0917677576867323
maxR=1
minG=0.061875333791104
maxG=0.914885175860227
minVal=0.061875333791104
maxVal=1
numOOG=0
minCnt=0
maxCnt=65

By default, each image in the input list is replaced by a square image.

The output square image represents increasing red from left to right, and increasing green from top to bottom. The top-right corner is red (with no green) and the bottom-left corner is green (with no red). The diagonal line from top-left to bottom-right represents grays from black to white. toes.png has low saturation, so the counts are clustered near this diagonal.

The output text shows the range of values in the input red and green channels and the minimum and maximum values found in either channel (on a nominal scale of 0.0 to 1.0), the number of pixels that were outside the range 0.0 to 1.0 (OOG = out of gamut), and the minimum and maximum counts in the buckets.

By default, each pixel will increment the contents of a bucket by one. The regardAlpha option will increment according to the pixel's alpha, on a scale of zero to one, so fully transparent pixels will not increment any bucket. If regardAlpha is used and the input is fully transparent, all buckets will be empty so the output will be entirely black.

%IM7DEV%magick ^
  toes_holed.png ^
  -precision 15 ^
  -process ^
    'plotrg regardAlpha norm f stdout verbose' ^
  cu_prg2.png 
cu_prg2.png
totalCnt=23042
minR=0
maxR=0.886274509803922
minG=0
maxG=0.835294117647059
minVal=0
maxVal=0.886274509803922
numOOG=0
minCnt=0
maxCnt=42

Any pair of channels can be plotted by arranging them to be the first two of three channels.

Plot hue horizontally versus chroma vertically.
Zero chroma is at the top row.

%IM7DEV%magick ^
  toes.png ^
  -colorspace HCL ^
  -set colorspace sRGB ^
  -process ^
    'plotrg dim 256 norm' ^
  cu_prg3.png
cu_prg3.png

For reference, we make an image where the corresponding pixels have the given hue and chroma:

%IM7DEV%magick ^
  -size 256x256 xc: ^
  -sparse-color Bilinear ^
0,0,#008,^
%%[fx:w-1],0,#f08,^
0,%%[fx:h-1],#0f8,^
%%[fx:w-1],%%[fx:h-1],#ff8 ^
  -set colorspace HCL ^
  -colorspace sRGB ^
  cu_prg3a.png
cu_prg3a.png

So we can see pixels in toes.png have low chroma (though virtually no pixels at zero chroma), with clusters at red and green, and magenta to purple.

We can process the hue-chroma diagram in various ways, such as finding a smoothed maximum:

%IM7DEV%magick ^
  cu_prg3.png ^
  -fill White +opaque Black ^
  -morphology dilate:-1 rectangle:1x2+0+1 ^
  -morphology Convolve Blur:0x2 ^
  cu_prg_3b.png
cu_prg_3b.png

This could be used as data for increasing the chroma of hues that have low maximums. (HCL is a primitive colorspace. In practice, LCH or Jzbzbz are better for this purpose.) For example, for each pixel we set the chroma to the maximum chroma of any pixel with the same hue:

%IM7DEV%magick ^
  toes.png ^
  -colorspace HCL ^
  -separate ^
  ( -clone 0 ^
+write x0.png ^
    ( cu_prg_3b.png -scale "256x1^!" ) ^
    -clut ^
+write x1.png ^
  ) ^
  -swap 1,3 +delete ^
  -combine ^
  -set colorspace HCL -colorspace sRGB ^
  cu_prg_3c.png
cu_prg_3c.png

We can check the result by plotting the hue versus the chroma, as before, but changing non-black to white:

Plot hue horizontally versus chroma vertically.
Zero chroma is at the top row.

%IM7DEV%magick ^
  cu_prg_3c.png ^
  -colorspace HCL ^
  -set colorspace sRGB ^
  -process ^
    'plotrg dim 256 norm' ^
  -fill White +opaque Black ^
  cu_prg_3d.png
cu_prg_3d.png

Apparently some hues have a number of chroma values. I suspect this is because we are using 256 buckets, a very small number. 4000 buckets would be more reasonable.

More examples:

Plot chroma horizontally versus lightness vertically.

%IM7DEV%magick ^
  toes.png ^
  -colorspace HCL ^
  -channel RGB ^
    -separate -insert 0 -insert 0 -combine ^
  +channel ^
  -set colorspace sRGB ^
  -process ^
    'plotrg dim 256 norm' ^
  cu_prg4.png
cu_prg4.png

Convert to xyY colorspace;
plot x horizontally versus y vertically
(with y=0 at the bottom).

%IM7DEV%magick ^
  toes.png ^
  -colorspace xyY ^
  -set colorspace sRGB ^
  -process ^
    'plotrg dim 256 norm' ^
  -flip ^
  cu_prg5.png
cu_prg5.png

As previous,
but show white if any pixels are in that bucket.

%IM7DEV%magick ^
  toes.png ^
  -colorspace xyY ^
  -set colorspace sRGB ^
  -process ^
    'plotrg dim 256 norm' ^
  -flip ^
  -fill White +opaque Black ^
  cu_prg6.png
cu_prg6.png

The last two examples are chromaticity diagrams, and can be compared to CIE "horseshoe" diagrams. Most pixels are close to x=y=0.33, which is (speaking loosely and approximately) the location of zero saturation on chromaticity diagrams.

Practical module: centroid

centroid.c calculates the centroid of the pixels, according to their intensities. This is where the centre of weight would be if intensities represented weights.

The centroid location (Cx,Cy) is defined by:

Cx = sum (x*Ixy) / sum(Ixy)
Cy = sum (y*Ixy) / sum(Ixy)

... where Ixy is the intensity of the pixel at (x,y); x is its x-coordinate and y is its y-cooordinate.

The coordinates are written to stderr, in a line that starts with "centroid: ".

An image always has a centroid, with coordinates that are within the image, though the pixel at that location may be black.

If all the pixels have equal intensity, the centroid is at (w-1)/2,(h-1)/2.

The module respects -intensity and -precision settings.

%IM7DEV%magick ^
  -size 10x10 ^
  xc:black ^
  xc:white ^
  toes.png ^
  -precision 9 ^
  -process centroid ^
  NULL: 
centroid: 4.5,4.5
centroid: 4.5,4.5
centroid: 138.015153,111.419086

When the image is a single group of connected white pixels on a black background, "-connected-components" will give the same result (but writes to stdout):

%IMG7%magick ^
  -size 10x10 xc:white ^
  -define connected-components:verbose=true ^
  -connected-components 4 ^
  NULL:
Objects (id: bounding-box centroid area mean-color):
  0: 10x10+0+0 4.5,4.5 100 srgb(255,255,255)

Practical module: barymap

barymap.c takes an image encoded as xyY or XYZ or RGB, manipulates colour chromaticities (x and y components only), making the result image in xyY, XYZ or RGB space.

Option Description
Short
form
Long form
ic string inChannels string Channels (colour model) of input.
String is one of RGB, xyY or XYZ.
Default: from input image.
ip string inPrim string Input primaries.
String is one of:
    a named set of primaries, eg sRGB;
    or a comma-separated list of six numbers.
Default: from input image.
iw string inWP string Input white point.
String is one of:
    a named white point, eg D60;
    a number suffixed with K or k, eg 5000K or 5000k;
    or a comma-separated list of two numbers, the xy coordinates.
Default: taken from inPrim.
it number inTransfer number Gamma of input, or named transfer function.
Default: taken from inPrim.
xw string XYZwp string xy coordinates of XYZ white point.
String is one of:
    a named white point, eg D60;
    a number suffixed with K or k, eg 5000K or 5000k;
    or a comma-separated list of two numbers, the xy coordinates.
Default: taken from inWP.
gn2 number gain2 number Gain factor for squared distance from WP.
Default: 0.0.
gn number gain number Gain factor for distance from WP.
Default: 1.0.
bs number bias number Bias added to distance from WP.
Default: 0.0.
pow number power number Power factor for distance from WP.
Default: 1.0.
am string angleMult string Input white point.
String is list of tuples separated by semi-colon ';'.
Each tuple is three numbers separated by comma ','.
The three numbers are:
    distance
    width
    factor
Default: angleMult is not used.
st skipTri Skip triangle processing.
ign ignoreWP Ignore white point for triangulation.
clC clampCartesian Clamp cartesian coordinates.
clB clampBarycentric Clamp barycentric coordinates.
oc string outChannels string Channels (colour model) of output.
String is one of RGB, xyY or XYZ.
Default: inChannels.
op string outPrim string Output primaries.
String is one of:
    a named set of primaries, eg sRGB;
    or a comma-separated list of six numbers.
Default: from inPrim.
ow string outWP string Output white point.
String is one of:
    a named white point, eg D60;
    a number suffixed with K or k, eg 5000K or 5000k;
    or a comma-separated list of two numbers, the xy coordinates.
Default: taken from outPrim.
ot number outTransfer number Gamma of output, or named transfer function.
Default: taken from outPrim.
ca string chromAdap string Name of Chromatic Adaptation Transform (CAT).
Default: Bradford.
Use list cats to see available CATs
r16 round16 Round some fractions to 16 binary places.
list string list string Write list to stderr or stdout.
String is one of list, primaries, illuminants, transfers, cats, or wpnums.
string file string Write text data to stderr or stdout.
Default = stderr.
v verbose Write some text output to stderr.
v2 verbose2 Write more text.
v9 verbose9 Write voluminous text (every pixel).
version Write version information to stdout.

See Colours as barycentric coordinates for further details and examples.

Application: matching histograms

We can follow the same process given in Clut cookbook: Application: Matching histograms.

As inputs, we take toes.png and toes_x.jpg.

toes.pngjpg toes_x.jpg

Make cumulative normalised histograms, and the inverse of both. We limit to 500 buckets only for the web display.

We need the "+" version of +write. If we used -write, in-memory pixels would be be reduced to 16 bits.
%IM7DEV%magick ^
  ( toes.png ^
    -process 'mkhisto verbose capnumbuckets 500 cumul norm' ^
    -depth 32 +write cu_toes_chist.png ^
    -process 'mkhisto verbose capnumbuckets 500 cumul norm' ^
    -depth 32 +write cu_toes_chist_icl.png ^
    +delete ^
  ) ^
  ( toes_x.jpg ^
    -process 'mkhisto verbose capnumbuckets 500 cumul norm' ^
    -depth 32 +write cu_toes_x_chist.png ^
    -process 'mkhisto verbose capnumbuckets 500 cumul norm' ^
    -depth 32 +write cu_toes_x_chist_icl.png ^
  ) ^
  NULL: 
mkhisto options: capnumbuckets 500  multiply 1  cumul norm verbose 
mkhisto: Input image [toes.png] depth is 16
  NumBuckets 500  BucketSize 8589934.6
  counts: min_value 1, max_value 619
  sum_red 62211, sum_green 62211, sum_blue 62211
  Cumulating and normalising...
    max_sum=62211 mult_fact=69038.712
mkhisto options: capnumbuckets 500  multiply 1  cumul norm verbose 
mkhisto: Input image [toes.png] depth is 32
  NumBuckets 500  BucketSize 8589934.6
  counts: min_value 1, max_value 85
  sum_red 500, sum_green 500, sum_blue 500
  Cumulating and normalising...
    max_sum=500 mult_fact=8589934.6
mkhisto options: capnumbuckets 500  multiply 1  cumul norm verbose 
mkhisto: Input image [toes_x.jpg] depth is 8
  NumBuckets 256  BucketSize 16777216
  counts: min_value 1, max_value 1423
  sum_red 62211, sum_green 62211, sum_blue 62211
  Cumulating and normalising...
    max_sum=62211 mult_fact=69038.712
mkhisto options: capnumbuckets 500  multiply 1  cumul norm verbose 
mkhisto: Input image [toes_x.jpg] depth is 32
  NumBuckets 500  BucketSize 8589934.6
  counts: min_value 1, max_value 9
  sum_red 256, sum_green 256, sum_blue 256
  Cumulating and normalising...
    max_sum=256 mult_fact=16777216

The cumulative histograms

call %PICTBAT%graphLineCol ^
  cu_toes_chist.png

call %PICTBAT%graphLineCol ^
  cu_toes_x_chist.png
cu_toes_chist_glc.png cu_toes_x_chist_glc.png

The inverses of the cumulative histograms

call %PICTBAT%graphLineCol ^
  cu_toes_chist_icl.png

call %PICTBAT%graphLineCol ^
  cu_toes_x_chist_icl.png
cu_toes_chist_icl_glc.png cu_toes_x_chist_icl_glc.png

The right-hand graphs, from toes_x.jpg, are stepped. This is because toes_x.jpg is an 8-bit file. The channels do not have 500 values.

From the CHs and their inverses, we calculate two transformation clut files. They are inverses of each other.

Apply the inverse of toes CH to toes_x CH ...

%IM7DEV%magick ^
  cu_toes_x_chist.png ^
  cu_toes_chist_icl.png ^
  -clut ^
  cu_tx_it.png

call %PICTBAT%graphLineCol cu_tx_it.png
cu_tx_it_glc.png

... and the inverse of toes_x CH to toes CH.

 %IM7DEV%magick ^
  cu_toes_chist.png ^
  cu_toes_x_chist_icl.png ^
  -clut ^
  cu_t_ixt.png

call %PICTBAT%graphLineCol cu_t_ixt.png
cu_t_ixt_glc.png

We have created two cluts. One will transform from toes.png to toes_x.jpg. The other will transform in the opposite direction.

Apply these to toes and toes_x.

 %IMG7%magick ^
  toes.png ^
  cu_t_ixt.png ^
  -clut ^
  cu_toes_txit.png

%IMG7%magick ^
  toes_x.jpg ^
  cu_tx_it.png ^
  -clut ^
  cu_toes_x_txit.png
cu_toes_txit.pngjpg cu_toes_x_txit.pngjpg

Check the results:

%IM7DEV%magick compare -metric RMSE toes.png cu_toes_x_txit.png NULL: 
echo. 
%IM7DEV%magick compare -metric RMSE toes_x.jpg cu_toes_txit.png NULL: 
31504745 (0.0073352701)20795968 (0.0048419385)

The results are similar to those at Clut cookbook: Application: Matching histograms.

We do it again, but not limiting the buckets to 500:

%IM7DEV%magick ^
  ( toes.png ^
    -process 'mkhisto verbose cumul norm' ^
    -depth 32 +write cu_w_toes_chist.png ^
    -process 'mkhisto verbose cumul norm' ^
    -depth 32 +write cu_w_toes_chist_icl.png ^
    +delete ^
  ) ^
  ( toes_x.jpg ^
    -process 'mkhisto verbose cumul norm' ^
    -depth 32 +write cu_w_toes_x_chist.png ^
    -process 'mkhisto verbose cumul norm' ^
    -depth 32 +write cu_w_toes_x_chist_icl.png ^
  ) ^
  NULL: 
mkhisto options: capnumbuckets 65536  multiply 1  cumul norm verbose 
mkhisto: Input image [toes.png] depth is 16
  NumBuckets 65536  BucketSize 65536
  counts: min_value 1, max_value 49
  sum_red 62211, sum_green 62211, sum_blue 62211
  Cumulating and normalising...
    max_sum=62211 mult_fact=69038.712
mkhisto options: capnumbuckets 65536  multiply 1  cumul norm verbose 
mkhisto: Input image [toes.png] depth is 32
  NumBuckets 65536  BucketSize 65536
  counts: min_value 1, max_value 6014
  sum_red 65536, sum_green 65536, sum_blue 65536
  Cumulating and normalising...
    max_sum=65536 mult_fact=65536
mkhisto options: capnumbuckets 65536  multiply 1  cumul norm verbose 
mkhisto: Input image [toes_x.jpg] depth is 8
  NumBuckets 256  BucketSize 16777216
  counts: min_value 1, max_value 1423
  sum_red 62211, sum_green 62211, sum_blue 62211
  Cumulating and normalising...
    max_sum=62211 mult_fact=69038.712
mkhisto options: capnumbuckets 65536  multiply 1  cumul norm verbose 
mkhisto: Input image [toes_x.jpg] depth is 32
  NumBuckets 65536  BucketSize 65536
  counts: min_value 1, max_value 2
  sum_red 256, sum_green 256, sum_blue 256
  Cumulating and normalising...
    max_sum=256 mult_fact=16777216

Calculate the two transformation cluts:

%IMG7%magick ^
  cu_w_toes_x_chist.png ^
  cu_w_toes_chist_icl.png ^
  -clut ^
  cu_w_tx_it.png

%IMG7%magick ^
  cu_w_toes_chist.png ^
  cu_w_toes_x_chist_icl.png ^
  -clut ^
  cu_w_t_ixt.png

We have created two cluts. One will transform from toes.png to toes_x.jpg. The other will transform in the opposite direction.

Apply these to toes and toes_x.

 %IMG7%magick ^
  toes.png ^
  cu_w_t_ixt.png ^
  -clut ^
  cu_w_toes_txit.png

%IMG7%magick ^
  toes_x.jpg ^
  cu_w_tx_it.png ^
  -clut ^
  cu_w_toes_x_txit.png
cu_w_toes_txit.pngjpg cu_w_toes_x_txit.pngjpg

Check the results:

%IM7DEV%magick compare -metric RMSE toes.png cu_w_toes_x_txit.png NULL: 
echo. 
%IM7DEV%magick compare -metric RMSE toes_x.jpg cu_w_toes_txit.png NULL: 
29408242 (0.00684714)17833878 (0.0041522734)

The result isn't much more accurate than the 500-bucket version.

Summarising the process to make the histogram of image_A match that of image_B:

magick ^
  image_A ^
  -process 'mkhisto cumul norm' ^
  image_A_chist.png

magick ^
  image_B ^
  -process 'mkhisto cumul norm' ^
  -process 'mkhisto cumul norm' ^
  image_B_chist_icl.png

magick ^
  image_A_chist.png ^
  image_B_chist_icl.png ^
  -clut ^
  cu_w_t_ixt.png

magick image_A cu_w_t_ixt.png -clut out_image

A bug in Q32 v6.8.9-6, (see forum report -clut in Q32), and probably in all previous Q32 versions, prevented me from combining these commands. That bug is now fixed, so we can do this:

magick ^
  image_A ^
  ( -clone 0 ^
    -process 'mkhisto cumul norm' ^
  ) ^
  ( image_B ^
    -process 'mkhisto cumul norm' ^
    -process 'mkhisto cumul norm' ^
  ) ^
  ( -clone 1-2 -clut ) ^
  -delete 1-2 ^
  -clut ^
  out_image

This is equivalent to taking image_A and clutting it with its own cumulative histogram (which equalises it), and clutting that with the inverted cumulative histogram of image_B. For convenience, this is put into a script matchHisto.bat.

Application: mean histograms

See Implementation of the Midway Image Equalization, Thierry Guillemot, Julie Delon, 2016.

We can change the histograms of two images to match a midway histogram. For "midway", we use the harmonic mean of the two normalised cumulative histograms. The harmonic mean is defined as the reciprocal of the mean of the reciprocals of the inputs.

H = 1/((1/v1 + 1/v2)/2)

Or:

H = 2.v1.v2 
    v2 + v1

Using the normalised cumulative histograms of toes.png and toes_x.jpg (resizing them to be the same size):

Find the harmonic mean:

%IMG7%magick ^
  ( cu_toes_chist.png   -resize "500x1^!" +write mpr:ONE ) ^
  ( cu_toes_x_chist.png -resize "500x1^!" +write mpr:TWO ) ^
  -compose Mathematics -define compose:args=0,0.5,0.5,0 -composite ^
  ( mpr:ONE mpr:TWO -compose Multiply -composite ) ^
  -compose DivideDst -composite ^
  cu_midway.png

call %PICTBAT%graphLineCol cu_midway.png
cu_midway_glc.png

We can apply this midway histogram to toes.png and toes_x.jpg by clutting each one with a clut of its own histogram clutted with the inverse of the midway histogram.

Give toes this midway histogram:

%IM7DEV%magick ^
  toes.png ^
  ( cl_toes_chist.png ^
    ( cu_midway.png -process invclut ) ^
    -clut ^
  ) ^
  -clut ^
  cu_toes_mw.png
cu_toes_mw.pngjpg

Give toes_x this midway histogram:

%IM7DEV%magick ^
  toes_x.jpg ^
  ( cl_toes_x_chist.png ^
    ( cu_midway.png -process invclut ) ^
    -clut ^
  ) ^
  -clut ^
  cu_toes_x_mw.png
cu_toes_x_mw.pngjpg

The results are almost exactly the same.

Application: equalisation

Clutting an image with its 3-channel cumulative histogram (CH) will equalise the histogram of the image. As three different cluts are applied to the channels, this shifts the hue.

Equalise with the built-in method,
processing the channels independently.

%IMG7%magick ^
  toes.png ^
  -channel RGB ^
  -equalize ^
  cu_eq.png
cu_eq.pngjpg

Equalise by using the cumulative histogram.

%IMG7%magick ^
  toes.png ^
  cu_toes_chist.png ^
  -clut ^
  cu_cheq.png
cu_cheq.pngjpg

De-equalise by using the inverse cumulative histogram.

%IMG7%magick ^
  cu_eq.png ^
  cu_toes_chist_icl.png ^
  -clut ^
  cu_cheq_i.png
cu_cheq_i.pngjpg

The de-equalise step could have used the inverse cumulative histogram of the toes_x.jpg image, to give us that result.

To prevent a hue shift, we can find the single CH of a grayscale image, and apply that as a clut.

Equalise with the built-in method,
processing the channels together.

%IMG7%magick ^
  toes.png ^
  -equalize ^
  cu_eq_sync.png
cu_eq_sync.pngjpg

Equalise by using the cumulative histogram.

%IM7DEV%magick ^
  toes.png ^
  -modulate 100,0,100 ^
  -process 'mkhisto cumul norm' ^
  cu_chsync.png

%IMG7%magick ^
  toes.png ^
  cu_chsync.png ^
  -clut ^
  cu_cheq_sync.png
cu_cheq_sync.pngjpg

As we have the histogram data in an image format, we can use ordinary IM tools to limit the contrast of the histogram equalisation (Contrast-Limited Histogram Equalisation, CLHE). Here is the normalised but not cumulative histogram of greyscaled toes.png. As usual, we capnumbuckets for the web dispay.

Equalise by using the cumulative histogram.

%IM7DEV%magick ^
  toes.png ^
  -modulate 100,0,100 ^
  -process 'mkhisto capnumbuckets 500 norm' ^
  cu_gch.png

call %PICTBAT%graphLineCol ^
  cu_gch.png . . 0 cu_gch_glc.png
cu_gch_glc.png

We could cumulate and normalise this, to create the clut that can be used for equalisation ...

Equalise by using the cumulative histogram.

%IM7DEV%magick ^
  cu_gch.png ^
  -process 'cumulhisto norm' ^
  cu_gchc.png

call %PICTBAT%graphLineCol ^
  cu_gchc.png . . 0 cu_gchc_glc.png

%IMG7%magick ^
  toes.png ^
  cu_gchc.png ^
  -clut ^
  cu_gchc_cl.png
cu_gchc_glc.png cu_gchc_cl.pngjpg

... but the resulting contrast may be too high, exaggerating noise in areas of flat colour (not a problem in toes.png). We could mix the result with the original, or we can cap the value of the non-cumulative histogram, which limits the slope of the cumulative histogram. (This limits the overall contrast, rather than a local contrast. A related technique is used for that: CLAHE (A=Adaptive). See below Contrast Limited Adaptive Histogram Equalisation.)

Equalise by using the capped cumulative histogram.

%IM7DEV%magick ^
  cu_gch.png ^
  -channel RGB ^
  -evaluate Min 70%% ^
  +channel ^
  +write cu_gchc_cap.png ^
  -process 'cumulhisto norm' ^
  cu_gchc_clhe.png

call %PICTBAT%graphLineCol ^
  cu_gchc_cap.png  . . 0 cu_gchc_cap_glc.png

call %PICTBAT%graphLineCol ^
  cu_gchc_clhe.png . . 0 cu_gchc_clhe_glc.png

%IMG7%magick ^
  toes.png ^
  cu_gchc_clhe.png ^
  -clut ^
  cu_gchc_clhe_cl.png
cu_gchc_cap_glc.png cu_gchc_clhe_glc.png cu_gchc_clhe_cl.pngjpg

Rather than capping at a particular percentage of the maximum, it is generally better to base the cap on the mean and standard deviation, eg mean + 2 * SD.

for /F "usebackq" %%L in (`%IMG7%magick ^
  cu_gch.png ^
  -format "statMEAN=%%[fx:mean]\nstatSD=%%[fx:standard_deviation]\nhistcap=%%[fx:(mean+2*standard_deviation)*100]" ^
  info:`) do set %%L

echo statMEAN=%statMEAN% 
echo statSD=%statSD% 
echo histcap=%histcap% 
statMEAN=0.254442 
statSD=0.264227 
histcap=78.2896 

So we could use the value of %histcap% instead of 70% above.

It is said (see Wikipedia: Contrast Limited AHE) that the counts removed from the histogram should be redistributed among all the buckets, before cumulating. The mean of the difference gives us this, multiplied by 100 to get a percentage.

for /F "usebackq" %%L in (`%IMG7%magick ^
  cu_gch.png ^
  cu_gchc_cap.png ^
  -compose MinusSrc -composite ^
  -format "MeanDiffPC=%%[fx:mean*100]" ^
  info:`) do set %%L

echo MeanDiffPC=%MeanDiffPC% 
MeanDiffPC=1.07066 

Equalise by using the capped cumulative histogram, redistributing the excess.

%IM7DEV%magick ^
  cu_gch.png ^
  -channel RGB ^
  -evaluate Min 70%% ^
  +channel ^
  -evaluate Add %MeanDiffPC%%% ^
  +write cu_gchc_capred.png ^
  -process 'cumulhisto norm' ^
  cu_gchc_clhe_red.png

call %PICTBAT%graphLineCol ^
  cu_gchc_capred.png  . . 0 cu_gchc_capred_glc.png

call %PICTBAT%graphLineCol ^
  cu_gchc_clhe_red.png . . 0 cu_gchc_clhe_red_glc.png

%IMG7%magick ^
  toes.png ^
  cu_gchc_clhe_red.png ^
  -clut ^
  cu_gchc_clhe_red_cl.png
cu_gchc_capred_glc.png cu_gchc_clhe_red_glc.png cu_gchc_clhe_red_cl.pngjpg

Redistributing the excess raises all the counts, which will bring some over the %histcap% threshold. So we can iterate the process until the excess is sufficiently small.

This is developed into the script eqLimit.bat. The script takes as a parameter the multiple of the histogram's standard-deviation that will be added to the mean to calculate %histcap%. It recalculates the clut file until the count redistributed is less than 1%.

call %PICTBAT%eqLimit toes.png . . . cu_eql.png
cu_eql.pngjpg

The script is explained more fully in [Adaptive] Contrast-limited equalisation and also demonstrated in Adding zing to photographs: equalising the histogram.

Application: de-histogram

From a histogram H we can derive an image I that contains the same distribution of channel values as the source image S of which H is the histogram. An almost infinite number of images will make the same histogram. This method makes one of them.

To do this, we apply the general method above of histogram-matching, where the source image is a grayscale gradient. The process can be simplified by noting that:

So the process simplifies to: take the histogram H, cumulate it to make a cumulative histogram, invert this, and the result is the required image I. (See also Clut cookbook: Histogram algebra.)

For example, suppose we want an image that has the same histogram as toes.png. We make a cumulative histogram, and from that we make another cumulative histogram which inverts it. The result should have the same histogram as toes.png.

We also make a 500-bucket histogram of a clone of toes.png, purely for display on this web page.

%IM7DEV%magick ^
  toes.png ^
  ( +clone ^
    -process 'mkhisto capnumbuckets 500 norm' ^
    +write cu_dhh.%IFFEXT% ^
    -delete 0 ^
  ) ^
  -process 'mkhisto cumul norm' ^
  -process 'mkhisto cumul norm' ^
  cu_dehist.%IFFEXT%

call %PICTBAT%graphLineCol ^
  cu_dhh.%IFFEXT% . . 0 cu_dhh_glc.png
cu_dhh_glc.png

Show the histogram of cu_dehist.%IFFEXT%.

%IM7DEV%magick ^
  cu_dehist.%IFFEXT% ^
  -process 'mkhisto capnumbuckets 500 norm' ^
  cu_dehist_h.%IFFEXT%

call %PICTBAT%graphLineCol ^
  cu_dehist_h.%IFFEXT% . . 0 cu_dehist_h_glc.png
cu_dehist_h_glc.png

The histograms are the same. We have created a small image with the same histogram of what could be a very large image. This small image can then be used as a proxy for the large image, for some purposes.

From toes.png, we did "-process 'mkhisto cumul norm'" twice. The first time was to make a cumulative histogram. The second time was to invert it. For the second, we could have used "-process 'invclut'".

If the input had been a cumulative histogram, we wouldn't have needed the first mkhisto.

If the input had been a non-cumulative histogram, the first operation could have been "-process 'cumulhisto norm verbose'".

The created small image is size 65536x1. If we resize it, we can see it:

%IM7DEV%magick ^
  cu_dehist.%IFFEXT% ^
  -resize "500x100^!" ^
  cu_dehist_web.png
cu_dehist_web.png

Resizing loses data. Instead, we can chop it into lines and append these, after flopping to get the lighter tones at top-left:

%IM7DEV%magick ^
  cu_dehist.%IFFEXT% ^
  -flop ^
  -crop 256x1 +repage ^
  -append +repage ^
  cu_dehist_lines.png
cu_dehist_lines.png

The distribution of channel values in this image is the same as in the source image. This doesn't mean the colours are the same. In an ordinary photograph, without heavily saturated colours, they will be similar. But we can't expect them to be the same.

For comparison, here is toes.png, resized to be the same size, with the pixels sorted and arranged in the same way.

%IM7DEV%magick ^
  toes.png ^
  -crop x1 +repage ^
  +append +repage ^
  -process sortpixels ^
  -flop ^
  -resize "65536x1^!" ^
  -crop 256x1 +repage ^
  -append +repage ^
  cu_toes_lines.png
cu_toes_lines.pngjpg

Other possibilities

Process modules could be written for many purposes.

Count unique

Like -unique-colors but with alpha channel set to the (normalised) count of the pixels at that colour. Also allow sorting on alpha channel.

De-cumulate histogram

Finds the slope of a cumulative histogram.

Normalised or not.

This is an differentiation of the input, defined as, for all y:

out_image[0,y] = in_image[0,y]
out_image[x,y] = in_image[x,y] - in_image[x-1,y] for x > 0.

I can't see a use for this module. There is a use for cumulating histograms, so providing the opposite seems logical.

Quick search

Given two input images, the second smaller than the first, finds the position of the second in the first. Much like subimage-search, but faster (and potentially less accurate).

Add pixels

Make one pixel that is sum of all pixels. Or image 1xN that sums each row, equivalent to -scale 1x{height} -evaluate Multiply {width}.

Auto-top

Multiplies pixel values by whatever factor is needed to set the maximum to quantum.

This is similar "-auto-level" which also stretches down to black.

Append lines

From input image size WxH, makes output (W*H)x1, appending lines together (like +append).

No -- we can chop it into tiles, and +append. Reverse is chop, then -append.

De-append lines

From input image size WxH, and argument N, makes output N wide and height just sufficient to contain all input pixels. This is the inverse of append lines when N is the width of the original input image.

Any unfilled pixels are given background colour.

Innertrim

See my Innertrim page.

Adjacent floodfill

Fuzzy floodfill, where the boundary is defined by the difference between adjacent pixels rather than the difference from the seed pixel.

nLightest

Given an image, integer N>0 and optionally a radius, returns a list of coordinates of the (N) lightest pixels, where each found pixel prevents a search for more within the radius. Optionally after each found pixel, flood-fills from that point. See my Details, details page.

GamMaxSd

Find the gamma that maximises the standard deviation of an image.

Contrast Limited Adaptive Histogram Equalisation, CLAHE

See Wikipedia: Adaptive histogram equalization.

An image can be cropped into tiles and a CH made for each, optionally capped and redistributed. These can be appended into a single "appended cluts" image. Each output pixel is calculated as the corresponding input pixel clutted by either one row of the "appended cluts" image, or a bilinear interplation of 2 rows, or a bilinear interpolation of four rows.

The interpolation needs only one entry from each row; we don't need to interpolate the entire row.

I currently implement this as a script. See Contrast-limited adaptive equalisation.

Plotter

A plotter could take two input images of the same size, and the required size for an output image. It makes the output image, initially transparent. The red and green channels of one input are the (x,y) coordinates, indexing into the output. Each pixel from the other input is placed at the appropriate point in the output. (The index might be (x,y,z), and the output is a 3-D view. This is quite complex, as we have to use the painter's algorithm. Probably create a temporary "image" with (x',y',z').)

Shell

This would shell to a specified command (or script).

Segmentation

Various techniques for image segmentation could be written.

Skeleton

A skeleton program, or test harness, could be written.

Shuffle

Probably based on Wikipedia: Fisher-Yates shuffle.

Difference control

diff and patch

To statically link process modules, we edit module.c and static.c. To ensure we can easily patch future versions of IM source code, we can use diff and patch.

set IMCYGDEVDIR=/home/Alan/imdevsrc

diff -u --strip-trailing-cr %IMCYGDEVDIR%/../ImageMagick-6.8.9-6/magick/static.c %IMCYGDEVDIR%/magick/static.c >ci_static.diff
--- /home/Alan/imdevsrc/../ImageMagick-6.8.9-6/magick/static.c	2014-04-26 19:21:31.000000000 +0100
+++ /home/Alan/imdevsrc/magick/static.c	2014-08-23 18:19:33.789354600 +0100
@@ -108,7 +108,25 @@
 #else
   {
     extern size_t
-      analyzeImage(Image **,const int,char **,ExceptionInfo *);
+      analyzeImage(Image **,const int,char **,ExceptionInfo *),
+      hellowImage(Image **,const int,const char **,ExceptionInfo *),
+      echostuffImage(Image **,const int,const char **,ExceptionInfo *),
+      addendImage(Image **,const int,const char **,ExceptionInfo *),
+      replacelastImage(Image **,const int,const char **,ExceptionInfo *),
+      replacefirstImage(Image **,const int,const char **,ExceptionInfo *),
+      replaceallImage(Image **,const int,const char **,ExceptionInfo *),
+      replaceeachImage(Image **,const int,const char **,ExceptionInfo *),
+      grad2Image(Image **,const int,const char **,ExceptionInfo *),
+      drawcircImage(Image **,const int,const char **,ExceptionInfo *),
+      sortpixelsImage(Image **,const int,char **,ExceptionInfo *),
+      sortlinesImage(Image **,const int,char **,ExceptionInfo *),
+      appendlinesImage(Image **,const int,char **,ExceptionInfo *),
+      mkhistoImage(Image **,const int,char **,ExceptionInfo *),
+      cumulhistoImage(Image **,const int,const char **,ExceptionInfo *),
+      geodistImage(Image **,const int,char **,ExceptionInfo *),
+      invclutImage(Image **,const int,char **,ExceptionInfo *),
+      rect2eqfishImage(Image **,const int,const char **,ExceptionInfo *),
+      eqfish2rectImage(Image **,const int,const char **,ExceptionInfo *);
 
     ImageFilterHandler
       *image_filter;
@@ -116,6 +134,43 @@
     image_filter=(ImageFilterHandler *) NULL;
     if (LocaleCompare("analyze",tag) == 0)
       image_filter=(ImageFilterHandler *) analyzeImage;
+    else if (LocaleCompare("hellow",tag) == 0)
+      image_filter=(ImageFilterHandler *) hellowImage;
+    else if (LocaleCompare("echostuff",tag) == 0)
+      image_filter=(ImageFilterHandler *) echostuffImage;
+    else if (LocaleCompare("addend",tag) == 0)
+      image_filter=(ImageFilterHandler *) addendImage;
+    else if (LocaleCompare("replacelast",tag) == 0)
+      image_filter=(ImageFilterHandler *) replacelastImage;
+    else if (LocaleCompare("replacefirst",tag) == 0)
+      image_filter=(ImageFilterHandler *) replacefirstImage;
+    else if (LocaleCompare("replaceall",tag) == 0)
+      image_filter=(ImageFilterHandler *) replaceallImage;
+    else if (LocaleCompare("replaceeach",tag) == 0)
+      image_filter=(ImageFilterHandler *) replaceeachImage;
+    else if (LocaleCompare("grad2",tag) == 0)
+      image_filter=(ImageFilterHandler *) grad2Image;
+    else if (LocaleCompare("drawcirc",tag) == 0)
+      image_filter=(ImageFilterHandler *) drawcircImage;
+    else if (LocaleCompare("sortpixels",tag) == 0)
+      image_filter=(ImageFilterHandler *) sortpixelsImage;
+    else if (LocaleCompare("sortlines",tag) == 0)
+      image_filter=(ImageFilterHandler *) sortlinesImage;
+    else if (LocaleCompare("applines",tag) == 0)
+      image_filter=(ImageFilterHandler *) appendlinesImage;
+    else if (LocaleCompare("mkhisto",tag) == 0)
+      image_filter=(ImageFilterHandler *) mkhistoImage;
+    else if (LocaleCompare("cumulhisto",tag) == 0)
+      image_filter=(ImageFilterHandler *) cumulhistoImage;
+    else if (LocaleCompare("geodist",tag) == 0)
+      image_filter=(ImageFilterHandler *) geodistImage;
+    else if (LocaleCompare("invclut",tag) == 0)
+      image_filter=(ImageFilterHandler *) invclutImage;
+    else if (LocaleCompare("rect2eqfish",tag) == 0)
+      image_filter=(ImageFilterHandler *) rect2eqfishImage;
+    else if (LocaleCompare("eqfish2rect",tag) == 0)
+      image_filter=(ImageFilterHandler *) eqfish2rect;
+
     if (image_filter == (ImageFilterHandler *) NULL)
       (void) ThrowMagickException(exception,GetMagickModule(),ModuleError,
         "UnableToLoadModule","`%s'",tag);
diff -u --strip-trailing-cr %IMCYGDEVDIR%/../ImageMagick-6.8.9-6/magick/module.c %IMCYGDEVDIR%/magick/module.c >ci_module.diff

cmd /c exit /B 0
--- /home/Alan/imdevsrc/../ImageMagick-6.8.9-6/magick/module.c	2014-04-04 14:59:23.000000000 +0100
+++ /home/Alan/imdevsrc/magick/module.c	2014-08-23 18:19:25.585751900 +0100
@@ -1616,7 +1616,25 @@
 
 #if !defined(MAGICKCORE_BUILD_MODULES)
 extern size_t
-  analyzeImage(Image **,const int,const char **,ExceptionInfo *);
+  analyzeImage(Image **,const int,const char **,ExceptionInfo *),
+  hellowImage(Image **,const int,const char **,ExceptionInfo *),
+  echostuffImage(Image **,const int,const char **,ExceptionInfo *),
+  addendImage(Image **,const int,const char **,ExceptionInfo *),
+  replacelastImage(Image **,const int,const char **,ExceptionInfo *),
+  replacefirstImage(Image **,const int,const char **,ExceptionInfo *),
+  replaceallImage(Image **,const int,const char **,ExceptionInfo *),
+  replaceeachImage(Image **,const int,const char **,ExceptionInfo *),
+  grad2Image(Image **,const int,const char **,ExceptionInfo *),
+  drawcircImage(Image **,const int,const char **,ExceptionInfo *),
+  sortpixelsImage(Image **,const int,const char **,ExceptionInfo *),
+  sortlinesImage(Image **,const int,const char **,ExceptionInfo *),
+  appendlinesImage(Image **,const int,const char **,ExceptionInfo *),
+  mkhistoImage(Image **,const int,const char **,ExceptionInfo *),
+  cumulhistoImage(Image **,const int,const char **,ExceptionInfo *),
+  geodistImage(Image **,const int,const char **,ExceptionInfo *),
+  invclutImage(Image **,const int,const char **,ExceptionInfo *),
+  rect2eqfishImage(Image **,const int,const char **,ExceptionInfo *),
+  eqfish2rectImage(Image **,const int,const char **,ExceptionInfo *);
 #endif
 
 MagickExport MagickBooleanType ListModuleInfo(FILE *magick_unused(file),
@@ -1658,6 +1676,43 @@
     image_filter=(ImageFilterHandler *) NULL;
     if (LocaleCompare("analyze",tag) == 0)
       image_filter=(ImageFilterHandler *) analyzeImage;
+    else if (LocaleCompare("hellow",tag) == 0)
+      image_filter=(ImageFilterHandler *) hellowImage;
+    else if (LocaleCompare("echostuff",tag) == 0)
+      image_filter=(ImageFilterHandler *) echostuffImage;
+    else if (LocaleCompare("addend",tag) == 0)
+      image_filter=(ImageFilterHandler *) addendImage;
+    else if (LocaleCompare("replacelast",tag) == 0)
+      image_filter=(ImageFilterHandler *) replacelastImage;
+    else if (LocaleCompare("replacefirst",tag) == 0)
+      image_filter=(ImageFilterHandler *) replacefirstImage;
+    else if (LocaleCompare("replaceall",tag) == 0)
+      image_filter=(ImageFilterHandler *) replaceallImage;
+    else if (LocaleCompare("replaceeach",tag) == 0)
+      image_filter=(ImageFilterHandler *) replaceeachImage;
+    else if (LocaleCompare("grad2",tag) == 0)
+      image_filter=(ImageFilterHandler *) grad2Image;
+    else if (LocaleCompare("drawcirc",tag) == 0)
+      image_filter=(ImageFilterHandler *) drawcircImage;
+    else if (LocaleCompare("sortpixels",tag) == 0)
+      image_filter=(ImageFilterHandler *) sortpixelsImage;
+    else if (LocaleCompare("sortlines",tag) == 0)
+      image_filter=(ImageFilterHandler *) sortlinesImage;
+    else if (LocaleCompare("applines",tag) == 0)
+      image_filter=(ImageFilterHandler *) appendlinesImage;
+    else if (LocaleCompare("mkhisto",tag) == 0)
+      image_filter=(ImageFilterHandler *) mkhistoImage;
+    else if (LocaleCompare("cumulhisto",tag) == 0)
+      image_filter=(ImageFilterHandler *) cumulhistoImage;
+    else if (LocaleCompare("geodist",tag) == 0)
+      image_filter=(ImageFilterHandler *) geodistImage;
+    else if (LocaleCompare("invclut",tag) == 0)
+      image_filter=(ImageFilterHandler *) invclutImage;
+    else if (LocaleCompare("rect2eqfish",tag) == 0)
+      image_filter=(ImageFilterHandler *) rect2eqfishImage;
+    else if (LocaleCompare("eqfish2rect",tag) == 0)
+      image_filter=(ImageFilterHandler *) eqfish2rectImage;
+
     if (image_filter == (ImageFilterHandler *) NULL)
       (void) ThrowMagickException(exception,GetMagickModule(),ModuleError,
         "UnableToLoadModule","`%s'",tag);

[patch: Not yet written.]

Conclusion

Process modules are powerful tools that can be easily inserted into magick commands. Modules are fairly easy to write.

I expect to add further modules over time.


Scripts, makefile and C code

For convenience, these .bat scripts and .c code and makefile.am are also available in a single zip file. See Zipped BAT files.

vsn_defines.h

By including this file, we can often write code that is independent of v6 or v7.

This file is in both directories %IMSRC% and %IM7SRC%.

// First written: 21-May-2017
//
// Last updated:
//   21-July-2017 Added IS_ALPHA_CH
//   9-September-2017 Added WRITEIMAGE, COMPOSITE, COPY_OPACITY, CLAMP
//   3-April-2018 Added ACQUIRE_KERNEL and MAGICK_THREADS
//   14-March-2020 Added NumColChannels()

// If we haven't defined the version, do this.
#ifndef IMV6OR7


#include "imv6or7.h"

#ifndef IMV6OR7
#error IMV6OR7 not defined
#endif

#if IMV6OR7==6
#include "magick/MagickCore.h"
#define MAGICK_CORE_SIG MagickSignature
#define VIEW_PIX_PTR PixelPacket
#define PIX_INFO MagickPixelPacket
#define GET_PIX_INFO(image,p) GetMagickPixelPacket(image, p)
#define GET_PIXEL_RED(image,p) GetPixelRed(p)
#define GET_PIXEL_GREEN(image,p) GetPixelGreen(p)
#define GET_PIXEL_BLUE(image,p) GetPixelBlue(p)
#define GET_PIXEL_ALPHA(image,p) GetPixelAlpha(p)
#define SET_PIXEL_RED(image,val,p) SetPixelRed(p,val)
#define SET_PIXEL_GREEN(image,val,p) SetPixelGreen(p,val)
#define SET_PIXEL_BLUE(image,val,p) SetPixelBlue(p,val)
#define SET_PIXEL_ALPHA(image,val,p) SetPixelAlpha(p,val)
#define IS_ALPHA_CH(image) ((image)->matte==MagickTrue)
#define LAYER_METHOD ImageLayerMethod

#define GET_PIX_INF_ALPHA(p) (QuantumRange - (p)->opacity)
#define SET_PIX_INF_ALPHA(p,v) (p)->opacity=QuantumRange - (v)

#define WRITEIMAGE(ii,image,excep) WriteImage (ii, image)
#define COMPOSITE(dst,op,src,x,y,exc) \
  CompositeImage(dst, op, src, x,y)
#define COPY_OPACITY CopyOpacityCompositeOp
#define CLAMP(img,exc) ClampImage(img)
#define RESIZEIMG(img,wi,ht,exc) ResizeImage(img,wi,ht,UndefinedFilter,1.0,exc)
#define ACQUIRE_KERNEL(str,exc) AcquireKernelInfo(str)

#define VIRT_NONE(img,exc) \
  SetImageVirtualPixelMethod (img, TransparentVirtualPixelMethod);\
  SetImageAlphaChannel (img, ActivateAlphaChannel);

#define MAGICK_THREADS(source,destination,chunk,expression) \
  num_threads((expression) == 0 ? 1 : \
    (((chunk) > (32*GetMagickResourceLimit(ThreadResource))) && \
     (GetImagePixelCacheType(source) != DiskCache)) && \
    (GetImagePixelCacheType(destination) != DiskCache) ? \
      GetMagickResourceLimit(ThreadResource) : \
      GetMagickResourceLimit(ThreadResource) < 2 ? 1 : 2)

static size_t inline Inc_ViewPixPtr (const Image * image)
{
  return 1;
}

static MagickBooleanType inline SetNoPalette (
  Image * image, ExceptionInfo *exception)
{
  return SetImageStorageClass (image, DirectClass);
}

static void inline SetAllBlack (Image * image, ExceptionInfo *exception)
{
  MagickPixelPacket
    mppBlack;

  GetMagickPixelPacket (image, &mppBlack);

  SetImageColor(image, &mppBlack);
}

static void inline SetAllOneCol (Image * image, char * sCol, ExceptionInfo *exception)
{
  PIX_INFO mpp;

  GetMagickPixelPacket (image, &mpp);
  QueryMagickColor (sCol, &mpp, exception);
  SetImageColor (image, &mpp);
}

static MagickBooleanType inline NumColChannels (const Image * image)
{
  return 3;
}

static MagickBooleanType inline Has3Channels (const Image * image)
{
  return MagickTrue;
}

static MagickBooleanType inline Ensure3Channels (
  Image * image,
  ExceptionInfo *exception)
{
  return MagickTrue;
}

#elif IMV6OR7==7
#include "MagickCore/MagickCore.h"
//#include "MagickCore/cache-private.h" // for SetPixelCacheVirtualMethod()
#define MAGICK_CORE_SIG MagickCoreSignature
#define VIEW_PIX_PTR Quantum
#define PIX_INFO PixelInfo
#define GET_PIX_INFO(image,p) GetPixelInfo(image, p)
#define GET_PIXEL_RED(image,p) GetPixelRed(image,p)
#define GET_PIXEL_GREEN(image,p) GetPixelGreen(image,p)
#define GET_PIXEL_BLUE(image,p) GetPixelBlue(image,p)
#define GET_PIXEL_ALPHA(image,p) GetPixelAlpha(image,p)
#define SET_PIXEL_RED(image,val,p) SetPixelRed(image,val,p)
#define SET_PIXEL_GREEN(image,val,p) SetPixelGreen(image,val,p)
#define SET_PIXEL_BLUE(image,val,p) SetPixelBlue(image,val,p)
#define SET_PIXEL_ALPHA(image,val,p) SetPixelAlpha(image,val,p)
#define IS_ALPHA_CH(image) ((image)->alpha_trait!=UndefinedPixelTrait)
#define LAYER_METHOD LayerMethod

#define GET_PIX_INF_ALPHA(p) ((p)->alpha)
#define SET_PIX_INF_ALPHA(p,v) (p)->alpha=(v)
#define WRITEIMAGE(ii,image,excep) WriteImage (ii, image, excep)
#define COMPOSITE(dst,op,src,x,y,exc) \
  CompositeImage(dst, src, op, MagickFalse, x,y, exc)
#define COPY_OPACITY CopyAlphaCompositeOp
#define CLAMP(img,exc) ClampImage(img,exc)
#define RESIZEIMG(img,wi,ht,exc) ResizeImage(img,wi,ht,UndefinedFilter,exc)
#define ACQUIRE_KERNEL(str,exc) AcquireKernelInfo(str,exc)

#define VIRT_NONE(img,exc) \
  SetImageVirtualPixelMethod (img, TransparentVirtualPixelMethod, exc);\
  SetImageAlphaChannel (img, ActivateAlphaChannel, exc);

#define MAGICK_THREADS(source,destination,chunk,multithreaded) \
  num_threads((multithreaded) == 0 ? 1 : \
    ((GetImagePixelCacheType(source) != MemoryCache) && \
     (GetImagePixelCacheType(source) != MapCache)) || \
    ((GetImagePixelCacheType(destination) != MemoryCache) && \
     (GetImagePixelCacheType(destination) != MapCache)) ? \
    MagickMax(MagickMin(GetMagickResourceLimit(ThreadResource),2),1) : \
    MagickMax(MagickMin((ssize_t) GetMagickResourceLimit(ThreadResource),(ssize_t) (chunk)/64),1));

static size_t inline Inc_ViewPixPtr (const Image * image)
{
  return GetPixelChannels (image);
}

static MagickBooleanType inline SetNoPalette (
  Image * image, ExceptionInfo *exception)
{
  return SetImageStorageClass (image, DirectClass, exception);
}

static void inline SetAllBlack (Image * image, ExceptionInfo *exception)
{
  PixelInfo
    mppBlack;

  GetPixelInfo (image, &mppBlack);

  SetImageColor (image, &mppBlack, exception);
}

static void inline SetAllOneCol (Image * image, char * sCol, ExceptionInfo *exception)
{
  PIX_INFO mpp;

  GetPixelInfo (image, &mpp);
  QueryColorCompliance (sCol, AllCompliance, &mpp, exception);
  SetImageColor (image, &mpp, exception);
}

static int inline NumColChannels (const Image * image)
{
  // Following possibly naff if IM ever handles more than 3 colour channels.
  if (GetPixelChannels (image) >= 3) return 3;
  return GetPixelChannels (image);
}

static MagickBooleanType inline Has3Channels (const Image * image)
{
  return (GetPixelChannels (image) >= 3);
}

static MagickBooleanType inline Ensure3Channels (
  Image * image,
  ExceptionInfo *exception)
// Returns MagickFalse if unable to perform.
{
  if (GetPixelChannels (image) >= 3) return MagickTrue;

  // Following is naff for linear grayscale images; should not be sRGB.
  return TransformImageColorspace (image, sRGBColorspace, exception);
}

#else
#error IMV6OR7 defined but not valid
#endif

#endif  // ifndef IMV6OR7

imv6or7.h

There are two different version of this file.

This file, for version 6, is in directory %IMSRC% only:

#ifndef IMV6OR7
#define IMV6OR7 6
#endif

This file, for version 7, is in directory %IM7SRC% only:

#ifndef IMV6OR7
#define IMV6OR7 7
#endif

All the following .c files, and makefile.am, are in both directories %IMSRC%\filters and %IM7SRC%\filters.

hellow.c

#include <stdio.h>
#include <stdlib.h>
#include "vsn_defines.h"

ModuleExport size_t hellowImage(Image **images,const int argc,
  const char **argv,ExceptionInfo *exception)
{
  int
    precision;

  // Prevent compilers warning about unused parameters:
  (void) images;
  (void) argc;
  (void) argv;
  (void) exception;

  precision = GetMagickPrecision();

  printf ("Greetings to wizards of the ImageMagick world!\n\n");

  printf ("MAGICKCORE_QUANTUM_DEPTH %.*g\n",
    precision, (double)MAGICKCORE_QUANTUM_DEPTH);

  printf ("QuantumRange %.*g\n",
    precision, (double)QuantumRange);
  printf ("  V/V-1 %.*g\n",
    precision, (double)QuantumRange/(double)QuantumRange-1.0);

  printf ("QuantumScale %.*g\n", precision, QuantumScale);

  printf ("MagickEpsilon %.*g\n", precision, MagickEpsilon);


  printf ("Quantum %s\n", MagickStringify(Quantum));

  printf ("sizeof:\n");
  printf ("  MagickRealType %i\n", (int)sizeof(MagickRealType));
  printf ("  MagickSizeType %i\n", (int)sizeof(MagickSizeType));
  printf ("  int %i\n", (int)sizeof(int));
  printf ("  long int %i\n", (int)sizeof(long int));
  printf ("  long long int %i\n", (int)sizeof(long long int));
  printf ("  float %i\n", (int)sizeof(float));
  printf ("  double %i\n", (int)sizeof(double));
  printf ("  long double %i\n", (int)sizeof(long double));
  printf ("  _Float128 %i\n", (int)sizeof(_Float128));
  printf ("  Image %i\n", (int)sizeof(Image));
  printf ("  ImageInfo %i\n", (int)sizeof(ImageInfo));
  printf ("  Quantum %i\n", (int)sizeof(Quantum));

  printf ("MagickSizeType [%s]\n", MagickStringify(MagickSizeType));
  printf ("MagickSizeFormat [%s]\n", MagickSizeFormat);
  printf ("QuantumFormat: [%s]\n", QuantumFormat);
  printf ("MaxMap %i\n", (int)MaxMap);

  printf ("MaxTextExtent %i\n", (int)MaxTextExtent);
#if IMV6OR7==7
  printf ("MagickPathExtent %i\n", (int)MagickPathExtent);
#endif

#if defined(MAGICKCORE_HDRI_SUPPORT)
  printf ("Includes HDRI\n");
#else
  printf ("Does not includes HDRI\n");
#endif


#if defined(MAGICKCORE_WINDOWS_SUPPORT)
  printf ("MAGICKCORE_WINDOWS_SUPPORT defined\n");
#endif

#if defined(WIN32)
  printf ("WIN32 defined\n");
#endif

#if defined(WIN64)
  printf ("WIN64 defined\n");
#endif

#if defined(MAGICKCORE_OPENMP_SUPPORT)
  printf ("MAGICKCORE_OPENMP_SUPPORT defined\n");
#endif

  return(MagickImageFilterSignature);
}

echostuff.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include "vsn_defines.h"


// The next function corresponds in style to functions in enhance.c
// It takes one image, and returns a status.
//
static MagickBooleanType EchoImage(const Image *image,
  int count,
  ExceptionInfo *exception)
{
  VirtualPixelMethod
    vpm;

  const char *mattecolor = GetImageArtifact (image, "mattecolor");

  if (!mattecolor) mattecolor = "black";

  printf ("mc=%s\n", mattecolor);


  assert(image != (Image *) NULL);
  assert(image->signature == MAGICK_CORE_SIG);
  if (image->debug != MagickFalse)
    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);

  printf ("echostuff: Input image [%i] [%s]  depth %i  size %ix%i\n",
          count,
          image->filename, (int)image->depth,
          (int)image->columns, (int)image->rows);

  vpm = GetImageVirtualPixelMethod(image);
  printf ("  Virtual pixel method %i [%s]\n",
   (int)vpm,
   GetImageArtifact(image, "virtual-pixel"));

  printf ("  mattecolor: %s\n", GetImageArtifact (image, "mattecolor"));

  printf ("  fuzz %g\n",       image->fuzz);

//  printf ("  channels %li\n",   image->channels);

  printf ("  alpha is on: %s\n", IS_ALPHA_CH(image) ? "yes" : "no");

  ResetImageArtifactIterator(image);
  const char * p = GetNextImageArtifact(image);
  while (p && *p) {
    printf ("Artifact=[%s]  value=[%s]\n",
      p,
      GetImageArtifact(image, p));
    p = GetNextImageArtifact(image);
  }

  return MagickTrue;
}


ModuleExport size_t echostuffImage(Image **images,const int argc,
  const char **argv,ExceptionInfo *exception)
{
  Image
    *image;

  int
    i;

  MagickBooleanType
    status;


  (void) argc;
  (void) argv;

  assert(images != (Image **) NULL);
  assert(*images != (Image *) NULL);
  assert((*images)->signature == MAGICK_CORE_SIG);

  if (argc) {
    printf ("echostuff: %i arguments:", argc);
    for (i=0; i < argc; i++) {
      printf (" [%s]", argv[i]);
    }
    printf ("\n");
  } else {
    printf ("echostuff: no arguments\n");
  }


  i = 0;
  for (image=(*images); image != (Image *) NULL; image=GetNextImageInList(image))
  {
    status = EchoImage(image, i, exception);

    if (status == MagickFalse)
      continue;

    i++;
  }

  assert (status == MagickTrue);

  return(MagickImageFilterSignature);
}

dumpimage.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include "vsn_defines.h"

typedef struct {
  MagickBooleanType
    coords,
    percent,
    ignore_transp;
} dumpimageT;


// The next function corresponds in style to functions in enhance.c
// It takes one image, and returns a status.
//
static MagickBooleanType DumpImage(const Image *image,
  int count,
  dumpimageT *pdi,
  ExceptionInfo *exception)
{
  MagickBooleanType
    status;

  CacheView
    *image_view;

  ssize_t
    y;

  int
    precision;


  assert(image != (Image *) NULL);
  assert(image->signature == MAGICK_CORE_SIG);
  if (image->debug != MagickFalse)
    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);

  printf ("Input image [%i] [%s]  depth %i  size %ix%i\n",
          count,
          image->filename, (int)image->depth,
          (int)image->columns, (int)image->rows);

#if IMV6OR7==6
  printf ("channels=%li\n", image->channels);
#else
  printf ("channels=%li\n", GetPixelChannels(image));
#endif

  image_view = AcquireVirtualCacheView(image,exception);

  status = MagickTrue;

  precision = GetMagickPrecision();

  for (y=0; y < (ssize_t) image->rows; y++)
  {
    VIEW_PIX_PTR const
      *p;

    register ssize_t
      x;


    if (status == MagickFalse)
      continue;

    p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
    if (p == (const VIEW_PIX_PTR *) NULL)
      status=MagickFalse;

    for (x=0; x < (ssize_t) image->columns; x++)
    {
      printf ("%lu,%lu: ", x, y);

      printf ("%.*lg,%.*lg,%.*lg",
        precision, (double)(GET_PIXEL_RED(image,p)),
        precision, (double)(GET_PIXEL_GREEN(image,p)),
        precision, (double)(GET_PIXEL_BLUE(image,p)));

        // FIXME: Allow any nmber of channels.

//      if (image->matte) {
        printf (",%.*lg",
          precision, (double)(GET_PIXEL_ALPHA(image,p)));
//      }

      printf ("\n");

      p += Inc_ViewPixPtr (image);
    }
  }
  return MagickTrue;
}


ModuleExport size_t dumpimageImage(Image **images,const int argc,
  const char **argv,ExceptionInfo *exception)
{
  Image
    *image;

  int
    i;

  MagickBooleanType
    status;

  dumpimageT
    dumpimage;

  (void) argc;
  (void) argv;

  assert(images != (Image **) NULL);
  assert(*images != (Image *) NULL);
  assert((*images)->signature == MAGICK_CORE_SIG);

  i = 0;
  for (image=(*images); image != (Image *) NULL; image=GetNextImageInList(image))
  {
    status = DumpImage(image, i, &dumpimage, exception);

    if (status == MagickFalse)
      continue;

    i++;
  }

  assert (status == MagickTrue);

  return(MagickImageFilterSignature);
}

addend.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include "vsn_defines.h"

// The next function corresponds in style to functions in transform.c
// It takes one image, and returns an image.
//
static Image *CopyBlack(const Image *image,
  ExceptionInfo *exception)
{
  Image
    *new_image;

  assert(image != (Image *) NULL);
  assert(image->signature == MAGICK_CORE_SIG);
  if (image->debug != MagickFalse)
    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);


  // Make a clone of this image, same size but undefined pixel values:
  //
  new_image=CloneImage(image, image->columns, image->rows, MagickTrue, exception);
  if (new_image == (Image *) NULL)
    return(new_image);

#if IMV6OR7==6
//  if (SetImageStorageClass(new_image,DirectClass) == MagickFalse)
//    return (Image *)NULL;

//  GetMagickPixelPacket (new_image, &mppBlack);
//  (void)SetImageColor(new_image, &mppBlack);
#else
//  if (SetImageStorageClass(new_image,DirectClass, exception) == MagickFalse)
//    return (Image *)NULL;

//  GetPixelInfo (new_image, &mppBlack);
//  (void)SetImageColor(new_image, &mppBlack, exception);
#endif

  if (SetNoPalette (new_image, exception) == MagickFalse)
    return (Image *)NULL;

  SetAllBlack (new_image, exception);

  return (new_image);
}



ModuleExport size_t addendImage(Image **images,const int argc,
  const char **argv,ExceptionInfo *exception)
{
  Image
    *image,
    *new_image;

  assert(images != (Image **) NULL);
  assert(*images != (Image *) NULL);
  assert((*images)->signature == MAGICK_CORE_SIG);

  (void) argc;
  (void) argv;

  // Point to the last image in the list
  //
  image=GetLastImageInList(*images);
  if (image == (Image *) NULL) {
    fprintf (stderr, "addend: no images in list\n");
    return (-1);
  }

  new_image = CopyBlack(image,exception);
  if (new_image == (Image *) NULL)
    return(-1);

  // Add the new image to the end of the list:
  AppendImageToList(images,new_image);

  return(MagickImageFilterSignature);
}

replacelast.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include "vsn_defines.h"


// The next function corresponds in style to functions in transform.c
// It takes one image, and returns an image.
//
static Image *CopyGreen(const Image *image,
  ExceptionInfo *exception)
{
  Image
    *new_image;

  PIX_INFO
    mpp;

  assert(image != (Image *) NULL);
  assert(image->signature == MAGICK_CORE_SIG);
  if (image->debug != MagickFalse)
    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);

  // Make a clone of this image, same size but undefined pixel values:
  //
  new_image=CloneImage(image, image->columns, image->rows, MagickTrue, exception);
  if (new_image == (Image *) NULL)
    return(new_image);

  if (SetNoPalette (new_image, exception) == MagickFalse)
    return (Image *)NULL;

#if IMV6OR7==6
  // Make a "colour", which will be black.
  GetMagickPixelPacket (new_image, &mpp);

  // We could set it to any colour:
  (void)QueryMagickColor ("green", &mpp, exception);

  // Set all the pixels to this colour.
  (void)SetImageColor(new_image, &mpp);
#else
  GetPixelInfo (new_image, &mpp);

  QueryColorCompliance ("green", AllCompliance, &mpp, exception);

  (void)SetImageColor(new_image, &mpp, exception);
#endif

  return (new_image);
}



ModuleExport size_t replacelastImage(Image **images,const int argc,
  const char **argv,ExceptionInfo *exception)
{
  Image
    *image,
    *new_image;

  assert(images != (Image **) NULL);
  assert(*images != (Image *) NULL);
  assert((*images)->signature == MAGICK_CORE_SIG);

  (void) argc;
  (void) argv;

  // Point to the last image in the list
  //
  image=GetLastImageInList(*images);
  if (image == (Image *) NULL) {
    fprintf (stderr, "replacelast: no images in list\n");
    return (-1);
  }

  new_image = CopyGreen(image,exception);
  if (new_image == (Image *) NULL)
    return(-1);

  ReplaceImageInList(&image,new_image);

  // (Replacing the last image will mess up the images pointer,
  //  if this was the only image.)
  *images=GetFirstImageInList(new_image);

  return(MagickImageFilterSignature);
}

replacefirst.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include "vsn_defines.h"


// The next function corresponds in style to functions in transform.c
// It takes one image, and returns an image.
//
static Image *CopyRed(const Image *image,
  ExceptionInfo *exception)
{
  Image
    *new_image;

  PIX_INFO
    mpp;

  assert(image != (Image *) NULL);
  assert(image->signature == MAGICK_CORE_SIG);
  if (image->debug != MagickFalse)
    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);


  // Make a clone of this image, same size but undefined pixel values:
  //
  new_image=CloneImage(image, image->columns, image->rows, MagickTrue, exception);
  if (new_image == (Image *) NULL)
    return(new_image);

  if (SetNoPalette (new_image, exception) == MagickFalse)
    return (Image *)NULL;

#if IMV6OR7==6
  // Make a "colour", which will be black.
  GetMagickPixelPacket (new_image, &mpp);

  // We could set it to any colour:
  (void)QueryMagickColor ("red", &mpp, exception);

  // Set all the pixels to this colour.
  (void)SetImageColor(new_image, &mpp);
#else
  GetPixelInfo (new_image, &mpp);

  QueryColorCompliance ("red", AllCompliance, &mpp, exception);

  (void)SetImageColor(new_image, &mpp, exception);
#endif


  return (new_image);
}



ModuleExport size_t replacefirstImage(Image **images,const int argc,
  const char **argv,ExceptionInfo *exception)
{
  Image
    *image,
    *new_image;

  assert(images != (Image **) NULL);
  assert(*images != (Image *) NULL);
  assert((*images)->signature == MAGICK_CORE_SIG);

  (void) argc;
  (void) argv;

  // Point to the last image in the list
  //
  image=GetLastImageInList(*images);
  if (image == (Image *) NULL) {
    fprintf (stderr, "replacelast: no images in list\n");
    return (-1);
  }

  new_image = CopyRed(image,exception);
  if (new_image == (Image *) NULL)
    return(-1);

  // Replace the first image.
  ReplaceImageInList(images,new_image);
  // Replace messes up the images pointer. Make it good:
  *images=GetFirstImageInList(new_image);

  return(MagickImageFilterSignature);
}

replaceall.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <assert.h>
#include <math.h>
#include "vsn_defines.h"


// The next function corresponds in style to functions in transform.c
// It takes one image, and returns an image.
//
static Image *CopyBlue(const Image *image,
  ExceptionInfo *exception)
{
  Image
    *new_image;

  PIX_INFO
    mpp;

  assert(image != (Image *) NULL);
  assert(image->signature == MAGICK_CORE_SIG);
  if (image->debug != MagickFalse)
    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);

  // Make a clone of this image, same size but undefined pixel values:
  //
  new_image=CloneImage(image, image->columns, image->rows, MagickTrue, exception);
  if (new_image == (Image *) NULL)
    return(new_image);

  if (SetNoPalette (new_image, exception) == MagickFalse)
    return (Image *)NULL;

#if IMV6OR7==6
  // Make a "colour", which will be black.
  GetMagickPixelPacket (new_image, &mpp);

  // We could set it to any colour:
  (void)QueryMagickColor("blue", &mpp, exception);

  // Set all the pixels to this colour.
  (void)SetImageColor(new_image, &mpp);
#else
  GetPixelInfo (new_image, &mpp);

  QueryColorCompliance ("blue", AllCompliance, &mpp, exception);

  (void)SetImageColor(new_image, &mpp, exception);
#endif

  return (new_image);
}



ModuleExport size_t replaceallImage(Image **images,const int argc,
  const char **argv,ExceptionInfo *exception)
{
  Image
    *image,
    *new_image,
    *new_list;

  assert(images != (Image **) NULL);
  assert(*images != (Image *) NULL);
  assert((*images)->signature == MAGICK_CORE_SIG);

  (void) argc;
  (void) argv;

  // Point to the last image in the list
  //
  image=GetLastImageInList(*images);
  if (image == (Image *) NULL) {
    fprintf (stderr, "replacelast: no images in list\n");
    return (-1);
  }


  new_image = CopyBlue(image,exception);
  if (new_image == (Image *) NULL)
    return(-1);

  // To replace all the images,
  // we could add the new image to the end of the current list
  // and delete all the previous images.
  //
  // It seems easier to wipeout the current list,
  // create a new list,
  // and add the image to the new list.
  //
  DestroyImageList (*images);
  new_list = NewImageList();
  AppendImageToList(&new_list, new_image);

  *images = GetFirstImageInList(new_list);

  return(MagickImageFilterSignature);
}

replaceeach.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include "vsn_defines.h"


// The next function corresponds in style to functions in transform.c
// It takes one image, and returns an image.
//
static Image *CopyMagenta(const Image *image,
  ExceptionInfo *exception)
{
  Image
    *new_image;

  PIX_INFO
    mpp;

  assert(image != (Image *) NULL);
  assert(image->signature == MAGICK_CORE_SIG);
  if (image->debug != MagickFalse)
    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);


  // Make a clone of this image, same size but undefined pixel values:
  //
  new_image=CloneImage(image, image->columns, image->rows, MagickTrue, exception);
  if (new_image == (Image *) NULL)
    return(new_image);

  if (SetNoPalette (new_image, exception) == MagickFalse)
    return (Image *)NULL;

  SetAllBlack (new_image, exception);

#if IMV6OR7==6
  // We could set it to any colour:
  (void)QueryMagickColor("Magenta",&mpp,exception);

  // Set all the pixels to this colour.
  (void)SetImageColor(new_image, &mpp);
#else
  GetPixelInfo (new_image, &mpp);

  QueryColorCompliance ("Magenta", AllCompliance, &mpp, exception);

  (void)SetImageColor(new_image, &mpp, exception);
#endif


  return (new_image);
}


/*
We replace each image as we come across it.
An alternative technique is to create a new list, and add new images to it;
at the end, replace the original list with the new one.

That alternative is slightly more complex
and needs more memory as it would hold all input and output images in memory.
*/

ModuleExport size_t replaceeachImage(Image **images,const int argc,
  const char **argv,ExceptionInfo *exception)
{
  Image
    *image,
    *new_image;

  assert(images != (Image **) NULL);
  assert(*images != (Image *) NULL);
  assert((*images)->signature == MAGICK_CORE_SIG);

  (void) argc;
  (void) argv;

  for (image=(*images); image != (Image *) NULL; image=GetNextImageInList(image))
  {
    new_image = CopyMagenta(image,exception);
    if (new_image == (Image *) NULL)
      return(-1);

    ReplaceImageInList(&image,new_image);
    // Replace messes up the images pointer. Make it good:
    *images=GetFirstImageInList(image);
  }

  return(MagickImageFilterSignature);
}

replacespec.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include "vsn_defines.h"


typedef struct {
  char
    sSize[MaxTextExtent],
    sColor[MaxTextExtent];

  size_t
    width,
    height;

  MagickBooleanType
    do_verbose;
} replspecT;

static void usage (void)
{
  printf ("Usage: -process 'replacespec [OPTION]...'\n");
  printf ("Replace each image.\n");
  printf ("\n");
  printf ("  s, size WxH         size of new image\n");
  printf ("  c, color string     make new image this color\n");
  printf ("\n");
}


static MagickBooleanType IsArg (char * pa, char * ShtOpt, char * LongOpt)
{
  if ((LocaleCompare(pa, ShtOpt)==0) || (LocaleCompare(pa, LongOpt)==0))
    return MagickTrue;

  return MagickFalse;
}


static MagickBooleanType menu(
  const int argc,
  const char **argv,
  replspecT *prs
)
// Returns MagickTrue if okay.
{
  int
    i;

  MagickBooleanType
    status;

  status = MagickTrue;

  *prs->sSize = '\0';
  *prs->sColor = '\0';
  prs->width = prs->height = 0;
  prs->do_verbose = MagickFalse;

  for (i=0; i < argc; i++) {
    char * pa = (char *)argv[i];

    if (IsArg (pa, "s", "size")==MagickTrue) {
      i++;
      strncpy (prs->sSize, argv[i], MaxTextExtent-1);
      if (sscanf (prs->sSize, "%lux%lu", &prs->width, &prs->height) == 0) {
        if (sscanf (prs->sSize, "x%lu", &prs->height) != 1) {
          fprintf (stderr, "replacespec: invalid 'size' argument [%s]\n",
            prs->sSize);
          status = MagickFalse;
        }
      }
    } else if (IsArg (pa, "c", "color")==MagickTrue) {
      i++;
      strncpy (prs->sColor, argv[i], MaxTextExtent-1);
    } else if (IsArg (pa, "v", "verbose")==MagickTrue) {
      prs->do_verbose = MagickTrue;
    } else {
      fprintf (stderr, "replacespec: ERROR: unknown option [%s]\n", pa);
      status = MagickFalse;
    }
  }

  if (prs->do_verbose) {
    fprintf (stderr, "replacespec options:");
    if (*prs->sSize) fprintf (stderr, "  size %s ",
      prs->sSize);
    if (*prs->sColor) fprintf (stderr, "  color %s ",
      prs->sColor);
    if (prs->do_verbose) fprintf (stderr, "  verbose");
    fprintf (stderr, "\n");
  }

  if (status == MagickFalse)
    usage ();

  return (status);
}



// The next function corresponds in style to functions in transform.c
// It takes one image, and returns an image.
//
static Image *CopyColor(const Image *image,
  replspecT *prs,
  ExceptionInfo *exception)
{
  Image
    *new_image;

  PIX_INFO
    mpp;

  size_t
    width,
    height;

  assert(image != (Image *) NULL);
  assert(image->signature == MAGICK_CORE_SIG);
  if (image->debug != MagickFalse)
    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);


  if (prs->do_verbose) {
    fprintf (stderr, "  input image size %lux%lu\n", image->columns, image->rows);
    if (*prs->sSize) {
      fprintf (stderr, "  requested size %lux%lu\n", prs->width, prs->height);
    }
  }

  //width = image->columns;
  //height = image->rows;
  //if (*prs->sSize) {
  //  width = prs->width;
  //  height = prs->height;
  //}

  width  = (prs->width)  ? prs->width  : image->columns;
  height = (prs->height) ? prs->height : image->rows;


  new_image=CloneImage(image, width, height, MagickTrue, exception);
  if (new_image == (Image *) NULL)
    return(new_image);

  if (SetNoPalette (new_image, exception) == MagickFalse)
    return (Image *)NULL;

#if IMV6OR7==6
  // Make a "colour", which will initially be black.
  GetMagickPixelPacket (new_image, &mpp);

  // We set it to any colour:
  (void)QueryMagickColor(
    *prs->sColor ? prs->sColor : "brown",
    &mpp,
    exception);

  // Set all the pixels to this colour.
  (void)SetImageColor(new_image, &mpp);
#else
  GetPixelInfo (new_image, &mpp);

  QueryColorCompliance (
    *prs->sColor ? prs->sColor : "brown",
    AllCompliance, &mpp, exception);

  (void)SetImageColor(new_image, &mpp, exception);
#endif


  return (new_image);
}


/*
We replace each image as we come across it.
An alternative technique is to create a new list, and add new images to it;
at the end, replace the original list with the new one.

That alternative is slightly more complex
and needs more memory as it would hold all input and output images in memory.
*/

ModuleExport size_t replacespecImage(Image **images,const int argc,
  const char **argv,ExceptionInfo *exception)
{
  Image
    *image,
    *new_image;

  MagickBooleanType
    status;

  replspecT
    replace_spec;

  assert(images != (Image **) NULL);
  assert(*images != (Image *) NULL);
  assert((*images)->signature == MAGICK_CORE_SIG);

  status = menu (argc, argv, &replace_spec);
  if (status == MagickFalse)
    return (-1);

  for (image=(*images); image != (Image *) NULL; image=GetNextImageInList(image))
  {
    new_image = CopyColor(image, &replace_spec, exception);
    if (new_image == (Image *) NULL)
      return(-1);

    ReplaceImageInList(&image,new_image);
    // Replace messes up the images pointer. Make it good:
    *images=GetFirstImageInList(image);
  }

  return(MagickImageFilterSignature);
}

grad2.c

/* Updated:
     3-April-2018 for v7.0.7-28
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <assert.h>
#include <math.h>
#include "vsn_defines.h"


// The next function corresponds in style to functions in transform.c
// It takes one image, and returns an image.
//
static Image *CopyGrad2(const Image *image,
  ExceptionInfo *exception)
{
  Image
    *new_image;

  CacheView
    *image_view;

  ssize_t
    y;

  MagickBooleanType
    status;

  assert(image != (Image *) NULL);
  assert(image->signature == MAGICK_CORE_SIG);
  if (image->debug != MagickFalse)
    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);

  // Make a clone of this image, same size but undefined pixel values:
  //
  new_image=CloneImage(image, image->columns, image->rows, MagickTrue, exception);
  if (new_image == (Image *) NULL)
    return(new_image);

  if (SetNoPalette (new_image, exception) == MagickFalse)
    return (Image *)NULL;

  image_view = AcquireAuthenticCacheView(new_image,exception);

  status = MagickTrue;

#if defined(MAGICKCORE_OPENMP_SUPPORT)
  #pragma omp parallel for schedule(static,4) shared(status) \
    MAGICK_THREADS(new_image,new_image,new_image->rows,1)
#endif
  for (y=0; y < (ssize_t) new_image->rows; y++)
  {
    register VIEW_PIX_PTR
      *q;

    register ssize_t
      x;

    if (status == MagickFalse)
      continue;
    q=GetCacheViewAuthenticPixels(image_view,0,y,new_image->columns,1,exception);
    if (q == (const VIEW_PIX_PTR *) NULL)
    {
      status=MagickFalse;
      continue;
    }
    for (x=0; x < (ssize_t) image->columns; x++)
    {
      SET_PIXEL_RED   (new_image, QuantumRange * (x/(image->columns-1.0)), q);
      SET_PIXEL_GREEN (new_image, QuantumRange * (y/(image->rows-1.0)), q);
      SET_PIXEL_BLUE  (new_image, QuantumRange/2, q);
      q += Inc_ViewPixPtr (new_image);
    }
    if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
      status=MagickFalse;
  }
  image_view=DestroyCacheView(image_view);

  return (new_image);
}



ModuleExport size_t grad2Image(Image **images,const int argc,
  const char **argv,ExceptionInfo *exception)
{
  Image
    *image,
    *new_image;

  assert(images != (Image **) NULL);
  assert(*images != (Image *) NULL);
  assert((*images)->signature == MAGICK_CORE_SIG);

  (void) argc;
  (void) argv;

  for (image=(*images); image != (Image *) NULL; image=GetNextImageInList(image))
  {
    new_image = CopyGrad2(image,exception);
    if (new_image == (Image *) NULL)
      return(-1);

    ReplaceImageInList(&image,new_image);
    // Replace messes up the images pointer. Make it good:
    *images=GetFirstImageInList(image);
  }

  return(MagickImageFilterSignature);
}

drawcirc.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include "vsn_defines.h"


// The next function corresponds in style to functions in enhance.c
// It takes one image, and returns a status.
//
static MagickBooleanType DrawCircleImage(Image *image,
  ExceptionInfo *exception)
{
  double
    cx,
    cy,
    rad;

  MagickBooleanType
    status;

  DrawInfo
    *draw_info;

  char
    primitive[MaxTextExtent];


  assert(image != (Image *) NULL);
  assert(image->signature == MAGICK_CORE_SIG);
  if (image->debug != MagickFalse)
    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);

  // We don't need SetImageStorageClass().

  cx = (image->columns -1) / 2.0;
  cy = (image->rows    -1) / 2.0;
  rad = cx;
  if (rad > cy) rad = cy;

  status=MagickTrue;
  draw_info = AcquireDrawInfo();
  //
  // Or:
  //   draw_info=CloneDrawInfo((ImageInfo *) NULL,(DrawInfo *) NULL);
  // ??

#if IMV6OR7==6
  QueryColorDatabase (
    "blue", &draw_info->fill, exception);
#else
  QueryColorCompliance (
    "blue", AllCompliance, &draw_info->fill, exception);
#endif

  (void) FormatLocaleString(primitive,MaxTextExtent,
    "circle %g,%g %g,%g", cx, cy, cx, cy+rad);
  (void) CloneString(&draw_info->primitive,primitive);

#if IMV6OR7==6
  status = DrawImage (image, draw_info);
#else
  status = DrawImage (image, draw_info, exception);
#endif

  draw_info = DestroyDrawInfo(draw_info);


  return(status);

}

// Style of next is for "-process".
// It loops through all images in a list, processing each in-place.
//
ModuleExport size_t drawcircImage(Image **images,const int argc,
  const char **argv,ExceptionInfo *exception)
{
  Image
    *image;

  MagickBooleanType
    status;

  (void) argc;
  (void) argv;


  assert(images != (Image **) NULL);
  assert(*images != (Image *) NULL);
  assert((*images)->signature == MAGICK_CORE_SIG);

  for (image=(*images); image != (Image *) NULL; image=GetNextImageInList(image))
  {
    status = DrawCircleImage(image,exception);

    if (status == MagickFalse)
      continue;
  }
  assert (status == MagickTrue);

  return(MagickImageFilterSignature);
}

sortpixels.c

/* Updated:
     1-October-2016 Use GetPixelIntensity() for both v6 and v7. (Previously,
                      v6 used PixelPacketIntensity()).
     3-April-2018 for v7.0.7-28
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include "vsn_defines.h"

static Image
  *gimage;


static int compare_pixels (const void *a, const void *b)
{
//#if IMV6OR7==6
//  const Quantum intensity_a = PixelPacketIntensity( (const VIEW_PIX_PTR *) a);
//  const Quantum intensity_b = PixelPacketIntensity( (const VIEW_PIX_PTR *) b);
//#else
//  const Quantum intensity_a = GetPixelIntensity(gimage, (const VIEW_PIX_PTR *) a);
//  const Quantum intensity_b = GetPixelIntensity(gimage, (const VIEW_PIX_PTR *) b);
//#endif

  const Quantum intensity_a = GetPixelIntensity(gimage, (const VIEW_PIX_PTR *) a);
  const Quantum intensity_b = GetPixelIntensity(gimage, (const VIEW_PIX_PTR *) b);

  return (intensity_a > intensity_b) - (intensity_a < intensity_b);
}

// The next function corresponds in style to functions in enhance.c
// It takes one image, and returns a status.
//
static MagickBooleanType SortPixImage(Image *image,
  ExceptionInfo *exception)
{
  CacheView
    *image_view;

  ssize_t
    y;

  MagickBooleanType
    status;


  assert(image != (Image *) NULL);
  assert(image->signature == MAGICK_CORE_SIG);
  if (image->debug != MagickFalse)
    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);

  if (SetNoPalette (image, exception) == MagickFalse)
    return MagickFalse;

  gimage = image;

  status=MagickTrue;
  image_view=AcquireAuthenticCacheView(image,exception);

#if defined(MAGICKCORE_OPENMP_SUPPORT)
  #pragma omp parallel for schedule(static,4) shared(status) \
    MAGICK_THREADS(image,image,image->rows,1)
#endif
  for (y=0; y < (ssize_t) image->rows; y++)
  {
    register const VIEW_PIX_PTR
      *p;

    if (status == MagickFalse)
      continue;
    p=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
    if (p == (const VIEW_PIX_PTR *) NULL)
    {
      status=MagickFalse;
      continue;
    }

#if IMV6OR7==6
    qsort ((void *)p, image->columns, sizeof (VIEW_PIX_PTR), compare_pixels);
#else
    qsort (
      (void *)p,
      image->columns,
      sizeof (VIEW_PIX_PTR) * GetPixelChannels(image),
      compare_pixels);
#endif


    if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
      status=MagickFalse;
  }
  image_view=DestroyCacheView(image_view);

  return(status);

}

// Style of next is for "-process".
// It loops through all images in a list, processing each in-place.
//
ModuleExport size_t sortpixelsImage(Image **images,const int argc,
  const char **argv,ExceptionInfo *exception)
{
  Image
    *image;

  MagickBooleanType
    status;

  (void) argc;
  (void) argv;


  assert(images != (Image **) NULL);
  assert(*images != (Image *) NULL);
  assert((*images)->signature == MAGICK_CORE_SIG);

  for (image=(*images); image != (Image *) NULL; image=GetNextImageInList(image))
  {
    status = SortPixImage(image,exception);

    if (status == MagickFalse)
      continue;
  }
  assert (status == MagickTrue);

  return(MagickImageFilterSignature);
}

sortpixelsblue.c

/* Updated:
     3-April-2018 for v7.0.7-28
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include "vsn_defines.h"

static Image
  *gimage;


static int compare_pixels (const void *a, const void *b)
{
  Quantum intensity_a = GET_PIXEL_BLUE(gimage, (const VIEW_PIX_PTR *) a);
  Quantum intensity_b = GET_PIXEL_BLUE(gimage, (const VIEW_PIX_PTR *) b);

  int r = (intensity_a > intensity_b) - (intensity_a < intensity_b);

  if (r != 0) return r;

  intensity_a = GET_PIXEL_GREEN(gimage, (const VIEW_PIX_PTR *) a);
  intensity_b = GET_PIXEL_GREEN(gimage, (const VIEW_PIX_PTR *) b);

  r = (intensity_a > intensity_b) - (intensity_a < intensity_b);

  if (r != 0) return r;

  intensity_a = GET_PIXEL_RED(gimage, (const VIEW_PIX_PTR *) a);
  intensity_b = GET_PIXEL_RED(gimage, (const VIEW_PIX_PTR *) b);

  return (intensity_a > intensity_b) - (intensity_a < intensity_b);
}

// The next function corresponds in style to functions in enhance.c
// It takes one image, and returns a status.
//
static MagickBooleanType SortPixImageBlue(Image *image,
  ExceptionInfo *exception)
{
  CacheView
    *image_view;

  ssize_t
    y;

  MagickBooleanType
    status;


  assert(image != (Image *) NULL);
  assert(image->signature == MAGICK_CORE_SIG);
  if (image->debug != MagickFalse)
    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);

  if (SetNoPalette (image, exception) == MagickFalse)
    return MagickFalse;

  gimage = image;

  status=MagickTrue;
  image_view=AcquireAuthenticCacheView(image,exception);

#if defined(MAGICKCORE_OPENMP_SUPPORT)
  #pragma omp parallel for schedule(static,4) shared(status) \
    MAGICK_THREADS(image,image,image->rows,1)
#endif
  for (y=0; y < (ssize_t) image->rows; y++)
  {
    register const VIEW_PIX_PTR
      *p;

    if (status == MagickFalse)
      continue;
    p=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
    if (p == (const VIEW_PIX_PTR *) NULL)
    {
      status=MagickFalse;
      continue;
    }

#if IMV6OR7==6
    qsort ((void *)p, image->columns, sizeof (VIEW_PIX_PTR), compare_pixels);
#else
    qsort (
      (void *)p,
      image->columns,
      sizeof (VIEW_PIX_PTR) * GetPixelChannels(image),
      compare_pixels);
#endif


    if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
      status=MagickFalse;
  }
  image_view=DestroyCacheView(image_view);

  return(status);

}

// Style of next is for "-process".
// It loops through all images in a list, processing each in-place.
//
ModuleExport size_t sortpixelsblueImage(Image **images,const int argc,
  const char **argv,ExceptionInfo *exception)
{
  Image
    *image;

  MagickBooleanType
    status;

  (void) argc;
  (void) argv;


  assert(images != (Image **) NULL);
  assert(*images != (Image *) NULL);
  assert((*images)->signature == MAGICK_CORE_SIG);

  for (image=(*images); image != (Image *) NULL; image=GetNextImageInList(image))
  {
    status = SortPixImageBlue(image,exception);

    if (status == MagickFalse)
      continue;
  }
  assert (status == MagickTrue);

  return(MagickImageFilterSignature);
}

shadowsortpixels.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include "vsn_defines.h"

static void inline SwapPixels (const VIEW_PIX_PTR *a, const VIEW_PIX_PTR *b, int nBytes)
{
  char * pa = (char *) a;
  char * pb = (char *) b;
  char t;

  int i;
  for (i=0; i < nBytes; i++) {
    t = *pa;
    *pa++ = *pb;
    *pb++ = t;
  }
}

// The next function corresponds in style to functions in enhance.c
// It takes two images, modifies both, and returns a status.
//
static MagickBooleanType ShadowSortPixImage(Image **images,
  ExceptionInfo *exception)
{
  CacheView
    *imageA_view, *imageB_view;

  ssize_t
    y;

  MagickBooleanType
    status = MagickTrue;


  Image * imageA = *images;

  assert(imageA != (Image *) NULL);
  assert(imageA->signature == MAGICK_CORE_SIG);
  if (imageA->debug != MagickFalse)
    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",imageA->filename);

  Image * imageB = GetNextImageInList (imageA);
  if (!imageB) {
    fprintf (stderr, "shadowsortpixels: needs two images, the same size\n");
    return MagickFalse;
  }

  if (imageA->columns != imageB->columns ||
      imageA->rows    != imageB->rows)
  {
    fprintf (stderr, "shadowsortpixels: the two images must be the same size\n");
    return MagickFalse;
  }

  if (SetNoPalette (imageA, exception) == MagickFalse)
    return MagickFalse;

  if (SetNoPalette (imageB, exception) == MagickFalse)
    return MagickFalse;

  const ssize_t incA = Inc_ViewPixPtr (imageA);
  const ssize_t incB = Inc_ViewPixPtr (imageB);

  const int nBytesA = sizeof (VIEW_PIX_PTR) * incA;
  const int nBytesB = sizeof (VIEW_PIX_PTR) * incB;

  imageA_view=AcquireAuthenticCacheView(imageA,exception);
  imageB_view=AcquireAuthenticCacheView(imageB,exception);

#if defined(MAGICKCORE_OPENMP_SUPPORT)
  #pragma omp parallel for schedule(static,4) shared(status) \
    MAGICK_THREADS(imageA,imageA,imageA->rows,1)
#endif
  for (y=0; y < (ssize_t) imageA->rows; y++)
  {
    VIEW_PIX_PTR
      *pA, *pB;

    if (status == MagickFalse)
      continue;

    pA=GetCacheViewAuthenticPixels(imageA_view,0,y,imageA->columns,1,exception);
    if (!pA)
    {
      status=MagickFalse;
      continue;
    }

    pB=GetCacheViewAuthenticPixels(imageB_view,0,y,imageB->columns,1,exception);
    if (!pB)
    {
      status=MagickFalse;
      continue;
    }

    // Shell sort.

    ssize_t x, xSwitch, xLimit;

    ssize_t xOffset = imageA->columns / 2;

    while (xOffset) {

      xLimit = imageA->columns - xOffset - 1 ;
      do {
        xSwitch = 0;     // Assume no switches at this offset.

        // Compare elements and switch ones out of order.
        for( x = 0; x <= xLimit; x++ ) {
          const VIEW_PIX_PTR * a = pA + x*incA;
          const VIEW_PIX_PTR * b = pA + (x+xOffset)*incA;

          if (GetPixelIntensity(imageA, a) > GetPixelIntensity(imageA, b)) {
            SwapPixels (a, b, nBytesA);
            SwapPixels (pB + x*incB, pB + (x+xOffset)*incB, nBytesB);
            xSwitch = x;
          }
        }

        // Sort on next pass only to where last switch was made.
        xLimit = xSwitch - xOffset;
      } while (xSwitch);

      // No switches at last offset, try one half as big.
      xOffset /= 2;
    }

    if (SyncCacheViewAuthenticPixels(imageA_view,exception) == MagickFalse)
      status=MagickFalse;

    if (SyncCacheViewAuthenticPixels(imageB_view,exception) == MagickFalse)
      status=MagickFalse;
  }
  imageA_view=DestroyCacheView(imageA_view);
  imageB_view=DestroyCacheView(imageB_view);

  return(status);
}

ModuleExport size_t shadowsortpixelsImage(Image **images,const int argc,
  const char **argv,ExceptionInfo *exception)
{
  (void) argc;
  (void) argv;

  assert(images != (Image **) NULL);
  assert(*images != (Image *) NULL);
  assert((*images)->signature == MAGICK_CORE_SIG);

  int ListLen = (int)GetImageListLength(*images);
  if (ListLen != 2) {
    fprintf (stderr, "shadowsortpixels: needs 2 images\n");
    return (-1);
  }

  if (!ShadowSortPixImage (images, exception)) return (-1);

  return(MagickImageFilterSignature);
}

fillholes.c

/*
    Reference: http://im.snibgo.com/fillholes.htm

    Update 21-Nov-2015
      Added copy_radius option.
      Normalise MSE result from CompareWindow to 0.0-1.0, for threshold option.
      Added threshold option.
      Added "unfilled" warning.
      Moved most code to fillholescommon.inc.

    Updated
      2-Feb-2016 for v7.
      3-April-2018 for v7.0.7-28
*/


#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <ctype.h>
#include "vsn_defines.h"


#include "fillholescommon.inc"

#define DEBUG 0


// The next function corresponds in style to functions in transform.c
// It takes one image, and returns an image with filled holes.
//
static Image *fillholes (
  Image *image,
  FillHoleT * pfh,
  ExceptionInfo *exception)
{
  Image
    *holed_image,
    *new_image;

  CacheView
    *copy_inp_view,
    *copy_new_view2,
    *transp_view,
    *new_view;

  ssize_t
    y,
    holedXmult;

  int
    cols_plus_3,
    nIter = 0,
    frameNum = 0;

  MagickBooleanType
    unfilled,
    changedAny,
    status = MagickTrue;

  if (image->debug != MagickFalse)
    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);

  if (pfh->do_verbose) {
    fprintf (stderr, "fillholes: Input image [%s] %ix%i depth is %i\n",
             image->filename,
             (int)image->columns, (int)image->rows,
             (int)image->depth);
  }

  ResolveImageParams (image, pfh);

  InitAutoLimit (pfh);

  // Clone the image, same size, copied pixels:
  //
  holed_image=CloneImage(image, 0, 0, MagickTrue, exception);
  if (holed_image == (Image *) NULL)
    return(holed_image);

  if (SetNoPalette (holed_image, exception) == MagickFalse)
    return (Image *)NULL;

  holedXmult = Inc_ViewPixPtr (holed_image);

  // Clone holed_image into new_image.
  new_image=CloneImage(holed_image, 0, 0, MagickTrue, exception);
  if (new_image == (Image *) NULL)
    return(new_image);

  if (pfh->write_frames > 0) {
    WriteFrame (pfh, new_image, frameNum++, exception);
  }

  do {  // one onion-ring

#if DEBUG==1
    printf ("Do one onion-ring\n");
#endif

    changedAny = MagickFalse;
    unfilled = MagickFalse;

    // FIXME: performance: we can acquire/release some of these outside do loop?
//    inp_view    = AcquireVirtualCacheView (image, exception);
//    srch_holed_view = AcquireVirtualCacheView (holed_image, exception);

    pfh->CompWind.ref_image = image;
    pfh->CompWind.sub_image = holed_image;
    pfh->CompWind.ref_view = AcquireVirtualCacheView (image, exception);
    pfh->CompWind.sub_view = AcquireVirtualCacheView (holed_image, exception);

    transp_view = AcquireVirtualCacheView (holed_image, exception);
    new_view    = AcquireAuthenticCacheView (new_image, exception);

    copy_inp_view   = CloneCacheView (pfh->CompWind.ref_view);
    copy_new_view2  = AcquireVirtualCacheView (new_image, exception);

    cols_plus_3 = holed_image->columns + 3;
#if defined(MAGICKCORE_OPENMP_SUPPORT)
    #pragma omp parallel for schedule(static,4) shared(status,changedAny,frameNum) \
      MAGICK_THREADS(image,new_image,image->rows,1)
#endif
    for (y=0; y < (ssize_t) new_image->rows; y++)
    {
      ssize_t
        x;

      const VIEW_PIX_PTR
        *pp_this_pix,
        *pp_transp3,
        *pp_line0,
        *pp_line1,
        *pp_line2;

      CopyWhereT
        cw;

      if (pfh->copyWhat == copyWindow) {
        cw.wi = pfh->sqCopyDim;
        cw.ht = pfh->sqCopyDim;
      } else {
        cw.wi = 1;
        cw.ht = 1;
      }

      if (status == MagickFalse) continue;

      pp_this_pix = GetCacheViewVirtualPixels (
        copy_new_view2,0,y,holed_image->columns,1,exception); // FIXME: need diff view?
      if (pp_this_pix == (const VIEW_PIX_PTR *) NULL)
      {
        status=MagickFalse;
        continue;
      }

      pp_transp3 = GetCacheViewVirtualPixels (
        transp_view,-1,y-1,holed_image->columns+2,3,exception);
      if (pp_transp3 == (const VIEW_PIX_PTR *) NULL)
      {
        status=MagickFalse;
        continue;
      }

      // FIXME: offsets are wrong for v7.
//      pp_line0 = pp_transp3 + 1;
//      pp_line1 = pp_transp3 + cols_plus_3;
//      pp_line2 = pp_transp3 + 2 * cols_plus_3 - 1;

      pp_line0 = pp_transp3 + holedXmult;
      pp_line1 = pp_transp3 + holedXmult * cols_plus_3;
      pp_line2 = pp_transp3 + holedXmult * (2 * cols_plus_3 - 1);

#if DEBUG==1
      printf ("y=%li  ", (long int)y);
#endif

      for (x=0; x < (ssize_t) new_image->columns; x++)
      {
        // FIXME: get the 9 alphas from new_image, so we can copy a window-full each time.
        // But then we do too many.

        // FIXME: offsets are wrong for v7.

        //if (GetPixelAlpha (pp_line1 + x) <= 0) {
        if (GET_PIXEL_ALPHA (new_image, pp_this_pix + holedXmult * x) <= 0) {
          // The pixel is fully transparent.
          MagickBooleanType HasAdjOpaq = 
               (GET_PIXEL_ALPHA (holed_image, pp_line0 + holedXmult*(x-1)) > 0)
            || (GET_PIXEL_ALPHA (holed_image, pp_line0 + holedXmult*x    ) > 0)
            || (GET_PIXEL_ALPHA (holed_image, pp_line0 + holedXmult*(x+1)) > 0)

            || (GET_PIXEL_ALPHA (holed_image, pp_line1 + holedXmult*(x-1)) > 0)
            || (GET_PIXEL_ALPHA (holed_image, pp_line1 + holedXmult*(x+1)) > 0)

            || (GET_PIXEL_ALPHA (holed_image, pp_line2 + holedXmult*(x-1)) > 0)
            || (GET_PIXEL_ALPHA (holed_image, pp_line2 + holedXmult*x    ) > 0)
            || (GET_PIXEL_ALPHA (holed_image, pp_line2 + holedXmult*(x+1)) > 0);

          if (HasAdjOpaq == MagickTrue) {

            // The pixel is fully transparent,
            // with at least one opaque 8-neighbour.

            status = MatchAndCopy (
              pfh,
              new_image,
              new_view,
              copy_inp_view,
              x,
              y,
              &cw,
              &frameNum,
              &unfilled,
              &changedAny,
              exception);

/*==
            ssize_t
              i0, i1,
              j0, j1,
              hx, hy,
              i, j,
              besti=0, bestj=0;

            hx = x - pfh->WindowRad;
            hy = y - pfh->WindowRad;
            if (pfh->SearchToEdges) {
              i0 = -pfh->WindowRad;
              i1 = (ssize_t) holed_image->rows - pfh->WindowRad;
              j0 = -pfh->WindowRad;
              j1 = (ssize_t) holed_image->columns - pfh->WindowRad;
            } else {
              i0 = 0;
              i1 = (ssize_t) holed_image->rows - pfh->sqDim + 1;
              j0 = 0;
              j1 = (ssize_t) holed_image->columns - pfh->sqDim + 1;
            }
            if (pfh->LimSrchRad) {
              ssize_t limj0 = x - pfh->LimSrchRad - pfh->WindowRad;
              ssize_t limj1 = x + pfh->LimSrchRad - pfh->WindowRad;
              ssize_t limi0 = y - pfh->LimSrchRad - pfh->WindowRad;
              ssize_t limi1 = y + pfh->LimSrchRad - pfh->WindowRad;
              if (i0 < limi0) i0 = limi0;
              if (i1 > limi1) i1 = limi1;
              if (j0 < limj0) j0 = limj0;
              if (j1 > limj1) j1 = limj1;
            }
            double BestScore = pfh->WorstCompare;
            for (i = i0; i < i1; i++) {
              for (j = j0; j < j1; j++) {
                double v = CompareWindow (
                  pfh, inp_view, srch_holed_view, exception,
                  hx, hy, j, i);
                if (BestScore > v) {
                  BestScore = v;
                  besti = i;
                  bestj = j;
                  if (BestScore <= pfh->thresholdSq) break;
                }
              }
              if (BestScore <= pfh->thresholdSq) break;
            }
            //if (x==30)
            //  printf ("Best ji=%i,%i %g => hxy %i,%i\n",
            //    (int)bestj, (int)besti, BestScore, (int)hx, (int)hy);
            if (BestScore < pfh->WorstCompare) {

              if (pfh->copyWhat == copyOnePixel)
                CopyOnePix (pfh, new_view, copy_inp_view, hx, hy, bestj, besti, exception);
              else
                CopyWindow (pfh, new_view, copy_inp_view, hx, hy, bestj, besti, exception);

              if (SyncCacheViewAuthenticPixels (new_view,exception) == MagickFalse)
                status=MagickFalse;

              changedAny = MagickTrue;
              if (pfh->write_frames == 2) {
                WriteFrame (pfh, new_image, frameNum++, exception);
              }
            } else {
              unfilled = MagickTrue;
            }
==*/

          }
        }
      } // loop x
    } // loop y

#if DEBUG==1
    printf ("Done x,y\n");
#endif

    if (pfh->Match.SetAutoLimit && pfh->Match.nLimitCnt <= 0) UseAutoLimit (pfh);

    copy_inp_view   = DestroyCacheView (copy_inp_view);
    new_view        = DestroyCacheView (new_view);
    transp_view     = DestroyCacheView (transp_view);
    copy_new_view2  = DestroyCacheView (copy_new_view2);

    pfh->CompWind.sub_view = DestroyCacheView (pfh->CompWind.sub_view);
    pfh->CompWind.ref_view = DestroyCacheView (pfh->CompWind.ref_view);

    if (changedAny) {
      DestroyImage (holed_image);
      holed_image = CloneImage(new_image, 0, 0, MagickTrue, exception);
      if (pfh->write_frames == 1) {
        WriteFrame (pfh, new_image, frameNum++, exception);
      }
    }
    nIter++;

#if DEBUG==1
    printf ("Done one onion-ring\n");
#endif

  } while (changedAny);


  if (pfh->do_verbose) {
    fprintf (stderr, "Finished fillholes nIter=%i\n", nIter);
    if (unfilled) {
      fprintf (stderr, "Warning: some pixels were not filled\n");
    }
    if (pfh->write_frames > 0) {
      fprintf (stderr, "  numFrames=%i\n", frameNum);
    }
  }

  DestroyImage (new_image);

  return (holed_image);
}


ModuleExport size_t fillholesImage (
  Image **images,
  const int argc,
  const char **argv,
  ExceptionInfo *exception)
{
  Image
    *image,
    *new_image;

  MagickBooleanType
    status;

  FillHoleT
    fh;

  assert(images != (Image **) NULL);
  assert(*images != (Image *) NULL);
  assert((*images)->signature == MAGICK_CORE_SIG);

  status = menu (argc, argv, &fh);
  if (status == MagickFalse)
    return (-1);

  InitRand (&fh);

  for (image=(*images); image != (Image *) NULL; image=GetNextImageInList(image))
  {
    new_image = fillholes (image, &fh, exception);

    ReplaceImageInList(&image,new_image);
    *images=GetFirstImageInList(image);
  }

  DeInitRand (&fh);

  return(MagickImageFilterSignature);
}

fillholespri.c

/*
    Reference: http://im.snibgo.com/fillholes.htm

    Created 21-Nov-2015

    Updated:
      17-July-2016 AutoRepeat.
      3-April-2018 for v7.0.7-28
*/


#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <ctype.h>
#include "vsn_defines.h"

#include "fillholescommon.inc"

#define DEBUG 0
#define VERIFY 0

#define CONF_FACT 0.9

typedef double fhpValueT;

typedef enum {
  psOpaq,    // Input pixel was opaque, or has become opaque.
  psEdge,    // Currently pixel is transparent but has an opaque neighbour.
  psInner,   // Currently pixel is transparent and has only transparent neghbours.
  psCantFill // Pixel is transparent and can't be filled, so remains transparent.
} pixStateT;

// Valid state transitions:
//   Inner -> Edge -> Opaq
//   Inner -> Opaq
//   Edge -> CantFill

typedef struct {
  pixStateT
    pixState;

  fhpValueT
    confidence,
    energy,
    priority;
} fhPriPixT;

typedef struct {
  ssize_t
    x,
    y;
} pqNodeT;

typedef struct {
  ssize_t
    width,
    height;

  fhPriPixT 
    **fhPriPix;

  ssize_t
    pqNumNodes;

  ssize_t
    pqMaxNodes;

  pqNodeT
    *pqNodes;
} fhPriT;


//---------------------------------------------------------------------------
// Priority queue functions.
// This is a MAX queue: higher values will come out first.

#define LCHILD(x) 2 * x + 1
#define RCHILD(x) 2 * x + 2
#define PARENT(x) (x - 1) / 2

static fhpValueT inline pqDataOfNode (fhPriT * fhp, ssize_t NodeNum)
{
  ssize_t x = fhp->pqNodes[NodeNum].x;
  ssize_t y = fhp->pqNodes[NodeNum].y;
  return fhp->fhPriPix[y][x].priority;
}

static void pqVerify (fhPriT * fhp, MagickBooleanType verbose)
{
  ssize_t
    i;

  if (verbose) printf ("pqVerify pqNumNodes=%li\n", fhp->pqNumNodes);

  for (i=0; i < fhp->pqNumNodes; i++) {
    ssize_t lch = LCHILD(i);
    ssize_t rch = RCHILD(i);

    MagickBooleanType HasL = lch < fhp->pqNumNodes;
    MagickBooleanType HasR = rch < fhp->pqNumNodes;

    if (HasL && pqDataOfNode (fhp, lch) > pqDataOfNode (fhp, i))
      printf ("** Bad %li left\n", i);

    if (HasR && pqDataOfNode (fhp, rch) > pqDataOfNode (fhp, i))
      printf ("** Bad %li right\n", i);
  }
}

#if 0
static void pqDump (FillHoleT * fh, fhPriT * fhp)
{
  ssize_t
    i;

  printf ("pqNumNodes=%li\n", fhp->pqNumNodes);
  printf ("  # x,y, state, confidence, energy, priority\n");

  for (i=0; i < fhp->pqNumNodes; i++) {
    pqNodeT * pqn = &fhp->pqNodes[i];
    fhPriPixT * pn = &(fhp->fhPriPix[pqn->y][pqn->x]);
    char cState;
    if      (pn->pixState == psOpaq) cState = 'o';
    else if (pn->pixState == psEdge) cState = 'e';
    else cState = 'i';

    printf ("  %li  %li,%li %c %g %g %g\n",
      i, pqn->x, pqn->y, cState, pn->confidence, pn->energy, pn->priority);
  }

  pqVerify (fhp, fh->debug);
}
#endif

static void inline pqSwapNodes (fhPriT * fhp, ssize_t n1, ssize_t n2)
{
  ssize_t t = fhp->pqNodes[n1].x;
  fhp->pqNodes[n1].x = fhp->pqNodes[n2].x;
  fhp->pqNodes[n2].x = t;

  t = fhp->pqNodes[n1].y;
  fhp->pqNodes[n1].y = fhp->pqNodes[n2].y;
  fhp->pqNodes[n2].y = t;
}

static void pqBalanceNode (fhPriT * fhp, ssize_t NodeNum)
// "Bubble-down".
// Finds the smallest of this node and its children.
// If the node isn't the smallest of the three,
//   swaps data with that child and recursively balances that child.
{
  ssize_t largest = NodeNum;
  ssize_t lch = LCHILD(NodeNum);
  ssize_t rch = RCHILD(NodeNum);

  fhpValueT SmData = pqDataOfNode (fhp, NodeNum);

  if (lch < fhp->pqNumNodes) {
    fhpValueT ldist = pqDataOfNode (fhp, lch);
    if (ldist > SmData) {
      SmData = ldist;
      largest = lch;
    }
  }

  if (rch < fhp->pqNumNodes) {
    fhpValueT rdist = pqDataOfNode (fhp, rch);
    if (rdist > SmData) {
      SmData = rdist;
      largest = rch;
    }
  }

  if (largest != NodeNum) {
    pqSwapNodes (fhp, largest, NodeNum);
    pqBalanceNode (fhp, largest);
  }
}

static void pqInsertNew (fhPriT * fhp, ssize_t x, ssize_t y)
{
  //printf ("pqIns %li,%li ", x, y);

  if (fhp->pqNumNodes >= fhp->pqMaxNodes) {
    printf ("pq bust\n");
  }

  fhpValueT newPri = fhp->fhPriPix[y][x].priority;
  // Priority could be zero, eg for graphics files with flat colours.

  ssize_t n = fhp->pqNumNodes;

  // "Bubble up".
  // If this data is more than node's parent,
  //   drop parent data into this node
  //   and consider putting new data into that parent.
  while (n && newPri > pqDataOfNode (fhp, PARENT(n))) {
    fhp->pqNodes[n] = fhp->pqNodes[PARENT(n)];
    n = PARENT(n);
  }
  fhp->pqNodes[n].x = x;
  fhp->pqNodes[n].y = y;

  fhp->pqNumNodes++;

  //pqVerify (fhp, MagickTrue);
}

static ssize_t pqFindNode (
  fhPriT * fhp, ssize_t x, ssize_t y, fhpValueT val, ssize_t NodeStart)
// Returns -1 if not present.
// Note: recursive.
{
  if (fhp->pqNumNodes==0) return -1;

  pqNodeT * pqn = &fhp->pqNodes[NodeStart];

  if (pqn->x == x && pqn->y == y) return NodeStart;

  if (pqDataOfNode (fhp, NodeStart) < val) return -1;

  ssize_t lch = LCHILD(NodeStart);
  if (lch < fhp->pqNumNodes) {
    ssize_t nch = pqFindNode (fhp, x, y, val, lch);
    if (nch >= 0) return nch;
  }

  ssize_t rch = RCHILD(NodeStart);
  if (rch < fhp->pqNumNodes) {
    ssize_t nch = pqFindNode (fhp, x, y, val, rch);
    if (nch >= 0) return nch;
  }

  return -1;
}

static ssize_t pqDelNode (
  fhPriT * fhp, ssize_t x, ssize_t y)
// Returns -1 if not present.
{
  fhpValueT oldPri = fhp->fhPriPix[y][x].priority;

  ssize_t n = pqFindNode (fhp, x, y, oldPri, 0);

  if (n < 0) {
    printf ("** Can't find to del %li,%li %g\n", x, y, oldPri);
    return n;
  }

  // Move last to here, delete last, rebalance.

  pqNodeT * pqn = &fhp->pqNodes[n];
  pqNodeT * pqLast = &fhp->pqNodes[fhp->pqNumNodes-1];
  pqn->x = pqLast->x;
  pqn->y = pqLast->y;
  fhp->pqNumNodes--;

  fhpValueT newPri = fhp->fhPriPix[pqn->y][pqn->x].priority;

  // "Bubble up".
  // If this data is more than node's parent,
  //   swap with parent and iterate.
  ssize_t bn = n;
  while (bn && newPri > pqDataOfNode (fhp, PARENT(bn))) {
    pqSwapNodes (fhp, bn, PARENT(bn));
    bn = PARENT(bn);
  }

  // Bubble down from here.
  pqBalanceNode (fhp, n);

  // Bubble down from the top.
  // FIXME: do we need this?
  pqBalanceNode (fhp, 0);

#if VERIFY==1
  pqVerify (fhp, MagickFalse);
#endif

  return n;
}

/*==
static void pqFindMax (fhPriT * fhp, ssize_t *x, ssize_t *y)
{
  if (!fhp->pqNumNodes) printf ("** Bad: fm: empty");

  pqNodeT * pqn = &fhp->pqNodes[0];
  *x = pqn->x;
  *y = pqn->y;
}
==*/

static int pqRemoveMax (fhPriT * fhp, ssize_t *x, ssize_t *y)
// Returns 1 if okay, or 0 if no data.
{
  if (!fhp->pqNumNodes) {
    //printf ("** Bad: rm: empty");
    return 0;
  }

  pqNodeT * pqn = &fhp->pqNodes[0];
  *x = pqn->x;
  *y = pqn->y;

  // Put last into root, and rebalance.

  fhp->pqNodes[0] = fhp->pqNodes[--fhp->pqNumNodes];

  pqBalanceNode (fhp, 0);

  return 1;
}


static void UpdIfData (
  FillHoleT * fh,
  fhPriT * fhp, ssize_t x, ssize_t y, fhpValueT oldPri, fhpValueT newPri)
// If the pixel is in the queue, updates the priority.
{
  //assert(newPri >= oldPri);

  printf ("UpdIfData %li,%li: ", x, y);

  ssize_t n = pqFindNode (fhp, x, y, oldPri, 0);

  if (n < 0) {
    // Not in queue.
    return;
  } else {
    printf ("%li\n", n);
  }

  fhp->fhPriPix[y][x].priority = newPri;

  // "Bubble up".
  // If this data is more than node's parent,
  //   swap with parent and iterate.
  while (n && newPri > pqDataOfNode (fhp, PARENT(n))) {
    pqSwapNodes (fhp, n, PARENT(n));
    n = PARENT(n);
  }

  // Bubble down from the top.
  // FIXME: do we need this?
  pqBalanceNode (fhp, 0);

#if VERIFY==1
  pqVerify (fhp, fh->do_verbose);

  if (fh->debug==MagickTrue) {
    ssize_t n = pqFindNode (fhp, x, y, newPri, 0);
    printf ("  UpdIfData: n=%li\n", n);
    assert (n >= 0);
  }
#endif
}

static void UpdInsData (
  FillHoleT * fh,
  fhPriT * fhp, ssize_t x, ssize_t y, fhpValueT oldPri, fhpValueT newPri)
// Updates the priority of a pixel, or inserts if not already present.
{
  ssize_t n = pqFindNode (fhp, x, y, oldPri, 0);

  if (fh->debug==MagickTrue) printf ("UpdInsData %li,%li: %li\n", x, y, n);

  fhp->fhPriPix[y][x].priority = newPri;

  if (n < 0) {
    pqInsertNew (fhp, x, y);
  } else {
    if (fh->debug==MagickTrue) printf ("UpdInsData %g -> %g\n", oldPri, newPri);

    //assert(newPri >= oldPri);

    if (newPri >= oldPri) {
      // "Bubble up".
      // If this data is more than node's parent,
      //   swap with parent and iterate.
      while (n && newPri > pqDataOfNode (fhp, PARENT(n))) {
        pqSwapNodes (fhp, n, PARENT(n));
        n = PARENT(n);
      }
    } else {
      pqBalanceNode (fhp, n);
    }

    // Bubble down from the top.
    // FIXME: do we need this?
    pqBalanceNode (fhp, 0);

#if VERIFY==1
    pqVerify (fhp, fh->debug);
#endif
  }

  if (fh->debug==MagickTrue) {
    ssize_t n = pqFindNode (fhp, x, y, newPri, 0);
    printf ("  UpdInsData: n=%li\n", n);
    assert (n >= 0);
  }
}


//---------------------------------------------------------------------------

/* Energy of a pixel is defined as the maximum gradient between that pixel
   and an 8-connected neighbour.

   The gradient is delta(channel)/distance,
   where distance is 1 for adjacent NSEW or sqrt(2) for NW, NE, SW, SE.

   Knowing dRed, dGreen and dBlue, do we use the max of these, or RMS or what? Max, I think.

   Neighbours that are fully transparent are ignored.

   Neighbours that are partially transparent???


   NO!!! We need the energy of a transparent (unknown) pixel, as defined by its neighbours.
   Bugger.

   Maybe we could take "this pixel" to be the average of is neighbours, then calc as above.

   Or, we say above definition is for opaque pixels.
   For edge pixels, energy is the maximum energy of its opaque neighbours.
*/

/*
  We initially put only the edge pixels in the queue.
  The others have zero priority, so no need to put them in until we know the priority.
*/

#if 0
static void DumpPriPix (
  fhPriT * fhp
)
{
  ssize_t x, y;

  printf ("x,y, state, confidence, energy, priority\n");
  for (y = 0; y < fhp->height; y++) {
    for (x = 0; x < fhp->width; x++) {
      fhPriPixT * pn = &(fhp->fhPriPix[y][x]);
      printf ("%li,%li %i %g %g %g\n",
       x, y, pn->pixState, pn->confidence, pn->energy, pn->priority);
    }
  }
}
#endif


#define CALC_CE \
  if (pno->pixState == psOpaq) { \
    if (pno->confidence > 0 && confid > pno->confidence * CONF_FACT) confid = pno->confidence * CONF_FACT; \
    if (energy < pno->energy) energy = pno->energy; \
    num_opaq++; \
  }

static fhpValueT inline CalcPixConfEn (
  FillHoleT * fh,
  fhPriT * fhp,
  ssize_t x,
  ssize_t y
// Calculate pixel confidence and energy.
// Returns calculated priority (but doesn't set it).
)
{
  return 0;
}


static void inline CalcOnePixEdgeData (
  FillHoleT * fh,
  fhPriT * fhp,
  ssize_t x,
  ssize_t y
)
// Confidence of an edge pixel is the lowest confidence of 8-connected opaque neighbours,
//   multiplied by a factor.
// Energy of an edge pixel is the highest energy of 8-connected opaque neighbours.
// Priority is confidence * energy * num_opaq/8.
{
  if (fh->debug==MagickTrue) printf ("      coped %li,%li", x, y);

  fhPriPixT * pno;

  fhPriPixT * pn = &(fhp->fhPriPix[y][x]);

  fhpValueT oldPri = pn->priority;  // In case we need to update queue.

  pn->priority = 0;


  fhpValueT confid = 99;
  fhpValueT energy = 0;
  int num_opaq = 0;

  if (y > 0) {
    if (x > 0) {
      pno = &(fhp->fhPriPix[y-1][x-1]);
      CALC_CE;
    }
    fhPriPixT * pno = &(fhp->fhPriPix[y-1][x]);
    CALC_CE;
    if (x < fhp->width-1) {
      pno = &(fhp->fhPriPix[y-1][x+1]);
      CALC_CE;
    }
  }

  if (x > 0) {
    pno = &(fhp->fhPriPix[y][x-1]);
    CALC_CE;
  }
  if (x < fhp->width-1) {
    pno = &(fhp->fhPriPix[y][x+1]);
    CALC_CE;
  }

  if (y < fhp->height-1) {
    if (x > 0) {
      pno = &(fhp->fhPriPix[y+1][x-1]);
      CALC_CE;
    }
    pno = &(fhp->fhPriPix[y+1][x]);
    CALC_CE;
    if (x < fhp->width-1) {
      pno = &(fhp->fhPriPix[y+1][x+1]);
      CALC_CE;
    }
  }

  assert (num_opaq > 0);
  assert (confid > 0);

  pn->confidence = confid;
  pn->energy = energy;

  if (pn->energy==0) pn->energy=0.00001; // FIXME?

  // If this is edge pixel, we need to update queue.

  fhpValueT newPri = pn->confidence * pn->energy * num_opaq/8.0;

  if (fh->debug==MagickTrue) printf (" co=%g en=%g no=%i  ", pn->confidence, pn->energy, num_opaq);

  if (fh->debug==MagickTrue) printf (" coped %g->%g \n", oldPri, newPri);

  if (pn->pixState == psEdge) {
    if (fh->debug==MagickTrue) printf (" edge\n");
    UpdInsData (fh, fhp, x, y, oldPri, newPri);
  } else {
    if (fh->debug==MagickTrue) printf (" notEdge\n");
    //pn->priority = newPri;
    // Pixel may be in queue, even though no longer an edge.
    assert (1==0);
    if (oldPri != newPri) UpdIfData (fh, fhp, x, y, oldPri, newPri);
  }
}


static void CalcEdgeData (
  FillHoleT * fh,
  fhPriT * fhp
)
// Calculates confidence, energy and priority of all edge pixels,
// and insert them into queue.
{
  ssize_t
    x, y;

  for (y = 0; y < fhp->height; y++) {
    for (x = 0; x < fhp->width; x++) {
      fhPriPixT * pn = &(fhp->fhPriPix[y][x]);
      if (pn->pixState == psEdge) {
        if (fh->debug==MagickTrue) printf ("ced %li,%li ", x, y);
        CalcOnePixEdgeData (fh, fhp, x, y);
      }
    }
  }

#if VERIFY==1
  pqVerify (fhp, fh->debug);
#endif
}


static fhpValueT CalcEnergy (
  const Image * image,
  const VIEW_PIX_PTR * p1,
  const VIEW_PIX_PTR * p2,
  fhpValueT dist)
{
  fhpValueT
    d,
    dMax;

  dMax = fabs(GET_PIXEL_RED(image,p1) - GET_PIXEL_RED(image,p2));
  d = fabs(GET_PIXEL_GREEN(image,p1) - GET_PIXEL_GREEN(image,p2));
  if (dMax < d) dMax = d;
  d = fabs(GET_PIXEL_BLUE(image,p1) - GET_PIXEL_BLUE(image,p2));
  if (dMax < d) dMax = d;

  dMax = dMax / (QuantumRange * dist) * (GET_PIXEL_ALPHA(image,p2) / QuantumRange);

  return dMax;
}

/*===
static void CalcEdgePixelData (
  FillHoleT * fh,
  fhPriT * fhp)
// Edge pixels are exactly those in the priority queue.
{
  int i;

  for (i=0; i < fhp->pqNumNodes; i++) {
    pqNodeT * pqn = &fhp->pqNodes[i];
    CalcOnePixEdgeData (fh, fhp, pqn->x, pqn->y);
  }
}
===*/


static MagickBooleanType Initialise (const Image *image,
  FillHoleT * fh,
  fhPriT * fhp,
  ExceptionInfo *exception
)
{
  ssize_t
    x, y, xMult;

  MagickBooleanType
    status = MagickTrue;

  CacheView
    *image_view;

  fhp->width = image->columns;
  fhp->height = image->rows;

  // Allocate memory

  if (fh->do_verbose) printf ("Alloc %lix%li\n", fhp->width, fhp->height);

  fhp->pqMaxNodes = fhp->height * fhp->width;
  fhp->pqNodes = (pqNodeT *) AcquireQuantumMemory(fhp->pqMaxNodes, sizeof(pqNodeT));
  if (fhp->pqNodes == (pqNodeT *) NULL) {
    return MagickFalse;
  }

  fhp->pqNumNodes = 0;

  fhp->fhPriPix = (fhPriPixT **) AcquireQuantumMemory(fhp->height, sizeof(*fhp->fhPriPix));
  if (fhp->fhPriPix == (fhPriPixT **) NULL) {
    RelinquishMagickMemory(fhp->pqNodes);
    return MagickFalse;
  }
  for (y = 0; y < fhp->height; y++) {
    fhp->fhPriPix[y] = (fhPriPixT *) AcquireQuantumMemory(fhp->width, sizeof(**fhp->fhPriPix));
    if (fhp->fhPriPix[y] == (fhPriPixT *) NULL) break;
  }
  if (y < fhp->height) {
    for (y--; y >= 0; y--) {
      if (fhp->fhPriPix[y] != (fhPriPixT *) NULL)
        fhp->fhPriPix[y] = (fhPriPixT *) RelinquishMagickMemory(fhp->fhPriPix[y]);
    }
    fhp->fhPriPix = (fhPriPixT **) RelinquishMagickMemory(fhp->fhPriPix);
    RelinquishMagickMemory(fhp->pqNodes);
    return MagickFalse;
  }

  xMult = Inc_ViewPixPtr (image);

  // Populate values

  InitAutoLimit (fh);

  if (fh->do_verbose) printf ("Populate\n");

  image_view = AcquireVirtualCacheView(image,exception);

#if defined(MAGICKCORE_OPENMP_SUPPORT)
    #pragma omp parallel for schedule(static,4) shared(status) \
      MAGICK_THREADS(image,image,image->rows,1)
#endif
  for (y = 0; y < fhp->height; y++) {
    VIEW_PIX_PTR const
      *p, *pxy, *pup, *pdn;

    if (status == MagickFalse)
      continue;

    // We use virtual pixels for energy calculation.

    p=GetCacheViewVirtualPixels(image_view,-1,y-1,image->columns+2,3,exception);
    if (p == (const VIEW_PIX_PTR *) NULL)
      status=MagickFalse;

    // 31 Jan 2015: FIXME: Eek. Following were wrong for v7?
    pup = p + xMult;
    pxy = p + xMult * (image->columns + 3);
    pdn = p + xMult * (2*image->columns + 5);

    for (x = 0; x < fhp->width; x++) {
      fhpValueT
        e;

      //if (fh->debug==MagickTrue) printf ("Pop %i,%i ", (int)x, (int)y);

      fhPriPixT * pn = &(fhp->fhPriPix[y][x]);
      pn->confidence = GET_PIXEL_ALPHA(image,pxy)/QuantumRange;

      pn->energy = CalcEnergy (image, pxy, pup-xMult, M_SQRT2);
      e = CalcEnergy (image, pxy, pup, 1);
      if (pn->energy < e) pn->energy = e;
      e = CalcEnergy (image, pxy, pup+xMult, M_SQRT2);
      if (pn->energy < e) pn->energy = e;

      e = CalcEnergy (image, pxy, pxy-xMult, 1);
      if (pn->energy < e) pn->energy = e;
      e = CalcEnergy (image, pxy, pxy+xMult, 1);
      if (pn->energy < e) pn->energy = e;

      e = CalcEnergy (image, pxy, pdn-xMult, M_SQRT2);
      if (pn->energy < e) pn->energy = e;
      e = CalcEnergy (image, pxy, pdn, 1);
      if (pn->energy < e) pn->energy = e;
      e = CalcEnergy (image, pxy, pdn+xMult, M_SQRT2);
      if (pn->energy < e) pn->energy = e;

      //if (fh->debug==MagickTrue) printf (" e=%g\n", pn->energy);

      if (pn->confidence <= 0) {
        // FIXME? If any 8-connected neighbour is opaque, this is an edge.
        if (GET_PIXEL_ALPHA(image,pup-xMult)>0
         || GET_PIXEL_ALPHA(image,pup)>0
         || GET_PIXEL_ALPHA(image,pup+xMult)>0
         || GET_PIXEL_ALPHA(image,pxy-xMult)>0
         || GET_PIXEL_ALPHA(image,pxy+xMult)>0
         || GET_PIXEL_ALPHA(image,pdn-xMult)>0
         || GET_PIXEL_ALPHA(image,pdn)>0
         || GET_PIXEL_ALPHA(image,pdn+xMult)>0)
        {
          pn->pixState = psEdge;
          fh->Match.nLimitCnt++;
        } else {
          pn->pixState = psInner;
        }

      } else {
        pn->pixState = psOpaq;
      }
      if (pn->confidence == 0 ) pn->energy = 0;
      pn->priority = 0; // Until we know better.

      // We can't find data for edge pixels until we know data for all edge neighbours.

      // FIXME? following were wrong for v7.
      p   += xMult;
      pup += xMult;
      pxy += xMult;
      pdn += xMult;
    }
  }

  if (fh->debug==MagickTrue) printf ("fh->Match.nLimitCnt=%li\n", fh->Match.nLimitCnt);

  image_view=DestroyCacheView(image_view);

  pqVerify (fhp, fh->debug);

  CalcEdgeData (fh, fhp);

  //if (fh->debug==MagickTrue) pqDump (fh, fhp);
  pqVerify (fhp, fh->debug);
  //if (fh->debug==MagickTrue) DumpPriPix (fhp);

  if (fh->do_verbose) printf ("End populate\n");

  return (status);
}


static void deInitialise(
  FillHoleT * fh,
  fhPriT * fhp
)
{
  ssize_t
    y;

  for (y = 0; y < fhp->height; y++) {
    fhp->fhPriPix[y] = (fhPriPixT *) RelinquishMagickMemory(fhp->fhPriPix[y]);
  }
  fhp->fhPriPix = (fhPriPixT **) RelinquishMagickMemory(fhp->fhPriPix);

  RelinquishMagickMemory(fhp->pqNodes);
}

static MagickBooleanType inline IsStateOpaque (
  fhPriT * fhp,
  ssize_t x,
  ssize_t y)
{
  if (x >= 0 && x < fhp->width &&
      y >= 0 && y < fhp->height)
  {
    return fhp->fhPriPix[y][x].pixState == psOpaq;
  }
  return MagickFalse;
}

static void inline MakeEdgePix (
  FillHoleT * fh,
  fhPriT * fhp,
  ssize_t x,
  ssize_t y)
// If not outside image, and inner, set to edge and calc priority and add to queue.
// If it's already an edge, re-calc data and update queue,
{
  if (fh->debug==MagickTrue) printf ("  mep %li,%li\n", x, y);

  if (x >= 0 && x < fhp->width &&
      y >= 0 && y < fhp->height)
  {
    fhPriPixT * pn = &(fhp->fhPriPix[y][x]);
    if (pn->pixState == psInner) {
      // FIXME: only if 4-connected neighbours are opaque?

      if (
          IsStateOpaque (fhp, x,   y-1) ||
          IsStateOpaque (fhp, x-1, y)   ||
          IsStateOpaque (fhp, x+1, y)   ||
          IsStateOpaque (fhp, x,   y+1)
       /*=== ||
          IsStateOpaque (fhp, x-1, y-1) ||
          IsStateOpaque (fhp, x+1, y-1) ||
          IsStateOpaque (fhp, x-1, y+1) ||
          IsStateOpaque (fhp, x+1, y+1) ===*/
         )
      {
        if (fh->debug==MagickTrue) printf ("    mepI %li,%li\n", x, y);

        pn->pixState = psEdge;

        CalcOnePixEdgeData (fh, fhp, x, y);

        // FIXME: again, batch entry probably quicker.
      } else {
        if (fh->debug==MagickTrue) printf ("    mepU %li,%li no_opaque_neighbours", x, y);
      }
    } else if (pn->pixState == psEdge) {
      if (fh->debug==MagickTrue) printf ("    mepU %li,%li already_edge", x, y);

      CalcOnePixEdgeData (fh, fhp, x, y);
    }
  }
}


static MagickBooleanType inline IsPixelOpaque (
  Image * image,
  CacheView * view,
  ssize_t x,
  ssize_t y,
  ExceptionInfo *exception)
{
  const VIEW_PIX_PTR
    *src;

  src = GetCacheViewVirtualPixels(view,x,y,1,1,exception);
  if (src == (const VIEW_PIX_PTR *) NULL) {
    printf ("bad src");
    return MagickFalse;
  }

  return (GET_PIXEL_ALPHA (image, src) > 0);
}


static fhpValueT inline PixelEnergy (
  Image * image,
  CacheView * view,
  ssize_t x,
  ssize_t y,
  ExceptionInfo *exception)
{
  const VIEW_PIX_PTR
    *src,
    *this,
    *other;

  int
    i;

  src = GetCacheViewVirtualPixels(view,x-1,y-1,3,3,exception);
  if (src == (const VIEW_PIX_PTR *) NULL) { printf ("bad src"); return MagickFalse; }

  // FIXME: for v7

  ssize_t xMult = Inc_ViewPixPtr (image);

  other = src;
  this = src + xMult*4;
  fhpValueT e = 0;

  for (i = 0; i < 9; i++) {
    if (other != this && GET_PIXEL_ALPHA (image, other) > 0) {
      fhpValueT v = CalcEnergy (image, this, other,
        (i==0 || i==2 || i==6 || i==8) ? M_SQRT2 : 1);
      if (e < v) e = v;
    }
    other += Inc_ViewPixPtr (image);
  }

  return (e);
}


static MagickBooleanType ProcessPixels (
  Image *image,
  Image *new_image,
  FillHoleT * fh,
  fhPriT * fhp,
  MagickBooleanType *ChangedSome,
  MagickBooleanType *unfilled,
  ExceptionInfo *exception)
{
  Image
    *holed_image;

  CacheView
    *copy_inp_view,
    *new_view;

  ssize_t
    x, y;

  int
    GotOne,
    frameNum = 0;

  MagickBooleanType
    changedAny,
    status=MagickTrue;

  CopyWhereT
    cw;

  if (fh->copyWhat == copyWindow) {
    cw.wi = fh->sqCopyDim;
    cw.ht = fh->sqCopyDim;
  } else {
    cw.wi = 1;
    cw.ht = 1;
  }

  // Clone the image, same size, copied pixels:
  //
  holed_image=CloneImage(image, 0, 0, MagickTrue, exception);
  if (holed_image == (Image *) NULL)
    return MagickFalse;

  if (SetNoPalette (holed_image, exception) == MagickFalse)
    return MagickFalse;

  fh->CompWind.ref_image = image;
  fh->CompWind.ref_view = AcquireVirtualCacheView (image, exception);

  new_view        = AcquireAuthenticCacheView (new_image, exception);
  fh->CompWind.sub_image = new_image;
  fh->CompWind.sub_view = new_view;
  copy_inp_view   = CloneCacheView (fh->CompWind.ref_view);

  *ChangedSome = MagickFalse;
  changedAny = MagickFalse;
  *unfilled = MagickFalse;

//#if DEBUG==1
  if (fh->debug==MagickTrue) printf ("ProcessPixels: start do\n");
//#endif

  do {
    GotOne = pqRemoveMax (fhp, &x, &y);

    if (fh->debug==MagickTrue) printf ("GotOne? %i\n", GotOne);

    if (GotOne)
    {
      assert (fhp->fhPriPix[y][x].pixState == psEdge);

      if (fh->debug==MagickTrue) printf ("pp %li,%li\n", x, y);

      changedAny = MagickFalse;

      fh->Match.nLimitCnt--;

      status = MatchAndCopy (
        fh,
        new_image,
        new_view,
        copy_inp_view,
        x,
        y,
        &cw,
        &frameNum,
        unfilled,
        &changedAny,
        exception);

      if (!changedAny) {
        if (fh->debug==MagickTrue) printf ("  Can't change %li,%li r=%i\n", x, y, (int)fh->CompWind.Reason);

        fhp->fhPriPix[y][x].pixState = psCantFill;
      } else {
        ssize_t i, j;

        if (fh->debug==MagickTrue)
          printf (" doneMaC st=%i  %lix%li+%li+%li\n", status, cw.wi, cw.ht, cw.dstX, cw.dstY);

        *ChangedSome = MagickTrue;

        // We may not have changed them all.
        MagickBooleanType OpaqAny = MagickFalse;
        if (fh->debug==MagickTrue) printf ("toOpaq ");

        assert (cw.dstX >= 0 && cw.dstX+cw.wi <= fh->Width);
        assert (cw.dstY >= 0 && cw.dstY+cw.ht <= fh->Height);

        fhpValueT conf = 99;

        for (j=0; j < cw.ht; j++) {
          ssize_t adjY = cw.dstY+j;
          for (i=0; i < cw.wi; i++) {
            ssize_t adjX = cw.dstX+i;
            fhPriPixT * pn = &(fhp->fhPriPix[adjY][adjX]);
            if (pn->pixState != psOpaq) {
              if (IsPixelOpaque (new_image, new_view, adjX, adjY, exception)) {
                if (fh->debug==MagickTrue) printf (" %li,%li\n", adjX, adjY);
                if (pn->pixState == psEdge && (adjX != x || adjY != y)) {
                  if (pqDelNode (fhp, adjX, adjY) < 0) status=MagickFalse;
                  fh->Match.nLimitCnt--;
                }
                pn->pixState = psOpaq; // FIXME: or "filled"?
                if (pn->confidence > 0 && conf > pn->confidence) conf = pn->confidence;
                // We will also update energy, at least of pixels on edge of copied block.
                // We can only do this after we know which neighbours are opaque.
                OpaqAny = MagickTrue;
              }
            }
          }
        }

        if (fh->debug==MagickTrue) printf ("done toOpaq\n");

        if (conf == 99) conf = 0.001;
        else conf *= CONF_FACT;

        if (OpaqAny) {
          // Calc energies of pixels on edge of copied block.
          // FIXME: only for cw.wi>1 or cw.ht>1?
          // Possible optimisation: If a pixel is surrounded by opaque pixels,
          // we will never care what energy it has.
          if (fh->debug==MagickTrue) printf ("calcE\n");
          for (i=0; i < cw.wi; i++) {
            // Top
            fhPriPixT * pn = &(fhp->fhPriPix[cw.dstY][cw.dstX+i]);
            pn->energy = PixelEnergy (new_image, new_view, cw.dstX+i, cw.dstY, exception);
            if (pn->confidence==0) pn->confidence = conf;
            if (cw.ht > 1) {
              // Bottom
              pn = &(fhp->fhPriPix[cw.dstY+cw.ht-1][cw.dstX+i]);
              pn->energy = PixelEnergy (new_image, new_view, cw.dstX+i, cw.dstY+cw.ht-1, exception);
              if (pn->confidence==0) pn->confidence = conf;
            }
          }
          for (j=1; j < cw.ht-1; j++) {
            // Left
            fhPriPixT * pn = &(fhp->fhPriPix[cw.dstY+j][cw.dstX]);
            pn->energy = PixelEnergy (new_image, new_view, cw.dstX, cw.dstY+j, exception);
            if (pn->confidence==0) pn->confidence = conf;
            if (cw.wi > 1) {
              // Right
              pn = &(fhp->fhPriPix[cw.dstY+j][cw.dstX+cw.wi-1]);
              pn->energy = PixelEnergy (new_image, new_view, cw.dstX+cw.wi-1, cw.dstY+j, exception);
              if (pn->confidence==0) pn->confidence = conf;
            }
          }

          // Walk around border outside the copied pixels.
          // If not outside image, and inner, set to edge and calc priority and add to queue.
          // But if we haven't changed them all, some of these may not really be edges.
          if (fh->debug==MagickTrue) printf ("\nWalk border from %li,%li\n", cw.dstX-1, cw.dstY-1);
          for (i=0; i < cw.wi+2; i++) {
            MakeEdgePix (fh, fhp, cw.dstX-1+i, cw.dstY-1);
            MakeEdgePix (fh, fhp, cw.dstX-1+i, cw.dstY+cw.ht);
          }
          for (j=0; j < cw.ht; j++) {
            MakeEdgePix (fh, fhp, cw.dstX-1, cw.dstY+j);
            MakeEdgePix (fh, fhp, cw.dstX+cw.wi, cw.dstY+j);
          }
          if (fh->debug==MagickTrue) printf ("End walk border\n");
          // FIXME: should we also update data on pixels that are already edges, updating queue?
          // I think so.
        }
      }
      if (fh->debug==MagickTrue) printf ("\n");
    }

    //if (fh->Match.SetAutoLimit) printf ("fh->Match.nLimitCnt=%li\n", fh->Match.nLimitCnt);

    if (fh->Match.SetAutoLimit && fh->Match.nLimitCnt <= 0) UseAutoLimit (fh);

  } while (GotOne==1 && status==MagickTrue);

#if DEBUG==1
  printf ("ProcessPixels: done do\n");
#endif

  if (status==MagickFalse) printf ("** Bug: ProcessPixels: status==MagickFalse\n");

  if (*unfilled) printf ("** Some pixels are not filled\n");

  copy_inp_view   = DestroyCacheView (copy_inp_view);
  new_view        = DestroyCacheView (new_view);

  fh->CompWind.ref_view = DestroyCacheView (fh->CompWind.ref_view);

//  srch_holed_view = DestroyCacheView (srch_holed_view);
//  inp_view        = DestroyCacheView (inp_view);

  if (fh->debug==MagickTrue) printf ("unfilled=%i\n", *unfilled);

  return status;
}


// The next function corresponds in style to functions in transform.c
// It takes one image, and returns an image with filled holes.
//
static Image *fillholespri (
  Image *image,
  FillHoleT * fh,
  ExceptionInfo *exception)
{
  Image
    *new_image;

  fhPriT
    fhPri;

  MagickBooleanType
    ChangedSome,
    unfilled;

  ResolveImageParams (image, fh);

  if (fh->debug==MagickTrue) printf ("sizeof(fhPriPixT)=%li sizeof(pqNodeT)=%li\n",
    sizeof(fhPriPixT), sizeof(pqNodeT));

  if (fh->debug==MagickTrue) printf ("Initialise ...");
  if (Initialise (image, fh, &fhPri, exception) == MagickFalse)
    return (Image *) NULL;

  // Clone the image, same size, copied pixels:
  //
  new_image=CloneImage(image, 0, 0, MagickTrue, exception);
  if (new_image == (Image *) NULL)
    return(new_image);

  if (fh->debug==MagickTrue) printf ("Process pixels ...");
  ProcessPixels (image, new_image, fh, &fhPri, &ChangedSome, &unfilled, exception);

  if (fh->debug==MagickTrue) printf ("Deinitialise ...\n");
  deInitialise (fh, &fhPri);

  while (unfilled && ChangedSome && fh->AutoRepeat) {
    if (fh->do_verbose) printf ("fillholespri: doing it again.\n");

    Image *tmp_copy = CloneImage(new_image, 0, 0, MagickTrue, exception);
    if (tmp_copy == (Image *) NULL) return(tmp_copy);

    if (Initialise (tmp_copy, fh, &fhPri, exception) == MagickFalse)
      return (Image *) NULL;

    ProcessPixels (tmp_copy, new_image, fh, &fhPri, &ChangedSome, &unfilled, exception);

    tmp_copy = DestroyImage (tmp_copy);

    if (fh->debug==MagickTrue) printf ("Deinitialise ...\n");
    deInitialise (fh, &fhPri);

  }

  return (new_image);
}

ModuleExport size_t fillholespriImage (
  Image **images,
  const int argc,
  const char **argv,
  ExceptionInfo *exception)
{
  Image
    *image,
    *new_image;

  MagickBooleanType
    status;

  FillHoleT
    fh;

  assert(images != (Image **) NULL);
  assert(*images != (Image *) NULL);
  assert((*images)->signature == MAGICK_CORE_SIG);

  status = menu (argc, argv, &fh);
  if (status == MagickFalse)
    return (-1);

  InitRand (&fh);

  for (image=(*images); image != (Image *) NULL; image=GetNextImageInList(image))
  {
    new_image = fillholespri (image, &fh, exception);

    ReplaceImageInList(&image,new_image);
    *images=GetFirstImageInList(image);
  }

  DeInitRand (&fh);

  return(MagickImageFilterSignature);
}

fillholescommon.inc

/*
    Reference: http://im.snibgo.com/fillholes.htm

    Data structures and functions common to fillholes process modules.

    Created 21-Nov-2015
*/

#define CH_R 1
#define CH_G 2
#define CH_B 4

#include "compwind.h"
#include "match.h"


typedef enum {
  copyOnePixel,
  copyWindow
} CopyWhatT;

typedef struct {
  CompWindT
    CompWind;

  MatchT
    Match;

  /* Set by user. */
  MagickBooleanType
    do_verbose,
    AutoRepeat,
    CopyRad_IsPc,
    WindowRad_IsPc,
    debug;

  double
    WindowRad,
    CopyRad,
    ImgDiagSq;

  int
    WindowRadPix,
    CopyRadPix,
    write_frames;

  char
    write_filename[MaxTextExtent];

  CopyWhatT
    copyWhat;

  /* Calculated. */
  int
    sqDim,
    sqCopyDim;

  ssize_t
    Width,
    Height;

  RandomInfo
    *restrict random_info;

} FillHoleT;

typedef struct {
  ssize_t
    srcX,
    srcY,
    dstX,
    dstY,
    wi,
    ht;
} CopyWhereT;

#include "compwind.inc"
#include "match.inc"


static void usage (void)
{
  printf ("Usage: -process 'fillhole [OPTION]...'\n");
  printf ("Populates transparent pixels, InFill.\n");
  printf ("\n");
  printf ("  wr,  window_radius N        radius of search window, >= 1\n");
  printf ("  lsr, limit_search_radius N  limit radius from transparent pixel to search\n");
  printf ("                                for source, >= 0\n");
  printf ("                                default = 0 = no limit\n");
  printf ("  als,  auto_limit_search X   automatic limit search, on or off\n");
  printf ("                                default on\n");
  printf ("  hc,  hom_chk X              homogeneity check, X is off or a small number\n");
  printf ("                                default 0.1\n");
  printf ("  e,   search_to_edges X      search for matches to image edges, on or off\n");
  printf ("                                default on\n");
  printf ("  s,   search X               X=entire or random or skip\n");
  printf ("                                default entire\n");
  printf ("  rs,  rand_searches N        number of random searches (eg 100)\n");
  printf ("                                default 0\n");
  printf ("  sn,  skip_num N             number of searches to skip in each direction\n");
  printf ("                                (eg 10)\n");
  printf ("                                default 0\n");
  printf ("  ref, refine X               whether to refine random and skip searches,\n");
  printf ("                                on or off\n");
  printf ("                                default on\n");
  printf ("  st,  similarity_threshold N\n");
  printf ("                              stop searching when RMSE <= N (eg 0.01)\n");
  printf ("                                default 0\n");
  printf ("  dt,  dissimilarity_threshold N\n");
  printf ("                              don't copy if best RMSE >= N (eg 0.05)\n");
  printf ("                                default: no threshold\n");
  printf ("  cp,  copy X                 X=onepixel or window\n");
  printf ("  cr,  copy_radius N          radius of pixels to copy, >= 1, <= wr\n");
  printf ("                                default: 0 or wr\n");
  printf ("  a,   auto_repeat            if pixels changed but any unfilled, repeat\n");
  printf ("  w,   write filename         write frames to files\n");
  printf ("  w2,  write2 filename        write frames to files\n");
  printf ("  v,   verbose                 write text information to stdout\n");
  printf ("\n"); 
}


static MagickBooleanType IsArg (char * pa, char * ShtOpt, char * LongOpt)
{
  if ((LocaleCompare(pa, ShtOpt)==0) || (LocaleCompare(pa, LongOpt)==0))
    return MagickTrue;

  return MagickFalse;
}


static inline MagickBooleanType EndsPc (const char *s)
{
  char c = *(s+strlen(s)-1);
  if (c == '%' || c == 'c')
    return MagickTrue;
  return MagickFalse;
}


static MagickBooleanType menu(
  const int argc,
  const char **argv,
  FillHoleT * pfh
)
// Returns MagickTrue if okay.
{
  int
    i;

  MagickBooleanType
    status;

  status = MagickTrue;

  pfh->do_verbose = MagickFalse;
  pfh->WindowRad = 1.0;
  pfh->CopyRad = 0.0;
  pfh->WindowRad_IsPc = pfh->CopyRad_IsPc = MagickFalse;
  pfh->WindowRadPix = 1;
  pfh->CopyRadPix = 0;
  pfh->write_frames = 0;
  pfh->write_filename[0] = '\0';
  pfh->copyWhat = copyOnePixel;
  pfh->debug = MagickFalse;
  pfh->AutoRepeat = MagickFalse;

  pfh->Match.LimSrchRad = 0.0;
  SetDefaultMatch (&pfh->Match);

  pfh->CompWind.HomChkOn = MagickTrue;
  pfh->CompWind.HomChk = 0.1;
  pfh->CompWind.nCompares = 0;


  for (i=0; i < argc; i++) {
    char * pa = (char *)argv[i];

    if (IsArg (pa, "wr", "window_radius")==MagickTrue) {
      i++;
      if (!isdigit ((int)*argv[i]))
      {
        fprintf (stderr, "Non-numeric argument to 'window_radius'.\n");
        status = MagickFalse;
      }
      else {
        pfh->WindowRad = atof(argv[i]);
        pfh->WindowRad_IsPc = EndsPc (argv[i]);
        if (!pfh->WindowRad_IsPc)
          pfh->WindowRadPix = pfh->WindowRad + 0.5;
      }
      if (pfh->WindowRad <= 0) {
        fprintf (stderr, "Bad 'window_radius' value.\n");
        status = MagickFalse;
      }
    } else if (IsArg (pa, "cr", "copy_radius")==MagickTrue) {
      i++;
      if (!isdigit ((int)*argv[i]))
      {
        fprintf (stderr, "Non-numeric argument to 'copy_radius'.\n");
        status = MagickFalse;
      }
      else {
        pfh->CopyRad = atof(argv[i]);
        pfh->CopyRad_IsPc = EndsPc (argv[i]);
        if (!pfh->CopyRad_IsPc)
          pfh->CopyRadPix = pfh->CopyRad + 0.5;
      }
      if (pfh->CopyRad <= 0) {
        fprintf (stderr, "Bad 'copy_radius' value.\n");
        status = MagickFalse;
      }
    } else if (IsArg (pa, "lsr", "limit_search_radius")==MagickTrue) {
      // FIXME: also allow percentage of smaller image dimension.
      i++;
      if (!isdigit ((int)*argv[i])) {
        fprintf (stderr, "Non-numeric argument to 'limit_search_radius'.\n");
        status = MagickFalse;
      } else {
        pfh->Match.LimSrchRad = atof(argv[i]);
        pfh->Match.LimSrchRad_IsPc = EndsPc (argv[i]);
        if (!pfh->Match.LimSrchRad_IsPc)
          pfh->Match.limSrchRadX = pfh->Match.limSrchRadY = pfh->Match.LimSrchRad + 0.5;
      }
      if (pfh->Match.LimSrchRad < 0) {
        fprintf (stderr, "Bad 'limit_search_radius' value.\n");
        status = MagickFalse;
      }

/*---
    } else if (IsArg (pa, "c", "channel")==MagickTrue) {
      i++;
      pfh->channels = 0;
      const char * p = argv[i];
      while (*p) {
        switch (toupper ((int)*p)) {
          case 'R':
            pfh->channels |= CH_R;
            break;
          case 'G':
            pfh->channels |= CH_G;
            break;
          case 'B':
            pfh->channels |= CH_B;
            break;
          case 'L':
            pfh->channels |= CH_R;
            break;
          case 'A':
            pfh->channels |= CH_G;
            break;
          default:
            fprintf (stderr, "Invalid 'channels' [%s]\n", argv[i]);
            status = MagickFalse;
        }
        p++;
      }
---*/
    } else if (IsArg (pa, "als", "auto_limit_search")==MagickTrue) {
      i++;
      if (LocaleCompare(argv[i], "on")==0)
        pfh->Match.AutoLs = MagickTrue;
      else if (LocaleCompare(argv[i], "off")==0)
        pfh->Match.AutoLs = MagickFalse;
      else {
        fprintf (stderr, "Invalid 'auto_limit_search' [%s]\n", argv[i]);
        status = MagickFalse;
      }
    } else if (IsArg (pa, "ref", "refine")==MagickTrue) {
      i++;
      if (LocaleCompare(argv[i], "on")==0)
        pfh->Match.Refine = MagickTrue;
      else if (LocaleCompare(argv[i], "off")==0)
        pfh->Match.Refine = MagickFalse;
      else {
        fprintf (stderr, "Invalid 'refine' [%s]\n", argv[i]);
        status = MagickFalse;
      }
    } else if (IsArg (pa, "s", "search")==MagickTrue) {
      i++;
      if (LocaleCompare(argv[i], "entire")==0)
        pfh->Match.searchWhat = swEntire;
      else if (LocaleCompare(argv[i], "random")==0)
        pfh->Match.searchWhat = swRandom;
      else if (LocaleCompare(argv[i], "skip")==0)
        pfh->Match.searchWhat = swSkip;
      else {
        fprintf (stderr, "Invalid 'search' [%s]\n", argv[i]);
        status = MagickFalse;
      }
    } else if (IsArg (pa, "rs", "rand_searches")==MagickTrue) {
      i++;
      if (!isdigit ((int)*argv[i])) {
        fprintf (stderr, "Non-numeric argument to 'rand_searches'.\n");
        status = MagickFalse;
      } else {
        pfh->Match.RandSearchesF = atof (argv[i]);
        pfh->Match.RandSearches_IsPc = EndsPc (argv[i]);
        if (!pfh->Match.RandSearches_IsPc)
          pfh->Match.RandSearchesI = pfh->Match.RandSearchesF + 0.5;
      }
      if (pfh->Match.RandSearchesF <= 0) {
        fprintf (stderr, "Bad 'rand_searches' value.\n");
        status = MagickFalse;
      }
    } else if (IsArg (pa, "sn", "skip_num")==MagickTrue) {
      i++;
      if (!isdigit ((int)*argv[i])) {
        fprintf (stderr, "Non-numeric argument to 'skip_num'.\n");
        status = MagickFalse;
      } else {
        pfh->Match.SkipNumF = atof (argv[i]);
        pfh->Match.SkipNum_IsPc = EndsPc (argv[i]);
        if (!pfh->Match.SkipNum_IsPc)
          pfh->Match.SkipNumI = pfh->Match.SkipNumF + 0.5;
      }
      if (pfh->Match.SkipNumF <= 0) {
        fprintf (stderr, "Bad 'skip_num' value.\n");
        status = MagickFalse;
      }
    } else if (IsArg (pa, "hc", "hom_chk")==MagickTrue) {
      i++;
      if (LocaleCompare(argv[i], "off")==0) {
        pfh->CompWind.HomChkOn = MagickFalse;
      } else {
        pfh->CompWind.HomChkOn = MagickTrue;
        if (!isdigit ((int)*argv[i])) {
          fprintf (stderr, "'hom_chk' argument must be number or 'off'.\n");
          status = MagickFalse;
        } else {
          pfh->CompWind.HomChk = atof (argv[i]);
        }
      }
    } else if (IsArg (pa, "e", "search_to_edges")==MagickTrue) {
      i++;
      if (LocaleCompare(argv[i], "on")==0)
        pfh->Match.SearchToEdges = MagickTrue;
      else if (LocaleCompare(argv[i], "off")==0)
        pfh->Match.SearchToEdges = MagickFalse;
      else {
        fprintf (stderr, "Invalid 'search_to_edges' [%s]\n", argv[i]);
        status = MagickFalse;
      }
    } else if (IsArg (pa, "a", "auto_repeat")==MagickTrue) {
      pfh->AutoRepeat = MagickTrue;
    } else if (IsArg (pa, "cp", "copy")==MagickTrue) {
      i++;
      if (LocaleCompare(argv[i], "onepixel")==0)
        pfh->copyWhat = copyOnePixel;
      else if (LocaleCompare(argv[i], "window")==0)
        pfh->copyWhat = copyWindow;
      else {
        fprintf (stderr, "Invalid 'copy' [%s]\n", argv[i]);
        status = MagickFalse;
      }
    } else if (IsArg (pa, "st", "similarity_threshold")==MagickTrue) {
      i++;
      pfh->Match.simThreshold = atof (argv[i]);
    } else if (IsArg (pa, "dt", "dissimilarity_threshold")==MagickTrue) {
      i++;
      pfh->Match.DissimOn = MagickTrue;
      pfh->Match.dissimThreshold = atof (argv[i]);
    } else if (IsArg (pa, "w", "write")==MagickTrue) {
      pfh->write_frames = 1;
      i++;
      CopyMagickString (pfh->write_filename, argv[i], MaxTextExtent);
      if (!*pfh->write_filename) {
        fprintf (stderr, "Invalid 'write' [%s]\n", argv[i]);
        status = MagickFalse;
      }
    } else if (IsArg (pa, "w2", "write2")==MagickTrue) {
      pfh->write_frames = 2;
      i++;
      CopyMagickString (pfh->write_filename, argv[i], MaxTextExtent);
      if (!*pfh->write_filename) {
        fprintf (stderr, "Invalid 'write' [%s]\n", argv[i]);
        status = MagickFalse;
      }
    } else if (IsArg (pa, "v", "verbose")==MagickTrue) {
      pfh->do_verbose = MagickTrue;
    } else if (IsArg (pa, "d", "debug")==MagickTrue) {
      pfh->debug = MagickTrue;
    } else {
      fprintf (stderr, "fillhole: ERROR: unknown option [%s]\n", pa);
      status = MagickFalse;
    }
  }

/*
  pfh->num_chan = 0;
  if (pfh->channels & CH_R) pfh->num_chan++;
  if (pfh->channels & CH_G) pfh->num_chan++;
  if (pfh->channels & CH_B) pfh->num_chan++;
  if (pfh->channels == 0) {
    fprintf (stderr, "No channels\n");
    status = MagickFalse;
  }
*/


  if (pfh->CopyRadPix==0 || pfh->CopyRadPix > pfh->WindowRadPix) {
    pfh->CopyRadPix = pfh->WindowRadPix;
  }
  if (pfh->copyWhat==copyOnePixel) pfh->CopyRadPix=0;

  pfh->Match.simThresholdSq = pfh->Match.simThreshold * pfh->Match.simThreshold;
  pfh->Match.dissimThresholdSq = pfh->Match.dissimThreshold * pfh->Match.dissimThreshold;

  pfh->CompWind.win_wi = pfh->sqDim;
  pfh->CompWind.win_ht = pfh->sqDim;

  if (pfh->do_verbose) {
    fprintf (stderr, "fillhole options: ");

    fprintf (stderr, "  window_radius %g", pfh->WindowRad);
    if (pfh->WindowRad_IsPc) fprintf (stderr, "%c", '%');

    fprintf (stderr, "  limit_search_radius %g", pfh->Match.LimSrchRad);
    if (pfh->Match.LimSrchRad_IsPc) fprintf (stderr, "%c", '%');

    fprintf (stderr, "  auto_lsr %s", pfh->Match.AutoLs ? "on" : "off");

    fprintf (stderr, "  search");
    switch (pfh->Match.searchWhat) {
      case swEntire: fprintf (stderr, " entire"); break;
      case swSkip:
        fprintf (stderr, " skip  skip_num %g", pfh->Match.SkipNumF);
        if (pfh->Match.SkipNum_IsPc) fprintf (stderr, "%c", '%');
        break;
      case swRandom:
        fprintf (stderr, " random  rand_searches %g", pfh->Match.RandSearchesF);
        if (pfh->Match.RandSearches_IsPc) fprintf (stderr, "%c", '%');
        break;
      default: fprintf (stderr, " ??");
    }

    fprintf (stderr, "  hom_chk ");
    if (pfh->CompWind.HomChkOn) {
      fprintf (stderr, "%g", pfh->CompWind.HomChk);
    } else {
      fprintf (stderr, "off");
    }

/*-
    if (pfh->max_iter) fprintf (stderr, " max_iterations %i", pfh->max_iter);

    if (pfh->channels != (CH_R | CH_G | CH_B)) {
      fprintf (stderr, " channels ");
      if (pfh->channels & CH_R) fprintf (stderr, "R");
      if (pfh->channels & CH_G) fprintf (stderr, "G");
      if (pfh->channels & CH_B) fprintf (stderr, "B");
    }
-*/

    fprintf (stderr, "  refine %s", pfh->Match.Refine ? "on" : "off");

    if (!pfh->Match.SearchToEdges) fprintf (stderr, "  search_to_edges off");

    fprintf (stderr, "  similarity_threshold %g", pfh->Match.simThreshold);

    if (pfh->Match.DissimOn == MagickTrue)
      fprintf (stderr, "  dissimilarity_threshold %g", pfh->Match.dissimThreshold);

    if (pfh->AutoRepeat) fprintf (stderr, "  auto_repeat");

    fprintf (stderr, "  copy ");
    if (pfh->copyWhat==copyOnePixel) fprintf (stderr, "onepixel");
    else if (pfh->copyWhat==copyWindow) fprintf (stderr, "window");
    else fprintf (stderr, "??");

    if (pfh->CopyRadPix != pfh->WindowRadPix) {
      fprintf (stderr, "  copy_radius %g", pfh->CopyRad);
      if (pfh->CopyRad_IsPc) fprintf (stderr, "%c", '%');
    }

    if (pfh->debug) fprintf (stderr, "  debug");

    if (pfh->do_verbose) fprintf (stderr, "  verbose");

    fprintf (stderr, "\n");
  }

  if (status == MagickFalse)
    usage ();

  return (status);
}



static MagickStatusType WriteFrame (
  FillHoleT *pfh,
  Image * img,
  int frame_num,
  ExceptionInfo *exception)
{
  ImageInfo
    *ii;

  MagickStatusType
    status;

  Image
    *copy_img;

  ii = AcquireImageInfo ();

  copy_img = CloneImage(img, 0, 0, MagickTrue, exception);
  if (copy_img == (Image *) NULL)
    return(MagickFalse); // FIXME: raise error

  copy_img->scene = frame_num;

  CopyMagickString (copy_img->filename, pfh->write_filename, MaxTextExtent);

#if IMV6OR7==6
  status = WriteImage (ii, copy_img);
#else
  status = WriteImage (ii, copy_img, exception);
#endif

  DestroyImageList(copy_img);

  ii = DestroyImageInfo (ii);

  return status;
}


/*===
static double CompareWindow (
  FillHoleT * pfh,
  CacheView * inp_view,
  CacheView * subimage_view,
  ExceptionInfo *exception,
  ssize_t hx, ssize_t hy,
  ssize_t sx, ssize_t sy)
// Compares subimage window with hole starting at top-left=hx,hy
// with window in inp_view (which may contain hole) starting at top-left=sx, sy.
// Returns positive number MSE; closer to zero is closer match.
// If sx,sy has same number or more transparent pixels as hx,hy,
//   or either window is entirely transparent,
//   returns very large number.
{
  ssize_t
    xy;

  const VIEW_PIX_PTR
    *sxy, *sxySave,
    *hxy, *hxySave;

  VIEW_PIX_PTR
    minSub,
    maxSub;

  double
    score = 0;

  int
    cntNonTrans = 0;

  //if (pfh->debug==MagickTrue) printf ("CompareWindow %i %i  %i %i  ", (int)hx, (int)hy, (int)sx, (int)sy);

  if (sx==hx && sy==hy)
    return pfh->CompWind.WorstCompare;

  sxy = GetCacheViewVirtualPixels(inp_view,sx,sy,pfh->sqDim,pfh->sqDim,exception);
  if (sxy == (const VIEW_PIX_PTR *) NULL) {
    printf ("bad sxy");
    return pfh->CompWind.WorstCompare;
  }
  // If central pixel in sxy is transparent, forget about it.
  if (GetPixelAlpha (sxy + pfh->WindowRadPix*(pfh->sqDim+1)) <= 0)
    return pfh->CompWind.WorstCompare;

  hxy = GetCacheViewVirtualPixels(subimage_view,hx,hy,pfh->sqDim,pfh->sqDim,exception);
  if (hxy == (const VIEW_PIX_PTR *) NULL) {
    printf ("bad hxy");
    return pfh->CompWind.WorstCompare;
  }

  sxySave = sxy;
  hxySave = hxy;
  instantPp (&minSub);
  instantPp (&maxSub);

  MagickBooleanType InitMinMax = MagickFalse;
  for (xy=0; xy < pfh->sqDim * pfh->sqDim; xy++) {
    double sa = GetPixelAlpha (sxy);
    double ha = GetPixelAlpha (hxy);

    if (sa <= 0 && ha > 0) return pfh->CompWind.WorstCompare;

    if (sa > 0 && ha > 0) {
      double d = (GetPixelRed(sxy)-GetPixelRed(hxy)) / QuantumRange;
      score += d*d;
      d = (GetPixelGreen(sxy)-GetPixelGreen(hxy)) / QuantumRange;
      score += d*d;
      d = (GetPixelBlue(sxy)-GetPixelBlue(hxy)) / QuantumRange;
      score += d*d;
      cntNonTrans++;
    }

    if (pfh->CompWind.HomChkOn && ha > 0) {
      if (!InitMinMax) {
        minSub = *hxy;
        maxSub = *hxy;
        InitMinMax = MagickTrue;
      } else {
        // FIXME: or could record limits of sxy instead of hxy?
        if (GetPixelRed(&minSub) > GetPixelRed(hxy)) SetPixelRed(&minSub, GetPixelRed(hxy));
        if (GetPixelGreen(&minSub) > GetPixelGreen(hxy)) SetPixelGreen(&minSub, GetPixelGreen(hxy));
        if (GetPixelBlue(&minSub) > GetPixelBlue(hxy)) SetPixelBlue(&minSub, GetPixelBlue(hxy));
        if (GetPixelRed(&maxSub) < GetPixelRed(hxy)) SetPixelRed(&maxSub, GetPixelRed(hxy));
        if (GetPixelGreen(&maxSub) < GetPixelGreen(hxy)) SetPixelGreen(&maxSub, GetPixelGreen(hxy));
        if (GetPixelBlue(&maxSub) < GetPixelBlue(hxy)) SetPixelBlue(&maxSub, GetPixelBlue(hxy));
      }
    }

    sxy++;
    hxy++;
  }

  if (cntNonTrans == 0) return pfh->CompWind.WorstCompare;

  if (pfh->CompWind.HomChkOn) {
    sxy = sxySave;
    hxy = hxySave;
    int outside = 0;

    VIEW_PIX_PTR tppMin, tppMax;

    double hcp1 = pfh->CompWind.HomChk + 1;

    SetPixelRed   (&tppMin, hcp1*GetPixelRed(&minSub)   - pfh->CompWind.HomChk*GetPixelRed(&maxSub));
    SetPixelGreen (&tppMin, hcp1*GetPixelGreen(&minSub) - pfh->CompWind.HomChk*GetPixelGreen(&maxSub));
    SetPixelBlue  (&tppMin, hcp1*GetPixelBlue(&minSub)  - pfh->CompWind.HomChk*GetPixelBlue(&maxSub));

    SetPixelRed   (&tppMax, hcp1*GetPixelRed(&maxSub)   - pfh->CompWind.HomChk*GetPixelRed(&minSub));
    SetPixelGreen (&tppMax, hcp1*GetPixelGreen(&maxSub) - pfh->CompWind.HomChk*GetPixelGreen(&minSub));
    SetPixelBlue  (&tppMax, hcp1*GetPixelBlue(&maxSub)  - pfh->CompWind.HomChk*GetPixelBlue(&minSub));

    for (xy=0; xy < pfh->sqDim * pfh->sqDim; xy++) {
      double sa = GetPixelAlpha (sxy);
      double ha = GetPixelAlpha (hxy);

      if (sa > 0 && ha <= 0) {
        if      (GetPixelRed (sxy) < GetPixelRed (&tppMin)) outside++;
        else if (GetPixelRed (sxy) > GetPixelRed (&tppMax)) outside++;

        if      (GetPixelGreen (sxy) < GetPixelGreen (&tppMin)) outside++;
        else if (GetPixelGreen (sxy) > GetPixelGreen (&tppMax)) outside++;

        if      (GetPixelBlue (sxy) < GetPixelBlue (&tppMin)) outside++;
        else if (GetPixelBlue (sxy) > GetPixelBlue (&tppMax)) outside++;
      }
    }

    if (outside > 0) return pfh->CompWind.WorstCompare;
    // FIXME? Seems a bit harsh. Maybe make score just a bit worse.
  }

  score /= (cntNonTrans);

  return score;
}
===*/

static void inline CopyOnePix (
  FillHoleT * pfh,
  Image * image,
  CacheView * new_view,
  CacheView * inp_view,
  CopyWhereT *cw,
  ExceptionInfo *exception)
// Copies one pixel from inp_view to new_view.
// Adjusts cw coords.
{
  const VIEW_PIX_PTR
    *src;

  VIEW_PIX_PTR
    *dst;

  cw->srcX += pfh->WindowRadPix;
  cw->srcY += pfh->WindowRadPix;
  cw->dstX += pfh->WindowRadPix;
  cw->dstY += pfh->WindowRadPix;

  if (pfh->debug==MagickTrue) printf ("CopyOnePix %li,%li => %li,%li\n",
    cw->srcX, cw->srcY,
    cw->dstX, cw->dstY);

  src = GetCacheViewVirtualPixels(
    inp_view,cw->srcX,cw->srcY,1,1,exception);
  if (src == (const VIEW_PIX_PTR *) NULL) printf ("bad src");

  dst = GetCacheViewAuthenticPixels(
    new_view,cw->dstX,cw->dstY,1,1,exception);
  if (dst == (const VIEW_PIX_PTR *) NULL) printf ("bad dst");

  SET_PIXEL_RED   (image, GET_PIXEL_RED   (image, src), dst);
  SET_PIXEL_GREEN (image, GET_PIXEL_GREEN (image, src), dst);
  SET_PIXEL_BLUE  (image, GET_PIXEL_BLUE  (image, src), dst);
  SET_PIXEL_ALPHA (image, QuantumRange, dst);
}


static void inline CopyWindow (
  FillHoleT * pfh,
  Image * image,
  CacheView * new_view,
  CacheView * inp_view,
  CopyWhereT *cw,
  ExceptionInfo *exception)
// Copies non-transparent pixels from inp_view to replace transparent pixels in new_view.
// If CopyRad < WindowRad, or if out of bounds, the cw coords will be adjusted.
{
  const VIEW_PIX_PTR
    *src;

  VIEW_PIX_PTR
    *dst;

  int
    ij;

  // dst pixels could be outside authentic
  // We need to adjust GCV parameters so we are not outside authentic.

  // FIXME: Check somewhere for case of sqDim <= image dimensions.

  if (pfh->CopyRadPix < pfh->WindowRadPix) {
    int dRad = pfh->WindowRadPix - pfh->CopyRadPix;
    cw->srcX += dRad;
    cw->srcY += dRad;
    cw->dstX += dRad;
    cw->dstY += dRad;
  }

  int offsX = 0;
  int offsY = 0;

  if (cw->dstX < 0) offsX = -cw->dstX;
  if (cw->dstY < 0) offsY = -cw->dstY;

  cw->wi = pfh->sqCopyDim - offsX;
  cw->ht = pfh->sqCopyDim - offsY;

  cw->srcX += offsX;
  cw->srcY += offsY;

  cw->dstX += offsX;
  cw->dstY += offsY;

  // FIXME: chk next 22-Nov-2015:
  if (cw->wi + cw->dstX > pfh->Width)  cw->wi = pfh->Width - cw->dstX;
  if (cw->ht + cw->dstY > pfh->Height) cw->ht = pfh->Height - cw->dstY;

  if (pfh->debug==MagickTrue) printf ("CW offsXY=%i,%i dstXY=%li,%li wi=%li ht=%li\n", offsX, offsY, cw->dstX, cw->dstY, cw->wi, cw->ht);

  assert (cw->wi > 0 && cw->wi <= pfh->sqCopyDim);
  assert (cw->ht > 0 && cw->ht <= pfh->sqCopyDim);

  src = GetCacheViewVirtualPixels(inp_view,cw->srcX,cw->srcY,cw->wi,cw->ht,exception);
  if (src == (const VIEW_PIX_PTR *) NULL) { printf ("bad src"); return; }

  dst = GetCacheViewAuthenticPixels(new_view,cw->dstX,cw->dstY,cw->wi,cw->ht,exception);
  if (dst == (const VIEW_PIX_PTR *) NULL) { printf ("bad dst"); return; }

  // FIXME: "image" in following may be wrong.
  for (ij=0; ij < cw->wi*cw->ht; ij++) {
    if (GET_PIXEL_ALPHA (image, src) > 0 && GET_PIXEL_ALPHA (image, dst) == 0) {
      SET_PIXEL_RED   (image, GET_PIXEL_RED (image, src), dst);
      SET_PIXEL_GREEN (image, GET_PIXEL_GREEN (image, src), dst);
      SET_PIXEL_BLUE  (image, GET_PIXEL_BLUE (image, src), dst);
      SET_PIXEL_ALPHA (image, QuantumRange, dst);
    }
    src += Inc_ViewPixPtr (image);
    dst += Inc_ViewPixPtr (image);
  }

  if (pfh->debug==MagickTrue) printf ("doneCW\n");
}


static void ResolveImageParams (
  const Image *image,
  FillHoleT * pfh
)
{
  pfh->Width = image->columns;
  pfh->Height = image->rows;

  pfh->Match.ref_columns = image->columns;
  pfh->Match.ref_rows = image->rows;

  pfh->ImgDiagSq = image->rows * image->rows + image->columns * image->columns;

  if (pfh->WindowRad_IsPc)
    pfh->WindowRadPix = (0.5 + pfh->WindowRad/100.0
                         * (pfh->Width<pfh->Height ? pfh->Width:pfh->Height));

  if (pfh->WindowRadPix < 1) pfh->WindowRadPix = 1;

  pfh->Match.matchRadX = pfh->Match.matchRadY = pfh->WindowRadPix;

  if (pfh->CopyRad_IsPc)
    pfh->CopyRadPix = (0.5 + pfh->CopyRad/100.0
                         * (pfh->Width<pfh->Height ? pfh->Width:pfh->Height));

  if (pfh->CopyRadPix < 0) pfh->CopyRadPix = 0;

  if (pfh->Match.LimSrchRad_IsPc)
    pfh->Match.limSrchRadX = pfh->Match.limSrchRadY = (0.5 + pfh->Match.LimSrchRad/100.0
                         * (pfh->Width<pfh->Height ? pfh->Width:pfh->Height));

  pfh->sqDim = 2 * pfh->WindowRadPix + 1;
  pfh->sqCopyDim = 2 * pfh->CopyRadPix + 1;

  pfh->CompWind.win_wi = pfh->sqDim;
  pfh->CompWind.win_ht = pfh->sqDim;

//  pfh->WorstCompare = (4*QuantumRange*QuantumRange * pfh->sqDim * pfh->sqDim);
//  pfh->WorstCompare *= (pfh->ImgDiagSq * 2);

  pfh->CompWind.WorstCompare = (4*QuantumRange*QuantumRange * pfh->sqDim * pfh->sqDim);
  pfh->CompWind.WorstCompare *= (pfh->ImgDiagSq * 2);

  if (pfh->Match.DissimOn == MagickFalse) {
    pfh->Match.dissimThreshold = pfh->CompWind.WorstCompare;
    pfh->Match.dissimThresholdSq = pfh->CompWind.WorstCompare;
  }

  pfh->CompWind.AllowEquCoord = MagickFalse;

  if (pfh->do_verbose) {
    fprintf (stderr, "pfh->WindowRadPix = %i\n", pfh->WindowRadPix);
    fprintf (stderr, "pfh->Match.limSrchRad = %lix%li\n",
      pfh->Match.limSrchRadX, pfh->Match.limSrchRadY);
    fprintf (stderr, "pfh->CopyRadPix = %i\n", pfh->CopyRadPix);
    fprintf (stderr, "pfh->CompWind.WorstCompare = %g\n", pfh->CompWind.WorstCompare);
    fprintf (stderr, "pfh->sqDim = %i\n", pfh->sqDim);
    fprintf (stderr, "pfh->sqCopyDim = %i\n", pfh->sqCopyDim);
    fprintf (stderr, "pfh->Match.RandSearchesI = %i\n", pfh->Match.RandSearchesI);
    fprintf (stderr, "pfh->Match.SkipNumI = %i\n", pfh->Match.SkipNumI);
    fprintf (stderr, "pfh->Match.simThresholdSq = %g\n", pfh->Match.simThresholdSq);
    fprintf (stderr, "pfh->Match.dissimThresholdSq = %g\n", pfh->Match.dissimThresholdSq);
  }
}


static MagickBooleanType MatchAndCopy (
  FillHoleT * pfh,
  Image * new_image,
  CacheView *new_view,        // destination for changes
  CacheView *copy_inp_view,   // source for changes
  ssize_t x,
  ssize_t y,
  CopyWhereT *cpwh,
  int *frameNum,
  MagickBooleanType *unfilled,
  MagickBooleanType *changedAny,
  ExceptionInfo *exception)
// x,y is central pixel of window to be matched.
// Returns true if okay, or false if serious problem.
{
  MagickBooleanType
    status = MagickTrue;

  if (pfh->debug==MagickTrue) printf ("MaC %li,%li \n", x, y);

  MatchT * pm = &pfh->Match;
  CompWindT * cmpwin = &pfh->CompWind;

  Match (x, y, x, y, pm, cmpwin, pfh->random_info, exception);

  if (pfh->debug==MagickTrue) printf ("Mac Best ref xy=%li,%li %g  from sub xy %li,%li\n",
      pm->bestX, pm->bestY, pm->bestScore, cmpwin->subX, cmpwin->subY);

  if (pm->bestScore < pm->dissimThresholdSq) {

    // Copy functions expect these are the top-left of the search windows.
    cpwh->srcX = pm->bestX - pm->matchRadX;
    cpwh->srcY = pm->bestY - pm->matchRadY;
    cpwh->dstX = cmpwin->subX;
    cpwh->dstY = cmpwin->subY;

    // Note: cpwh->wi and cpwh->ht is the window for copy, not search.

    if (pfh->debug==MagickTrue) printf (" MaC cpwh: %li,%li %lix%li+%li+%li\n",
      cpwh->srcX, cpwh->srcY, cpwh->wi, cpwh->ht, cpwh->dstX, cpwh->dstY);

    if (pfh->copyWhat == copyOnePixel)
      CopyOnePix (pfh, new_image, new_view, copy_inp_view, cpwh, exception);
    else
      CopyWindow (pfh, new_image, new_view, copy_inp_view, cpwh, exception);

    if (SyncCacheViewAuthenticPixels (new_view,exception) == MagickFalse)
      status=MagickFalse;

    *changedAny = MagickTrue;
    if (pfh->write_frames == 2) {
      WriteFrame (pfh, new_image, *frameNum, exception);
      (*frameNum)++;
    }
  } else {
    *unfilled = MagickTrue;
  }
  if (pfh->debug==MagickTrue) printf ("doneMac\n");
  return (status);
}

static void InitRand (FillHoleT * pfh)
{
  pfh->random_info=AcquireRandomInfo();

  // There seems to be a problem: the first few values show coherency,
  // so skip over them.

  int i;
  for (i=0; i < 20; i++) {
    GetPseudoRandomValue(pfh->random_info);
  }
}

static void DeInitRand (FillHoleT * pfh)
{
  pfh->random_info=DestroyRandomInfo(pfh->random_info);
}

static void InitAutoLimit (FillHoleT * pfh)
{
  pfh->Match.SetAutoLimit = MagickTrue;
  pfh->Match.UseAutoLimit = MagickFalse;

  pfh->Match.limLeft = pfh->Width;
  pfh->Match.limTop = pfh->Height;
  pfh->Match.limRight = 0;
  pfh->Match.limBot = 0;

  pfh->Match.nLimitCnt = 0;
}

static void UseAutoLimit (FillHoleT * pfh)
{
  pfh->Match.SetAutoLimit = MagickFalse;
  pfh->Match.UseAutoLimit = pfh->Match.AutoLs;

  if (pfh->debug==MagickTrue)
    printf ("UseAutoLimit: LTRB %li,%li %li,%li\n",
      pfh->Match.limLeft, pfh->Match.limTop, pfh->Match.limRight, pfh->Match.limBot);
}

compwind.h

This is used by various modules.

typedef enum {
  cwrSuccess,
  cwrGetCache,
  cwrEquCoord,
  cwrSubAllTrans,
  cwrCentTrans,
  cwrRefTrans,
  cwrNoOpaq,
  cwrOutside
} cwReasonT;

// Compares window in sub_view with windows in ref_view.
// Treatment of alpha is not symmetrical,
// so sub and ref are _not_ interchangable.

typedef struct {
  Image
    *ref_image,
    *sub_image;

  CacheView
    *ref_view,  // These can be views ...
    *sub_view;  //   ... of the same image

  ssize_t
    win_wi,
    win_ht,
    refX,  // Top-left corner of ...
    refY,  //   ... window in ref_view
    subX,  // Top-left corner of ...
    subY;  //   ... window in sub_view

  double
    WorstCompare,  //  Some large number
    HomChk;

  MagickBooleanType
    HomChkOn,
    AllowEquCoord;

  // Returns:
  cwReasonT
    Reason;

  ssize_t
    nCompares;

} CompWindT;

compwind.inc

This is used by various modules.

/* Compare windows. 

   Compare a window in sub_view with the same-size window in ref_view.
   Returns a score.

   First written: 30-Nov-2015.
     (Hived-off from fillholes.c)

   Updates:
     1-Dec-2015 No longer rejects when sub coords == ref coords.
     1-Feb-2016 For v7.
*/

static void inline instantPp (Image * image, PIX_INFO *p)
{
#if IMV6OR7==6
  GetMagickPixelPacket (image, p);
#else
  GetPixelInfo (image, p);
#endif

//  SET_PIXEL_RED   (image, 0, p);
//  SET_PIXEL_GREEN (image, 0, p);
//  SET_PIXEL_BLUE  (image, 0, p);
}


static MagickBooleanType IsSubTrans (
  CompWindT * pcw,
  ExceptionInfo *exception
)
// Returns true if all pixels in subimage window are entirely transparent.
// Retuns false if any have any opacity.
{
  ssize_t
    xy;

  const VIEW_PIX_PTR
    *subxy;

  int
    wiht = pcw->win_wi * pcw->win_ht;

  subxy = GetCacheViewVirtualPixels(pcw->sub_view,
    pcw->subX,pcw->subY,
    pcw->win_wi,pcw->win_ht,exception);

  if (subxy == (const VIEW_PIX_PTR *) NULL) {
    printf ("bad subxy");
    pcw->Reason = cwrGetCache;
    return MagickTrue;
  }

  for (xy=0; xy < wiht; xy++) {
    // FIXME: Bug: we should use subxy+xy*NumChannels.
    if (GET_PIXEL_ALPHA (pcw->sub_image,subxy) > 0) {
      pcw->Reason = cwrSubAllTrans;
      return MagickFalse;
    }
  }
  pcw->Reason = cwrSuccess;
  return MagickTrue;
}

static void inline ViewPixPtr2Info (Image * img, const VIEW_PIX_PTR * vpp, PIX_INFO * pi)
{
  pi->red   = GET_PIXEL_RED   (img, vpp);
  pi->green = GET_PIXEL_GREEN (img, vpp);
  pi->blue  = GET_PIXEL_BLUE  (img, vpp);
}

static double CompareWindow (
  CompWindT * pcw,
  ExceptionInfo *exception
)
// Compares subimage window with reference starting at given top-left of each.
// Returns positive number MSE; closer to zero is closer match.
// If sx,sy has same number or more transparent pixels as hx,hy,
//   or either window is entirely transparent,
//   returns very large number.
{
  ssize_t
    xy;

  const VIEW_PIX_PTR
    *refxy, *refxySave,
    *subxy, *subxySave;

  // FIXME: V7 oops. In v7, these aren't structures.
  PIX_INFO
    minSub,
    maxSub;

  double
    score = 0;

  int
    wiht = pcw->win_wi * pcw->win_ht,
    cntNonTrans = 0,
    refXmult;
    //subXmult;

//printf ("cw\n");

  //printf ("CompareWindow %li,%li  %li,%li  %lix%li \n",
  //  pcw->refX, pcw->refY,
  //  pcw->subX, pcw->subY,
  //  pcw->win_wi,pcw->win_ht);

  pcw->Reason = cwrSuccess;
  pcw->nCompares++;

  if (   pcw->refX==pcw->subX
      && pcw->refY==pcw->subY
      && !pcw->AllowEquCoord)
  {
    pcw->Reason = cwrEquCoord;
    return pcw->WorstCompare;
  }

//printf ("cw1\n");

  refxy = GetCacheViewVirtualPixels(pcw->ref_view,
    pcw->refX,pcw->refY,
    pcw->win_wi,pcw->win_ht,exception);
  if (refxy == (const VIEW_PIX_PTR *) NULL) {
    printf ("bad refxy\n");
    pcw->Reason = cwrGetCache;
    return pcw->WorstCompare;
  }

//printf ("cw1a\n");

#if IMV6OR7==6
  refXmult = 1;
//  subXmult = 1;
#else
  refXmult = GetPixelChannels (pcw->ref_image);
//  subXmult = GetPixelChannels (pcw->sub_image);
#endif

//printf ("cw1b\n");

  // If central pixel in ref window is transparent, forget about it.
  if (GET_PIXEL_ALPHA (pcw->ref_image,refxy + refXmult*(wiht/2)) <= 0) {
    pcw->Reason = cwrCentTrans;
    return pcw->WorstCompare;
  }

  subxy = GetCacheViewVirtualPixels(pcw->sub_view,
    pcw->subX,pcw->subY,
    pcw->win_wi,pcw->win_ht,exception);
  if (subxy == (const VIEW_PIX_PTR *) NULL) {
    printf ("bad subxy");
    pcw->Reason = cwrGetCache;
    return pcw->WorstCompare;
  }

//printf ("cw2\n");

  refxySave = refxy;
  subxySave = subxy;
  instantPp (pcw->sub_image, &minSub);
  instantPp (pcw->sub_image, &maxSub);

//printf ("cw3\n");

  MagickBooleanType InitMinMax = MagickFalse;
  for (xy=0; xy < wiht; xy++) {
    double refa = GET_PIXEL_ALPHA (pcw->ref_image,refxy);
    double suba = GET_PIXEL_ALPHA (pcw->sub_image,subxy);

    if (refa <= 0 && suba > 0) {
      pcw->Reason = cwrRefTrans;
      return pcw->WorstCompare;
    }

    if (refa > 0 && suba > 0) {
      double d = (GET_PIXEL_RED(pcw->ref_image,refxy)-GET_PIXEL_RED(pcw->sub_image,subxy)) / QuantumRange;
      score += d*d;
      d = (GET_PIXEL_GREEN(pcw->ref_image,refxy)-GET_PIXEL_GREEN(pcw->sub_image,subxy)) / QuantumRange;
      score += d*d;
      d = (GET_PIXEL_BLUE(pcw->ref_image,refxy)-GET_PIXEL_BLUE(pcw->sub_image,subxy)) / QuantumRange;
      score += d*d;
      cntNonTrans++;
    }

    if (pcw->HomChkOn && suba > 0) {
      if (!InitMinMax) {
        // FIXME: in v7, these aren't structures.
//        minSub = *subxy;
//        maxSub = *subxy;

        ViewPixPtr2Info (pcw->sub_image, subxy, &minSub);
        ViewPixPtr2Info (pcw->sub_image, subxy, &maxSub);

        InitMinMax = MagickTrue;
      } else {
        // FIXME: or could record limits of sxy instead of hxy?
        if (minSub.red > GET_PIXEL_RED(pcw->sub_image, subxy))
            minSub.red = GET_PIXEL_RED(pcw->sub_image, subxy);

//          SET_PIXEL_RED(pcw->sub_image,
//                        GET_PIXEL_RED(pcw->sub_image, subxy),
//                        &minSub);

        if (minSub.green > GET_PIXEL_GREEN(pcw->sub_image, subxy))
            minSub.green = GET_PIXEL_GREEN(pcw->sub_image, subxy);

//          SET_PIXEL_GREEN(pcw->sub_image,
//                          GET_PIXEL_GREEN(pcw->sub_image, subxy),
//                          &minSub);

        if (minSub.blue > GET_PIXEL_BLUE(pcw->sub_image, subxy))
            minSub.blue = GET_PIXEL_BLUE(pcw->sub_image, subxy);

//          SET_PIXEL_BLUE(pcw->sub_image,
//                         GET_PIXEL_BLUE(pcw->sub_image, subxy),
//                         &minSub);

        if (maxSub.red < GET_PIXEL_RED(pcw->sub_image, subxy))
            maxSub.red = GET_PIXEL_RED(pcw->sub_image, subxy);

//          SET_PIXEL_RED(pcw->sub_image,
//                        GET_PIXEL_RED(pcw->sub_image, subxy),
//                        &maxSub);

        if (maxSub.green < GET_PIXEL_GREEN(pcw->sub_image, subxy))
            maxSub.green = GET_PIXEL_GREEN(pcw->sub_image, subxy);

//          SET_PIXEL_GREEN(pcw->sub_image,
//                          GET_PIXEL_GREEN(pcw->sub_image, subxy),
//                          &maxSub);

        if (maxSub.blue < GET_PIXEL_BLUE(pcw->sub_image, subxy))
            maxSub.blue = GET_PIXEL_BLUE(pcw->sub_image, subxy);

//          SET_PIXEL_BLUE(pcw->sub_image,
//                         GET_PIXEL_BLUE(pcw->sub_image, subxy),
//                         &maxSub);
      }
    }

    refxy += Inc_ViewPixPtr (pcw->ref_image);
    subxy += Inc_ViewPixPtr (pcw->sub_image);
  }

//printf ("cw4\n");

  if (cntNonTrans == 0) {
    pcw->Reason = cwrNoOpaq;
    return pcw->WorstCompare;
  }

  score /= (cntNonTrans * 3);

  if (pcw->HomChkOn) {
    refxy = refxySave;
    subxy = subxySave;
    int outside = 0;

    // FIXME: Again, in v7 these aren't structures.
    PIX_INFO tppMin, tppMax;
    instantPp (pcw->sub_image, &tppMin);
    instantPp (pcw->sub_image, &tppMax);

    double hcp1 = pcw->HomChk + 1;

//    SET_PIXEL_RED   (pcw->sub_image, hcp1*GET_PIXEL_RED(pcw->sub_image, &minSub)
//                          - pcw->HomChk*GET_PIXEL_RED(pcw->sub_image, &maxSub), &tppMin);
//    SET_PIXEL_GREEN (pcw->sub_image, hcp1*GET_PIXEL_GREEN(pcw->sub_image, &minSub)
//                          - pcw->HomChk*GET_PIXEL_GREEN(pcw->sub_image, &maxSub), &tppMin);
//    SET_PIXEL_BLUE  (pcw->sub_image, hcp1*GET_PIXEL_BLUE(pcw->sub_image, &minSub)
//                          - pcw->HomChk*GET_PIXEL_BLUE(pcw->sub_image, &maxSub), &tppMin);

    tppMin.red   = hcp1*minSub.red   - pcw->HomChk*maxSub.red;
    tppMin.green = hcp1*minSub.green - pcw->HomChk*maxSub.green;
    tppMin.blue  = hcp1*minSub.blue  - pcw->HomChk*maxSub.blue;


//    SET_PIXEL_RED   (pcw->sub_image, hcp1*GET_PIXEL_RED(pcw->sub_image, &maxSub)
//                          - pcw->HomChk*GET_PIXEL_RED(pcw->sub_image, &minSub), &tppMax);
//    SET_PIXEL_GREEN (pcw->sub_image, hcp1*GET_PIXEL_GREEN(pcw->sub_image, &maxSub)
//                          - pcw->HomChk*GET_PIXEL_GREEN(pcw->sub_image, &minSub), &tppMax);
//    SET_PIXEL_BLUE  (pcw->sub_image, hcp1*GET_PIXEL_BLUE(pcw->sub_image, &maxSub)
//                          - pcw->HomChk*GET_PIXEL_BLUE(pcw->sub_image, &minSub), &tppMax);

    tppMax.red   = hcp1*maxSub.red   - pcw->HomChk*minSub.red;
    tppMax.green = hcp1*maxSub.green - pcw->HomChk*minSub.green;
    tppMax.blue  = hcp1*maxSub.blue  - pcw->HomChk*minSub.blue;


    for (xy=0; xy < wiht; xy++) {
      double refa = GET_PIXEL_ALPHA (pcw->ref_image, refxy);
      double suba = GET_PIXEL_ALPHA (pcw->sub_image, subxy);

      if (refa > 0 && suba <= 0) {
//        if      (GET_PIXEL_RED (pcw->ref_image, refxy)
//               < GET_PIXEL_RED (pcw->sub_image, &tppMin)) outside++;
//        else if (GET_PIXEL_RED (pcw->ref_image, refxy)
//               > GET_PIXEL_RED (pcw->sub_image, &tppMax)) outside++;
//
//        if      (GET_PIXEL_GREEN (pcw->ref_image, refxy)
//               < GET_PIXEL_GREEN (pcw->sub_image, &tppMin)) outside++;
//        else if (GET_PIXEL_GREEN (pcw->ref_image, refxy)
//               > GET_PIXEL_GREEN (pcw->sub_image, &tppMax)) outside++;
//
//        if      (GET_PIXEL_BLUE (pcw->ref_image, refxy)
//               < GET_PIXEL_BLUE (pcw->sub_image, &tppMin)) outside++;
//        else if (GET_PIXEL_BLUE (pcw->ref_image, refxy)
//               > GET_PIXEL_BLUE (pcw->sub_image, &tppMax)) outside++;

        if      (GET_PIXEL_RED (pcw->ref_image, refxy) < tppMin.red) outside++;
        else if (GET_PIXEL_RED (pcw->ref_image, refxy) > tppMax.red) outside++;

        if      (GET_PIXEL_GREEN (pcw->ref_image, refxy) < tppMin.green) outside++;
        else if (GET_PIXEL_GREEN (pcw->ref_image, refxy) > tppMax.green) outside++;

        if      (GET_PIXEL_BLUE (pcw->ref_image, refxy)  < tppMin.blue) outside++;
        else if (GET_PIXEL_BLUE (pcw->ref_image, refxy)  > tppMax.blue) outside++;

      }
      // FIXME: Clear bugs. We don't use suba. We don't increment pointer refxy.
    }

    if (outside > 0) {
      pcw->Reason = cwrOutside;
      return pcw->WorstCompare;
    }
    // FIXME? Seems a bit harsh. Maybe make score just a bit worse.
  }

//printf ("end cw\n");

  return score;
}

match.h

This is used by various modules.

typedef enum {
  swEntire,
  swRandom,
  swSkip
} SearchWhatT;


typedef struct {

  ssize_t
    ref_rows,       // Size of reference image.
    ref_columns,
    matchRadX,      // >= 0
    matchRadY,      // >= 0
    limSrchRadX,
    limSrchRadY,
    limLeft,  // Each limit is for top-left of window (in reference), not the centre of the window.
    limTop,
    limRight,
    limBot,
    nLimitCnt; // countdown before we can use the limit

  double
    LimSrchRad,
    simThreshold,
    simThresholdSq,
    dissimThreshold,
    dissimThresholdSq,
    autoCorrectThreshold,
    autoCorrectThresholdSq,
    RandSearchesF,
    SkipNumF;

  SearchWhatT
    searchWhat;

  MagickBooleanType
    Refine,  // Whether to refine random and skip searches
    SearchToEdges,
    AutoLs,
    SetAutoLimit,
    UseAutoLimit,
    LimSrchRad_IsPc,
    RandSearches_IsPc,
    SkipNum_IsPc,
    DissimOn,
    DoSubTransTest;

  int
    RandSearchesI,
    SkipNumI;

  // Results are written here:
  // bestX and bestY are for centre of window in reference.
  ssize_t
    bestX,
    bestY;

  double
    bestScore;

} MatchT;


typedef struct {
  ssize_t
    j0,
    j1,
    i0,
    i1;
} RangeT;

match.inc

This is used by various modules.

#define DEBUG_MATCH 0

static void SetDefaultMatch (MatchT * pm)
{
  pm->ref_rows = pm->ref_columns = 0;
  pm->matchRadX = pm->matchRadY = 1;
  pm->AutoLs = MagickTrue;
  pm->Refine = MagickTrue;
  pm->SearchToEdges = MagickTrue;
  pm->simThreshold = pm->simThresholdSq = 0.0;
  pm->dissimThreshold = pm->dissimThresholdSq = 0.0;
  pm->autoCorrectThreshold = pm->autoCorrectThresholdSq = 0.0;
  pm->DissimOn = MagickFalse;
  pm->searchWhat = swEntire;
  pm->LimSrchRad = 0;
  pm->LimSrchRad_IsPc = MagickFalse;
  pm->limSrchRadX = pm->limSrchRadY = 0;
  pm->SetAutoLimit = pm->UseAutoLimit = MagickFalse;
  pm->RandSearchesF = pm->SkipNumF = 0;
  pm->RandSearchesI = pm->SkipNumI = 0;
  pm->RandSearches_IsPc = pm->SkipNum_IsPc = MagickFalse;
  pm->DoSubTransTest = MagickFalse;
  pm->limLeft = pm->limTop = pm->limRight = pm->limBot = 0;
  pm->nLimitCnt = 0;

  pm->bestX = pm->bestY = 0;
  pm->bestScore = 0;
}

static void CalcMatchRange (
  ssize_t ref_x,
  ssize_t ref_y,
  ssize_t limX,
  ssize_t limY,
  MatchT * pm,
  RangeT * rng)
{
  if (pm->SearchToEdges) {
    rng->i0 = -pm->matchRadY;
    rng->i1 = pm->ref_rows - pm->matchRadY;
    rng->j0 = -pm->matchRadX;
    rng->j1 = pm->ref_columns - pm->matchRadX;
  } else {
    rng->i0 = 0;
    rng->i1 = pm->ref_rows - 2*pm->matchRadY - 1;
    rng->j0 = 0;
    rng->j1 = pm->ref_columns - 2*pm->matchRadX - 1;
  }

  if (limX) {
    ssize_t
      limj0 = ref_x - limX - pm->matchRadX,
      limj1 = ref_x + limX - pm->matchRadX;

    if (rng->j0 < limj0) rng->j0 = limj0;
    if (rng->j1 > limj1) rng->j1 = limj1;
  }

  if (limY) {
    ssize_t
      limi0 = ref_y - limY - pm->matchRadY,
      limi1 = ref_y + limY - pm->matchRadY;

    if (rng->i0 < limi0) rng->i0 = limi0;
    if (rng->i1 > limi1) rng->i1 = limi1;
  }

  //if (pfh->debug==MagickTrue) printf ("CalcMatchRange ji A %li-%li %li-%li\n", rng->j0, rng->j1, rng->i0, rng->i1);

  if (pm->UseAutoLimit) {
    ssize_t
      j0s = rng->j0,
      j1s = rng->j1,
      i0s = rng->i0,
      i1s = rng->i1;

    if (rng->j0 < pm->limLeft)    rng->j0 = pm->limLeft;
    if (rng->i0 < pm->limTop)     rng->i0 = pm->limTop;
    if (rng->j1 > pm->limRight+1) rng->j1 = pm->limRight+1;
    if (rng->i1 > pm->limBot+1)   rng->i1 = pm->limBot+1;

    if (rng->j1 <= rng->j0 || rng->i1 <= rng->i0) {
      // AutoLimit would reduce search space to nothing, so don't do it.

      printf ("AutoLimit %li %li %li %li\n", rng->j0, rng->j1, rng->i0, rng->i1);
      rng->j0 = j0s;
      rng->j1 = j1s;
      rng->i0 = i0s;
      rng->i1 = i1s;
    }
  }

}

static double Match (
  ssize_t x,
  ssize_t y,
  ssize_t ref_x,
  ssize_t ref_y,
  MatchT * pm,
  CompWindT * cmpwin,
  RandomInfo *restrict random_info,
  ExceptionInfo *exception)
// x,y is central pixel of window to be matched.
//
// ref_x, ref_y is guess for central pixel in reference.
//   Relevant only when search radius is limited.
//
// Always updates pm->bestX, pm->bestY and pm->bestScore,
//   even if bestScore is lousy.

{
  RangeT
    rng;

  ssize_t
    besti=0, bestj=0;

  if (pm->DoSubTransTest && IsSubTrans (cmpwin, exception)) {
    pm->bestX = 0;
    pm->bestY = 0;
    pm->bestScore = cmpwin->WorstCompare;
    return pm->bestScore;
  }

  CalcMatchRange (ref_x, ref_y, pm->limSrchRadX, pm->limSrchRadY, pm, &rng);

  double bestScore = cmpwin->WorstCompare;
  double v;

#if DEBUG_MATCH==1
  printf ("Match ji B %li to %li, %li to %li\n", rng.j0, rng.j1, rng.i0, rng.i1);
#endif

  cmpwin->subX = x - pm->matchRadX;
  cmpwin->subY = y - pm->matchRadY;

  switch (pm->searchWhat) {
    case swEntire:
    default: {
      for (cmpwin->refY = rng.i0; cmpwin->refY < rng.i1; cmpwin->refY++) {
        for (cmpwin->refX = rng.j0; cmpwin->refX < rng.j1; cmpwin->refX++) {
          v = CompareWindow (cmpwin, exception);
          if (bestScore > v) {
            bestScore = v;
            besti = cmpwin->refY;
            bestj = cmpwin->refX;
            if (bestScore <= pm->simThresholdSq) break;
          }
        }
        if (bestScore <= pm->simThresholdSq) break;
      }

      break;
    }

    case swRandom: {
      int
        defaultN,
        nRand,
        n;

      defaultN = rng.i1-rng.i0+1;
      if (defaultN > rng.j1-rng.j0+1) defaultN = rng.j1-rng.j0+1;

      if (pm->RandSearches_IsPc) {
        nRand = defaultN * pm->RandSearchesF/100.0;
      } else {
        nRand = (pm->RandSearchesI == 0) ? defaultN : pm->RandSearchesI;
      }

      if (nRand < 1) nRand = 1;

      for (n = 0; n < nRand; n++) {
        cmpwin->refY = rng.i0 + (rng.i1-rng.i0)*GetPseudoRandomValue(random_info);
        cmpwin->refX = rng.j0 + (rng.j1-rng.j0)*GetPseudoRandomValue(random_info);

        v = CompareWindow (cmpwin, exception);
        if (bestScore > v) {
          bestScore = v;
          besti = cmpwin->refY;
          bestj = cmpwin->refX;
          if (bestScore <= pm->simThresholdSq) break;
        }
      }

      // FIXME: just RadY?
      if (pm->Refine && bestScore > pm->simThresholdSq && pm->matchRadY > 1) {
        // Refine the search.
        if (rng.i0 < besti - pm->matchRadY) rng.i0 = besti - pm->matchRadY;
        if (rng.i1 > besti + pm->matchRadY) rng.i1 = besti + pm->matchRadY;
        if (rng.j0 < bestj - pm->matchRadX) rng.j0 = bestj - pm->matchRadX;
        if (rng.j1 > bestj + pm->matchRadX) rng.j1 = bestj + pm->matchRadX;

        for (cmpwin->refY = rng.i0; cmpwin->refY < rng.i1; cmpwin->refY++) {
          for (cmpwin->refX = rng.j0; cmpwin->refX < rng.j1; cmpwin->refX++) {
            v = CompareWindow (cmpwin, exception);
            if (bestScore > v) {
              bestScore = v;
              besti = cmpwin->refY;
              bestj = cmpwin->refX;
              if (bestScore <= pm->simThresholdSq) break;
            }
          }
          if (bestScore <= pm->simThresholdSq) break;
        }
      }
      break;
    }
    case swSkip: {
      int defaultN = pm->matchRadX;  // FIXME: x or y or ?
      int nSkip;

      if (pm->SkipNum_IsPc) {
        nSkip = defaultN * pm->SkipNumF/100.0;
      } else {
        nSkip = (pm->SkipNumI == 0) ? defaultN : pm->SkipNumI;
      }

      if (nSkip < 1) nSkip = 1;
//printf ("skip:\n");
      for (cmpwin->refY = rng.i0; cmpwin->refY < rng.i1; cmpwin->refY += nSkip) {
        for (cmpwin->refX = rng.j0; cmpwin->refX < rng.j1; cmpwin->refX += nSkip) {
          v = CompareWindow (cmpwin, exception);
          if (bestScore > v) {
            bestScore = v;
            besti = cmpwin->refY;
            bestj = cmpwin->refX;
            if (bestScore <= pm->simThresholdSq) break;
          }
        }
        if (bestScore <= pm->simThresholdSq) break;
      }

      if (pm->Refine && bestScore > pm->simThresholdSq && nSkip > 1) {
        // Refine the search.
//printf ("refine skip:\n");
        if (rng.i0 < besti-nSkip+1) rng.i0 = besti-nSkip+1;
        if (rng.i1 > besti+nSkip  ) rng.i1 = besti+nSkip;
        if (rng.j0 < bestj-nSkip+1) rng.j0 = bestj-nSkip+1;
        if (rng.j1 > bestj+nSkip  ) rng.j1 = bestj+nSkip;

        for (cmpwin->refY = rng.i0; cmpwin->refY < rng.i1; cmpwin->refY++) {
          for (cmpwin->refX = rng.j0; cmpwin->refX < rng.j1; cmpwin->refX++) {
            v = CompareWindow (cmpwin, exception);
            if (bestScore > v) {
              bestScore = v;
              besti = cmpwin->refY;
              bestj = cmpwin->refX;
              if (bestScore <= pm->simThresholdSq) break;
            }
          }
          if (bestScore <= pm->simThresholdSq) break;
        }
      }
      break;
    }
  }

  if (bestScore < pm->dissimThresholdSq) {
    if (pm->SetAutoLimit) {
      if (pm->limLeft  > bestj) pm->limLeft  = bestj;
      if (pm->limTop   > besti) pm->limTop   = besti;
      if (pm->limRight < bestj) pm->limRight = bestj;
      if (pm->limBot   < besti) pm->limBot   = besti;
    }
  }


  // Experiment: auto-correction
  if (pm->autoCorrectThresholdSq > 0 && bestScore > pm->autoCorrectThresholdSq) {

    if (pm->SearchToEdges) {
      rng.i0 = -pm->matchRadY;
      rng.i1 = pm->ref_rows - pm->matchRadY;
      rng.j0 = -pm->matchRadX;
      rng.j1 = pm->ref_columns - pm->matchRadX;
    } else {
      rng.i0 = 0;
      rng.i1 = pm->ref_rows - 2*pm->matchRadY - 1;
      rng.j0 = 0;
      rng.j1 = pm->ref_columns - 2*pm->matchRadX - 1;
    }

    //MagickBooleanType HasCorrected = MagickFalse;

    for (cmpwin->refY = rng.i0; cmpwin->refY < rng.i1; cmpwin->refY++) {
      for (cmpwin->refX = rng.j0; cmpwin->refX < rng.j1; cmpwin->refX++) {
        v = CompareWindow (cmpwin, exception);
        if (bestScore > v) {
          bestScore = v;
          besti = cmpwin->refY;
          bestj = cmpwin->refX;
          //HasCorrected = MagickTrue;
          if (bestScore <= pm->simThresholdSq) break;
        }
      }
      if (bestScore <= pm->simThresholdSq) break;
    }

    //if (HasCorrected) printf ("hasc %g ", bestScore);
  }

  pm->bestX = bestj + pm->matchRadX;
  pm->bestY = besti + pm->matchRadY;
  pm->bestScore = bestScore;

#if DEBUG_MATCH==1
  printf ("Match best %li %li %g\n", pm->bestX, pm->bestY, pm->bestScore);
#endif

  return bestScore;
}

applines.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <assert.h>
#include <math.h>
#include "vsn_defines.h"

/* Updated:
     3-April-2018 for v7.0.7-28
*/

/* Maybe see transform.c TransformImages and TransformImage.
*/

ModuleExport size_t appendlinesImage(Image **images,const int argc,
  const char **argv,ExceptionInfo *exception)
{
  (void) argc;
  (void) argv;

  Image
    *image;

  Image
    *new_image,
    *transform_images;

  assert(images != (Image **) NULL);
  assert(*images != (Image *) NULL);
  assert((*images)->signature == MAGICK_CORE_SIG);

  transform_images=NewImageList();

  image=(*images);
  for ( ; image != (Image *) NULL; image=GetNextImageInList(image))
  {
    CacheView
      *image_view,
      *out_view;

    ssize_t
      y;

    MagickBooleanType
      status;

    VIEW_PIX_PTR
      *q_out;

printf ("applines: in loop\n");

    new_image=CloneImage(image, image->rows*image->columns, 1, MagickTrue, exception);
    if (new_image == (Image *) NULL)
      return(-1);

    out_view=AcquireVirtualCacheView(new_image,exception);  // FIXME: or authentic?

    status=MagickTrue;

    q_out=GetCacheViewAuthenticPixels(out_view,0,0,new_image->columns,1,exception);
    if (q_out == (const VIEW_PIX_PTR *) NULL)
      status=MagickFalse;

    image_view=AcquireVirtualCacheView(image,exception);

#if defined(MAGICKCORE_OPENMP_SUPPORT)
    #pragma omp parallel for schedule(static,4) shared(status) \
      MAGICK_THREADS(image,image,image->rows,1)
#endif
    for (y=0; y < (ssize_t) image->rows; y++)
    {
      register const VIEW_PIX_PTR
        *p;

      register ssize_t
        x;

      if (status == MagickFalse)
        continue;
      p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
      if (p == (const VIEW_PIX_PTR *) NULL)
      {
        status=MagickFalse;
        continue;
      }

      for (x=0; x < (ssize_t) image->columns; x++)
      {
        SET_PIXEL_RED   (new_image,GET_PIXEL_RED(image,p),   q_out);
        SET_PIXEL_GREEN (new_image,GET_PIXEL_GREEN(image,p), q_out);
        SET_PIXEL_BLUE  (new_image,GET_PIXEL_BLUE(image,p),  q_out);
//        p++;
//        q_out++;

        p += Inc_ViewPixPtr (image);
        q_out += Inc_ViewPixPtr (new_image);

      }
      if (SyncCacheViewAuthenticPixels(out_view,exception) == MagickFalse)
        status=MagickFalse;
    }

    AppendImageToList(&transform_images,new_image);
    image_view=DestroyCacheView(image_view);
    out_view=DestroyCacheView(out_view);

  }
  *images=transform_images;

  return(MagickImageFilterSignature);
}

geodist.c

/* Updated:
     3-April-2018 for v7.0.7-28
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <assert.h>
#include <math.h>
#include "vsn_defines.h"

#if IMV6OR7==7
#include "MagickCore/gem-private.h"
#endif

#include "chklist.h"


static void usage (void)
{
  printf ("Usage: -process 'geodist [OPTION]...'\n");
  printf ("Distort image.\n");
  printf ("\n");
  printf ("  s, style N     style N: 0 to 3\n");
}

static MagickBooleanType IsArg (char * pa, char * ShtOpt, char * LongOpt)
{
  if ((LocaleCompare(pa, ShtOpt)==0) || (LocaleCompare(pa, LongOpt)==0))
    return MagickTrue;

  return MagickFalse;
}


static MagickBooleanType menu(
  const int argc,
  const char **argv,
  int *pstyle)
// Returns MagickTrue if okay.
{
  int
    i;

  MagickBooleanType
    status;

  *pstyle = 0;

  status = MagickTrue;

  for (i=0; i < argc; i++) {
    char * pa = (char *)argv[i];
    //printf ("Arg %i [%s]\n", i, pa);

    if (IsArg (pa, "s", "style")==MagickTrue) {
      i++;
      *pstyle = atoi (argv[i]);
    } else {
      fprintf (stderr, "ERROR\n");
      status = MagickFalse;
    }
  }
  if (status == MagickFalse)
    usage ();

  return (status);
}


// The next function corresponds in style to functions in transform.c
// It takes one image, and returns an image.
//
static Image *GeoDImage(const Image *image,
  int style,
  ExceptionInfo *exception)
{
  Image
    *new_image;

  long double
    twoPi;

  CacheView
    *image_view,
    *out_view;

  ssize_t
    y;

  MagickBooleanType
    status;

  double
    dx,
    dy;

  assert(image != (Image *) NULL);
  assert(image->signature == MAGICK_CORE_SIG);
  if (image->debug != MagickFalse)
    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);

  twoPi = 2 * M_PI;

  dx = image->columns * 0.10;
  dy = image->rows    * 0.10;

  // Clone, and we must initialise.
  new_image=CloneImage(image, 0, 0, MagickTrue, exception);
  if (new_image == (Image *) NULL)
    return(new_image);

  if (SetNoPalette (new_image, exception) == MagickFalse)
    return (Image *)NULL;

  out_view = AcquireAuthenticCacheView(new_image,exception);

  image_view = AcquireVirtualCacheView(image,exception);

  status = MagickTrue;

#if defined(MAGICKCORE_OPENMP_SUPPORT)
  #pragma omp parallel for schedule(static,4) shared(status) \
    MAGICK_THREADS(image,image,image->rows,1)
#endif
  for (y=0; y < (ssize_t) image->rows; y++)
  {
    VIEW_PIX_PTR
      *q_out;

    register ssize_t
      x;

    if (status == MagickFalse)
      continue;

    q_out=GetCacheViewAuthenticPixels(out_view,0,y,new_image->columns,1,exception);
    if (q_out == (const VIEW_PIX_PTR *) NULL)
      status=MagickFalse;

    for (x=0; x < (ssize_t) image->columns; x++)
    {
      PIX_INFO
        src_pixel;

      switch (style)
      {
        case 0:
        default:
        {
          break;
        }
        case 1:
        {
          double
            hue, saturation, brightness;

// For v7, use:
// static MagickBooleanType sRGBTransformImage(Image *image,
//  const ColorspaceType colorspace,ExceptionInfo *exception)

/*++
          {
  double r=(double) GET_PIXEL_RED(image,q_out);
  double g=(double) GET_PIXEL_GREEN(image,q_out);
  double b=(double) GET_PIXEL_BLUE(image,q_out);
  double min = r < g ? r : g;
  if (b < min) min = b;
  double max = r > g ? r : g;
  if (b > max) max = b;
  if (max > 0.0) {
    delta = max-min;
    saturation = delta/max;
    brightness = QuantumScale*max;
    if (delta == 0.0)
      return;
    if (r == max)
      hue = (g-b)/delta;
    else {
      if (g == max) hue = 2.0+(b-r)/delta;
      else          hue = 4.0+(r-g)/delta;
    }
    hue /= 6.0;
    if (hue < 0.0) hue += 1.0;
  } else {
    saturation = 0.0;
    brightness = 0.0;
    hue = 0.0;
  }
          }
++*/

          ConvertRGBToHSB(
              GET_PIXEL_RED(image,q_out), GET_PIXEL_GREEN(image,q_out), GET_PIXEL_BLUE(image,q_out),
              &hue, &saturation, &brightness);

          // hsb are 0.0 to 1.0

          dx = (saturation - 0.5) * image->columns;
          dy = (brightness - 0.5) * image->rows;

          break;
        }
        case 2:
        {
          double
            hue, saturation, brightness, rads;

          ConvertRGBToHSB(
              GET_PIXEL_RED(image,q_out), GET_PIXEL_GREEN(image,q_out), GET_PIXEL_BLUE(image,q_out),
              &hue, &saturation, &brightness);

          rads = twoPi * hue;
          dx = sin(rads) * brightness * image->columns;
          dy = sin(rads) * brightness * image->rows;

          break;
        }
      }

#if IMV6OR7==6
      InterpolateMagickPixelPacket (
        image, image_view,
        image->interpolate,
        x + dx, y + dy, &src_pixel, exception);
#else
     InterpolatePixelInfo (
        image, image_view,
        image->interpolate,
        x + dx, y + dy, &src_pixel, exception);
#endif

      SET_PIXEL_RED   (new_image, src_pixel.red,   q_out);
      SET_PIXEL_GREEN (new_image, src_pixel.green, q_out);
      SET_PIXEL_BLUE  (new_image, src_pixel.blue,  q_out);
#if IMV6OR7==6
      SET_PIXEL_ALPHA (new_image, QuantumRange - src_pixel.opacity, q_out);
#else
      SET_PIXEL_ALPHA (new_image, src_pixel.alpha, q_out);
#endif

      q_out += Inc_ViewPixPtr (new_image);
    }
    if (SyncCacheViewAuthenticPixels(out_view,exception) == MagickFalse)
      status=MagickFalse;
  }

  image_view=DestroyCacheView(image_view);
  out_view=DestroyCacheView(out_view);

  //chklist("end GeoDImage new_image",&new_image);

  return (new_image);
}


ModuleExport size_t geodistImage(Image **images,const int argc,
  const char **argv,ExceptionInfo *exception)
{
  Image
    *image,
    *new_image;

  int
    style;

  MagickBooleanType
    Error;

  assert(images != (Image **) NULL);
  assert(*images != (Image *) NULL);
  assert((*images)->signature == MAGICK_CORE_SIG);

  Error = !menu (argc, argv, &style);

  if (Error == MagickTrue) {
    return (-1);
  }

  for (image=(*images); image != (Image *) NULL; image=GetNextImageInList(image))
  {
    new_image = GeoDImage (image, style, exception);
    if (new_image == (Image *) NULL)
      return(-1);

    //chklist("after GeoDImage: &new_image",&new_image);
    //chklist("after GeoDImage: images",images);
    //chklist("after GeoDImage: images",images);

    ReplaceImageInList(&image,new_image);
    //chkentry("after replace: &image",&image);
    *images=GetFirstImageInList(image);
    //chklist("after replace: images",images);
  }

  //chklist("end geodistImage: images",images);

  return(MagickImageFilterSignature);
}

onewhite.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include "vsn_defines.h"


// The next function corresponds in style to functions in enhance.c
// It takes one image, and returns a status.
//
// Note: Image *image is no longer const, for SetImageProperty().
//
static MagickBooleanType onewhite(Image *image,
  ExceptionInfo *exception)
{
  MagickBooleanType
    status,
    found;

  CacheView
    *image_view;

  ssize_t
    y;

  char
    text[MaxTextExtent];

  assert(image != (Image *) NULL);
  assert(image->signature == MAGICK_CORE_SIG);
  if (image->debug != MagickFalse)
    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);

  image_view = AcquireVirtualCacheView(image,exception);

  status = MagickTrue;
  found = MagickFalse;

#define LIMIT (QuantumRange - image->fuzz)

  for (y=0; y < (ssize_t) image->rows; y++)
  {
    VIEW_PIX_PTR const
      *p;

    register ssize_t
      x;


    if (status == MagickFalse)
      continue;

    p=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
    if (p == (const VIEW_PIX_PTR *) NULL)
      status=MagickFalse;

    for (x=0; x < (ssize_t) image->columns; x++)
    {
      if (  GET_PIXEL_RED(image,p)   >= LIMIT
         && GET_PIXEL_GREEN(image,p) >= LIMIT
         && GET_PIXEL_BLUE(image,p)  >= LIMIT)
      {
        sprintf (text, "%lu,%lu", x, y);
        fprintf (stderr, "onewhite: %s\n", text);
        found = MagickTrue;
        break;
      }

      p += Inc_ViewPixPtr (image);
    }
    if (found == MagickTrue) break;
  }

  if (found == MagickFalse) {
    fprintf (stderr, "onewhite: none\n");
    DeleteImageProperty (image, "filter:onewhite"); // FIXME: Or set to "none"?
  } else {
#if IMV6OR7==6
    SetImageProperty (image, "filter:onewhite", text);
#else
    SetImageProperty (image, "filter:onewhite", text, exception);
#endif
  }
  return MagickTrue;
}


ModuleExport size_t onewhiteImage(Image **images,const int argc,
  const char **argv,ExceptionInfo *exception)
{
  Image
    *image;

  MagickBooleanType
    status;

  (void) argc;
  (void) argv;

  assert(images != (Image **) NULL);
  assert(*images != (Image *) NULL);
  assert((*images)->signature == MAGICK_CORE_SIG);

  for (image=(*images); image != (Image *) NULL; image=GetNextImageInList(image))
  {
    status = onewhite(image, exception);

    if (status == MagickFalse)
      continue;
  }

  assert (status == MagickTrue);

  return(MagickImageFilterSignature);
}

nearestwhite.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include "vsn_defines.h"


typedef enum {
  ctPixel,
  ctPercent,
  ctProportion
} CoordTypeT;

typedef struct {
  double 
    incx, incy;

  CoordTypeT
    ctx, cty;

  MagickBooleanType
    do_verbose;
} nearestWhiteT;


static void usage (void)
{
  printf ("Usage: -process 'nearestwhite [OPTION]...'\n");
  printf ("Finds the white pixel nearest to the centre.\n");
  printf ("\n");
  printf ("  cx N                     centre x-coordinate\n");
  printf ("  cy N                     centre y-coordinate\n");
  printf ("  v,  verbose              write text information\n");
  printf ("\n");
}

static CoordTypeT CoordType (const char * s)
{
  char c = *(s+strlen(s)-1);

  if (c=='%' || c == 'c' || c == 'C') return ctPercent;
  if (c=='p' || c == 'P') return ctProportion;
  return ctPixel;
}

static MagickBooleanType IsArg (char * pa, char * ShtOpt, char * LongOpt)
{
  if ((LocaleCompare(pa, ShtOpt)==0) || (LocaleCompare(pa, LongOpt)==0))
    return MagickTrue;

  return MagickFalse;
}


static MagickBooleanType menu (
  const int argc,
  const char **argv,
  nearestWhiteT * nw
)
// Returns MagickTrue if okay.
{
  int
    i;

  MagickBooleanType
    status;

  status = MagickTrue;

  nw->incx = nw->incy = 50;
  nw->ctx = nw->cty = ctPercent;

  nw->do_verbose = MagickFalse;

  for (i=0; i < argc; i++) {
    char * pa = (char *)argv[i];

    if (IsArg (pa, "cx", "cx")==MagickTrue) {
      i++;
      if (i >= argc) {
        fprintf (stderr, "nearestwhite: 'cx' needs an argument\n");
        status = MagickFalse;
        break;
      }
      nw->incx = atof(argv[i]);
      nw->ctx = CoordType (argv[i]);

    } else if (IsArg (pa, "cy", "cy")==MagickTrue) {
      i++;
      if (i >= argc) {
        fprintf (stderr, "nearestwhite: 'cy' needs an argument\n");
        status = MagickFalse;
        break;
      }
      nw->incy = atof(argv[i]);
      nw->cty = CoordType (argv[i]);

    } else if (IsArg (pa, "v", "verbose")==MagickTrue) {
      nw->do_verbose = MagickTrue;
    } else {
      fprintf (stderr, "nearestwhite: ERROR: unknown option [%s]\n", pa);
      status = MagickFalse;
    }
  }

  if (nw->do_verbose) {
    fprintf (stderr, "nearestwhite options:");
    fprintf (stderr, "  cx %g", nw->incx);
    if (nw->ctx == ctPercent) fprintf (stderr, "%%");
    else if (nw->ctx == ctProportion) fprintf (stderr, "p");
    fprintf (stderr, "  cy %g", nw->incy);
    if (nw->cty == ctPercent) fprintf (stderr, "%%");
    else if (nw->cty == ctProportion) fprintf (stderr, "p");

    if (nw->do_verbose) fprintf (stderr, "  verbose");
    fprintf (stderr, "\n");
  }

  if (status == MagickFalse)
    usage ();

  return (status);
}




// The next function corresponds in style to functions in enhance.c
// It takes one image, and returns a status.
//
// Note: Image *image is no longer const, for SetImageProperty().
//
static MagickBooleanType nearestwhite(Image *image,
  nearestWhiteT * nw,
  ExceptionInfo *exception)
{
  MagickBooleanType
    status,
    found;

  CacheView
    *image_view;

  ssize_t
    y,
    nearX, nearY;

  double cx=0, cy=0, nearDistSq = 0;

  char
    text[MaxTextExtent];

  assert(image != (Image *) NULL);
  assert(image->signature == MAGICK_CORE_SIG);
  if (image->debug != MagickFalse)
    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);

  int precision = GetMagickPrecision();

  image_view = AcquireVirtualCacheView(image,exception);

  status = MagickTrue;
  found = MagickFalse;

  switch (nw->ctx) {
    case ctPixel:      cx = nw->incx; break;
    case ctPercent:    cx = nw->incx * (image->columns - 1) / 100.0; break;
    case ctProportion: cx = nw->incx * (image->columns - 1); break;
    default: status=MagickFalse;
  }

  switch (nw->cty) {
    case ctPixel:      cy = nw->incy; break;
    case ctPercent:    cy = nw->incy * (image->rows - 1) / 100.0; break;
    case ctProportion: cy = nw->incy * (image->rows - 1); break;
    default: status=MagickFalse;
  }

  if (nw->do_verbose) {
    fprintf (stderr, "nearestwhite.cx = %.*g\n", precision, cx);
    fprintf (stderr, "nearestwhite.cy = %.*g\n", precision, cy);

    //fprintf (stderr, "(fuzz = %.*g)\n", precision, image->fuzz);
  }

#define LIMIT (QuantumRange - image->fuzz)

  for (y=0; y < (ssize_t) image->rows; y++)
  {
    VIEW_PIX_PTR const
      *p;

    register ssize_t
      x;

    double
      dx, dy;

    if (status == MagickFalse)
      continue;

    p=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
    if (p == (const VIEW_PIX_PTR *) NULL)
      status=MagickFalse;

    for (x=0; x < (ssize_t) image->columns; x++)
    {
      if (  GET_PIXEL_RED(image,p)   >= LIMIT
         && GET_PIXEL_GREEN(image,p) >= LIMIT
         && GET_PIXEL_BLUE(image,p)  >= LIMIT)
      {
        if (found) {
          dx = cx - x;
          dy = cy - y;
          double DistSq = dx*dx + dy*dy;
          if (nearDistSq > DistSq) {
            nearDistSq = DistSq;
            nearX = x;
            nearY = y;
          }
        } else {
          found = MagickTrue;
          dx = cx - x;
          dy = cy - y;
          nearDistSq = dx*dx + dy*dy;
          nearX = x;
          nearY = y;
        }
      }

      p += Inc_ViewPixPtr (image);
    }
  }

  if (found == MagickFalse) {
    fprintf (stderr, "nearestwhite: none\n");
    DeleteImageProperty (image, "filter:nearestwhite"); // FIXME: Or set to "none"?
  } else {
    sprintf (text, "%lu,%lu", nearX, nearY);
    fprintf (stderr, "nearestwhite: %s\n", text);

#if IMV6OR7==6
    SetImageProperty (image, "filter:nearestwhite", text);
#else
    SetImageProperty (image, "filter:nearestwhite", text, exception);
#endif
  }
  return MagickTrue;
}


ModuleExport size_t nearestwhiteImage(Image **images,const int argc,
  const char **argv,ExceptionInfo *exception)
{
  Image
    *image;

  MagickBooleanType
    status;

  nearestWhiteT nw;

  assert(images != (Image **) NULL);
  assert(*images != (Image *) NULL);
  assert((*images)->signature == MAGICK_CORE_SIG);

  status = menu (argc, argv, &nw);

  if (status == MagickTrue) {
    for (image=(*images); image != (Image *) NULL; image=GetNextImageInList(image))
    {
      status = nearestwhite(image, &nw, exception);

      if (status == MagickFalse)
        continue;
    }
  }

  assert (status == MagickTrue);

  return(MagickImageFilterSignature);
}

allwhite.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include "vsn_defines.h"


// The next function corresponds in style to functions in enhance.c
// It takes one image, and returns a status.
//
static MagickBooleanType allwhite(const Image *image,
  ExceptionInfo *exception)
{
  MagickBooleanType
    status;

  CacheView
    *image_view;

  ssize_t
    y;


  assert(image != (Image *) NULL);
  assert(image->signature == MAGICK_CORE_SIG);
  if (image->debug != MagickFalse)
    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);

  image_view = AcquireVirtualCacheView(image,exception);

  status = MagickTrue;

  fprintf (stderr, "allwhite:\n");

#if defined(MAGICKCORE_HDRI_SUPPORT)
#define LIMIT QuantumRange-0.001
#else
#define LIMIT QuantumRange
#endif

  for (y=0; y < (ssize_t) image->rows; y++)
  {
    VIEW_PIX_PTR const
      *p;

    register ssize_t
      x;


    if (status == MagickFalse)
      continue;

    p=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
    if (p == (const VIEW_PIX_PTR *) NULL)
      status=MagickFalse;

    for (x=0; x < (ssize_t) image->columns; x++)
    {
      if (  GET_PIXEL_RED(image,p)   >= LIMIT
         && GET_PIXEL_GREEN(image,p) >= LIMIT
         && GET_PIXEL_BLUE(image,p)  >= LIMIT)
      {
        fprintf (stderr, "%lu,%lu\n", x, y);
      }

      p += Inc_ViewPixPtr (image);
    }
  }

  return MagickTrue;
}


ModuleExport size_t allwhiteImage(Image **images,const int argc,
  const char **argv,ExceptionInfo *exception)
{
  Image
    *image;

  MagickBooleanType
    status;

  (void) argc;
  (void) argv;

  assert(images != (Image **) NULL);
  assert(*images != (Image *) NULL);
  assert((*images)->signature == MAGICK_CORE_SIG);

  for (image=(*images); image != (Image *) NULL; image=GetNextImageInList(image))
  {
    status = allwhite(image, exception);

    if (status == MagickFalse)
      continue;
  }

  assert (status == MagickTrue);

  return(MagickImageFilterSignature);
}

onelightest.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include "vsn_defines.h"

// Updated:
//   19-August-2017 Use "virtual", not "authentic".


// The next function corresponds in style to functions in enhance.c
// It takes one image, and returns a status.
//
static MagickBooleanType onelightest(const Image *image,
  ExceptionInfo *exception)
{
  MagickBooleanType
    status;

  CacheView
    *image_view;

  ssize_t
    y;

  ssize_t
    xLightest,
    yLightest;

  long double
    val,
    valLightest;


  assert(image != (Image *) NULL);
  assert(image->signature == MAGICK_CORE_SIG);
  if (image->debug != MagickFalse)
    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);

  image_view = AcquireVirtualCacheView(image,exception);

  status = MagickTrue;

  valLightest = 0;
  xLightest = yLightest = 0;

  for (y=0; y < (ssize_t) image->rows; y++)
  {
    VIEW_PIX_PTR const *p;

    register ssize_t x;

    if (status == MagickFalse) continue;

    p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
    if (p == (const VIEW_PIX_PTR *) NULL) status=MagickFalse;

    for (x=0; x < (ssize_t) image->columns; x++) {
      val = GetPixelIntensity(image, p);

      if (valLightest < val) {
        valLightest = val;
        xLightest = x;
        yLightest = y;
      }
      p += Inc_ViewPixPtr (image);
    }
  }

  fprintf (stderr, "%lu,%lu\n", xLightest, yLightest);

  image_view = DestroyCacheView (image_view);

  return MagickTrue;
}


ModuleExport size_t onelightestImage(Image **images,const int argc,
  const char **argv,ExceptionInfo *exception)
{
  Image
    *image;

  MagickBooleanType
    status;

  (void) argc;
  (void) argv;

  assert(images != (Image **) NULL);
  assert(*images != (Image *) NULL);
  assert((*images)->signature == MAGICK_CORE_SIG);

  for (image=(*images); image != (Image *) NULL; image=GetNextImageInList(image))
  {
    status = onelightest(image, exception);

    if (status == MagickFalse)
      continue;
  }

  assert (status == MagickTrue);

  return(MagickImageFilterSignature);
}

midlightest.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include "vsn_defines.h"

/*
    Find coordinate of the lightest pixel.

    If there is more than one with equal lightness,
    finds the one that is nearest the centroid of those pixels.
*/


// The next function corresponds in style to functions in enhance.c
// It takes one image, and returns a status.
//
static MagickBooleanType midlightest(const Image *image,
  ExceptionInfo *exception)
{
  MagickBooleanType
    status;

  CacheView
    *image_view;

  ssize_t
    y;

  ssize_t
    xLightest,
    yLightest;

  long double
    val,
    valLightest;


  assert(image != (Image *) NULL);
  assert(image->signature == MAGICK_CORE_SIG);
  if (image->debug != MagickFalse)
    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);

  image_view = AcquireVirtualCacheView(image,exception);

  status = MagickTrue;

  valLightest = 0;
  xLightest = yLightest = 0;
  int nFound = 0;

  int sigX=0, sigY=0;

  for (y=0; y < (ssize_t) image->rows; y++)
  {
    VIEW_PIX_PTR const *p;

    register ssize_t x;

    if (status == MagickFalse) continue;

    p=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
    if (p == (const VIEW_PIX_PTR *) NULL) status=MagickFalse;

    for (x=0; x < (ssize_t) image->columns; x++) {
      val = GetPixelIntensity(image, p);

      if (valLightest < val) {
        valLightest = val;
        sigX = x;
        sigY = y;
        nFound = 1;
      } else if (valLightest == val) {
        sigX += x;
        sigY += y;
        nFound++;
      }
      p += Inc_ViewPixPtr (image);
    }
  }

  if (nFound < 2) {
    xLightest = sigX;
    yLightest = sigY;
  } else {
    float midX = sigX / (float)nFound;
    float midY = sigY / (float)nFound;

    // Find the closest pixel to (midX,midY) with value valLightest.

    float minRad=999999999e9;

    for (y=0; y < (ssize_t) image->rows; y++)
    {
      VIEW_PIX_PTR const *p;

      register ssize_t x;

      if (status == MagickFalse) continue;

      p=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
      if (p == (const VIEW_PIX_PTR *) NULL) status=MagickFalse;

      for (x=0; x < (ssize_t) image->columns; x++) {
        val = GetPixelIntensity(image, p);

        if (valLightest == val) {
          float r = hypot (x-midX, y-midY);
          if (minRad > r) {
            minRad = r;
            xLightest = x;
            yLightest = y;
          }
        }
        p += Inc_ViewPixPtr (image);
      }
    }
  }

  fprintf (stderr, "%lu,%lu\n", xLightest, yLightest);

  return MagickTrue;
}


ModuleExport size_t midlightestImage(Image **images,const int argc,
  const char **argv,ExceptionInfo *exception)
{
  Image
    *image;

  MagickBooleanType
    status;

  (void) argc;
  (void) argv;

  assert(images != (Image **) NULL);
  assert(*images != (Image *) NULL);
  assert((*images)->signature == MAGICK_CORE_SIG);

  for (image=(*images); image != (Image *) NULL; image=GetNextImageInList(image))
  {
    status = midlightest(image, exception);

    if (status == MagickFalse)
      continue;
  }

  assert (status == MagickTrue);

  return(MagickImageFilterSignature);
}

rect2eqfish.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <ctype.h>
#include "vsn_defines.h"

/* Updated:
     3-April-2018 for v7.0.7-28
*/

/* When an output pixel is outside the circle,
   the value will come from the "-virtual-pixel" setting.
*/

typedef enum {
  fullframe,
  circular
} FormatT;

typedef struct {
  double
    ifov,
    ofov,
    rad;
  FormatT
    format;
  MagickBooleanType
    do_verbose;
} FishSpecT;


static void usage (void)
{
  printf ("Usage: -process 'rect2eqfish [OPTION]...'\n");
  printf ("From an rectilinear image, makes equidistant (linear) fisheye image.\n");
  printf ("\n");
  printf ("  i, ifov N           input field of view, default 120\n");
  printf ("  o, ofov N           output field of view, default 180\n");
  printf ("  f, format string    fullframe [default] or circular\n");
  printf ("  r, radius N         output radius (overrides format)\n");
  printf ("  v, verbose          write text information to stdout\n");
  printf ("\n");
  printf ("Fields of view are angles in degrees from a corner to opposite corner.\n");
}


static MagickBooleanType IsArg (char * pa, char * ShtOpt, char * LongOpt)
{
  if ((LocaleCompare(pa, ShtOpt)==0) || (LocaleCompare(pa, LongOpt)==0))
    return MagickTrue;

  return MagickFalse;
}


static MagickBooleanType menu(
  const int argc,
  const char **argv,
  FishSpecT * pfs
)
// Returns MagickTrue if okay.
{
  int
    i;

  MagickBooleanType
    status;

  status = MagickTrue;

  pfs->ifov = 120;
  pfs->ofov = 180;
  pfs->do_verbose = MagickFalse;
  pfs->format = fullframe;
  pfs->rad = 0;

  for (i=0; i < argc; i++) {
    char * pa = (char *)argv[i];

    if (IsArg (pa, "i", "ifov")==MagickTrue) {
      i++;
      if (!isdigit ((int)*argv[i]))
      {
        fprintf (stderr, "Non-numeric argument to 'ifov'.\n");
        status = MagickFalse;
      }
      else pfs->ifov = atof(argv[i]);
      if (pfs->ifov <= 0 || pfs->ifov >= 180)
      {
        fprintf (stderr, "'ifov' must be > 0, < 180.\n");
        status = MagickFalse;
      }
    } else if (IsArg (pa, "o", "ofov")==MagickTrue) {
      i++;
      if (!isdigit ((int)*argv[i]))
      {
        fprintf (stderr, "Non-numeric argument to 'ofov'.\n");
        status = MagickFalse;
      }
      else pfs->ofov = atof(argv[i]);
      if (pfs->ofov <= 0 || pfs->ofov > 180)
      {
        fprintf (stderr, "'ofov' must be > 0, <= 180.\n");
        status = MagickFalse;
      }
    } else if (IsArg (pa, "r", "radius")==MagickTrue) {
      i++;
      if (!isdigit ((int)*argv[i]))
      {
        fprintf (stderr, "Non-numeric argument to 'radius'.\n");
        status = MagickFalse;
      }
      else pfs->rad = atof(argv[i]);
      if (pfs->rad <= 0)
      {
        fprintf (stderr, "'radius' must be > 0.\n");
        status = MagickFalse;
      }
    } else if (IsArg (pa, "f", "format")==MagickTrue) {
      i++;
      char * pa = (char *)argv[i];
      if      (LocaleCompare(pa, "circular" )==0) pfs->format = circular;
      else if (LocaleCompare(pa, "fullframe")==0) pfs->format = fullframe;
      else {
        fprintf (stderr, "rect2eqfish: ERROR: unknown 'format' [%s]\n", pa);
        status = MagickFalse;
      }
    } else if (IsArg (pa, "v", "verbose")==MagickTrue) {
      pfs->do_verbose = MagickTrue;
    } else {
      fprintf (stderr, "rect2eqfish: ERROR: unknown option [%s]\n", pa);
      status = MagickFalse;
    }
  }

  if (pfs->do_verbose) {
    fprintf (stderr, "rect2eqfish options:");

    if (pfs->do_verbose) fprintf (stderr, "  verbose");

    // FIXME: thoughout, %g should respect "-precision"
    fprintf (stderr, "  ifov %g", pfs->ifov);

    fprintf (stderr, "  ofov %g", pfs->ofov);

    fprintf (stderr, "  format ");
    if (pfs->format == circular)
      fprintf (stderr, "circular");
    else if (pfs->format == fullframe)
      fprintf (stderr, "fullframe");
    else
      fprintf (stderr, "??");


    fprintf (stderr, "\n");
  }

  if (status == MagickFalse)
    usage ();

  return (status);
}


// The next function corresponds in style to functions in transform.c
// It takes one image, and returns an image.
//
static Image *rect2eqfish(const Image *image,
  FishSpecT * pfs,
  ExceptionInfo *exception)
{
  Image
    *new_image;

  CacheView
    *image_view,
    *out_view;

  ssize_t
    y;

  MagickBooleanType
    status;

  double
    CentX,
    CentY,
    dim,
    //dim2,
    ifoc,
    ofocinv;


  assert(image != (Image *) NULL);
  assert(image->signature == MAGICK_CORE_SIG);

  if (image->debug != MagickFalse)
    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);

  // Make a clone of this image, same size but undefined pixel values:
  //
  new_image=CloneImage(image, image->columns, image->rows, MagickTrue, exception);
  if (new_image == (Image *) NULL)
    return(new_image);

  if (SetNoPalette (new_image, exception) == MagickFalse)
    return (Image *)NULL;

  image_view = AcquireVirtualCacheView(image,exception);

  out_view = AcquireAuthenticCacheView(new_image,exception);

  status = MagickTrue;

  dim = 0.0;

  if (pfs->format == circular) {
    dim = image->columns;
    if (dim > image->rows) dim = image->rows;
  } else if (pfs->format == fullframe) {
    dim = hypot (image->columns, image->rows);
  }

  if (pfs->rad > 0) dim = 2 * pfs->rad;

  if (dim == 0.0) {
    fprintf (stderr, "BUG");
    return (Image *) NULL;
  }

  //dim2 = dim / 2.0;  // But not used??

  CentX = (image->columns - 1) / 2.0;
  CentY = (image->rows    - 1) / 2.0;

  ifoc = dim / (2 * tan(pfs->ifov*M_PI/360));

  ofocinv = (pfs->ofov*M_PI)/(dim * 180);

  if (pfs->do_verbose) {
    fprintf (stderr, "CentX=%g", CentX);
    fprintf (stderr, "  CentY=%g", CentY);
    fprintf (stderr, "  dim=%g", dim);
    fprintf (stderr, "  ifoc=%g", ifoc);
    fprintf (stderr, "  ofocinv=%g  (1/%g)\n", ofocinv, 1.0 / ofocinv);

    fprintf (stderr, "\nFX equivalent:\n");
    fprintf (stderr, "  -fx \"");
    fprintf (stderr, "xd=i-%g;", CentX);
    fprintf (stderr, "yd=j-%g;", CentY);
    fprintf (stderr, "rd=hypot(xd,yd);");
    fprintf (stderr, "phiang=%g*rd;", ofocinv);
      // (phiang would have a different formula for other fisheye projections.)
    fprintf (stderr, "rr=%g*tan(phiang);", ifoc);
    fprintf (stderr, "xs=(rd?rr/rd:0)*xd+%g;", CentX);
    fprintf (stderr, "ys=(rd?rr/rd:0)*yd+%g;", CentY);
    //fprintf (stderr, "(rd>$dim2)?$bgc:u.p{xs,ys}";
    fprintf (stderr, "u.p{xs,ys}\"\n");
  }


#if defined(MAGICKCORE_OPENMP_SUPPORT)
  #pragma omp parallel for schedule(static,4) shared(status) \
    MAGICK_THREADS(new_image,new_image,new_image->rows,1)
#endif
  for (y=0; y < (ssize_t) new_image->rows; y++)
  {
    register VIEW_PIX_PTR
      *q;

    register ssize_t
      x;

    if (status == MagickFalse)
      continue;

    q=GetCacheViewAuthenticPixels(out_view,0,y,new_image->columns,1,exception);
    if (q == (const VIEW_PIX_PTR *) NULL)
    {
      status=MagickFalse;
      continue;
    }

    for (x=0; x < (ssize_t) image->columns; x++)
    {
      double
        xd,
        yd,
        phi,
        rd,
        rr,
        mult,
        xs,
        ys;

      PIX_INFO
        src_pixel;

      xd = x - CentX;
      yd = y - CentY;

      rd = hypot (xd, yd);

      phi = ofocinv * rd;
      // (phi would have a different formula for other fisheye projections.)
      rr = ifoc * tan(phi);

      if (rd == 0) {
        xs = CentX;
        ys = CentY;
      } else {
        mult = rr / rd;
        xs = CentX + mult * xd;
        ys = CentY + mult * yd;
      }

#if IMV6OR7==6
      InterpolateMagickPixelPacket (
        image, image_view,
        image->interpolate,
        xs, ys, &src_pixel, exception);
#else
     InterpolatePixelInfo (
        image, image_view,
        image->interpolate,
        xs, ys, &src_pixel, exception);
#endif

      SET_PIXEL_RED   (new_image, src_pixel.red, q);
      SET_PIXEL_GREEN (new_image, src_pixel.green, q);
      SET_PIXEL_BLUE  (new_image, src_pixel.blue, q);
#if IMV6OR7==6
      SET_PIXEL_ALPHA (new_image, QuantumRange - src_pixel.opacity, q);
#else
      SET_PIXEL_ALPHA (new_image, src_pixel.alpha, q);
#endif

      q += Inc_ViewPixPtr (new_image);
    }
    if (SyncCacheViewAuthenticPixels(out_view,exception) == MagickFalse)
      status=MagickFalse;
  }
  out_view=DestroyCacheView(out_view);
  image_view=DestroyCacheView(image_view);

  return (new_image);
}



/*
We replace each image as we come across it.
An alternative technique is to create a new list, and add new images to it;
at the end, replace the original list with the new one.

That alternative is slightly more complex
and needs more memory as it would hold all input and output images in memory.
*/

ModuleExport size_t rect2eqfishImage(Image **images,const int argc,
  const char **argv,ExceptionInfo *exception)
{
  Image
    *image,
    *new_image;

  MagickBooleanType
    status;

  FishSpecT
    fs;

  assert(images != (Image **) NULL);
  assert(*images != (Image *) NULL);
  assert((*images)->signature == MAGICK_CORE_SIG);

  status = menu (argc, argv, &fs);
  if (status == MagickFalse)
    return (-1);

  for (image=(*images); image != (Image *) NULL; image=GetNextImageInList(image))
  {
    new_image = rect2eqfish(image, &fs, exception);
    if (new_image == (Image *) NULL)
      return(-1);

    ReplaceImageInList(&image,new_image);
    // Replace messes up the images pointer. Make it good:
    *images=GetFirstImageInList(image);
  }

  return(MagickImageFilterSignature);
}

img2knl.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include "vsn_defines.h"

// Updated:
//   16-September-2016  Output "-" for pixels with alpha < 50%.
//   6-September-2017 for v7.

// The next function corresponds in style to functions in enhance.c
// It takes one image, and returns a status.
//
static MagickBooleanType WriteKernel(Image *image,
  ExceptionInfo *exception)
{
  CacheView
    *image_view;

  ssize_t
    y;

  MagickBooleanType
    status;

  int
    precision;

  assert(image != (Image *) NULL);
  assert(image->signature == MAGICK_CORE_SIG);
  if (image->debug != MagickFalse)
    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);

  precision = GetMagickPrecision();

  if (SetNoPalette (image, exception) == MagickFalse)
    return MagickFalse;

  status=MagickTrue;
  image_view=AcquireAuthenticCacheView(image,exception);

#define OUTFILE stdout

  fprintf (OUTFILE, "%ix%i:",
    (int)image->columns, (int)image->rows);

  long double SemiQuantum = QuantumRange / 2.0;

  for (y=0; y < (ssize_t) image->rows; y++)
  {
    register const VIEW_PIX_PTR
      *p;

    register ssize_t
      x;

    if (status == MagickFalse)
      continue;

    p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
    if (p == (const VIEW_PIX_PTR *) NULL)
    {
      status=MagickFalse;
      continue;
    }

    for (x=0; x < (ssize_t) image->columns; x++)
    {
      if (x || y) fprintf (OUTFILE, ",");

      if (IS_ALPHA_CH(image) && GET_PIXEL_ALPHA(image,p) < SemiQuantum) {
        fprintf (OUTFILE, "-");
      } else {
        fprintf (OUTFILE, "%.*g",
          precision, GetPixelIntensity(image, p) / QuantumRange);
      }
      p += Inc_ViewPixPtr (image);
    }
  }

  fprintf (OUTFILE, "\n");

  image_view=DestroyCacheView(image_view);

  return(status);
}

// Style of next is for "-process".
// It loops through all images in a list, processing each in-place.
//
ModuleExport size_t img2knlImage(Image **images,const int argc,
  const char **argv,ExceptionInfo *exception)
{
  Image
    *image;

  MagickBooleanType
    status;

  (void) argc;
  (void) argv;


  assert(images != (Image **) NULL);
  assert(*images != (Image *) NULL);
  assert((*images)->signature == MAGICK_CORE_SIG);

  for (image=(*images); image != (Image *) NULL; image=GetNextImageInList(image))
  {
    status = WriteKernel (image,exception);

    if (status == MagickFalse)
      continue;
  }
  assert (status == MagickTrue);

  return(MagickImageFilterSignature);
}

interppix.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include "vsn_defines.h"


typedef enum {
  ctPixel,
  ctPercent,
  ctProportion
} CoordTypeT;

typedef struct {
  double inx, iny, x, y;
} intPixT;


static CoordTypeT CoordType (const char * s)
{
  char c = *(s+strlen(s)-1);

  if (c=='%' || c == 'c') return ctPercent;
  if (c=='p') return ctProportion;
  return ctPixel;
}


ModuleExport size_t interppixImage(Image **images,const int argc,
  const char **argv,ExceptionInfo *exception)
{
  Image
    *image;

  CacheView
    *image_view;

  int
    i,
    argNdx;

  intPixT
    ip;

  int
    precision;

  CoordTypeT
    ct;

  PIX_INFO
    src_pixel;

  (void) argc;
  (void) argv;

  assert(images != (Image **) NULL);
  assert(*images != (Image *) NULL);
  assert((*images)->signature == MAGICK_CORE_SIG);

  precision = GetMagickPrecision();

  if (argc < 2 || (argc % 2) != 0) {
    fprintf (stderr, "interppix needs x and y coordinate-pairs\n");
    return -1;
  }

  i = 0;

  for (image=(*images); image != (Image *) NULL; image=GetNextImageInList(image))
  {
    argNdx = 0;
    while (argNdx < argc) {
      ip.inx = atof (argv[argNdx]);
      ip.iny = atof (argv[argNdx+1]);

      ct = CoordType (argv[argNdx]);
      if (ct == ctPercent) {
        ip.x = ip.inx * (image->columns-1) / 100.0;
      } else if (ct == ctProportion) {
        ip.x = ip.inx * (image->columns-1);
      } else {
        ip.x = ip.inx;
      }

      ct = CoordType (argv[argNdx+1]);
      if (ct == ctPercent) {
        ip.y = ip.iny * (image->rows-1) / 100.0;
      } else if (ct == ctProportion) {
        ip.y = ip.iny * (image->rows-1);
      } else {
        ip.y = ip.iny;
      }

      fprintf (stderr, "interppix: %i: ", i);

      fprintf (stderr, "@%.*g,%.*g",
        precision, ip.x, precision, ip.y);

      image_view = AcquireVirtualCacheView(image,exception);

#if IMV6OR7==6
      InterpolateMagickPixelPacket (
        image, image_view,
        image->interpolate,
        ip.x, ip.y, &src_pixel, exception);

      fprintf (stderr, " (%.*lg,%.*lg,%.*lg,%.*lg,%.*lg)",
        precision, (double)src_pixel.red,
        precision, (double)src_pixel.green,
        precision, (double)src_pixel.blue,
        precision, (double)(QuantumRange - src_pixel.opacity),
        precision, (double)src_pixel.index);

      fprintf (stderr, " (%.*lg%%,%.*lg%%,%.*lg%%,%.*lg%%,%.*lg%%)",
        precision, (double)(src_pixel.red * 100.0 / QuantumRange),
        precision, (double)(src_pixel.green * 100.0 / QuantumRange),
        precision, (double)(src_pixel.blue * 100.0 / QuantumRange),
        precision, (double)(100.0 - src_pixel.opacity * 100.0 / QuantumRange),
        precision, (double)(src_pixel.index * 100.0 / QuantumRange));
#else
     InterpolatePixelInfo (
        image, image_view,
        image->interpolate,
        ip.x, ip.y, &src_pixel, exception);

      fprintf (stderr, " (%.*lg,%.*lg,%.*lg,%.*lg,%.*lg)",
        precision, (double)src_pixel.red,
        precision, (double)src_pixel.green,
        precision, (double)src_pixel.blue,
        precision, (double)src_pixel.alpha,
        precision, (double)src_pixel.index);

      fprintf (stderr, " (%.*lg%%,%.*lg%%,%.*lg%%,%.*lg%%,%.*lg%%)",
        precision, (double)(src_pixel.red * 100.0 / QuantumRange),
        precision, (double)(src_pixel.green * 100.0 / QuantumRange),
        precision, (double)(src_pixel.blue * 100.0 / QuantumRange),
        precision, (double)(src_pixel.alpha * 100.0 / QuantumRange),
        precision, (double)(src_pixel.index * 100.0 / QuantumRange));
#endif

      fprintf (stderr, "\n");

      argNdx += 2;

    }

    i++;
  }

  return(MagickImageFilterSignature);
}

mkgauss.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <math.h>
#include "vsn_defines.h"

/* Updated:
     15-August-2017 Permit 'c' suffix meaning "percent".
*/

typedef struct {
  size_t
    width;
  long double
    mean,
    std_dev,
    skew;
  int
    skew_meth;
  MagickBooleanType
    mean_IsPc,
    std_dev_IsPc,
    skew_IsPc,
    rev_skew,
    do_zeroize,
    do_cumul,
    do_norm,
    do_verbose;
} mkgaussT;

static void usage (void)
{
  printf ("Usage: -process 'mkgauss [OPTION]...'\n");
  printf ("Make a Gaussian clut.\n");
  printf ("\n");
  printf ("  w,  width N              output width in pixels\n");
  printf ("  m,  mean N               mean\n");
  printf ("  sd, standarddeviation N  standard deviation\n");
  printf ("  k,  skew N               skew to place peak way from mean\n");
  printf ("  st, skewtype N           skew type: 1 or 2\n");
  printf ("  z,  zeroize              subtract minimum value\n");
  printf ("  c,  cumul                cumulate the values\n");
  printf ("  n,  norm                 normalise output so maximum count is quantum\n");
  printf ("  v,  verbose              write text information to stdout\n");
  printf ("\n");
}


static MagickBooleanType IsArg (char * pa, char * ShtOpt, char * LongOpt)
{
  if ((LocaleCompare(pa, ShtOpt)==0) || (LocaleCompare(pa, LongOpt)==0))
    return MagickTrue;

  return MagickFalse;
}


static inline MagickBooleanType EndsPc (const char *s)
{
  char ch = *(s+strlen(s)-1);
  return ((ch == '%') || (ch == 'c'));
}

static MagickBooleanType menu(
  const int argc,
  const char **argv,
  mkgaussT * pmg
)
// Returns MagickTrue if okay.
{
  int
    i;

  MagickBooleanType
    status;

  status = MagickTrue;

  pmg->width = 256;
  pmg->mean = 50;
  pmg->mean_IsPc = MagickTrue;

  pmg->std_dev = 30;
  pmg->std_dev_IsPc = MagickTrue;

  pmg->skew = 0;
  pmg->skew_IsPc = MagickTrue;
  pmg->skew_meth = 1;

  pmg->do_zeroize = pmg->do_cumul = pmg->do_norm = pmg->do_verbose = MagickFalse;
  pmg->rev_skew = MagickFalse;

  for (i=0; i < argc; i++) {
    char * pa = (char *)argv[i];

    if (IsArg (pa, "w", "width")==MagickTrue) {
      i++;
      if (i >= argc) {
        fprintf (stderr, "mkgauss: 'width' needs an argument\n");
        status = MagickFalse;
        break;
      }
      pmg->width = atoi(argv[i]);
      if (pmg->width < 2) {
        fprintf (stderr, "mkgauss: invalid 'width' [%s]\n", argv[i]);
        status = MagickFalse;
      }
    } else if (IsArg (pa, "m", "mean")==MagickTrue) {
      i++;
      if (i >= argc) {
        fprintf (stderr, "mkgauss: 'mean' needs an argument\n");
        status = MagickFalse;
        break;
      }
      pmg->mean = atof(argv[i]);
      pmg->mean_IsPc = EndsPc (argv[i]);
    } else if (IsArg (pa, "sd", "standarddeviation")==MagickTrue) {
      i++;
      if (i >= argc) {
        fprintf (stderr, "mkgauss: 'standarddeviation' needs an argument\n");
        status = MagickFalse;
        break;
      }
      pmg->std_dev = atof(argv[i]);
      pmg->std_dev_IsPc = EndsPc (argv[i]);
      if (pmg->std_dev <= 0) {
        fprintf (stderr, "mkgauss: invalid 'standarddeviation' [%s]\n", argv[i]);
        status = MagickFalse;
      }
    } else if (IsArg (pa, "k", "skew")==MagickTrue) {
      i++;
      if (i >= argc) {
        fprintf (stderr, "mkgauss: 'skew' needs an argument\n");
        status = MagickFalse;
        break;
      }
      pmg->skew = atof(argv[i]);
      pmg->skew_IsPc = EndsPc (argv[i]);
      pmg->rev_skew = (pmg->skew > 0);
    } else if (IsArg (pa, "sm", "skewmethod")==MagickTrue) {
      i++;
      if (i >= argc) {
        fprintf (stderr, "mkgauss: 'skewmethod' needs an argument\n");
        status = MagickFalse;
        break;
      }
      pmg->skew_meth = atoi(argv[i]);
    } else if (IsArg (pa, "z", "zeroize")==MagickTrue) {
      pmg->do_zeroize = MagickTrue;
    } else if (IsArg (pa, "n", "norm")==MagickTrue) {
      pmg->do_norm = MagickTrue;
    } else if (IsArg (pa, "c", "cumul")==MagickTrue) {
      pmg->do_cumul = MagickTrue;
    } else if (IsArg (pa, "v", "verbose")==MagickTrue) {
      pmg->do_verbose = MagickTrue;
    } else {
      fprintf (stderr, "mkgauss: ERROR: unknown option [%s]\n", pa);
      status = MagickFalse;
    }
  }

  if (pmg->do_verbose) {
    fprintf (stderr, "mkgauss options:  ");
    fprintf (stderr, "  width %lu", pmg->width);
    fprintf (stderr, "  mean %g", (double)pmg->mean);
    if (pmg->mean_IsPc == MagickTrue) fprintf (stderr, "%%");

    fprintf (stderr, "  standarddeviation %g", (double)pmg->std_dev);
    if (pmg->std_dev_IsPc == MagickTrue) fprintf (stderr, "%%");

    fprintf (stderr, "  skew %g", (double)pmg->skew);
    if (pmg->skew_IsPc == MagickTrue) fprintf (stderr, "%%");
    fprintf (stderr, "  skewmethod %i", pmg->skew_meth);

    if (pmg->do_cumul)   fprintf (stderr, "  cumul");
    if (pmg->do_norm)    fprintf (stderr, "  norm");
    if (pmg->do_verbose) fprintf (stderr, "  verbose");
    fprintf (stderr, "\n");
  }

  if (status == MagickFalse)
    usage ();

  return (status);
}


#if defined(MAGICKCORE_HDRI_SUPPORT)
#define ADD_HALF
#else
#define ADD_HALF +0.5
#endif


// The next function corresponds in style to functions in transform.c
// It takes one image (but ignores it, aside from CloneImage),
// and returns an image.
//
static Image *make_gauss(const Image *image,
  mkgaussT * pmg,
  ExceptionInfo *exception)
{
  Image
    *new_image;

  CacheView
    *out_view;

  ssize_t
    y;

  int
    precision;

  MagickBooleanType
    status;

  long double
    fmean,
    fstd_dev,
    fskew,
    fpow,
    gnd_fact,
    two_sdsq,
    slopeA,
    slopeB;

  precision = GetMagickPrecision();

  if (pmg->mean_IsPc == MagickTrue) {
    fmean = pmg->mean / 100.0;
  } else {
    fmean = pmg->mean / pmg->width;
  }

  if (pmg->std_dev_IsPc == MagickTrue) {
    fstd_dev = pmg->std_dev / 100.0;
  } else {
    fstd_dev = pmg->std_dev / pmg->width;
  }

  gnd_fact = 1.0 / (fstd_dev * sqrt (2 * M_PI));
  two_sdsq = 2 * fstd_dev * fstd_dev;

  if (pmg->skew_IsPc == MagickTrue) {
    fskew = fmean + (pmg->rev_skew ? -pmg->skew : pmg->skew) / 100.0;
  } else {
    fskew = fmean + (pmg->rev_skew ? -pmg->skew : pmg->skew) / pmg->width;
  }

  fpow = 1.0;
  if (fskew != 0.0) {
    long double logskew = log(fskew);
    if (logskew == 0.0) {
      fprintf (stderr, "logskew is zero");
      return (Image *) NULL;
    }
    fpow = log(fmean) / logskew;
  }

  if (fskew==0 || fskew==1) {
    slopeA = slopeB = 0;
  } else {
    slopeA = 0.5 / fskew;
    slopeB = 0.5 / (1 - fskew);
  }

  if (image->debug != MagickFalse)
    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);

  if (pmg->do_verbose) {
    fprintf (stderr, "mkgauss: Input image [%s] depth is %i\n",
      image->filename, (int)image->depth);

    fprintf (stderr, "  mean %.*g  sd %.*g  skew %.*g  pow %.*g  slopeA %.*g  slopeB %.*g\n",
      precision, (double)fmean, precision, (double)fstd_dev,
      precision, (double)fskew, precision, (double)fpow,
      precision, (double)slopeA, precision, (double)slopeB);
  }

  new_image=CloneImage(image, pmg->width, 1, MagickTrue, exception);
  if (new_image == (Image *) NULL)
    return(new_image);

  if (SetNoPalette (new_image, exception) == MagickFalse)
    return (Image *)NULL;

  SetAllBlack (new_image, exception);

  status=MagickTrue;

  out_view=AcquireAuthenticCacheView(new_image,exception);

  for (y=0; y < (ssize_t) new_image->rows; y++)
  {
    VIEW_PIX_PTR
      *q_out;

    ssize_t
      x;

    long double
      xf,
      value,
      min_value,
      max_value,
      sum_value,
      delta_v,
      mult_fact,
      pix_value;

    if (status == MagickFalse)
      continue;

    q_out=GetCacheViewAuthenticPixels(out_view,0,y,new_image->columns,1,exception);
    if (q_out == (const VIEW_PIX_PTR *) NULL) {
      fprintf (stderr, "mkgauss: bad GetCacheView out_view\n");
      status=MagickFalse;
    }

    max_value = 0.0;
    min_value = QuantumRange + 1.0;
    sum_value = 0.0;

    mult_fact = 1.0;

    for (x=0; x < (ssize_t) new_image->columns; x++)
    {
      xf = x / (long double)(pmg->width-1);
      //if (fpow != 1.0) xf = pow (xf, fpow);
      //value = gnd_fact * exp (-(xf-fmean)*(xf-fmean)/two_sdsq);

      //if (fpow != 1.0) xf = pow (fabs(xf-fmean), fpow);
      //else xf -= fmean;

      if (pmg->rev_skew) xf = 1.0 - xf;

      switch (pmg->skew_meth) {
        case 1: default:
          if (fpow != 1.0) xf = pow (xf, fpow);
          value = gnd_fact * exp (-(xf-fmean)*(xf-fmean)/two_sdsq);
          break;
        case 2:
          xf = (xf < fskew) ? xf * slopeA : (1-xf) * slopeB;
          value = gnd_fact * exp (-(xf-fmean)*(xf-fmean)/two_sdsq);
          break;
      }

      if (max_value < value) max_value = value;
      if (min_value > value) min_value = value;
      sum_value += value;

      //FormatLocaleFile (stdout, "xf=%.*g value=%.*g\n",
      //  precision, (double)xf, precision, (double)value);
    }

    if (pmg->do_verbose) {
      FormatLocaleFile (stderr,
        "  min_value %.*g, max_value %.*g, sum_value %.*g\n",
        precision, (double)min_value,
        precision, (double)max_value,
        precision, (double)sum_value);
    }

    if (pmg->do_cumul && pmg->do_zeroize) {
      sum_value -= min_value * pmg->width;

      if (pmg->do_verbose) {
        FormatLocaleFile (stderr, "  revised sum_value %.*g\n",
          precision, (double)sum_value);
      }
    } else if (pmg->do_zeroize) {
      max_value -= min_value;
      if (pmg->do_verbose) {
        FormatLocaleFile (stderr, "  revised max_value %.*g\n",
          precision, (double)max_value);
      }
    }

    delta_v = pmg->do_zeroize ? min_value : 0.0;

    mult_fact = QuantumRange;

    if (pmg->do_norm && pmg->do_cumul) {
      mult_fact = QuantumRange / sum_value;
    } else if (pmg->do_norm) {
      mult_fact = QuantumRange / max_value;
    } else if (pmg->do_cumul) {
      mult_fact = QuantumRange / (double)pmg->width;
    }

    if (pmg->do_verbose) {
      FormatLocaleFile (stderr, "  mult_fact %.*g\n",
        precision, (double)mult_fact);
    }

    sum_value = 0.0;
    for (x=0; x < (ssize_t) new_image->columns; x++)
    {
      xf = x / (long double)(pmg->width-1);

      //if (fpow != 1.0) xf = pow (xf, fpow);
      //value = gnd_fact * exp (-(xf-fmean)*(xf-fmean)/two_sdsq) - delta_v;

      if (pmg->rev_skew) xf = 1.0 - xf;

//      if (fpow != 1.0) xf = pow (xf, fpow);
//      value = gnd_fact * exp (-(xf-fmean)*(xf-fmean)/two_sdsq) - delta_v;

//--      xf = (xf < fskew) ? xf * slopeA : (1-xf) * slopeB;
//--      value = gnd_fact * exp (-(xf-fmean)*(xf-fmean)/two_sdsq) - delta_v;

      switch (pmg->skew_meth) {
        case 1: default:
          if (fpow != 1.0) xf = pow (xf, fpow);
          value = gnd_fact * exp (-(xf-fmean)*(xf-fmean)/two_sdsq) - delta_v;
          break;
        case 2:
          xf = (xf < fskew) ? xf * slopeA : (1-xf) * slopeB;
          value = gnd_fact * exp (-(xf-fmean)*(xf-fmean)/two_sdsq) - delta_v;
          break;
      }

      if (pmg->do_cumul) {
        sum_value += value;
        pix_value = sum_value * mult_fact ADD_HALF ;
      } else {
        pix_value = value * mult_fact ADD_HALF ;
      }

      //FormatLocaleFile (stdout,
      //  "xf=%.*g value=%.*g sum_value=%.*g pix_value=%.*g\n",
      //  precision, (double)xf,
      //  precision, (double)value,
      //  precision, (double)sum_value,
      //  precision, (double)pix_value);

      SET_PIXEL_RED   (new_image, pix_value, q_out);
      SET_PIXEL_GREEN (new_image, pix_value, q_out);
      SET_PIXEL_BLUE  (new_image, pix_value, q_out);

      q_out += Inc_ViewPixPtr (new_image);
    }

    if (SyncCacheViewAuthenticPixels(out_view,exception) == MagickFalse) {
      fprintf (stderr, "bad sync\n");
      status=MagickFalse;
    }
  }

  out_view=DestroyCacheView(out_view);

  return (new_image);
}


ModuleExport size_t mkgaussImage(Image **images,const int argc,
  const char **argv,ExceptionInfo *exception)
{
  Image
    *image,
    *new_image;

  MagickBooleanType
    status;

  mkgaussT
    mkgauss;

  assert(images != (Image **) NULL);
  assert(*images != (Image *) NULL);
  assert((*images)->signature == MAGICK_CORE_SIG);

  status = menu (argc, argv, &mkgauss);
  if (status == MagickFalse)
    return (-1);

  // Point to the last image in the list
  //
  image=GetLastImageInList(*images);
  if (image == (Image *) NULL) {
    fprintf (stderr, "mkgauss: no images in list\n");
    return (-1);
  }

  new_image = make_gauss(image, &mkgauss, exception);
  if (new_image == (Image *) NULL)
    return(-1);

  // Add the new image to the end of the list:
  AppendImageToList(images,new_image);

  return(MagickImageFilterSignature);
}

mkhisto.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <assert.h>
#include <math.h>
#include <ctype.h>
#include "vsn_defines.h"

// Latest update:
//    27 September 2015
//    1 February 2016: for v7.
//    3 April 2018 for v7.0.7-28
//    9 April 2018 added log option.
//    14 March 2020 Limit processing to 1, 2 or 3 channels.

typedef struct {
  int
    cap_numbuckets;
  int
    mult;
  MagickBooleanType
    do_alpha,
    do_log,
    do_cumul,
    do_norm,
    do_verbose;
} mkhistoT;

/* Maybe see transform.c TransformImages and TransformImage.
*/

#define DEFAULT_CAP 65536

static void usage (void)
{
  printf ("Usage: -process 'mkhisto [OPTION]...'\n");
  printf ("Make a histogram image.\n");
  printf ("\n");
  printf ("  b, capnumbuckets N  limit number of buckets to N\n");
  printf ("  m, multiply N       multiply counts by N\n");
  printf ("  r, regardalpha      pre-multiply count by alpha\n");
  printf ("  l, log              use logs of counts\n");
  printf ("  c, cumul            make cumulative histogram\n");
  printf ("  n, norm             normalise output so maximum count is quantum\n");
  printf ("  v, verbose          write text information to stdout\n");
  printf ("\n");
  printf ("Default capnumbuckets is %i.\n", DEFAULT_CAP);
  printf ("Default multiply is 1.\n");
}


static MagickBooleanType IsArg (char * pa, char * ShtOpt, char * LongOpt)
{
  if ((LocaleCompare(pa, ShtOpt)==0) || (LocaleCompare(pa, LongOpt)==0))
    return MagickTrue;

  return MagickFalse;
}


static MagickBooleanType menu(
  const int argc,
  const char **argv,
  mkhistoT * mkh
)
// Returns MagickTrue if okay.
{
  int
    i;

  MagickBooleanType
    status;

  status = MagickTrue;

  mkh->cap_numbuckets = DEFAULT_CAP;
  mkh->mult = 1;
  mkh->do_norm =
    mkh->do_alpha =
    mkh->do_cumul =
    mkh->do_verbose =
    mkh->do_log =
      MagickFalse;

  //printf ("mkhisto argc=%i\n", argc);
  for (i=0; i < argc; i++) {
    char * pa = (char *)argv[i];
    //printf ("Arg %i [%s]\n", i, pa);

    if (IsArg (pa, "n", "norm")==MagickTrue) {
      mkh->do_norm = MagickTrue;
    } else if (IsArg (pa, "r", "regardalpha")==MagickTrue) {
      mkh->do_alpha = MagickTrue;
    } else if (IsArg (pa, "l", "log")==MagickTrue) {
      mkh->do_log = MagickTrue;
    } else if (IsArg (pa, "c", "cumul")==MagickTrue) {
      mkh->do_cumul = MagickTrue;
    } else if (IsArg (pa, "b", "capnumbuckets")==MagickTrue) {
      i++;
      if (!isdigit ((int)*argv[i]))
      {
        fprintf (stderr, "Non-numeric argument to 'capnumbuckets'.\n");
        status = MagickFalse;
      }
      else mkh->cap_numbuckets = atoi (argv[i]);
    } else if (IsArg (pa, "m", "multiply")==MagickTrue) {
      i++;
      if (!isdigit ((int)*argv[i]))
      {
        fprintf (stderr, "Non-numeric argument to 'multiply'.\n");
        status = MagickFalse;
      }
      else mkh->mult = atoi (argv[i]);
    } else if (IsArg (pa, "v", "verbose")==MagickTrue) {
      mkh->do_verbose = MagickTrue;
    } else {
      fprintf (stderr, "mkhisto: ERROR: unknown option [%s]\n", pa);
      status = MagickFalse;
    }
  }
  if (mkh->cap_numbuckets < 1)
    status = MagickFalse;

  if (mkh->do_verbose) {
    fprintf (stderr,
     "mkhisto options: capnumbuckets %i  multiply %i  ",
      mkh->cap_numbuckets, mkh->mult);
    if (mkh->do_alpha)   fprintf (stderr, "regardalpha ");
    if (mkh->do_log)     fprintf (stderr, "log ");
    if (mkh->do_cumul)   fprintf (stderr, "cumul ");
    if (mkh->do_norm)    fprintf (stderr, "norm ");
    if (mkh->do_verbose) fprintf (stderr, "verbose ");
    fprintf (stderr, "\n");
  }

  if (status == MagickFalse)
    usage ();

  return (status);
}


static MagickBooleanType MultiplyColor (
  const Image *new_image,
  CacheView *out_view,
  long double mult_fact,
  ExceptionInfo *exception)
/* For all pixels, multiply RGB channels by a factor.
*/
{
  ssize_t
    y;

  MagickBooleanType
    status = MagickTrue;

  int numChannels = NumColChannels (new_image);

  if (mult_fact != 1.0) {
#if defined(MAGICKCORE_OPENMP_SUPPORT)
    #pragma omp parallel for schedule(static,4) shared(status)\
      MAGICK_THREADS(new_image,new_image,new_image->rows,1)
#endif

    for (y=0; y < (ssize_t) new_image->rows; y++)
    {
      register VIEW_PIX_PTR
        *q;

      register ssize_t
        x;

      if (status == MagickFalse)
        continue;

      q=GetCacheViewAuthenticPixels(out_view,0,y,new_image->columns,1,exception);
      if (q == (const VIEW_PIX_PTR *) NULL)
        status=MagickFalse;

      for (x=0; x < (ssize_t) new_image->columns; x++)
      {
#if defined(MAGICKCORE_HDRI_SUPPORT)
        SET_PIXEL_RED (new_image,
          GET_PIXEL_RED (new_image, q) * mult_fact,
          q);
        if (numChannels >= 2) SET_PIXEL_GREEN (new_image,
          GET_PIXEL_GREEN (new_image, q) * mult_fact,
          q);
        if (numChannels >= 3) SET_PIXEL_BLUE (new_image,
          GET_PIXEL_BLUE (new_image, q) * mult_fact,
          q);
#else
        SET_PIXEL_RED (new_image,
          GET_PIXEL_RED (new_image, q) * mult_fact + 0.5,
          q);
        if (numChannels >= 2) SET_PIXEL_GREEN (new_image,
          GET_PIXEL_GREEN (new_image, q) * mult_fact + 0.5,
          q);
        if (numChannels >= 3) SET_PIXEL_BLUE (new_image,
          GET_PIXEL_BLUE (new_image, q) * mult_fact + 0.5,
          q);
#endif
        q += Inc_ViewPixPtr (new_image);
      }
      if (SyncCacheViewAuthenticPixels(out_view,exception) == MagickFalse)
        status=MagickFalse;
    }
  }
  return (status);
}


static MagickBooleanType CumulateColor (
  const Image *image,
  CacheView *out_view,
  long double mult_fact,
  ExceptionInfo *exception)
/* For all pixels, cumulate RGB channels.
   The accumulation is across the entire image, not each row separately.
*/
{
  ssize_t
    y;

  MagickBooleanType
    status = MagickTrue;

  int numChannels = NumColChannels (image);

  long double
    cumul_red, cumul_green, cumul_blue;

  cumul_red = cumul_green = cumul_blue = 0;

// I suppose multi-threading wouldn't work here.
//
//#if defined(MAGICKCORE_OPENMP_SUPPORT)
//    #pragma omp parallel for schedule(static,4) shared(status)
//      MAGICK_THREADS(image,image,image->rows,1)
//#endif

  for (y=0; y < (ssize_t) image->rows; y++)
  {
    register VIEW_PIX_PTR
      *q;

    register ssize_t
      x;

    if (status == MagickFalse)
      continue;

    q=GetCacheViewAuthenticPixels(out_view,0,y,image->columns,1,exception);
    if (q == (const VIEW_PIX_PTR *) NULL)
      status=MagickFalse;

    for (x=0; x < (ssize_t) image->columns; x++)
    {
      cumul_red   += GET_PIXEL_RED   (image, q);
      cumul_green += GET_PIXEL_GREEN (image, q);
      cumul_blue  += GET_PIXEL_BLUE  (image, q);

      SET_PIXEL_RED   (image, mult_fact * cumul_red,   q);
      if (numChannels >= 2) SET_PIXEL_GREEN (image, mult_fact * cumul_green, q);
      if (numChannels >= 3) SET_PIXEL_BLUE  (image, mult_fact * cumul_blue,  q);

      q += Inc_ViewPixPtr (image);
    }
    if (SyncCacheViewAuthenticPixels(out_view,exception) == MagickFalse)
      status=MagickFalse;
  }
  return (status);
}



#if defined(MAGICKCORE_HDRI_SUPPORT)
#define ADD_HALF
#else
#define ADD_HALF +0.5
#endif


// The next function corresponds in style to functions in transform.c
// It takes one image, and returns an image.
//
static Image *mkhImage(const Image *image,
  mkhistoT * pmkh,
  ExceptionInfo *exception)
{
  Image
    *new_image;

  CacheView
    *image_view,
    *out_view;

  ssize_t
    y,
    NumBuckets,
    outXmult;

  MagickBooleanType
    status;

  VIEW_PIX_PTR
    *q_out;

  MagickRealType
    BucketSize;

  long double
    Increment,
    value,
    min_value,
    max_value,
    sum_red,
    sum_green,
    sum_blue;

  int
    precision;

  int numChannels = NumColChannels (image);

  if (image->debug != MagickFalse)
    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);

  if (pmkh->do_verbose) {
    fprintf (stderr, "mkhisto: Input image [%s] depth is %i\n",
             image->filename, (int)image->depth);
  }

  precision = GetMagickPrecision();

  NumBuckets = exp2 (image->depth);
  if (NumBuckets > pmkh->cap_numbuckets) NumBuckets = pmkh->cap_numbuckets;
  Increment = pmkh->mult;

  BucketSize = exp2 (MAGICKCORE_QUANTUM_DEPTH) /(MagickRealType)NumBuckets;

  if (pmkh->do_verbose) {
    fprintf (stderr, "  NumBuckets %i  BucketSize %.*g\n",
             (int)NumBuckets, precision, (double)BucketSize);
  }

  new_image=CloneImage(image, NumBuckets, 1, MagickTrue, exception);
  if (new_image == (Image *) NULL)
    return(new_image);

  // FIXME: set new_image depth to Quantum?
  // new_image->depth=MAGICKCORE_QUANTUM_DEPTH;

  if (SetNoPalette (new_image, exception) == MagickFalse)
    return (Image *)NULL;

  SetAllBlack (new_image, exception);

#if IMV6OR7==6
  outXmult = 1;
#else
  outXmult = GetPixelChannels (new_image);
#endif

  out_view=AcquireAuthenticCacheView(new_image,exception);

  status=MagickTrue;

  q_out=GetCacheViewAuthenticPixels(out_view,0,0,new_image->columns,1,exception);
  if (q_out == (const VIEW_PIX_PTR *) NULL) {
    fprintf (stderr, "mkhisto: bad GetCacheViewAuthenticPixels out_view\n");
    status=MagickFalse;
  }

  image_view=AcquireVirtualCacheView(image,exception);

  sum_red = sum_green = sum_blue = 0.0;
  max_value = -QuantumRange;
  min_value = QuantumRange + 1.0;

/* I'm unsure about the multi-threading.
   What if two threads want to update the same histogram bucket,
   and sum_red etc?
*/

//#if defined(MAGICKCORE_OPENMP_SUPPORT)
//  #pragma omp parallel for schedule(static,4) shared(status)
//    MAGICK_THREADS(image,image,image->rows,1)
//#endif
  for (y=0; y < (ssize_t) image->rows; y++)
  {
    register const VIEW_PIX_PTR
      *p;

    ssize_t
      x,
      out_x;

    if (status == MagickFalse)
      continue;
    p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
    if (p == (const VIEW_PIX_PTR *) NULL)
    {
      fprintf (stderr, "bad GetCacheViewAuthenticPixels image_view\n");
      status=MagickFalse;
      continue;
    }

    for (x=0; x < (ssize_t) image->columns; x++)
    {

      if (pmkh->do_alpha) {
        Increment = 
          (pmkh->mult * GET_PIXEL_ALPHA(image,p) / (long double)QuantumRange) ADD_HALF ;
      }

      out_x = (GET_PIXEL_RED(image,p) / BucketSize);
      if (out_x >= NumBuckets) out_x = NumBuckets-1;
      else if (out_x < 0) out_x = 0;
      out_x *= outXmult;
      value = GET_PIXEL_RED(image,q_out+out_x)+Increment;
      if (min_value > value) min_value = value;
      if (max_value < value) max_value = value;
      sum_red += Increment;
      SET_PIXEL_RED (new_image, value, q_out+out_x);

      if (numChannels >= 2) {
        out_x = (GET_PIXEL_GREEN(image,p) / BucketSize);
        if (out_x >= NumBuckets) out_x = NumBuckets-1;
        else if (out_x < 0) out_x = 0;
        out_x *= outXmult;
        value = GET_PIXEL_GREEN(image,q_out+out_x)+Increment;
        if (min_value > value) min_value = value;
        if (max_value < value) max_value = value;
        sum_green += Increment;
        SET_PIXEL_GREEN (new_image, value, q_out+out_x);
      }

      if (numChannels >= 3) {
        out_x = (GET_PIXEL_BLUE(image,p) / BucketSize);
        if (out_x >= NumBuckets) out_x = NumBuckets-1;
        else if (out_x < 0) out_x = 0;
        out_x *= outXmult;
        value = GET_PIXEL_BLUE(image,q_out+out_x)+Increment;
        if (min_value > value) min_value = value;
        if (max_value < value) max_value = value;
        sum_blue += Increment;
        SET_PIXEL_BLUE (new_image, value, q_out+out_x);
      }

      p += Inc_ViewPixPtr (image);
    }

    if (SyncCacheViewAuthenticPixels(out_view,exception) == MagickFalse) {
      fprintf (stderr, "bad sync\n");
      status=MagickFalse;
    }
  }

  if (pmkh->do_log) {
    ssize_t
      x;

    max_value = -QuantumRange;
    min_value = QuantumRange + 1.0;

    VIEW_PIX_PTR *ql = q_out;

    for (x=0; x < (ssize_t) new_image->columns; x++)
    {
      value = GET_PIXEL_RED(new_image, ql);
      value = (value <= 1) ? 0 : log(value);
      if (min_value > value) min_value = value;
      if (max_value < value) max_value = value;
      SET_PIXEL_RED (new_image, value, ql);

      if (numChannels >= 2)  {
        value = GET_PIXEL_GREEN(new_image, ql);
        value = (value <= 1) ? 0 : log(value);
        if (min_value > value) min_value = value;
        if (max_value < value) max_value = value;
        SET_PIXEL_GREEN (new_image, value, ql);
      }

      if (numChannels >= 3) {
        value = GET_PIXEL_BLUE(new_image, ql);
        value = (value <= 1) ? 0 : log(value);
        if (min_value > value) min_value = value;
        if (max_value < value) max_value = value;
        SET_PIXEL_BLUE (new_image, value, ql);
      }

      ql += Inc_ViewPixPtr (new_image);
    }
    sum_red   = (sum_red   <= 1) ? 0 : log(sum_red);
    sum_green = (sum_green <= 1) ? 0 : log(sum_green);
    sum_blue  = (sum_blue  <= 1) ? 0 : log(sum_blue);

    if (SyncCacheViewAuthenticPixels(out_view,exception) == MagickFalse) {
      fprintf (stderr, "bad sync\n");
      status=MagickFalse;
    }
  }

  if (pmkh->do_verbose) {
    fprintf (stderr, "  counts: min_value %.*g, max_value %.*g\n",
            precision, (double)min_value,
            precision, (double)max_value);
    fprintf (stderr, "  sum_red %.*g, sum_green %.*g, sum_blue %.*g\n",
            precision, (double)sum_red,
            precision, (double)sum_green,
            precision, (double)sum_blue);
  }

#if !defined(MAGICKCORE_HDRI_SUPPORT)
  if (max_value >= (long double)QuantumRange) {
    fprintf (stderr, "Warning: max_value >= QuantumRange (%.*g >=  %.*g)\n",
             (double)max_value, (double)QuantumRange);
  }
#endif

  // FIXME: but we don't care about min_value??

  // As required: cumulate, normalise or both.
  //
  if (pmkh->do_cumul==MagickTrue && pmkh->do_norm==MagickTrue) {
    long double
      max_sum,
      mult_fact;

    max_sum = sum_red;
    if (max_sum < sum_green) max_sum = sum_green;
    if (max_sum < sum_blue)  max_sum = sum_blue;

    if (max_sum != 0.0) {
      mult_fact = QuantumRange / max_sum;
      if (pmkh->do_verbose) {
        fprintf (stderr, "  Cumulating and normalising...\n");
        fprintf (stderr, "    max_sum=%.*g mult_fact=%.*g\n",
                precision, (double)max_sum,
                precision, (double)mult_fact);
      }
      status = CumulateColor (new_image, out_view, mult_fact, exception);
    }
  } else if (pmkh->do_norm==MagickTrue) {
    long double
      mult_fact;

    if (pmkh->do_verbose) {
      fprintf (stderr, "  Normalising...\n");
    }
    if (max_value != 0.0) {
      mult_fact = QuantumRange / max_value;
      if (pmkh->do_verbose) {
        fprintf (stderr, "    max_value=%.*g  mult_fact=%.*g\n",
                precision, (double)max_value,
                precision, (double)mult_fact);
      }
      MultiplyColor (new_image, out_view, mult_fact, exception);
    }
  } else if (pmkh->do_cumul==MagickTrue) {
    if (pmkh->do_verbose) {
      fprintf (stderr, "  Cumulating...\n");
    }
    status = CumulateColor (new_image, out_view, 1.0, exception);
  }

  image_view=DestroyCacheView(image_view);
  out_view=DestroyCacheView(out_view);

  return (new_image);
}


ModuleExport size_t mkhistoImage(Image **images,const int argc,
  const char **argv,ExceptionInfo *exception)
{
  Image
    *image,
    *new_image;

  MagickBooleanType
    status;

  mkhistoT
    mkh;

  assert(images != (Image **) NULL);
  assert(*images != (Image *) NULL);
  assert((*images)->signature == MAGICK_CORE_SIG);

  status = menu (argc, argv, &mkh);
  if (status == MagickFalse)
    return (-1);

  for (image=(*images); image != (Image *) NULL; image=GetNextImageInList(image))
  {
    new_image = mkhImage (image, &mkh, exception);

    ReplaceImageInList(&image,new_image);
    *images=GetFirstImageInList(image);
  }

  return(MagickImageFilterSignature);
}

invclut.c

/* This needs more work.

  7-October-2014 Cope with multiple lines of output.
  1-February-2016 For v7.
  3-April-2018 for v7.0.7-28
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <assert.h>
#include <math.h>
#include <ctype.h>
#include "vsn_defines.h"



#define DEFAULT_CAP 65536

static void usage (void)
{
  printf ("Usage: -process 'invclut [OPTION]...'\n");
  printf ("Invert a histogram image.\n");
  printf ("\n");
  printf ("  v, verbose          write text information to stdout\n");
}


static MagickBooleanType IsArg (char * pa, char * ShtOpt, char * LongOpt)
{
  if ((LocaleCompare(pa, ShtOpt)==0) || (LocaleCompare(pa, LongOpt)==0))
    return MagickTrue;

  return MagickFalse;
}


#if defined(MAGICKCORE_HDRI_SUPPORT)
#define ADD_HALF
#else
#define ADD_HALF +0.5
#endif


ModuleExport size_t invclutImage(Image **images,const int argc,
  const char **argv,ExceptionInfo *exception)
{
  Image
    *image;

  Image
    *new_image,
    *transform_images;

  int
    i;

  MagickBooleanType
    Error,
    do_verbose;

  assert(images != (Image **) NULL);
  assert(*images != (Image *) NULL);
  assert((*images)->signature == MAGICK_CORE_SIG);

  Error = MagickFalse;

  do_verbose = MagickFalse;

  for (i=0; i < argc; i++) {
    char * pa = (char *)argv[i];

    if (IsArg (pa, "v", "verbose")==MagickTrue) {
      do_verbose = MagickTrue;
    } else {
      fprintf (stderr, "invclut: ERROR: unknown option\n");
      Error = MagickTrue;
    }
  }

  if (Error == MagickTrue) {
    usage ();
    return (-1);
  }

  if (do_verbose) {
    printf ("invclut options:  ");
    if (do_verbose) printf ("verbose ");
    printf ("\n");
  }

  transform_images=NewImageList();

  image=(*images);
  for ( ; image != (Image *) NULL; image=GetNextImageInList(image))
  {
    CacheView
      *image_view,
      *out_view;

    ssize_t
      y,
      inXmult;

    MagickBooleanType
      status;

    long double
      quant_over_width;

    if (do_verbose) {
      printf ("invclut: Input image [%s] depth is %i\n", image->filename, (int)image->depth);
    }

    quant_over_width = QuantumRange/(long double)image->columns;

    new_image=CloneImage(image, 0, 0, MagickTrue, exception);
    if (new_image == (Image *) NULL)
      return(-1);

    if (SetNoPalette (new_image, exception) == MagickFalse)
      return (-1);

    // FIXME: set new_image depth to Quantum?
    // new_image->depth=MAGICKCORE_QUANTUM_DEPTH;

    SetAllBlack (new_image, exception);

    inXmult = Inc_ViewPixPtr (image);

    out_view=AcquireAuthenticCacheView(new_image,exception);

    status=MagickTrue;

    image_view=AcquireVirtualCacheView(image,exception);

#if defined(MAGICKCORE_OPENMP_SUPPORT)
    #pragma omp parallel for schedule(static,4) shared(status) \
      MAGICK_THREADS(image,image,image->rows,1)
#endif
    for (y=0; y < (ssize_t) image->rows; y++)
    {
      register const VIEW_PIX_PTR
        *p;

      VIEW_PIX_PTR
        *q_out;

      ssize_t
        in_x_red,
        in_x_green,
        in_x_blue,
        out_x;

      in_x_red = in_x_green = in_x_blue = 0;

      if (status == MagickFalse)
        continue;
      p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
      if (p == (const VIEW_PIX_PTR *) NULL)
      {
        fprintf (stderr, "bad GetCacheViewAuthenticPixels image_view\n");
        status=MagickFalse;
        continue;
      }

      q_out=GetCacheViewAuthenticPixels(out_view,0,y,new_image->columns,1,exception);
      if (q_out == (const VIEW_PIX_PTR *) NULL) {
        fprintf (stderr, "invclut: bad GetCacheViewAuthenticPixels out_view\n");
        status=MagickFalse;
      }

      for (out_x=0; out_x < (ssize_t) image->columns; out_x++)
      {
        // Find the next input x with value (possibly scaled) >= out_x
        // This input x is the new value, possibly scaled.

        long double
          limit;

        limit = out_x * quant_over_width;

        // "+0.5" because we need the centre of the bucket.
        // 12/10/14 Remove +0.5

        while (in_x_red < image->columns
               && GET_PIXEL_RED(image,p + in_x_red * inXmult) < limit)
          in_x_red++;

        SET_PIXEL_RED (new_image,
          in_x_red * quant_over_width ADD_HALF, q_out);

        while (in_x_green < image->columns
               && GET_PIXEL_GREEN(image,p + in_x_green * inXmult) < limit)
          in_x_green++;

        SET_PIXEL_GREEN (new_image,
          in_x_green * quant_over_width ADD_HALF, q_out);

        while (in_x_blue < image->columns
               && GET_PIXEL_BLUE(image,p + in_x_blue * inXmult) < limit)
          in_x_blue++;

        SET_PIXEL_BLUE (new_image,
          in_x_blue * quant_over_width ADD_HALF, q_out);

        q_out += Inc_ViewPixPtr (new_image);
      }

      if (SyncCacheViewAuthenticPixels(out_view,exception) == MagickFalse) {
        fprintf (stderr, "bad sync\n");
        status=MagickFalse;
      }
    }

    if (do_verbose) {
    }

    image_view=DestroyCacheView(image_view);
    out_view=DestroyCacheView(out_view);
    AppendImageToList(&transform_images,new_image);

  }

  DestroyImageList(*images);
  *images=transform_images;

  return(MagickImageFilterSignature);
}

cumulhisto.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include "vsn_defines.h"

/*
   Latest update:
     1-Feb-2016 for V7.
     20-July-2017 added regardalpha.
     3-April-2018 for v7.0.7-28
*/

typedef struct {
  MagickBooleanType
    do_regardalpha,
    do_decumul,
    do_norm,
    do_verbose;
} cumulhistoT;

static void usage (void)
{
  printf ("Usage: -process 'cumulhisto [OPTION]...'\n");
  printf ("Cumulate (or decumulate) a histogram image.\n");
  printf ("\n");
  printf ("  r, regardalpha      pre-multiply RGB values by alpha\n");
  printf ("  d, decumul          de-cumulate histogram\n");
  printf ("  n, norm             normalise output so maximum count is quantum\n");
  printf ("  v, verbose          write text information to stdout\n");
  printf ("\n");
}


static MagickBooleanType IsArg (char * pa, char * ShtOpt, char * LongOpt)
{
  if ((LocaleCompare(pa, ShtOpt)==0) || (LocaleCompare(pa, LongOpt)==0))
    return MagickTrue;

  return MagickFalse;
}


static MagickBooleanType menu(
  const int argc,
  const char **argv,
  cumulhistoT * pch
)
// Returns MagickTrue if okay.
{
  int
    i;

  MagickBooleanType
    status;

  status = MagickTrue;

  pch->do_regardalpha =
    pch->do_decumul =
    pch->do_norm =
    pch->do_verbose = MagickFalse;

  for (i=0; i < argc; i++) {
    char * pa = (char *)argv[i];

    if (IsArg (pa, "r", "regardalpha")==MagickTrue) {
      pch->do_regardalpha = MagickTrue;
    } else if (IsArg (pa, "n", "norm")==MagickTrue) {
      pch->do_norm = MagickTrue;
    } else if (IsArg (pa, "d", "decumul")==MagickTrue) {
      pch->do_decumul = MagickTrue;
    } else if (IsArg (pa, "v", "verbose")==MagickTrue) {
      pch->do_verbose = MagickTrue;
    } else {
      fprintf (stderr, "cumulhisto: ERROR: unknown option\n");
      status = MagickFalse;
    }
  }

  if (pch->do_verbose) {
    fprintf (stderr, "cumulhisto options:  ");
    if (pch->do_regardalpha) fprintf (stderr, "regardalpha ");
    if (pch->do_decumul) fprintf (stderr, "decumul ");
    if (pch->do_norm)    fprintf (stderr, "norm ");
    if (pch->do_verbose) fprintf (stderr, "verbose ");
    fprintf (stderr, "\n");
  }

  if (status == MagickFalse)
    usage ();

  return (status);
}


#if defined(MAGICKCORE_HDRI_SUPPORT)
#define ADD_HALF
#else
#define ADD_HALF +0.5
#endif


// The next function corresponds in style to functions in transform.c
// It takes one image, and returns an image.
//
static Image *cumulhist (
  const Image *image,
  cumulhistoT * pch,
  ExceptionInfo *exception)
{
  Image
    *new_image;

  CacheView
    *image_view,
    *out_view;

  ssize_t
    y,
    inXmult;

  MagickBooleanType
    status;

  if (image->debug != MagickFalse)
    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);

  if (pch->do_verbose) {
    fprintf (stderr, "cumulhisto: Input image [%s] depth is %i\n",
             image->filename, (int)image->depth);
  }

  // De-cumul needs a new image.
  // Without this option, we could over-write the existing image.

  new_image=CloneImage(image, 0, 0, MagickTrue, exception);
  if (new_image == (Image *) NULL)
    return(new_image);

  // Just in case image had a palette.
  if (SetNoPalette (new_image, exception) == MagickFalse)
    return (Image *)NULL;

  SetAllBlack (new_image, exception);

//#if IMV6OR7==6
//  inXmult = 1;
//  outXmult = 1;
//#else
//  inXmult = GetPixelChannels (image);
//  outXmult = GetPixelChannels (new_image);
//#endif

  inXmult = Inc_ViewPixPtr (image);

  status=MagickTrue;

  out_view=AcquireAuthenticCacheView(new_image,exception);

  image_view=AcquireVirtualCacheView(image,exception);

#if defined(MAGICKCORE_OPENMP_SUPPORT)
  #pragma omp parallel for schedule(static,4) shared(status) \
    MAGICK_THREADS(image,image,image->rows,1)
#endif
  for (y=0; y < (ssize_t) image->rows; y++)
  {
    const VIEW_PIX_PTR
      *pSave,
      *p;

    VIEW_PIX_PTR
      *q_out;

    ssize_t
      x;

    long double
      mult_fact,
      alpha;

    if (status == MagickFalse)
      continue;
    p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
    pSave = p;
    if (p == (const VIEW_PIX_PTR *) NULL)
    {
      fprintf (stderr, "bad GetCacheViewVirtualPixels image_view\n");
      status=MagickFalse;
      continue;
    }

    q_out=GetCacheViewAuthenticPixels(out_view,0,y,new_image->columns,1,exception);
    if (q_out == (const VIEW_PIX_PTR *) NULL) {
      fprintf (stderr, "cumulhisto: bad GetCacheViewAuthenticPixels out_view\n");
      status=MagickFalse;
    }

    mult_fact = 1.0;
    alpha = 1.0;

    // FIXME: we need first pass only if normalising.
    if (pch->do_decumul) {
      long double
        diff,
        max_diff;

      max_diff = 0;

      p += Inc_ViewPixPtr (image);
      for (x=1; x < (ssize_t) image->columns; x++)
      {
// FIXME: subtract xfactor.
        diff = GET_PIXEL_RED(image,p) - GET_PIXEL_RED(image,p-inXmult);
        if (max_diff < diff) max_diff = diff;
        diff = GET_PIXEL_GREEN(image,p) - GET_PIXEL_GREEN(image,p-inXmult);
        if (max_diff < diff) max_diff = diff;
        diff = GET_PIXEL_BLUE(image,p) - GET_PIXEL_BLUE(image,p-inXmult);
        if (max_diff < diff) max_diff = diff;
        p += Inc_ViewPixPtr (image);
      }
      if (pch->do_verbose) {
        fprintf (stderr, "  row=%i  max_diff=%g\n",
                  (int)y,
                  (double)max_diff);
      }
      if (pch->do_norm && max_diff != 0.0) {
        mult_fact = QuantumRange / max_diff;
        if (pch->do_verbose) {
          fprintf (stderr, "  mult_fact=%g\n",
                    (double)mult_fact);
        }
      }
    } else {
      // We are not decumulating.
      long double
        sum_red,
        sum_green,
        sum_blue,
        max_sum;

      sum_red = sum_green = sum_blue = 0.0;

      for (x=0; x < (ssize_t) image->columns; x++)
      {
        if (pch->do_regardalpha)
          alpha = GET_PIXEL_ALPHA(image,p) / (long double)QuantumRange;

        sum_red   += alpha * GET_PIXEL_RED(image,p);
        sum_green += alpha * GET_PIXEL_GREEN(image,p);
        sum_blue  += alpha * GET_PIXEL_BLUE(image,p);
        p += Inc_ViewPixPtr (image);
      }

      max_sum = sum_red;
      if (max_sum < sum_green) max_sum = sum_green;
      if (max_sum < sum_blue)  max_sum = sum_blue;

      if (pch->do_verbose) {
        fprintf (stderr, "  row=%i  sum_red=%g  sum_green=%g  sum_blue=%g\n",
                  (int)y,
                  (double)sum_red, (double)sum_green, (double)sum_blue);
      }

      if (pch->do_norm && max_sum != 0.0) {
        mult_fact = QuantumRange / max_sum;
      }

      if (pch->do_verbose) {
        fprintf (stderr, "  max_sum=%g  mult_fact=%g\n",
                  (double)max_sum, (double)mult_fact);
      }
    }

    p = pSave;
    if (pch->do_decumul) {
      SET_PIXEL_RED   (new_image, GET_PIXEL_RED(image,p)   * mult_fact ADD_HALF,q_out);
      SET_PIXEL_GREEN (new_image, GET_PIXEL_GREEN(image,p) * mult_fact ADD_HALF,q_out);
      SET_PIXEL_BLUE  (new_image, GET_PIXEL_BLUE(image,p)  * mult_fact ADD_HALF,q_out);
      p += Inc_ViewPixPtr (image);
      q_out += Inc_ViewPixPtr (new_image);

      for (x=1; x < (ssize_t) image->columns; x++)
      {
        SET_PIXEL_RED
          (new_image,
           (GET_PIXEL_RED(image,p)   - GET_PIXEL_RED(image,p-inXmult))   * mult_fact ADD_HALF,
           q_out);
        SET_PIXEL_GREEN
          (new_image,
           (GET_PIXEL_GREEN(image,p) - GET_PIXEL_GREEN(image,p-inXmult)) * mult_fact ADD_HALF,
           q_out);
        SET_PIXEL_BLUE
          (new_image,
           (GET_PIXEL_BLUE(image,p)  - GET_PIXEL_BLUE(image,p-inXmult))  * mult_fact ADD_HALF,
           q_out);

        p += Inc_ViewPixPtr (image);
        q_out += Inc_ViewPixPtr (new_image);
      }
    } else {
      long double
        cumul_red,
        cumul_green,
        cumul_blue,
        cumul_alpha,
        alp_q;

      cumul_red = cumul_green = cumul_blue = cumul_alpha = 0;
      for (x=0; x < (ssize_t) image->columns; x++)
      {
        if (pch->do_regardalpha) {
          alp_q = GET_PIXEL_ALPHA(image,p);
          cumul_alpha += alp_q;
          SET_PIXEL_ALPHA (new_image, cumul_alpha ADD_HALF, q_out);
          alpha = alp_q / (long double)QuantumRange;
        } else {
          SET_PIXEL_ALPHA (new_image, GET_PIXEL_ALPHA(image,p), q_out);
        }

        cumul_red += (alpha * GET_PIXEL_RED(image,p));
        SET_PIXEL_RED (new_image, cumul_red * mult_fact ADD_HALF, q_out);

        cumul_green += (alpha * GET_PIXEL_GREEN(image,p));
        SET_PIXEL_GREEN (new_image, cumul_green * mult_fact ADD_HALF, q_out);

        cumul_blue += (alpha * GET_PIXEL_BLUE(image,p));
        SET_PIXEL_BLUE (new_image, cumul_blue * mult_fact ADD_HALF, q_out);

        p += Inc_ViewPixPtr (image);
        q_out += Inc_ViewPixPtr (new_image);
      }
    }

    if (SyncCacheViewAuthenticPixels(out_view,exception) == MagickFalse) {
      fprintf (stderr, "bad sync\n");
      status=MagickFalse;
    }
  }

  image_view=DestroyCacheView(image_view);
  out_view=DestroyCacheView(out_view);

  return (new_image);
}


ModuleExport size_t cumulhistoImage(Image **images,const int argc,
  const char **argv,ExceptionInfo *exception)
{
  Image
    *image,
    *new_image;

  MagickBooleanType
    status;

  cumulhistoT
    cumul_histo;

  assert(images != (Image **) NULL);
  assert(*images != (Image *) NULL);
  assert((*images)->signature == MAGICK_CORE_SIG);

  status = menu (argc, argv, &cumul_histo);
  if (status == MagickFalse)
    return (-1);

  for (image=(*images); image != (Image *) NULL; image=GetNextImageInList(image))
  {
    new_image = cumulhist (image, &cumul_histo, exception);

    ReplaceImageInList(&image,new_image);
    *images=GetFirstImageInList(image);
  }

  return(MagickImageFilterSignature);
}

invdispmap.c

/* Updated:
     3-April-2018 for v7.0.7-28
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include "vsn_defines.h"


typedef enum {
  mtAbsolute,
  mtRelative
} MapTypeT;

typedef struct {
  MagickBooleanType
    do_verbose;
  MapTypeT
    MapType;
} invDispMapT;

static void usage (void)
{
  printf ("Usage: -process 'invdispmap [OPTION]...'\n");
  printf ("Invert a displacement map.\n");
  printf ("\n");
  printf ("  t, type STRING      STRING is Relative or Absolute\n");
  printf ("  v, verbose          write text information to stdout\n");

  printf ("\n");
}


static MagickBooleanType IsArg (char * pa, char * ShtOpt, char * LongOpt)
{
  if ((LocaleCompare(pa, ShtOpt)==0) || (LocaleCompare(pa, LongOpt)==0))
    return MagickTrue;

  return MagickFalse;
}


static MagickBooleanType menu(
  const int argc,
  const char **argv,
  invDispMapT * idm
)
// Returns MagickTrue if okay.
{
  int
    i;

  MagickBooleanType
    status;

  status = MagickTrue;

  idm->do_verbose = MagickFalse;
  idm->MapType = mtAbsolute;

  for (i=0; i < argc; i++) {
    char * pa = (char *)argv[i];

    if (IsArg (pa, "t", "type")==MagickTrue) {
      i++;
      char * pa = (char *)argv[i];
      if (LocaleCompare(pa, "Absolute")==0) idm->MapType = mtAbsolute;
      else if (LocaleCompare(pa, "Relative")==0) idm->MapType = mtRelative;
      else {
        fprintf (stderr, "invdispmap: ERROR: unknown map type\n");
        status = MagickFalse;
      }

    } else if (IsArg (pa, "v", "verbose")==MagickTrue) {
      idm->do_verbose = MagickTrue;
    } else {
      fprintf (stderr, "invdispmap: ERROR: unknown option\n");
      status = MagickFalse;
    }
  }

  if (idm->do_verbose) {
    fprintf (stderr, "invdispmap options:  ");
    fprintf (stderr, "type ");
    fprintf (stderr, idm->MapType==mtAbsolute?"Absolute ":"Relative ");

    if (idm->do_verbose) fprintf (stderr, "verbose ");
    fprintf (stderr, "\n");
  }

  if (status == MagickFalse)
    usage ();

  return (status);
}


#if defined(MAGICKCORE_HDRI_SUPPORT)
#define ADD_HALF
#else
#define ADD_HALF +0.5
#endif


// The next function corresponds in style to functions in transform.c
// It takes one image, and returns an image.
//
static Image *invDispMap(const Image *image,
  invDispMapT * idm,
  ExceptionInfo *exception)
{
  Image
    *new_image;

  CacheView
    *image_view,
    *out_view;

  ssize_t
    y;

  MagickBooleanType
    status;

  PIX_INFO
    mppBlack;

  long double
    QoverCols,
    QoverRows,
    relHalf;

  if (image->debug != MagickFalse)
    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);

  if (idm->do_verbose) {
    fprintf (stderr, "invdispmap: Input image [%s] depth is %i\n",
             image->filename, (int)image->depth);
  }

  new_image=CloneImage(image, 0, 0, MagickTrue, exception);
  if (new_image == (Image *) NULL)
    return(new_image);

  // Just in case image had a palette.
  if (SetNoPalette (new_image, exception) == MagickFalse)
    return (Image *)NULL;

#if IMV6OR7==6
  GetMagickPixelPacket (new_image, &mppBlack);
  mppBlack.matte=MagickTrue;
  mppBlack.opacity=(MagickRealType) TransparentOpacity;
  SetImageColor (new_image, &mppBlack);
#else
  GetPixelInfo (new_image, &mppBlack);
  mppBlack.alpha = 0;
  SetImageColor (new_image, &mppBlack, exception);
#endif

  status=MagickTrue;

  image_view=AcquireVirtualCacheView(image,exception);

  out_view=AcquireAuthenticCacheView(new_image,exception);

  QoverCols = QuantumRange / (long double)(image->columns-1);
  QoverRows = QuantumRange / (long double)(image->rows-1);

  if (idm->MapType == mtRelative) {
    relHalf = QuantumRange / 2.0;
  } else {
    relHalf = 0.0;
  }

  // Unlike most processes,
  // this reads pixels sequentially
  // and writes them randomly.

// Two threads might want to update the same row,
// so don't allow multiple threads.
// #if defined(MAGICKCORE_OPENMP_SUPPORT)
//  #pragma omp parallel for schedule(static,4) shared(status)
//    MAGICK_THREADS(image,image,image->rows,1)
// #endif

  for (y=0; y < (ssize_t) image->rows; y++)
  {
    const VIEW_PIX_PTR
      *p;

    VIEW_PIX_PTR
      *q_out;

    ssize_t
      x, red, grn, xa, ya;

    long double
      x_norm, y_norm;

    if (status == MagickFalse)
      continue;
    p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
    if (p == (const VIEW_PIX_PTR *) NULL)
    {
      fprintf (stderr, "bad GetCacheViewVirtualPixels image_view\n");
      status=MagickFalse;
      continue;
    }

    for (x=0; x < (ssize_t) image->columns; x++)
    {
      if (GET_PIXEL_ALPHA (image, p) > 0) {
        red = floor((GET_PIXEL_RED (image, p)   - relHalf) / QoverCols + 0.5);
        grn = floor((GET_PIXEL_GREEN (image, p) - relHalf) / QoverRows + 0.5);

        if (idm->MapType == mtRelative) {
          red += x;
          grn += y;
          xa = x - red;
          ya = y - grn;
        } else {
          xa = x;
          ya = y;
        }

        if (red >= 0 && red < image->columns
         && grn >= 0 && grn < image->rows)
        {
          q_out=GetCacheViewAuthenticPixels(out_view,red,grn,1,1,exception);
          if (q_out == (const VIEW_PIX_PTR *) NULL) {
            fprintf (stderr, "invdispmap: bad GetCacheViewAuthenticPixels out_view (%lu %lu)\n", red, grn);
            status=MagickFalse;
          }

          x_norm  = xa * QoverCols + relHalf ADD_HALF;
          y_norm  = ya * QoverRows + relHalf ADD_HALF;

          SET_PIXEL_RED   (new_image, x_norm,       q_out);
          SET_PIXEL_GREEN (new_image, y_norm,       q_out);
          SET_PIXEL_BLUE  (new_image, 0,            q_out);
          SET_PIXEL_ALPHA (new_image, QuantumRange, q_out);

          if (SyncCacheViewAuthenticPixels(out_view,exception) == MagickFalse) {
            fprintf (stderr, "bad sync\n");
            status=MagickFalse;
          }
        } else {
          fprintf (stderr, "ignored: red=%li grn=%li\n", red, grn);
        }
      }

      p += Inc_ViewPixPtr (image);
    }
  }

  image_view=DestroyCacheView(image_view);
  out_view=DestroyCacheView(out_view);

  return (new_image);
}


ModuleExport size_t invdispmapImage(Image **images,const int argc,
  const char **argv,ExceptionInfo *exception)
{
  Image
    *image,
    *new_image;

  MagickBooleanType
    status;

  invDispMapT
    idm;

  assert(images != (Image **) NULL);
  assert(*images != (Image *) NULL);
  assert((*images)->signature == MAGICK_CORE_SIG);

  status = menu (argc, argv, &idm);
  if (status == MagickFalse)
    return (-1);

  for (image=(*images); image != (Image *) NULL; image=GetNextImageInList(image))
  {
    new_image = invDispMap (image, &idm, exception);

    ReplaceImageInList(&image,new_image);
    *images=GetFirstImageInList(image);
  }

  return(MagickImageFilterSignature);
}

darkestpath.c

/* Written by: Alan Gibson, 18 October 2015.

   References:

     http://graphics.cs.cmu.edu/people/efros/research/quilting/quilting.pdf
     Image Quilting for Texture Synthesis and Transfer</a>,
     Alexei A. Efros and William T. Freeman, 2001.

     http://im.snibgo.com/darkpath.htm

  Updated:
    21-May-2016
      For v7.
      To use intensity instead of merely red channel.
      Fix offset-1 bug in lowest value of bottom row.
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include "vsn_defines.h"

// FUTURE: We could weight diagonal steps by sqrt(2).

typedef struct {
  MagickBooleanType
    do_cumerr,
    do_verbose;
} darkestPathT;

static void usage (void)
{
  printf ("Usage: -process 'darkestpath [OPTION]...'\n");
  printf ("Finds darkest path from top to bottom.\n");
  printf ("\n");
  printf ("  c, cumerr           result is cumulative error instead of path\n");
  printf ("  v, verbose          write text information to stdout\n");
  printf ("\n");
}


static MagickBooleanType IsArg (char * pa, char * ShtOpt, char * LongOpt)
{
  if ((LocaleCompare(pa, ShtOpt)==0) || (LocaleCompare(pa, LongOpt)==0))
    return MagickTrue;

  return MagickFalse;
}


static MagickBooleanType menu(
  const int argc,
  const char **argv,
  darkestPathT * pch
)
// Returns MagickTrue if okay.
{
  int
    i;

  MagickBooleanType
    status;

  status = MagickTrue;

  pch->do_cumerr = pch->do_verbose = MagickFalse;

  for (i=0; i < argc; i++) {
    char * pa = (char *)argv[i];

    if (IsArg (pa, "c", "cumerr")==MagickTrue) {
      pch->do_cumerr = MagickTrue;
    } else if (IsArg (pa, "v", "verbose")==MagickTrue) {
      pch->do_verbose = MagickTrue;
    } else {
      fprintf (stderr, "darkestpath: ERROR: unknown option\n");
      status = MagickFalse;
    }
  }

  if (pch->do_verbose) {
    fprintf (stderr, "darkestpath options:  ");
    if (pch->do_cumerr)  fprintf (stderr, "cumerr ");
    if (pch->do_verbose) fprintf (stderr, "verbose ");
    fprintf (stderr, "\n");
  }

  if (status == MagickFalse)
    usage ();

  return (status);
}


#if defined(MAGICKCORE_HDRI_SUPPORT)
#define ADD_HALF
#else
#define ADD_HALF +0.5
#endif


static void inline SetPixIntensity (
  const Image *image,
  double v,
  VIEW_PIX_PTR *q_out)
{
  if (Has3Channels (image)) {
    SET_PIXEL_RED   (image, v, q_out);
    SET_PIXEL_GREEN (image, v, q_out);
    SET_PIXEL_BLUE  (image, v, q_out);
  } else {
    SET_PIXEL_RED   (image, v, q_out);
  }
}


// The next function corresponds in style to functions in transform.c
// It takes one image, and returns an image.
//
static Image *darkestpath (const Image *image,
  darkestPathT * pch,
  ExceptionInfo *exception)
{
  Image
    *new_image,
    *path_image;

  CacheView
    *out_view,
    *path_view;

  ssize_t
    y,
    x,
    atX;

  MagickBooleanType
    status;

  VIEW_PIX_PTR
    *q_out,
    *q_path;

  long double
    minVal,
    prevVal;

  int
    precision;


  precision = GetMagickPrecision();

  if (image->debug != MagickFalse)
    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);

  // Copy the current image.

  new_image=CloneImage(image, 0, 0, MagickTrue, exception);
  if (new_image == (Image *) NULL)
    return(new_image);

  if (SetNoPalette (new_image, exception) == MagickFalse)
    return (Image *)NULL;

  //if (Has3Channels (new_image) == MagickFalse) {
  //  fprintf (stderr, "Image has less than 3 channels.\n");
  //  return (Image *)NULL;
  //}

  status=MagickTrue;

  out_view=AcquireAuthenticCacheView(new_image,exception);

  if (pch->do_verbose) printf ("Get minimum from adjacent pixels on previous row.\n");

  for (y=1; y < (ssize_t) image->rows; y++)
  {
    //printf ("%i ", (int)y);

    const VIEW_PIX_PTR
      *p;

    if (status == MagickFalse)
      continue;
    p=GetCacheViewVirtualPixels(out_view,0,y-1,image->columns,1,exception);
    if (p == (const VIEW_PIX_PTR *) NULL)
    {
      fprintf (stderr, "bad GetCacheViewVirtualPixels out_view\n");
      status=MagickFalse;
      continue;
    }

    q_out=GetCacheViewAuthenticPixels(out_view,0,y,new_image->columns,1,exception);
    if (q_out == (const VIEW_PIX_PTR *) NULL) {
      fprintf (stderr, "darkestpath: bad GetCacheViewAuthenticPixels out_view\n");
      status=MagickFalse;
    }

// FIXME: for v7, -1 and +1 are wrong

    for (x=0; x < (ssize_t) image->columns; x++)
    {
      // Get minimum from adjacent pixels on previous row.
      //minVal = GET_PIXEL_RED(image,p);
      minVal = GetPixelIntensity(new_image, p);
      if (x > 0) {
        //prevVal = GET_PIXEL_RED(new_image,p-1);
        prevVal = GetPixelIntensity(new_image, p - Inc_ViewPixPtr(new_image));

        if (minVal > prevVal) {
          minVal = prevVal;
        }
      }
      if (x < image->columns-1) {
        //prevVal = GET_PIXEL_RED(new_image,p+1);
        prevVal = GetPixelIntensity(new_image, p + Inc_ViewPixPtr(new_image));
        if (minVal > prevVal) {
          minVal = prevVal;
        }
      }
      //SET_PIXEL_RED (new_image, GET_PIXEL_RED(new_image,q_out)+minVal, q_out);
      double intens = GetPixelIntensity(new_image,q_out)+minVal;
      SetPixIntensity (new_image, intens, q_out);

      p += Inc_ViewPixPtr (new_image);
      q_out += Inc_ViewPixPtr (new_image);
    }

    if (SyncCacheViewAuthenticPixels(out_view,exception) == MagickFalse) {
      fprintf (stderr, "bad sync\n");
      status=MagickFalse;
    }
  }

  // Find minimum value on the bottom row.
  // FIXME? If we have a span of equally good minimums, take the middle one.

  if (pch->do_verbose) printf ("Find minimum value on the bottom row.\n");

  q_out=GetCacheViewAuthenticPixels(out_view,0,image->rows-1,new_image->columns,1,exception);
  if (q_out == (const VIEW_PIX_PTR *) NULL) {
    fprintf (stderr, "darkestpath: bad GetCacheViewAuthenticPixels out_view\n");
    status=MagickFalse;
  }

  //minVal = GET_PIXEL_RED(new_image,q_out);
  minVal = GetPixelIntensity(new_image, q_out);
  atX = 0;

  for (x=0; x < (ssize_t) image->columns; x++)
  {
    long double
      thisVal;

    //thisVal = GET_PIXEL_RED(new_image,q_out);
    thisVal = GetPixelIntensity(new_image, q_out);
    if (minVal > thisVal) {
      minVal = thisVal;
      atX = x;
    }

    q_out += Inc_ViewPixPtr (new_image);
  }

  if (pch->do_verbose)
    printf ("Min value on bottom row %.*g at x=%i.\n",
     precision, (double)minVal,
     (int)atX);

  if (pch->do_verbose) printf ("Create a new black image for the path.\n");

  // Create a new black image for the path.

  // Make a clone of this image, same size but undefined pixel values:
  //
  path_image=CloneImage(image, image->columns, image->rows, MagickTrue, exception);
  if (path_image == (Image *) NULL)
    return(path_image);

  if (SetNoPalette (path_image, exception) == MagickFalse)
    return (Image *)NULL;

  SetAllBlack (path_image, exception);

  path_view=AcquireAuthenticCacheView(path_image,exception);



  if (pch->do_verbose) printf ("Set path end.\n");

  // Get just the one pixel we need.
  q_path=GetCacheViewAuthenticPixels(path_view,atX,new_image->rows-1,1,1,exception);
  if (q_path == (const VIEW_PIX_PTR *) NULL) {
    fprintf (stderr, "darkestpath: bad GetCacheViewAuthenticPixels path_view\n");
    status=MagickFalse;
  }

  //SET_PIXEL_RED   (path_image, QuantumRange, q_path);
  //SET_PIXEL_GREEN (path_image, QuantumRange, q_path);
  //SET_PIXEL_BLUE  (path_image, QuantumRange, q_path);
  SetPixIntensity (path_image, QuantumRange, q_path);

  if (SyncCacheViewAuthenticPixels(path_view,exception) == MagickFalse) {
    fprintf (stderr, "bad sync\n");
    status=MagickFalse;
  }


  if (pch->do_verbose) printf ("Walk up the image, painting the path white.\n");

  // Walk up the image, painting the path white.

  for (y=1; y < (ssize_t) image->rows; y++)
  {
    signed int
      dx;

    q_out=GetCacheViewAuthenticPixels(out_view,0,new_image->rows-y-1,new_image->columns,1,exception);
    if (q_out == (const VIEW_PIX_PTR *) NULL) {
      fprintf (stderr, "darkestpath: bad GetCacheViewAuthenticPixels out_view\n");
      status=MagickFalse;
    }

// FIXME: for v7, +atX-1 etc are wrong

    // Get position of minimum.
    //minVal = GET_PIXEL_RED(image,q_out+atX);
    minVal = GetPixelIntensity(new_image, q_out + (atX)*Inc_ViewPixPtr(new_image));
    dx = 0;
    if (atX > 0) {
      //prevVal = GET_PIXEL_RED(image,q_out+atX-1);
      prevVal = GetPixelIntensity(new_image, q_out + (atX-1)*Inc_ViewPixPtr(new_image));
      if (minVal > prevVal) {
        minVal = prevVal;
        dx = -1;
      }
    }
    if (atX < image->columns-1) {
      //prevVal = GET_PIXEL_RED(image,q_out+atX+1);
      prevVal = GetPixelIntensity(new_image, q_out + (atX+1)*Inc_ViewPixPtr(new_image));
      if (minVal > prevVal) {
        minVal = prevVal;
        dx = +1;
      }
    }

    // FIXME: get just the one pixel we need?
    q_path=GetCacheViewAuthenticPixels(path_view,0,new_image->rows-y-1,new_image->columns,1,exception);
    if (q_path == (const VIEW_PIX_PTR *) NULL) {
      fprintf (stderr, "darkestpath: bad GetCacheViewAuthenticPixels path_view\n");
      status=MagickFalse;
    }

    atX += dx;

    //if (pch->do_verbose) printf ("%i ", (int)atX);

//    SetPixelRed   (q_path+atX, QuantumRange);
//    SetPixelGreen (q_path+atX, QuantumRange);
//    SetPixelBlue  (q_path+atX, QuantumRange);
    //SET_PIXEL_RED   (path_image, QuantumRange, q_path+atX*Inc_ViewPixPtr(path_image));
    //SET_PIXEL_GREEN (path_image, QuantumRange, q_path+atX*Inc_ViewPixPtr(path_image));
    //SET_PIXEL_BLUE  (path_image, QuantumRange, q_path+atX*Inc_ViewPixPtr(path_image));
    SetPixIntensity (path_image, QuantumRange, q_path+atX*Inc_ViewPixPtr(path_image));

    if (SyncCacheViewAuthenticPixels(path_view,exception) == MagickFalse) {
      fprintf (stderr, "bad sync\n");
      status=MagickFalse;
    }
  }

  if (pch->do_verbose) printf ("End.\n");

  path_view=DestroyCacheView(path_view);
  out_view=DestroyCacheView(out_view);

  if (pch->do_cumerr) {
    DestroyImageList(path_image);
    return (new_image);
  } else {
    DestroyImageList(new_image);
    return (path_image);
  }
}


ModuleExport size_t darkestpathImage(Image **images,const int argc,
  const char **argv,ExceptionInfo *exception)
{
  Image
    *image,
    *new_image;

  MagickBooleanType
    status;

  darkestPathT
    cumul_histo;

  assert(images != (Image **) NULL);
  assert(*images != (Image *) NULL);
  assert((*images)->signature == MAGICK_CORE_SIG);

  status = menu (argc, argv, &cumul_histo);
  if (status == MagickFalse)
    return (-1);

  for (image=(*images); image != (Image *) NULL; image=GetNextImageInList(image))
  {
    new_image = darkestpath (image, &cumul_histo, exception);
    if (new_image == (Image *) NULL) return (-1);

    ReplaceImageInList(&image,new_image);
    *images=GetFirstImageInList(image);
  }

  return(MagickImageFilterSignature);
}

darkestmeander.c

/* Written by: Alan Gibson, 18 October 2015.

   References:

     http://im.snibgo.com/darkpath.htm
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <ctype.h>
#include "vsn_defines.h"

// FUTURE: We could weight diagonal steps by sqrt(2).

typedef struct {
  int
    max_iter,
    precision;

  MagickBooleanType
    autoIter,
    cumOnly,
    edgeLeft,
    edgeBottom,
    edgeRight,
    endIsGiven,
    do_verbose,
    do_verbose2;

  ssize_t
    endX,
    endY;

} darkestmeanderT;

static void usage (void)
{
  printf ("Usage: -process 'darkestmeander [OPTION]...'\n");
  printf ("Finds darkest meander from top to bottom.\n");
  printf ("\n");
  printf ("  m, maxiter N        maximum number of iterations [10]\n");
  printf ("  a, autoiter         stop iterating when path is stable\n");
  printf ("  c, cumerr           result is cumulative instead of meander\n");
  printf ("  s, side str         str contains any of lbr for left, bottom and right\n");
  printf ("  e, end_at X,Y       end the path at (integer) coordinates instead of a side\n");
  printf ("  v, verbose          write text information to stdout\n");
  printf ("  v2, verbose2        write more text information to stdout\n");
  printf ("\n");
}


static MagickBooleanType IsArg (char * pa, char * ShtOpt, char * LongOpt)
{
  if ((LocaleCompare(pa, ShtOpt)==0) || (LocaleCompare(pa, LongOpt)==0))
    return MagickTrue;

  return MagickFalse;
}


static MagickBooleanType menu(
  const int argc,
  const char **argv,
  darkestmeanderT * pch
)
// Returns MagickTrue if okay.
{
  int
    i;

  MagickBooleanType
    status;

  status = MagickTrue;

  pch->autoIter = pch->cumOnly = pch->do_verbose = pch->do_verbose2 = MagickFalse;
  pch->edgeLeft = pch->edgeRight = MagickFalse;
  pch->edgeBottom = MagickTrue;
  pch->endIsGiven = MagickFalse;
  pch->endX = pch->endY = 0;

  pch->max_iter = 10;

  for (i=0; i < argc; i++) {
    char * pa = (char *)argv[i];

    if (IsArg (pa, "c", "cumerr")==MagickTrue) {
      pch->cumOnly = MagickTrue;
    } else if (IsArg (pa, "m", "maxiter")==MagickTrue) {
      i++;
      if (!isdigit ((int)*argv[i]))
      {
        fprintf (stderr, "Non-numeric argument to 'maxiter'.\n");
        status = MagickFalse;
      }
      else pch->max_iter = atoi (argv[i]);
    } else if (IsArg (pa, "a", "autoiter")==MagickTrue) {
      pch->autoIter = MagickTrue;
    } else if (IsArg (pa, "e", "end_at")==MagickTrue) {
      pch->endIsGiven = MagickTrue;
      i++;
      int n = sscanf (argv[i],"%zd,%zd",&pch->endX,&pch->endY);
      if (n != 2) {
        fprintf (stderr, "'end_at' needs X,Y.\n");
        status = MagickFalse;
      }
      if (pch->endX < 0 || pch->endY < 0) {
        fprintf (stderr, "'end_at' needs positive X,Y.\n");
        status = MagickFalse;
      }
    } else if (IsArg (pa, "s", "side")==MagickTrue) {
      pch->edgeLeft = pch->edgeBottom = pch->edgeRight = MagickFalse;
      i++;
      const char * p = argv[i];
      while (*p) {
        if (*p=='l' || *p=='L') pch->edgeLeft = MagickTrue;
        else if (*p=='b' || *p=='B') pch->edgeBottom = MagickTrue;
        else if (*p=='r' || *p=='R') pch->edgeRight = MagickTrue;
        else {
          fprintf (stderr, "Argument to 'side' should contain lbr only.\n");
          status = MagickFalse;
        }
        p++;
      }
    } else if (IsArg (pa, "v", "verbose")==MagickTrue) {
      pch->do_verbose = MagickTrue;
    } else if (IsArg (pa, "v2", "verbose2")==MagickTrue) {
      pch->do_verbose2 = MagickTrue;
      pch->do_verbose = MagickTrue;
    } else {
      fprintf (stderr, "darkestmeander: ERROR: unknown option\n");
      status = MagickFalse;
    }
  }

  if (!pch->edgeLeft && !pch->edgeBottom && !pch->edgeRight) {
    fprintf (stderr, "side: must contain any of lbr\n");
    status = MagickFalse;
  }

  if (pch->do_verbose) {
    fprintf (stderr, "darkestmeander options:");
    if (pch->cumOnly)  fprintf (stderr, "  cumerr");
    fprintf (stderr, "  maxiter %i", pch->max_iter);
    if (pch->autoIter)  fprintf (stderr, "  autoiter");
    fprintf (stderr, "  side ");
    if (pch->edgeLeft) fprintf (stderr, "l");
    if (pch->edgeBottom) fprintf (stderr, "b");
    if (pch->edgeRight) fprintf (stderr, "r");
    if (pch->endIsGiven)  fprintf (stderr, "  end_at %li,%li", pch->endX, pch->endY);
    if (pch->do_verbose2) fprintf (stderr, "  verbose2");
    else if (pch->do_verbose) fprintf (stderr, "  verbose");
    fprintf (stderr, "\n");
  }

  if (status == MagickFalse)
    usage ();

  return (status);
}


static MagickBooleanType FindMinAtEdge (const Image *image,
  darkestmeanderT * pch,
  CacheView *out_view,
  ssize_t * patX,
  ssize_t * patY,
  ExceptionInfo *exception)
{
  // FIXME? If we have a span of equally good minimums, take the middle one.

  MagickBooleanType
    status = MagickTrue,
    FoundOne;

  ssize_t
    x, y,
    atYL, atXB, atYR,
    atX=0, atY=0;

  long double
    minVal,
    minValL, minValB, minValR;

  VIEW_PIX_PTR
    *q_out;

  FoundOne = MagickFalse;
  minVal = 999;

  if (pch->do_verbose2) printf ("FindMinAtEdge\n");

  if (pch->edgeBottom) {
    if (pch->do_verbose2)
      printf ("Find minimum value on the bottom row.\n");

    q_out=GetCacheViewAuthenticPixels(out_view,0,image->rows-1,image->columns,1,exception);
    if (q_out == (const VIEW_PIX_PTR *) NULL) {
      fprintf (stderr, "darkestmeander: bad q_out\n");
      status=MagickFalse;
    }

    minValB = GET_PIXEL_RED(image,q_out);
    atXB = 0;

    for (x=0; x < (ssize_t) image->columns; x++)
    {
      long double
        thisVal;

      thisVal = GET_PIXEL_RED(image,q_out);
      if (minValB > thisVal) {
        minValB = thisVal;
        atXB = x;
      }

      q_out += Inc_ViewPixPtr (image);
    }

    if (FoundOne == MagickFalse) {
      FoundOne = MagickTrue;
      minVal = minValB;
      atX = atXB;
      atY = image->rows-1;
    }

    if (pch->do_verbose)
      printf ("Min value on bottom row %.*g at x=%i.\n",
       pch->precision, (double)minValB,
       (int)atXB);
  }

  if (pch->edgeLeft) {
    if (pch->do_verbose2) {
      printf ("Find minimum value on the left edge.\n");
    }

    q_out=GetCacheViewAuthenticPixels(out_view,0,0,1,image->rows,exception);
    if (q_out == (const VIEW_PIX_PTR *) NULL) {
      fprintf (stderr, "darkestmeander: bad q_out\n");
      status=MagickFalse;
    }

    minValL = GET_PIXEL_RED(image,q_out);
    atYL = 0;

    for (y=0; y < (ssize_t) image->rows; y++)
    {
      long double
        thisVal;

      thisVal = GET_PIXEL_RED(image,q_out);
      if (minValL > thisVal) {
        minValL = thisVal;
        atYL = y;
      }

      q_out += Inc_ViewPixPtr (image);
    }

    if (FoundOne == MagickFalse || minVal > minValL) {
      FoundOne = MagickTrue;
      minVal = minValL;
      atX = 0;
      atY = atYL;
    }

    if (pch->do_verbose)
      printf ("Min value on left %.*g at y=%i.\n",
       pch->precision, (double)minValL,
       (int)atYL);
  }

  if (pch->edgeRight) {
    if (pch->do_verbose2) {
      printf ("Find minimum value on the right edge.\n");
    }

    q_out=GetCacheViewAuthenticPixels(out_view,image->columns-1,0,1,image->rows,exception);
    if (q_out == (const VIEW_PIX_PTR *) NULL) {
      fprintf (stderr, "darkestmeander: bad q_out\n");
      status=MagickFalse;
    }

    minValR = GET_PIXEL_RED(image,q_out);
    atYR = 0;

    for (y=0; y < (ssize_t) image->rows; y++)
    {
      long double
        thisVal;

      thisVal = GET_PIXEL_RED(image,q_out);
      if (minValR > thisVal) {
        minValR = thisVal;
        atYR = y;
      }

      q_out += Inc_ViewPixPtr (image);
    }

    if (FoundOne == MagickFalse || minVal > minValR) {
      FoundOne = MagickTrue;
      minVal = minValR;
      atX = image->columns-1;
      atY = atYR;
    }

    if (pch->do_verbose)
      printf ("Min value on right %.*g at y=%i.\n",
       pch->precision, (double)minValR,
       (int)atYR);

  }

  *patX = atX;
  *patY = atY;

  if (FoundOne) {
    if (pch->do_verbose)
      printf ("Min value at %i,%i\n", (int)atX, (int)atY);
  } else {
    status = MagickFalse;
  }

  return (status);
}



static MagickBooleanType CalcPathCumul (
  const Image *image,
  const Image *new_image,
  darkestmeanderT * pch,
  CacheView *out_view,
  ExceptionInfo *exception)
{
  // Calculates cumulation, as for darkestpath, more or less.

  ssize_t
    y;

  MagickBooleanType
    status = MagickTrue;

  if (pch->do_verbose2) printf ("CalcPathCumul: Get minimum from adjacent pixels on previous row.\n");

  // FIXME: green and blue values in row 0 are junk?

  // In q_out, we use blue and green channels to record delta x,y of parent of each pixel.
  // Values in these channels are 0, 50% or 100% representing delta of -1, 0, +1.
  // This simplifies finding the path after q_out is populated and stable.

  for (y=1; y < (ssize_t) image->rows; y++)
  {
    ssize_t
      x;

    long double
      minVal,
      prevVal;

    VIEW_PIX_PTR
      *q_out;

    //printf ("%i ", (int)y);

    const VIEW_PIX_PTR
      *p;

    if (status == MagickFalse)
      continue;

    p=GetCacheViewVirtualPixels(out_view,0,y-1,image->columns,1,exception);
    if (p == (const VIEW_PIX_PTR *) NULL)
    {
      fprintf (stderr, "bad GetCacheViewVirtualPixels out_view\n");
      status=MagickFalse;
      continue;
    }

    q_out=GetCacheViewAuthenticPixels(out_view,0,y,image->columns,1,exception);
    if (q_out == (const VIEW_PIX_PTR *) NULL) {
      fprintf (stderr, "darkestmeander: bad GetCacheViewAuthenticPixels out_view\n");
      status=MagickFalse;
    }

    for (x=0; x < (ssize_t) image->columns; x++)
    {
      signed int pv = 0;
      // Get minimum from adjacent pixels on previous row.
      minVal = GET_PIXEL_RED(image,p);
      if (x > 0) {
        prevVal = GET_PIXEL_RED(image,p-1);
        if (minVal > prevVal) {
          minVal = prevVal;
          pv = -1;
        }
      }
      if (x < image->columns-1) {
        prevVal = GET_PIXEL_RED(image,p+1);
        if (minVal > prevVal) {
          minVal = prevVal;
          pv = +1;
        }
      }

      if (minVal==0) printf ("minval==0 at %i,%i\n", (int)x,(int)y);

      SET_PIXEL_RED   (new_image, GET_PIXEL_RED(new_image,q_out)+minVal, q_out);
      SET_PIXEL_GREEN (new_image, 0, q_out);
      SET_PIXEL_BLUE  (new_image, (pv+1)/2.0*QuantumRange, q_out);

      p += Inc_ViewPixPtr (image);
      q_out += Inc_ViewPixPtr (new_image);
    }

    if (SyncCacheViewAuthenticPixels(out_view,exception) == MagickFalse) {
      fprintf (stderr, "bad sync\n");
      status=MagickFalse;
    }
  }

  return (status);
}


static MagickBooleanType Calc4Cumul (
  const Image *image,
  const Image *new_image,
  darkestmeanderT * pch,
  CacheView *image_view,
  CacheView *out_view,
  int * pnChanged,
  ExceptionInfo *exception)
{
  // Calculates cumulation in four directions.
  // Returns the number of pixels updated.

  MagickBooleanType
    status = MagickTrue;

  VIEW_PIX_PTR
    *q_out;

  int nIter;

  *pnChanged = 0;

  for (nIter=1; nIter <= 4; nIter++) {
    ssize_t
      startU, endU, dU,
      startV, endV, dV,
      u, v,
      cacheWi, cacheHt;

    int nCh = 0;

    switch (nIter % 4) {
      case 0:
      if (pch->do_verbose2) printf ("Down ");
      startU = 1;
      endU = (ssize_t)image->rows;
      dU = +1;
      startV = 0;
      endV = (ssize_t)image->columns;
      dV = +1;
      cacheWi = (ssize_t)image->columns;
      cacheHt = 1;
      break;

      case 1:
      if (pch->do_verbose2) printf ("Right ");
      startU = 1;
      endU = (ssize_t)image->columns;
      dU = +1;
      startV = 0;
      endV = (ssize_t)image->rows;
      dV = +1;
      cacheWi = 1;
      cacheHt = (ssize_t)image->rows;
      break;

      case 2:
      if (pch->do_verbose2) printf ("Up ");
      startU = (ssize_t)image->rows-2;
      endU = -1;
      dU = -1;
      startV = (ssize_t)image->columns-1;
      endV = -1;
      dV = -1;
      cacheWi = (ssize_t)image->columns;
      cacheHt = 1;
      break;

      case 3:
      default:
      if (pch->do_verbose2) printf ("Left ");
      startU = (ssize_t)image->columns-2;
      endU = -1;
      dU = -1;
      startV = (ssize_t)image->rows-1;
      endV = -1;
      dV = -1;
      cacheWi = 1;
      cacheHt = (ssize_t)image->rows;
      break;
    }

    // u and v are proxies for y and x respectively.

    for (u=startU; u != endU; u+=dU) {
      ssize_t
        cacheT, cacheL,
        cacheTm1, cacheLm1;

      const VIEW_PIX_PTR
        *p, *qm1;

      switch (nIter % 4) {
        case 0:
        cacheT = u;
        cacheL = 0;
        cacheTm1 = u-1;
        cacheLm1 = 0;
        break;

        case 1:
        cacheT = 0;
        cacheL = u;
        cacheTm1 = 0;
        cacheLm1 = u-1;
        break;

        case 2:
        cacheT = u;
        cacheL = 0;
        cacheTm1 = u+1;
        cacheLm1 = 0;
        break;

        case 3:
        default:
        cacheT = 0;
        cacheL = u;
        cacheTm1 = 0;
        cacheLm1 = u+1;
        break;
      }

      p=GetCacheViewVirtualPixels(image_view,cacheL,cacheT,cacheWi,cacheHt,exception);
      if (p == (const VIEW_PIX_PTR *) NULL)
      {
        fprintf (stderr, "bad GetCacheViewVirtualPixels image_view\n");
        status=MagickFalse;
        continue;
      }

      qm1=GetCacheViewVirtualPixels(out_view,cacheLm1,cacheTm1,cacheWi,cacheHt,exception);
      if (qm1 == (const VIEW_PIX_PTR *) NULL)
      {
        fprintf (stderr, "bad GetCacheViewVirtualPixels qm1\n");
        status=MagickFalse;
        continue;
      }

      q_out=GetCacheViewAuthenticPixels(out_view,cacheL,cacheT,cacheWi,cacheHt,exception);
      if (q_out == (const VIEW_PIX_PTR *) NULL)
      {
        fprintf (stderr, "bad GetCacheViewAthenticPixels q_out\n");
        status=MagickFalse;
        continue;
      }

      for (v=startV; v != endV; v+=dV) {
        long double
          minVal,
          prevVal;

        signed int pu=0, pv=0;

        minVal = (GET_PIXEL_RED(image,q_out)-GET_PIXEL_RED(image,p)) * 0.99999;

        prevVal = GET_PIXEL_RED(image,qm1);
        if (minVal > prevVal) {
          minVal = prevVal;
          pu=-1;
          pv=0;
        }


// FIXME: for v7, -1 or +1 should be  Inc_ViewPixPtr(image);

        if (v > startV) {
          prevVal = GET_PIXEL_RED(image,qm1-Inc_ViewPixPtr(image));
          if (minVal > prevVal) {
            minVal = prevVal;
            pu=-1;
            pv=-1;
          }
        }

        if (v < endV-1) {
          prevVal = GET_PIXEL_RED(image,qm1+Inc_ViewPixPtr(image));
          if (minVal > prevVal) {
            minVal = prevVal;
            pu=-1;
            pv=+1;
          }
        }

        if (v > startV) {
          prevVal = GET_PIXEL_RED(image,q_out-Inc_ViewPixPtr(image));
          if (minVal > prevVal) {
            minVal = prevVal;
            pu=0;
            pv=-1;
          }
        }

        if (v < endV-1) {
          prevVal = GET_PIXEL_RED(image,q_out+Inc_ViewPixPtr(image));
          if (minVal > prevVal) {
            minVal = prevVal;
            pu=0;
            pv=+1;
          }
        }

        if (pu != 0 || pv != 0) {
          pu *= dU;
          pv *= dV;

          if (minVal >= 0 || GET_PIXEL_RED(image,p) >= 0) {

            SET_PIXEL_RED (new_image, GET_PIXEL_RED(image,p)+minVal, q_out);

            if (nIter % 2 == 1) {
              signed int t = pu;
              pu = pv;
              pv = t;
            }
            SET_PIXEL_GREEN (new_image, (pu+1)/2.0*QuantumRange, q_out);
            SET_PIXEL_BLUE  (new_image, (pv+1)/2.0*QuantumRange, q_out);
            nCh++;
          }
        }

        p += Inc_ViewPixPtr (image);
        qm1 += Inc_ViewPixPtr (image);
        q_out += Inc_ViewPixPtr (image);
      }

      if (SyncCacheViewAuthenticPixels(out_view,exception) == MagickFalse) {
        fprintf (stderr, "bad sync\n");
        status=MagickFalse;
      }
    }

    if (pch->do_verbose2) printf ("%i nCh=%i\n", nIter, nCh);
    *pnChanged += nCh;
  }

  if (pch->do_verbose2) printf ("Calc4Cumul pnChanged=%i\n", *pnChanged);

  return (status);
}


static MagickBooleanType PaintPath (
  const Image *current_path_image,
  const Image *new_path_image,
  darkestmeanderT * pch,
  CacheView *out_view,
  CacheView *path_view,
  ssize_t atX,
  ssize_t atY,
  MagickBooleanType *Cyclic,
  ExceptionInfo *exception)
{
  ssize_t
    y,
    x;

  VIEW_PIX_PTR
    *q_path;

  MagickBooleanType
    status = MagickTrue;

  *Cyclic = MagickFalse;

  if (pch->do_verbose2) printf ("PaintPath: Set path end\n");

  // Get just the one pixel we need.
  q_path=GetCacheViewAuthenticPixels(path_view,atX,atY,1,1,exception);
  if (q_path == (const VIEW_PIX_PTR *) NULL) {
    fprintf (stderr, "darkestmeander: bad GetCacheViewAuthenticPixels q_path\n");
    status=MagickFalse;
  }

  SET_PIXEL_RED   (new_path_image, QuantumRange, q_path);
  SET_PIXEL_GREEN (new_path_image, QuantumRange, q_path);
  SET_PIXEL_BLUE  (new_path_image, QuantumRange, q_path);

  if (SyncCacheViewAuthenticPixels(path_view,exception) == MagickFalse) {
    fprintf (stderr, "bad sync\n");
    status=MagickFalse;
  }


  if (pch->do_verbose2) printf ("PaintPath: Walk up the image, painting the path white.\n");

  // Walk back through the meander, painting the path white.
  // At each point, find the parent.

  x = atX;
  y = atY;
  while (y > 0 && *Cyclic == MagickFalse && status == MagickTrue) {
    const VIEW_PIX_PTR
      *p;

    signed int di, dj;

    //if (pch->do_verbose2) printf ("PaintPath: xy=%i,%i  ", (int)x, (int)y);

    p=GetCacheViewVirtualPixels(out_view,x,y,1,1,exception);
    if (p == (const VIEW_PIX_PTR *) NULL) {
      fprintf (stderr, "darkestmeander: bad GetCacheViewVirtualPixels p\n");
      status=MagickFalse;
    }
    di = (signed int)(GET_PIXEL_BLUE  (current_path_image, p) / QuantumRange * 2.0 + 0.5) -1;
    dj = (signed int)(GET_PIXEL_GREEN (current_path_image, p) / QuantumRange * 2.0 + 0.5) -1;

    x += di;
    y += dj;

    //if (pch->do_verbose2) printf (" dij=%i,%i W%i,%i\n", di, dj, (int)x, (int)y);

    // Get just the one pixel we need.
    q_path=GetCacheViewAuthenticPixels(path_view,x,y,1,1,exception);
    if (q_path == (const VIEW_PIX_PTR *) NULL) {
      fprintf (stderr, "PaintPath: bad GetCacheViewAuthenticPixels path_view\n");
      status=MagickFalse;
    }

    if (GET_PIXEL_RED (new_path_image, q_path) > 0) {
      if (pch->do_verbose) fprintf (stderr, "PaintPath: Path not black at %i,%i: cyclic\n", (int)x, (int)y);
      y = 0;
      *Cyclic = MagickTrue;
    }

    SET_PIXEL_RED   (new_path_image, QuantumRange, q_path);
    SET_PIXEL_GREEN (new_path_image, QuantumRange, q_path);
    SET_PIXEL_BLUE  (new_path_image, QuantumRange, q_path);

    if (SyncCacheViewAuthenticPixels(path_view,exception) == MagickFalse) {
      fprintf (stderr, "bad sync\n");
      status=MagickFalse;
    }

  }

  if (pch->do_verbose2) printf ("PaintPath: done\n");

  return (status);
}


static Image *TryPath(Image *current_path_image,
  darkestmeanderT * pch,
  ssize_t atX,
  ssize_t atY,
  CacheView *out_view,
  MagickBooleanType *IsEqual,
  ExceptionInfo *exception)
// Makes a new path, and compare it to current_path_image.
// Destroys current_path_image, and returns the new path image (or NULL if failure).
{
  Image
    *new_path_image;

  CacheView
    *new_path_view;

  MagickBooleanType
    Cyclic;

  if (pch->do_verbose2) printf ("TryPath\n");

  // Create a new black image for the path.

  // Make a clone of this image, same size but undefined pixel values:
  //
  new_path_image = CloneImage (
    current_path_image,
    current_path_image->columns, current_path_image->rows,
    MagickTrue, exception);
  if (new_path_image == (Image *) NULL)
    return(new_path_image);

  if (SetNoPalette (new_path_image, exception) == MagickFalse)
    return (Image *)NULL;

  SetAllBlack (new_path_image, exception);

  new_path_view=AcquireAuthenticCacheView(new_path_image,exception);

  if (PaintPath (
       current_path_image, new_path_image,
       pch, out_view, new_path_view, atX, atY, &Cyclic, exception
                )==MagickFalse)
  {
    return (Image *)NULL;
  }

  *IsEqual = (Cyclic == MagickFalse)
              &&
#if IMV6OR7==6
              IsImagesEqual (new_path_image, current_path_image);
#else
              IsImagesEqual (new_path_image, current_path_image, exception);
#endif

  new_path_view=DestroyCacheView(new_path_view);

  DestroyImageList(current_path_image);

  return (new_path_image);
}


static MagickBooleanType CalcCumul (
  const Image *image,
  const Image *new_image,
  Image **current_path_image,
  darkestmeanderT * pch,
  CacheView *image_view,
  CacheView *out_view,
  ExceptionInfo *exception)
{
  MagickBooleanType
    status = MagickTrue;

  ssize_t
    prevAtX=-1, prevAtY=-1;

  status = CalcPathCumul (image, new_image, pch, out_view, exception);
  if (status == MagickFalse) return MagickFalse;

  if (pch->do_verbose2) printf ("CalcCumul: Get meander minimums.\n");

  int nIterCyc = 0;
  int nChanged = 0;

  MagickBooleanType IsEqual = MagickFalse;

  do {
    status = Calc4Cumul (image, new_image, pch, image_view, out_view, &nChanged, exception);
    nIterCyc++;

    if (pch->autoIter) {
      ssize_t
        atX, atY;

      if (pch->endIsGiven) {
        atX = pch->endX;
        atY = pch->endY;
      } else {
        status = FindMinAtEdge (image, pch, out_view, &atX, &atY, exception);
        if (status == MagickFalse) return MagickFalse;
      }

      //status = FindMinAtEdge (image, pch, out_view, &atX, &atY, exception);
      //if (status == MagickFalse) return MagickFalse;

      if (prevAtX == atX && prevAtY == atY) {
        *current_path_image =
        TryPath(*current_path_image, pch, atX, atY, out_view, &IsEqual, exception);
      }

      if (pch->do_verbose)
        printf ("CalcCumul: nIterCyc=%i at %i,%i IsEqual=%i\n",
                nIterCyc, (int)atX, (int)atY, (int)IsEqual);

      prevAtX = atX;
      prevAtY = atY;
    }
  } while (   nChanged > 0
           && IsEqual==MagickFalse
           && (pch->max_iter == 0 || nIterCyc < pch->max_iter));

  if (pch->do_verbose) {
    printf ("darkestmeander: nIterCyc=%i\n", nIterCyc);
    if (nChanged == 0) {
      printf ("darkestmeander: completed\n");
    }
    else if (pch->max_iter > 0 && nIterCyc >= pch->max_iter) {
      printf ("darkestmeander: nIterCyc bust: %i\n", nIterCyc);
    }
    else if (IsEqual) {
      printf ("darkestmeander: path stable\n");
    }
  }

  return (status);
}


static MagickBooleanType EnsureNonZero (Image *image,
  darkestmeanderT * pch,
  ExceptionInfo *exception)
{
  CacheView
    *image_view;

  ssize_t
    y;

  MagickBooleanType
    status = MagickTrue;

#if defined(MAGICKCORE_HDRI_SUPPORT)
#define NON_ZERO_VALUE 1.0
#else
#define NON_ZERO_VALUE 1
#endif

  if (pch->do_verbose2) {
    printf ("EnsureNonZero: %g\n", NON_ZERO_VALUE);
  }

  image_view=AcquireAuthenticCacheView(image,exception);

  for (y=0; y < (ssize_t) image->rows; y++)
  {
    ssize_t
      x;

    VIEW_PIX_PTR
      *p;

    if (status == MagickFalse)
      continue;

    p=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
    if (p == (const VIEW_PIX_PTR *) NULL) {
      fprintf (stderr, "EnsureNonZero: bad GetCacheViewAuthenticPixels p\n");
      status=MagickFalse;
    }

    for (x=0; x < (ssize_t) image->columns; x++)
    {
      if (GET_PIXEL_RED(image,p) == 0)
        SET_PIXEL_RED(image, NON_ZERO_VALUE, p);
      p += Inc_ViewPixPtr (image);
    }

    if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse) {
      fprintf (stderr, "bad sync\n");
      status=MagickFalse;
    }
  }

  image_view=DestroyCacheView(image_view);

  return (status);
}


// The next function corresponds in style to functions in transform.c
// It takes one image, and returns an image.
//
static Image *darkestmeander(Image *image,
  darkestmeanderT * pch,
  ExceptionInfo *exception)
{
  Image
    *new_image,
    *path_image;

  CacheView
    *image_view,
    *out_view;


  ssize_t
    atX, atY;

  MagickBooleanType
    status = MagickTrue;


  pch->precision = GetMagickPrecision();

  if (image->debug != MagickFalse)
    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);

  if (!Has3Channels (image)) {
    fprintf (stderr, "Image has less than three channels.\n");
    return (Image *)NULL;
  }

  // No good, because it knocks out negative values.
  // We must change _only_ where Red is exactly zero.
  // status = EvaluateImageChannel(image, RedChannel, MaxEvaluateOperator, 1, exception);
  // if (status==MagickFalse) return (Image *) NULL;


  EnsureNonZero (image, pch, exception);

  //const ChannelType channel,const MagickEvaluateOperator op,const double value,
  //ExceptionInfo *exception)

  // Copy the current image.

  new_image=CloneImage(image, 0, 0, MagickTrue, exception);
  if (new_image == (Image *) NULL)
    return(new_image);

  if (SetNoPalette (new_image, exception) == MagickFalse)
    return (Image *)NULL;

  //GetMagickVIEW_PIX_PTR(new_image, &mppBlack);
  //(void)SetImageColor(new_image, &mppBlack);

  status=MagickTrue;

  out_view=AcquireAuthenticCacheView(new_image,exception);

  image_view=AcquireVirtualCacheView(image,exception);


  if (pch->do_verbose2) printf ("Create a new black image for the path.\n");

  // Create a new black image for the path.

  // Make a clone of this image, same size but undefined pixel values:
  //
  path_image=CloneImage(image, image->columns, image->rows, MagickTrue, exception);
  if (path_image == (Image *) NULL)
    return(path_image);

  if (SetNoPalette (path_image, exception) == MagickFalse)
    return (Image *)NULL;

  SetAllBlack (path_image, exception);


  status = CalcCumul (image, new_image, &path_image, pch, image_view, out_view, exception);
  if (status == MagickFalse) return (Image *)NULL;

  if (pch->cumOnly) {

    if (pch->do_verbose2) printf ("End.\n");

    image_view=DestroyCacheView(image_view);
    out_view=DestroyCacheView(out_view);

    DestroyImageList(path_image);
    return (new_image);

  } else {

    CacheView
      *path_view;

    MagickBooleanType
      Cyclic;

    if (pch->endIsGiven) {
      atX = pch->endX;
      atY = pch->endY;
    } else {
      status = FindMinAtEdge (image, pch, out_view, &atX, &atY, exception);
      if (status == MagickFalse) return (Image *)NULL;
    }

    // FIXME: maybe we have a good path already.

    SetAllBlack (path_image, exception);

    path_view=AcquireAuthenticCacheView(path_image,exception);

    status = PaintPath (image, path_image, pch, out_view, path_view, atX, atY, &Cyclic, exception);
    if (status == MagickFalse) return (Image *)NULL;

    if (Cyclic == MagickTrue) {
      fprintf (stderr, "darkestmeander: Path is cyclic at %i,%i\n", (int)atX, (int)atY);
    }

    path_view=DestroyCacheView(path_view);
    image_view=DestroyCacheView(image_view);
    out_view=DestroyCacheView(out_view);

    DestroyImageList(new_image);
    return (path_image);
  }
}


ModuleExport size_t darkestmeanderImage(Image **images,const int argc,
  const char **argv,ExceptionInfo *exception)
{
  Image
    *image,
    *new_image;

  MagickBooleanType
    status;

  darkestmeanderT
    dm;

  assert(images != (Image **) NULL);
  assert(*images != (Image *) NULL);
  assert((*images)->signature == MAGICK_CORE_SIG);

#if defined(MAGICKCORE_HDRI_SUPPORT)
#else
  fprintf (stderr, "No HDRI. darkestmeander may not work correctly.");
#endif

  status = menu (argc, argv, &dm);
  if (status == MagickFalse)
    return (-1);

  for (image=(*images); image != (Image *) NULL; image=GetNextImageInList(image))
  {
    if (dm.endIsGiven) {
      if (dm.endX >= image->columns || dm.endY >= image->rows) {
        fprintf (stderr, "Given end is outside image.\n");
        return (-1);
      }
    }

    new_image = darkestmeander (image, &dm, exception);
    if (new_image == (Image *)NULL) return -1;

    ReplaceImageInList(&image,new_image);
    *images=GetFirstImageInList(image);
  }

  return(MagickImageFilterSignature);
}

darkestpntpnt.c

/* Written by: Alan Gibson, 18 October 2015.

   References:
     https://en.wikipedia.org/wiki/Dijkstra's_algorithm
     Wikipedia: Dijkstra's algorithm.

     http://im.snibgo.com/darkpath.htm

  Updated:
    9-May-2016 added "data" option.
    15-October-2016 fixed incorrect warning that start or end exceeds threshold.
    16-December-2016 Added "print" option.
    14-January-2017 Added coordList stuff.
    23-August-2017 Moved user-coordinate stuff to external file.
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <float.h>

#ifndef IMV6OR7
#include "vsn_defines.h"
#endif

#include "chklist.h"

#define VERIFY 0

#include "usercoord.inc"



//---------------------------------------------------------------------------
// Coordinate list data structures.

typedef struct {
  double x;
  double y;
} CoordT;

typedef struct {
  int listInitSize;
  int listSize;
  int numUsed;
  CoordT * coords;
} CoordListT;

//---------------------------------------------------------------------------
// Data structures.

typedef double dppValueT;

// Neighbours are numbered 1 to 9 except 5.
// Up-left is 1, up-right is 3, etc.
#define NUM_NEIGHB 9

typedef struct {
  Quantum
    value;

  dppValueT
    tentativeDistance;

  MagickBooleanType
    visited;

  int
    parent;
} NodeT;

#define INFINITE_DIST DBL_MAX

typedef struct {
  ssize_t
    x,
    y;
} pqNodeT;

typedef struct {
  MagickBooleanType
    stopAtEnd,
    wrData,
    suppressImage,
    printPix,
    do_verbose,
    do_warnings,
    hasVisitedThreshold,
    foundEnd;

  dppValueT
    visitedThreshold;

  UserCoordXyT
    start,
    end;

  ssize_t
    width,
    height;

  FILE *
    fh_data;

  int
    precision;

  dppValueT
    neighbourWeights[NUM_NEIGHB+1];

  NodeT
    **nodes;

  ssize_t
    pqNumNodes;

  ssize_t
    pqMaxNodes;

  pqNodeT
    *pqNodes;

  CoordListT
    *coordList; // If not null, list will be added to.
} darkestPntPntT;

static void InitDarkestPntPnt (darkestPntPntT * dpp)
{
  dpp->do_verbose = MagickFalse;
  dpp->do_warnings = MagickTrue;
  dpp->hasVisitedThreshold = MagickFalse;

  dpp->start.x.Pix = dpp->start.y.Pix = 0;
  dpp->start.x.cs = dpp->start.y.cs = csPix;
  dpp->end.x.Pix = dpp->end.y.Pix = 0;
  dpp->end.x.cs = dpp->end.y.cs = csPix;
  dpp->wrData = MagickFalse;
  dpp->suppressImage = MagickFalse;
  dpp->stopAtEnd = MagickTrue;
  dpp->printPix = MagickFalse;
  dpp->fh_data = stderr;

  dpp->coordList = NULL;

  int i;
  for (i=0; i <= NUM_NEIGHB; i++) dpp->neighbourWeights[i] = 1;

  dpp->neighbourWeights[1]
    = dpp->neighbourWeights[3]
    = dpp->neighbourWeights[7]
    = dpp->neighbourWeights[9] = sqrt (2.0);
}

//---------------------------------------------------------------------------
// Coordinate list functions.

static CoordListT * CreateCoordList (int initSize)
// If oom (or initSize is <= zero), returns NULL.
{
  if (initSize <= 0) return NULL;

  CoordListT * pcl = (CoordListT *)malloc(sizeof(CoordListT));
  if (pcl==NULL) return NULL;
  pcl->listInitSize = initSize;
  pcl->listSize = initSize;
  pcl->numUsed = 0;
  pcl->coords = (CoordT *)malloc(pcl->listSize*sizeof(CoordT));
  if (pcl->coords==NULL) {
    free (pcl);
    return NULL;
  }
  return pcl;
}

static CoordListT * DestroyCoordList (CoordListT * pcl)
{
  if (pcl) {
    if (pcl->coords) free (pcl->coords);
    free (pcl);
  }
  return NULL;
}

static MagickBooleanType AddToCoordList (CoordListT * pcl, double x, double y)
// If list full, expands it.
// If oom, returns false.
{
  if (!pcl) return MagickFalse;

  if (pcl->numUsed > pcl->listSize) {
    printf ("AddToCoordList: bug\n");
    return MagickFalse;
  }

  if (pcl->numUsed == pcl->listSize) {
    // Expand it.
    pcl->listSize += pcl->listInitSize;
    CoordT * tmp = (CoordT *)realloc(pcl->coords, pcl->listSize*sizeof(CoordT));
    if (tmp==NULL) {
      free (pcl->coords);
      free (pcl);
      return MagickFalse;
    }
    pcl->coords = tmp;
  }

  pcl->coords[pcl->numUsed].x = x;
  pcl->coords[pcl->numUsed].y = y;
  pcl->numUsed++;
  return MagickTrue;
}

static MagickBooleanType AddToCoordListIf (CoordListT * pcl, double x, double y)
// If list empty, or new coords different to last entry,
// adds to list.
{
  if (!pcl) return MagickFalse;

  if (pcl->numUsed == 0
   || pcl->coords[pcl->numUsed-1].x != x
   || pcl->coords[pcl->numUsed-1].y != y)
  {
    return AddToCoordList (pcl, x, y);
  }
  printf ("f");
  return MagickTrue;
}

static void InvertCoordList (CoordListT * pcl, int nStart)
{
  if (!pcl) return;

  printf ("InvertCoordList %i\n", pcl->numUsed);
  int i;
  int q = pcl->numUsed-1;
  double tx, ty;
  for (i = nStart; i < q; i++) {
    tx = pcl->coords[i].x;
    ty = pcl->coords[i].y;
    pcl->coords[i].x = pcl->coords[q].x;
    pcl->coords[i].y = pcl->coords[q].y;
    pcl->coords[q].x = tx;
    pcl->coords[q].y = ty;
    q--;
  }
}

static void EmptyCoordList (CoordListT * pcl)
{
  if (!pcl) return;

  pcl->numUsed = 0;
}

static void WrCoords (CoordListT * pcl, FILE * fhOut)
{
  if (!pcl) return;

  int i;
  for (i = 0; i < pcl->numUsed; i++) {
    fprintf (fhOut, "%g,%g\n",
      (double)pcl->coords[i].x, (double)pcl->coords[i].y);
  }
}

static void DumpCoordList (CoordListT * pcl, FILE * fhOut)
{
  if (!pcl) return;

  fprintf (fhOut, "Init=%i  Size=%i numUsed=%i\n",
    pcl->listInitSize, pcl->listSize, pcl->numUsed);
  WrCoords (pcl, fhOut);
}

//---------------------------------------------------------------------------
// Priority queue functions.

#define LCHILD(x) 2 * x + 1
#define RCHILD(x) 2 * x + 2
#define PARENT(x) (x - 1) / 2

static dppValueT inline pqDataOfNode (darkestPntPntT * dpp, ssize_t NodeNum)
{
  ssize_t x = dpp->pqNodes[NodeNum].x;
  ssize_t y = dpp->pqNodes[NodeNum].y;
  return dpp->nodes[y][x].tentativeDistance;
}


#if VERIFY==1

static void pqVerify (darkestPntPntT * dpp)
{
  ssize_t
    i;

  //if (dpp->do_verbose) printf ("pqVerify pqNumNodes=%li\n", dpp->pqNumNodes);

  for (i=0; i < dpp->pqNumNodes; i++) {
    ssize_t lch = LCHILD(i);
    ssize_t rch = RCHILD(i);

    MagickBooleanType HasL = lch < dpp->pqNumNodes;
    MagickBooleanType HasR = rch < dpp->pqNumNodes;

    if (HasL && pqDataOfNode (dpp, lch) < pqDataOfNode (dpp, i))
      printf ("** Bad %li left\n", i);

    if (HasR && pqDataOfNode (dpp, rch) < pqDataOfNode (dpp, i))
      printf ("** Bad %li right\n", i);
  }
}

static void pqDumpOrder (darkestPntPntT * dpp, ssize_t NodeNum)
{
  //printf("%d ", hp->elem[i].data) ;

  ssize_t x = dpp->pqNodes[NodeNum].x;
  ssize_t y = dpp->pqNodes[NodeNum].y;
  dppValueT v = dpp->nodes[y][x].tentativeDistance;

  printf ("  %li  %li,%li %.*g\n", NodeNum, x, y, dpp->precision, v);

  ssize_t lch = LCHILD(NodeNum);
  ssize_t rch = RCHILD(NodeNum);

  MagickBooleanType
    LFirst = MagickTrue,
    HasL = lch < dpp->pqNumNodes,
    HasR = rch < dpp->pqNumNodes;

  if (HasL && HasR) {
    LFirst = pqDataOfNode (dpp, lch) < pqDataOfNode (dpp, rch);
  }

  if(HasL && LFirst) {
    pqDumpOrder (dpp, lch) ;
  }
  if(HasR) {
    pqDumpOrder (dpp, rch) ;
  }
  if(HasL && !LFirst) {
    pqDumpOrder (dpp, lch) ;
  }
}

static void pqDump (darkestPntPntT * dpp)
{
  ssize_t
    i;

  printf ("pqNumNodes=%li\n", dpp->pqNumNodes);

  for (i=0; i < dpp->pqNumNodes; i++) {
    pqNodeT * pqn = &dpp->pqNodes[i];
    NodeT * pn = &(dpp->nodes[pqn->y][pqn->x]);
    printf ("  %li  %li,%li %.*g\n",
      i, pqn->x, pqn->y, dpp->precision, pn->tentativeDistance);
  }

  //printf ("In order:\n");
  //pqDumpOrder (dpp, 0);

  pqVerify (dpp);
}

#endif

static void inline pqSwapNodes (darkestPntPntT * dpp, ssize_t n1, ssize_t n2)
{
  ssize_t t = dpp->pqNodes[n1].x;
  dpp->pqNodes[n1].x = dpp->pqNodes[n2].x;
  dpp->pqNodes[n2].x = t;

  t = dpp->pqNodes[n1].y;
  dpp->pqNodes[n1].y = dpp->pqNodes[n2].y;
  dpp->pqNodes[n2].y = t;
}

static void pqBalanceNode (darkestPntPntT * dpp, ssize_t NodeNum)
// "Bubble-down".
// Finds the smallest of this node and its children.
// If the node isn't the smallest of the three,
//   swaps data with that child and recursively balances that child.
{
  ssize_t smallest = NodeNum;
  ssize_t lch = LCHILD(NodeNum);
  ssize_t rch = RCHILD(NodeNum);

  dppValueT SmData = pqDataOfNode (dpp, NodeNum);

  if (lch < dpp->pqNumNodes) {
    dppValueT ldist = pqDataOfNode (dpp, lch);
    if (ldist < SmData) {
      SmData = ldist;
      smallest = lch;
    }
  }

  if (rch < dpp->pqNumNodes) {
    dppValueT rdist = pqDataOfNode (dpp, rch);
    if (rdist < SmData) {
      SmData = rdist;
      smallest = rch;
    }
  }

  if (smallest != NodeNum) {
    pqSwapNodes (dpp, smallest, NodeNum);
    pqBalanceNode (dpp, smallest);
  }
}

static void pqInsertNew (darkestPntPntT * dpp, ssize_t x, ssize_t y)
{
  //printf ("pqIns %li,%li ", x, y);

  if (dpp->pqNumNodes >= dpp->pqMaxNodes) {
    fprintf (stderr, "pq bust\n");
  }

  dppValueT newDist = dpp->nodes[y][x].tentativeDistance;

  ssize_t n = dpp->pqNumNodes;

  // "Bubble up".
  // If this data is less than node's parent,
  //   drop parent data into this node
  //   and consider putting new data into that parent.
  while (n && newDist < pqDataOfNode (dpp, PARENT(n))) {
    dpp->pqNodes[n] = dpp->pqNodes[PARENT(n)];
    n = PARENT(n);
  }
  dpp->pqNodes[n].x = x;
  dpp->pqNodes[n].y = y;

  dpp->pqNumNodes++;

#if VERIFY==1
  pqVerify (dpp);
#endif
}

static ssize_t pqFindNode (
  darkestPntPntT * dpp,
  ssize_t x, ssize_t y,
  dppValueT val, ssize_t NodeStart)
// Returns -1 if not present.
// Note: recursive.
{
  if (dpp->pqNumNodes==0) return -1;

  pqNodeT * pqn = &dpp->pqNodes[NodeStart];

  if (pqn->x == x && pqn->y == y) return NodeStart;

  if (pqDataOfNode (dpp, NodeStart) > val) return -1;

  ssize_t lch = LCHILD(NodeStart);
  if (lch < dpp->pqNumNodes) {
    ssize_t nch = pqFindNode (dpp, x, y, val, lch);
    if (nch >= 0) return nch;
  }

  ssize_t rch = RCHILD(NodeStart);
  if (rch < dpp->pqNumNodes) {
    ssize_t nch = pqFindNode (dpp, x, y, val, rch);
    if (nch >= 0) return nch;
  }

  return -1;
}

#if VERIFY==1

static void pqFindMin (darkestPntPntT * dpp, ssize_t *x, ssize_t *y)
{
  if (!dpp->pqNumNodes) fprintf (stderr, "** Bad pqfm: empty");

  pqNodeT * pqn = &dpp->pqNodes[0];
  *x = pqn->x;
  *y = pqn->y;
}

#endif


static int pqRemoveMin (darkestPntPntT * dpp, ssize_t *x, ssize_t *y)
// Returns 1 if okay, or 0 if no data.
{
  if (!dpp->pqNumNodes) {
    //fprintf (stderr, "** Bad pqrm: empty");
    return 0;
  }

  pqNodeT * pqn = &dpp->pqNodes[0];
  *x = pqn->x;
  *y = pqn->y;

  // Put largest into root, and rebalance.

  dpp->pqNodes[0] = dpp->pqNodes[--dpp->pqNumNodes];

  pqBalanceNode (dpp, 0);

  return 1;
}


static void inline UpdateDist (
  darkestPntPntT * dpp,
  ssize_t nodeNum,
  ssize_t x, ssize_t y,
  //dppValueT oldVal,
  dppValueT newVal)
// Updates the tentative distance of a pixel.
{
  //printf ("UpdDist %li,%li: ", x, y);

/*==
  assert(newVal <= oldVal);

  ssize_t n = pqFindNode (dpp, x, y, oldVal, 0);

  if (n < 0) {
    fprintf (stderr, "** BUG: UpdateDist can't find %li,%li\n", x, y);
    return;
  }
==*/

  ssize_t n = nodeNum;

  dpp->nodes[y][x].tentativeDistance = newVal;

  // "Bubble up".
  // If this data is less than node's parent,
  //   swap with parent and iterate.
  while (n && newVal < pqDataOfNode (dpp, PARENT(n))) {
    pqSwapNodes (dpp, n, PARENT(n));
    n = PARENT(n);
  }

  // Bubble down from the top.
  // FIXME: do we need this?
//  pqBalanceNode (dpp, 0);

#if VERIFY==1
  pqVerify (dpp);
#endif
}


//---------------------------------------------------------------------------




static void usage (void)
{
  printf ("Usage: -process 'darkestpntpnt [OPTION]...'\n");
  printf ("Finds darkest path between two points.\n");
  printf ("\n");
  printf ("  s, start_at X,Y         start the path at (integer) coordinates\n");
  printf ("  e, end_at X,Y           end the path at (integer) coordinates\n");
  printf ("  t, threshold_visited N  where initial values above N, don't visit\n");
  printf ("  n, no_end               don't stop processing at path end\n");
  printf ("  d, data                 write data image instead of path image\n");
  printf ("  p, print                write each visited coordinate to stderr\n");
  printf ("  f, file string          write to file stream stdout or stderr\n");
  printf ("  v, verbose              write text information to stdout\n");
  printf ("\n");
  printf ("sizeof(NodeT)=%i\n", (int)sizeof(NodeT));
  printf ("sizeof(pqNodeT)=%i\n", (int)sizeof(pqNodeT));
  printf ("INFINITE_DIST=%g\n", INFINITE_DIST);
  printf ("\n");
}


static MagickBooleanType IsArg (char * pa, char * ShtOpt, char * LongOpt)
{
  if ((LocaleCompare(pa, ShtOpt)==0) || (LocaleCompare(pa, LongOpt)==0))
    return MagickTrue;

  return MagickFalse;
}


static MagickBooleanType menu(
  const int argc,
  const char **argv,
  darkestPntPntT * dpp
)
// Returns MagickTrue if okay.
{
  int
    i;

  MagickBooleanType
    status;

  status = MagickTrue;

  InitDarkestPntPnt (dpp);

  for (i=0; i < argc; i++) {
    char * pa = (char *)argv[i];

    if (IsArg (pa, "s", "start_at")==MagickTrue) {
      i++;

      int r = ParseXy (argv[i], &dpp->start);
      if (!r) {
        printf ("Failed to parse start [%s]\n", argv[i]);
        status = MagickFalse;
      }
    } else if (IsArg (pa, "e", "end_at")==MagickTrue) {
      i++;
      int r = ParseXy (argv[i], &dpp->end);
      if (!r) {
        printf ("Failed to parse end [%s]\n", argv[i]);
        status = MagickFalse;
      }
    } else if (IsArg (pa, "t", "threshold_visited")==MagickTrue) {
      dpp->hasVisitedThreshold = MagickTrue;
      i++;
      dpp->visitedThreshold = atof (argv[i]);
    } else if (IsArg (pa, "n", "no_end")==MagickTrue) {
      dpp->stopAtEnd = MagickFalse;
    } else if (IsArg (pa, "d", "data")==MagickTrue) {
      dpp->wrData = MagickTrue;
    } else if (IsArg (pa, "p", "print")==MagickTrue) {
      dpp->printPix = MagickTrue;
    } else if (IsArg (pa, "f", "file")==MagickTrue) {
      i++;
      if (strcasecmp (argv[i], "stdout")==0) dpp->fh_data = stdout;
      else if (strcasecmp (argv[i], "stderr")==0) dpp->fh_data = stderr;
      else status = MagickFalse;
    } else if (IsArg (pa, "v", "verbose")==MagickTrue) {
      dpp->do_verbose = MagickTrue;
    } else {
      fprintf (stderr, "darkestpntpnt: ERROR: unknown option\n");
      status = MagickFalse;
    }
  }

  if (dpp->do_verbose) {
    fprintf (stderr, "darkestpntpnt options: ");
    fprintf (stderr, "  start_at ");
    WrUserCoord (&dpp->start);
    fprintf (stderr, "  end_at ");
    WrUserCoord (&dpp->end);
    if (dpp->hasVisitedThreshold)
      fprintf (stderr, "  threshold_visited %.*g",
        dpp->precision, dpp->visitedThreshold);
    if (dpp->wrData) fprintf (stderr, "  data");
    if (dpp->printPix) fprintf (stderr, "  print");
    if (dpp->fh_data == stdout) fprintf (stderr, " file stdout");
    if (!dpp->stopAtEnd) fprintf (stderr, "  no_end");
    if (dpp->do_verbose) fprintf (stderr, "  verbose");
    fprintf (stderr, "\n");
  }

  if (status == MagickFalse)
    usage ();

  return (status);
}


static MagickBooleanType initialise(const Image *image,
  darkestPntPntT * dpp,
  ExceptionInfo *exception
)
{
  ssize_t
    x, y;

  MagickBooleanType
    status = MagickTrue;

  CacheView
    *image_view;

  dpp->width = image->columns;
  dpp->height = image->rows;

  // Allocate memory

  //if (dpp->do_verbose) printf ("Alloc %ix%i\n", (int)dpp->width, (int)dpp->height);

  dpp->pqMaxNodes = dpp->height * dpp->width;
  dpp->pqNodes = (pqNodeT *) AcquireQuantumMemory(dpp->pqMaxNodes, sizeof(pqNodeT));
  if (dpp->pqNodes == (pqNodeT *) NULL) {
    return MagickFalse;
  }

  dpp->pqNumNodes = 0;

  dpp->nodes = (NodeT **) AcquireQuantumMemory(dpp->height, sizeof(*dpp->nodes));
  if (dpp->nodes == (NodeT **) NULL) {
    RelinquishMagickMemory(dpp->pqNodes);
    return MagickFalse;
  }
  for (y = 0; y < dpp->height; y++) {
    dpp->nodes[y] = (NodeT *) AcquireQuantumMemory(dpp->width, sizeof(**dpp->nodes));
    if (dpp->nodes[y] == (NodeT *) NULL) break;
  }
  if (y < dpp->height) {
    for (y--; y >= 0; y--) {
      if (dpp->nodes[y] != (NodeT *) NULL)
        dpp->nodes[y] = (NodeT *) RelinquishMagickMemory(dpp->nodes[y]);
    }
    dpp->nodes = (NodeT **) RelinquishMagickMemory(dpp->nodes);
    RelinquishMagickMemory(dpp->pqNodes);
    return MagickFalse;
  }

  // Populate values

  //if (dpp->do_verbose) printf ("Populate\n");

  image_view = AcquireVirtualCacheView(image,exception);

  for (y = 0; y < dpp->height; y++) {
    VIEW_PIX_PTR const
      *p;

    if (status == MagickFalse)
      continue;

    p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
    if (p == (const VIEW_PIX_PTR *) NULL)
      status=MagickFalse;

    for (x = 0; x < dpp->width; x++) {
      //printf ("Pop %i,%i\n", (int)x, (int)y);

      NodeT * pn = &(dpp->nodes[y][x]);
      pn->value = GetPixelIntensity (image, p) / (dppValueT)QuantumRange;
      pn->tentativeDistance = INFINITE_DIST;
      pn->visited = MagickFalse;
      if (dpp->hasVisitedThreshold) {
        if (pn->value > dpp->visitedThreshold) {
          pn->visited = MagickTrue;
        }
      }
      pn->parent = 0;
      p += Inc_ViewPixPtr (image);
    }
  }

  image_view=DestroyCacheView(image_view);

  NodeT * pn = &dpp->nodes[dpp->start.y.Pix][dpp->start.x.Pix];
  pn->tentativeDistance = 0;
  if (dpp->hasVisitedThreshold && pn->value > dpp->visitedThreshold) {
    if (dpp->do_warnings) fprintf (stderr,
      "Problem: start pixel has value (%.*g) above threshold.\n",
      dpp->precision, pn->value);
  }

  pn = &dpp->nodes[dpp->end.y.Pix][dpp->end.x.Pix];
  if (dpp->hasVisitedThreshold && pn->value > dpp->visitedThreshold) {
    if (dpp->do_warnings) fprintf (stderr,
      "Problem: end pixel has value (%.*g) above threshold.\n",
      dpp->precision, pn->value);
  }

  return (status);
}


static void deIinitialise(
  darkestPntPntT * dpp
)
{
  ssize_t
    y;

  for (y = 0; y < dpp->height; y++) {
    dpp->nodes[y] = (NodeT *) RelinquishMagickMemory(dpp->nodes[y]);
  }
  dpp->nodes = (NodeT **) RelinquishMagickMemory(dpp->nodes);

  RelinquishMagickMemory(dpp->pqNodes);
}


#if VERIFY==1

static void DumpNodes (
  darkestPntPntT * dpp
)
{
  ssize_t
    x, y;

  printf ("DumpNodes %lix%li\n", dpp->width, dpp->height);

  for (y = 0; y < dpp->height; y++) {
    for (x = 0; x < dpp->width; x++) {
      NodeT * pn = &dpp->nodes[y][x];

      printf ("%li,%li %.*g %.*g %s %i\n",
        x, y,
        dpp->precision, (double)pn->value,
        dpp->precision, (double)pn->tentativeDistance,
        pn->visited? "V": "nv",
        pn->parent);
    }
  }
}

#endif

static MagickBooleanType NearestUnvNode(
  darkestPntPntT * dpp,
  ssize_t * nx,
  ssize_t * ny
)
// Returns whether okay.
// If none, returns FALSE.
{
  // This needs to be fast.

/*===
//
// This is how it would be done without a priority queue.
// Much simpler, but very much slower.
//
//  dppValueT
//    nearestDist;
//
//  ssize_t
//    x, y;
//
//  nearestDist = INFINITE_DIST;
//
//  // This could be parallelised.
//
//  for (y = 0; y < dpp->height; y++) {
//    for (x = 0; x < dpp->width; x++) {
//      NodeT * pn = &dpp->nodes[y][x];
//      if (!pn->visited) {
//        if (nearestDist > pn->tentativeDistance) {
//          nearestDist = pn->tentativeDistance;
//          *nx = x;
//          *ny = y;
//          *allDone = MagickFalse;
//        }
//      }
//    }
//  }
//  printf ("nun %li,%li ad=%i\n", *nx, *ny, (int)*allDone);
//
===*/

  ssize_t pqX=0, pqY=0;
  if ( pqRemoveMin (dpp, &pqX, &pqY) == 0) return MagickFalse;

//  *allDone = (r == 1) ? MagickFalse : MagickTrue;

  //if (*nx != pqX || *ny != pqY) {
  //  printf ("NearestUnvNode: %li %li %li %li\n", *nx, pqX, *ny, pqY);
  //}

  *nx = pqX;
  *ny = pqY;

  return MagickTrue;
}


static void inline ProcessNeighbour (
  darkestPntPntT * dpp,
  dppValueT thisDist,
  int nbx,
  int nby,
  int ParentNum)
{
  NodeT * pnb = &dpp->nodes[nby][nbx];

  if (!pnb->visited) {
    dppValueT newDist = thisDist + pnb->value * dpp->neighbourWeights[ParentNum];
    if (pnb->tentativeDistance > newDist) {
      ssize_t pqNode = -1;

      if (pnb->tentativeDistance < INFINITE_DIST) {
        pqNode = pqFindNode (dpp, nbx, nby, pnb->tentativeDistance, 0);
      }

      if (pqNode == -1) {
        pnb->tentativeDistance = newDist;
        pqInsertNew (dpp, nbx, nby);
      } else {
        UpdateDist (dpp, pqNode, nbx, nby, /* pnb->tentativeDistance, */ newDist);
      }

      pnb->parent = ParentNum;
    }
  }
}


static MagickBooleanType VisitNodes (
  darkestPntPntT * dpp
)
{
  MagickBooleanType
    finished = MagickFalse,
    DoCoordList = MagickFalse;

  ssize_t
    nx, ny;

  int nVisited = 0;

  nx = dpp->start.x.Pix;
  ny = dpp->start.y.Pix;

  if (dpp->printPix) {
    fprintf (dpp->fh_data, "%li,%li\n", nx, ny);
  }

  dpp->foundEnd = MagickFalse;

  if (dpp->coordList != NULL && dpp->wrData == MagickTrue) {
    DoCoordList = MagickTrue;

    // Record this start pixel.
    // FIXME: but not if out of threshold?

    AddToCoordList (dpp->coordList, nx, ny);
  }

  while (finished == MagickFalse) {

    //printf ("vn %li,%li\n", nx, ny);

    NodeT * pn = &dpp->nodes[ny][nx];

    // Process the 8 neighbours.
    if (ny > 0) {
      if (nx > 0) {
        ProcessNeighbour (dpp, pn->tentativeDistance, nx-1, ny-1, 1);
      }
      ProcessNeighbour (dpp, pn->tentativeDistance, nx, ny-1, 2);
      if (nx < dpp->width-1) {
        ProcessNeighbour (dpp, pn->tentativeDistance, nx+1, ny-1, 3);
      }
    }
    if (nx > 0) {
      ProcessNeighbour (dpp, pn->tentativeDistance, nx-1, ny, 4);
    }

    if (nx < dpp->width-1) {
      ProcessNeighbour (dpp, pn->tentativeDistance, nx+1, ny, 6);
    }

    if (ny < dpp->height-1) {
      if (nx > 0) {
        ProcessNeighbour (dpp, pn->tentativeDistance, nx-1, ny+1, 7);
      }
      ProcessNeighbour (dpp, pn->tentativeDistance, nx, ny+1, 8);

      if (nx < dpp->width-1) {
        ProcessNeighbour (dpp, pn->tentativeDistance, nx+1, ny+1, 9);
      }
    }

    //printf ("vn2 %li,%li\n", nx, ny);
    if ( pn->visited == MagickTrue
      && (   nx != dpp->start.x.Pix
          || ny != dpp->start.y.Pix ))
    {
      fprintf (stderr, "Bug: already visited %li,%li\n", nx, ny);
      finished = MagickTrue;

#if VERIFY==1
      DumpNodes (dpp);
      pqVerify (dpp);
      pqDump (dpp);
#endif

    }
    pn->visited = MagickTrue;

    if (dpp->do_verbose && ((++nVisited % 100000) == 0)) {
      printf (".\n");
    }

    if (nx == dpp->end.x.Pix && ny == dpp->end.y.Pix) {
      dpp->foundEnd = MagickTrue;
      if (dpp->stopAtEnd) {
        finished = MagickTrue;
      }
    }

    if (finished == MagickFalse) {
      if (NearestUnvNode (dpp, &nx, &ny)) {
        if (dpp->printPix && !finished) {
          fprintf (dpp->fh_data, "%li,%li\n", nx, ny);
        }
        if (DoCoordList && !finished) {
          AddToCoordList (dpp->coordList, nx, ny);
        }

      } else {
        finished = MagickTrue;
      }
    } else if (dpp->printPix) {
      fprintf (dpp->fh_data, "%li,%li\n", nx, ny);

      if (DoCoordList) {
        AddToCoordList (dpp->coordList, nx, ny);
      }
    }
  }

  if (dpp->do_verbose) fprintf (stderr, "nVisited: %i\n", nVisited);

  return (MagickTrue);
}

static Image * writePath(const Image *image,
  darkestPntPntT * dpp,
  ExceptionInfo *exception
)
{
  Image *
    path_image;

  CacheView
    *path_view;

  VIEW_PIX_PTR
    *q_path;

  MagickBooleanType
    status = MagickTrue;

  ssize_t
    x, y;

  //if (dpp->do_verbose) printf ("writePath: WH=%li,%li\n", image->columns, image->rows);

  path_image=CloneImage(image, image->columns, image->rows, MagickTrue, exception);
  if (path_image == (Image *) NULL)
    return(path_image);

  if (SetNoPalette (path_image, exception) == MagickFalse)
    return (Image *)NULL;

  SetAllBlack (path_image, exception);

  path_view=AcquireAuthenticCacheView(path_image,exception);

  x = dpp->end.x.Pix;
  y = dpp->end.y.Pix;

  ssize_t nInPath = 0;

  dppValueT totalValue = 0.0;

  MagickBooleanType DoCoordList;
  int clNumStart = 0;
  if (dpp->coordList==NULL) {
    DoCoordList = MagickFalse;
  } else {
    DoCoordList = MagickTrue;
    clNumStart = dpp->coordList->numUsed;
  }

  if (dpp->foundEnd == MagickTrue) {

    if (dpp->do_verbose) {
      NodeT * pn = &dpp->nodes[y][x];

      fprintf (stderr, "maxDist: %.*g\n",
        dpp->precision, pn->tentativeDistance * QuantumRange);
      fprintf (stderr, "maxDistPc: %.*g\n",
        dpp->precision, pn->tentativeDistance * 100.0);
    }

    do {
      //printf ("xy=%li,%li\n", x, y);

      // Get just the one pixel we need.
      q_path=GetCacheViewAuthenticPixels(path_view,x,y,1,1,exception);
      if (q_path == (const VIEW_PIX_PTR *) NULL) {
        fprintf (stderr, "darkestpntpng: bad GetCacheViewAuthenticPixels q_path\n");
        status=MagickFalse;
        break;
      }

      if (GET_PIXEL_RED (path_image, q_path) != 0) {
        fprintf (stderr, "Bug: cycle\n");
        status=MagickFalse;
        break;
      }

      SET_PIXEL_RED   (path_image, QuantumRange, q_path);
      SET_PIXEL_GREEN (path_image, QuantumRange, q_path);
      SET_PIXEL_BLUE  (path_image, QuantumRange, q_path);

      if (SyncCacheViewAuthenticPixels(path_view,exception) == MagickFalse) {
        fprintf (stderr, "bad sync\n");
        status=MagickFalse;
        break;
      }

      if (DoCoordList) {
        if (!AddToCoordList (dpp->coordList, x, y)) {
          status=MagickFalse;
        }
      }

      if (x == dpp->start.x.Pix && y == dpp->start.y.Pix) break;

      NodeT * pn = &dpp->nodes[y][x];

      nInPath++;
      totalValue += pn->value;

      switch (pn->parent) {
        case 1: y+=1; x+=1; break;
        case 2: y+=1;       break;
        case 3: y+=1; x-=1; break;
        case 4:       x+=1; break;
        case 6:       x-=1; break;
        case 7: y-=1; x+=1; break;
        case 8: y-=1;       break;
        case 9: y-=1; x-=1; break;
        default: 
          if (x != dpp->start.x.Pix || y != dpp->start.y.Pix) {
            fprintf (stderr, "bad parent\n");
            status=MagickFalse;
          }
      }
    } while (status==MagickTrue);

  } else {
    if (dpp->do_warnings) fprintf (stderr, "Problem: end is not on path.\n");
  }

  if (DoCoordList) {
    // FIXME: Oops, we can do this only if it was empty.
    InvertCoordList (dpp->coordList, clNumStart);
  }

  path_view=DestroyCacheView(path_view);

  if (dpp->do_verbose)
    fprintf (stderr, "nInPath: %li\nTotalValue: %.*g\nAvgValue: %.*g\n",
      nInPath,
       dpp->precision, totalValue,
       dpp->precision, totalValue / nInPath);

  return path_image;
}


static Image * writeData (const Image *image,
  darkestPntPntT * dpp,
  ExceptionInfo *exception
)
{
  // Copy values to red channel;
  // copy distances to green channel;
  // copy parent number to blue channel.

  Image *
    data_image;

  ssize_t
    x, y;

  MagickBooleanType
    status = MagickTrue;

  CacheView
    *data_view;

  data_image=CloneImage(image, image->columns, image->rows, MagickTrue, exception);
  if (data_image == (Image *) NULL)
    return(data_image);

  if (SetNoPalette (data_image, exception) == MagickFalse)
    return (Image *)NULL;

  data_view = AcquireAuthenticCacheView(data_image,exception);

  ssize_t nInPath = 0;
  dppValueT maxDist = -1;

  // FIXME: parallelise

  for (y = 0; y < dpp->height; y++) {
    VIEW_PIX_PTR
      *p;

    if (status == MagickFalse)
      continue;

    p=GetCacheViewAuthenticPixels(data_view,0,y,image->columns,1,exception);
    if (p == (const VIEW_PIX_PTR *) NULL)
      status=MagickFalse;

    for (x = 0; x < dpp->width; x++) {
      NodeT * pn = &(dpp->nodes[y][x]);

      SET_PIXEL_RED   (data_image, pn->value * QuantumRange, p);

      if (pn->tentativeDistance == INFINITE_DIST) {
        SET_PIXEL_GREEN (data_image, -1, p);
      } else {
        dppValueT dist = pn->tentativeDistance * QuantumRange;
        if (maxDist < dist) maxDist = dist;
        SET_PIXEL_GREEN (data_image, dist, p);
        nInPath++;
      }

      SET_PIXEL_BLUE  (data_image, pn->parent, p);

      p += Inc_ViewPixPtr (data_image);
    }

    if (SyncCacheViewAuthenticPixels(data_view,exception) == MagickFalse) {
      fprintf (stderr, "bad sync\n");
      status=MagickFalse;
      break;
    }
  }

  data_view=DestroyCacheView(data_view);

  if (dpp->do_verbose) {
    fprintf (stderr, "nInPath: %li\n", nInPath);

    if (maxDist > -1) {
      fprintf (stderr, "maxDist: %.*g\n",
        dpp->precision, maxDist);
      fprintf (stderr, "maxDistPc: %.*g\n",
        dpp->precision, maxDist * 100.0 / QuantumRange);
    }
  }

  return (data_image);
}


// The next function corresponds in style to functions in transform.c
// It takes one image, and returns an image.
//
static Image *darkestpntpnt(Image *image,
  darkestPntPntT * dpp,
  ExceptionInfo *exception)
{
  Image
    *out_image;

  MagickBooleanType
    status;


  dpp->precision = GetMagickPrecision();

  if (image->debug != MagickFalse)
    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);

  status = initialise(image, dpp, exception);
  if (status==MagickFalse) return (Image *) NULL;

  //if (dpp->do_verbose) printf ("DumpNodes.\n");
  //DumpNodes (dpp);

  //if (dpp->do_verbose) printf ("VisitNodes.\n");
  VisitNodes (dpp);

#if VERIFY==1
  //if (dpp->do_verbose) printf ("Verify pq.\n");
  pqVerify (dpp);
#endif

  //if (dpp->do_verbose) printf ("DumpNodes again.\n");
  //DumpNodes (dpp);

  //pqDump (dpp);

  if (dpp->suppressImage) {
    if (dpp->do_verbose) printf ("Image suppressed.\n");
    out_image = NULL;
  } else {
    if (dpp->wrData) {
      if (dpp->do_verbose) printf ("writeData.\n");
      out_image = writeData (image, dpp, exception);
    } else {
      if (dpp->do_verbose) printf ("writePath.\n");
      out_image = writePath (image, dpp, exception);
    }
  }

  deIinitialise(dpp);

  return (out_image);
}


ModuleExport size_t darkestpntpntImage(Image **images,const int argc,
  const char **argv,ExceptionInfo *exception)
{
  Image
    *image,
    *new_image;

  MagickBooleanType
    status;

  darkestPntPntT
    dpp;

  assert(images != (Image **) NULL);
  assert(*images != (Image *) NULL);
  assert((*images)->signature == MAGICK_CORE_SIG);

  InitDarkestPntPnt (&dpp);

  status = menu (argc, argv, &dpp);
  if (status == MagickFalse)
    return (-1);

  for (image=(*images); image != (Image *) NULL; image=GetNextImageInList(image))
  {
    int r;

    r = ResolveUserCoords (&dpp.start, image->columns, image->rows, 1.0);
    if (!r) return -1;

    r = ResolveUserCoords (&dpp.end, image->columns, image->rows, 1.0);
    if (!r) return -1;

    new_image = darkestpntpnt (image, &dpp, exception);

    ReplaceImageInList(&image,new_image);
    *images=GetFirstImageInList(image);
  }

  return(MagickImageFilterSignature);
}

srt3d.c

This file contains code specific to the process module, including the call(s) to DistortImage() to process the image(s). The mathematics and other file-handling is done by code in srt3d.h below.

/*
   Reference: http://im.snibgo.com/srt3d.htm

   Last update: 27-May-2017.
     7-September-2017 for v7.
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <math.h>
#include <ctype.h>
#include "vsn_defines.h"

#include "srt3d.h"

#define VERSION "srt3d v1.0  Copyright (c) 2017 Alan Gibson"

static void usage (void)
{
  printf ("Usage: -process 'srt3d [OPTION]...'\n");
  printf ("Scale, rotate and translate in 3D.\n");
  printf ("\n");
  printf ("  t, transform string     transformation string\n");
  printf ("  a, array filename       read array from file\n");
  printf ("  A, out-array filename   write array to file\n");
  printf ("  c, coords filename      read coords from file\n");
  printf ("  C, out-coords filename  write coords to file\n");
  printf ("  n, nOutCoords integer   number of output coordinates (0-3)\n");
  printf ("  f, focal-length number  for perspective\n");
  printf ("  r, hide-reversed        hide reversed polygons\n");
  printf ("  v, verbose              write text information to stdout\n");
  printf ("     version              write version information to stdout\n");
  printf ("\n");
}


static MagickBooleanType IsArg (char * pa, char * ShtOpt, char * LongOpt)
{
  // LocaleCompare is not case-sensitive,
  // so we use strcmp for the short option.

  if ((strcmp(pa, ShtOpt)==0) || (LocaleCompare(pa, LongOpt)==0))
    return MagickTrue;

  return MagickFalse;
}


#define NEXTARG \
  if (i < argc-1) i++; \
  else { \
    fprintf (stderr, "srt3d: ERROR: [%s] needs argument\n", pa); \
    status = MagickFalse; \
  }

static MagickBooleanType menu(
  const int argc,
  const char **argv,
  srt3dT * ps3d
)
// Returns MagickTrue if okay.
{
  int
    i;

  MagickBooleanType
    status = MagickTrue;

  char ** pargv = (char **)argv;

  srt3dInit (ps3d);

  for (i=0; i < argc; i++) {
    char * pa = (char *)argv[i];
    //printf ("Arg %i [%s]\n", i, pa);

    if (IsArg (pa, "t", "transform")==MagickTrue) {
      NEXTARG;
      ps3d->transStr = pargv[i];
    } else if (IsArg (pa, "a", "array")==MagickTrue) {
      NEXTARG;
      ps3d->fInArray = pargv[i];
    } else if (IsArg (pa, "A", "out-array")==MagickTrue) {
      NEXTARG;
      ps3d->fOutArray = pargv[i];
    } else if (IsArg (pa, "c", "coords")==MagickTrue) {
      NEXTARG;
      ps3d->fInCoords = pargv[i];
    } else if (IsArg (pa, "C", "out-coords")==MagickTrue) {
      NEXTARG;
      ps3d->fOutCoords = pargv[i];
    } else if (IsArg (pa, "n", "nOutCoords")==MagickTrue) {
      NEXTARG;
      ps3d->numCoordsOut = atoi(pargv[i]);
    } else if (IsArg (pa, "f", "focal-length")==MagickTrue) {
      NEXTARG;
      ps3d->focalLen = atof (pargv[i]);
    } else if (IsArg (pa, "r", "hide-reversed")==MagickTrue) {
      ps3d->calcHidden = MagickTrue;
    } else if (IsArg (pa, "v", "verbose")==MagickTrue) {
      ps3d->verbose = MagickTrue;
    } else if (IsArg (pa, "version", "version")==MagickTrue) {
      fprintf (stdout, "%s\n", VERSION);
    } else {
      fprintf (stderr, "srt3d: ERROR: unknown option [%s]\n", pa);
      status = MagickFalse;
    }
  }

  if (ps3d->verbose) {
    fprintf (stderr, "srt3d options: ");
    if (ps3d->transStr)   fprintf (stderr, " transform %s", ps3d->transStr);
    if (ps3d->fInArray)   fprintf (stderr, " array %s", ps3d->fInArray);
    if (ps3d->fOutArray)  fprintf (stderr, " out-array %s", ps3d->fOutArray);
    if (ps3d->fInCoords)  fprintf (stderr, " coords %s", ps3d->fInCoords);
    if (ps3d->fOutCoords) {
      fprintf (stderr, " out-coords %s", ps3d->fOutCoords);
      fprintf (stderr, " nOutCoords %i", ps3d->numCoordsOut);
    }
    if (ps3d->calcHidden)  fprintf (stderr, " hide-reversed");
    if (ps3d->focalLen!=0) fprintf (stderr, " focal-length %.*g", 
      ps3d->precision, ps3d->focalLen);
    if (ps3d->verbose)    fprintf (stderr, " verbose");
    fprintf (stderr, "\n");
  }

  if (status == MagickFalse)
    usage ();

  return (status);
}


// The next function corresponds in style to functions in transform.c
// It takes one image, and returns an image.
//
static Image *srt3d (
  Image *image,
  srt3dT * s3d,
  ExceptionInfo *exception)
{
  if (image->debug != MagickFalse)
    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);

  if (s3d->verbose) {
    fprintf (stderr, "srt3d: Input image [%s] %ix%i depth is %i\n",
             image->filename,
             (int)image->columns, (int)image->rows,
             (int)image->depth);
  }

  s3d->imgWidth = image->columns;
  s3d->imgHeight = image->rows;

  if (!srt3dCalc (s3d)) {
    return NULL;
  }

  if (s3d->verbose) {
    fprintf (stderr, "nEntries = %i\n", s3d->nEntries);
  }

  double * distArray = (double *)malloc (4 * s3d->nEntries * sizeof (double));

  int i;
  double *pd = distArray;
  for (i=0; i < s3d->nEntries; i++) {
    *(pd++) = s3d->coordPrimes[i].x;
    *(pd++) = s3d->coordPrimes[i].y;
    *(pd++) = s3d->coordPrimes[i].xPrime;
    *(pd++) = s3d->coordPrimes[i].yPrime;
  }

  Image *out_image = NULL;

  if (!s3d->isHidden) {
    out_image = DistortImage (
      image, PerspectiveDistortion,
      4 * s3d->nEntries, distArray, MagickTrue, exception);
  }

  if (!out_image) {
    ClearMagickException (exception);

    // Create an image with one transparent pixel.
    if (s3d->verbose) {
      fprintf (stderr, "Creating 1x1 transparent.\n");
    }
    out_image = CloneImage (image, 1,1, MagickFalse, exception);
    if (!out_image) {
      fprintf (stderr, "srt3d: DistortImage and CloneImage failed\n");
      return NULL;
    }
#if IMV6OR7==6
    SetImageOpacity (out_image, TransparentOpacity);
#else
    SetImageAlpha (out_image, TransparentAlpha, exception);
#endif
  }

  if (s3d->verbose) {
    fprintf (stderr, "Finished srt3d\n");
  }

  return (out_image);
}


ModuleExport size_t srt3dImage (
  Image **images,
  const int argc,
  const char **argv,
  ExceptionInfo *exception)
{
  Image
    *image,
    *new_image;

  MagickBooleanType
    status;

  srt3dT s3d;

  assert(images != (Image **) NULL);
  assert(*images != (Image *) NULL);
  assert((*images)->signature == MAGICK_CORE_SIG);

  status = menu (argc, argv, &s3d);
  if (status == MagickFalse)
    return (-1);

  s3d.precision = GetMagickPrecision();
  printf ("s3d.precision=%i\n", s3d.precision);

  for (image=(*images); image != (Image *) NULL; image=GetNextImageInList(image))
  {
    new_image = srt3d (image, &s3d, exception);
    if (!new_image) return (-1);

    ReplaceImageInList(&image,new_image);
    *images=GetFirstImageInList(image);
  }

  srt3dDeInit (&s3d);

  return(MagickImageFilterSignature);
}

srt3d.h

// Last update: 26-May-2017.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <ctype.h>

#ifndef BOOL
#define BOOL int
#define TRUE 1
#define FALSE 0
#endif

typedef double MatT[4][4];

typedef enum {
  opScale, opRotate, opTranslate
} OperationT;

typedef enum {
  dimX, dimY, dimZ
} DimensionT;

typedef enum {
  qNone, qPercent, qProp
} QualifierT;

typedef struct {
  double x;
  double y;
  double z;
} CoordT;

typedef struct {
  double x;
  double y;
  double z;
  double xPrime;
  double yPrime;
  double zPrime;
} CoordPrimeT;

typedef struct {
  // Inputs.
  ssize_t imgWidth;
  ssize_t imgHeight;
  int numCoordsOut;  // Typically 0, 2 or 3.
  double focalLen;   // For perspective. 0 = infinity.
  char * transStr;
  char * fInArray;   // NULL or filename or "-".
  char * fInCoords;  // NULL or filename.
  char * fOutArray;  // NULL or filename or "-".
  char * fOutCoords; // NULL or filename or "-".
  BOOL calcHidden;
  int precision;
  BOOL verbose;

  // Calculated outputs.
  MatT transMat;
  int nEntries;
  CoordT * coords;
  CoordPrimeT * coordPrimes;
  BOOL isHidden;
}  srt3dT;

#define LINE_LEN 1000


static void srt3dInit (srt3dT * psm)
{
  psm->imgWidth = 0;
  psm->imgHeight = 0;
  psm->numCoordsOut = 3;
  psm->focalLen = 0;
  psm->transStr = NULL;
  psm->fInArray = NULL;
  psm->fInCoords = NULL;
  psm->fOutArray = NULL;
  psm->fOutCoords = NULL;
  psm->calcHidden = FALSE;
  psm->precision = 6;
  psm->verbose = FALSE;
  psm->nEntries = 0;
  psm->coords = NULL;
  psm->coordPrimes = NULL;
  psm->isHidden = FALSE;
}

static void srt3dDeInit (srt3dT * psm)
{
  if (psm->coords) free (psm->coords);
  if (psm->coordPrimes) free (psm->coordPrimes);
}

static void srt3dMatId (MatT m)
{
  int x, y;
  for (y=0; y < 4; y++) {
    for (x=0; x < 4; x++) {
      m[x][y] = (x==y)? 1 : 0;
    }
  }
}

static void srt3dMatCopy (MatT r, MatT a)
// r := a
{
  int x, y;
  for (y=0; y < 4; y++) {
    for (x=0; x < 4; x++) {
      r[x][y] = a[x][y];
    }
  }
}

static void srt3dMatMult3 (MatT r, MatT a, MatT b)
// r := a * b
{
  int x, y, i;
  for (y=0; y < 4; y++) {
    for (x=0; x < 4; x++) {
      double v = 0;

      for (i=0; i < 4; i++) {
        v += a[x][i] * b[i][y];
      }
      r[x][y] = v;
    }
  }
}

static void srt3dMatMult2 (MatT r, MatT a)
// r := r * a
{
  MatT b;
  srt3dMatCopy (b, r);
  srt3dMatMult3 (r, b, a);
}

static void srt3dWrMat (FILE * fh, MatT m, int precision)
{
  printf ("precision=%i\n", precision);

  int x, y;
  for (y=0; y < 4; y++) {
    for (x=0; x < 4; x++) {
      if (x > 0) fprintf (fh, ", ");
      fprintf (fh, "%.*g", precision, m[x][y]);
    }
    fprintf (fh, "\n");
  }
}

static BOOL srt3dRdMat (FILE * fh, MatT m)
{
  int y;
  for (y=0; y < 4; y++) {
    if (fscanf (fh, "%lf,%lf,%lf,%lf", &m[0][y], &m[1][y], &m[2][y], &m[3][y]) != 4) {
      return FALSE;
    }
  }
  return TRUE;
}


static BOOL srt3dSkipSep (char ** p)
// Returns whether separator found.
{
  if (**p != ',' && **p != ' ') return FALSE;

  while (isspace((int) ((unsigned char)**p))) (*p)++;
  if (**p == ',') {
    (*p)++;
    while (isspace((int) ((unsigned char)**p))) (*p)++;
  }

  return TRUE;
}

static BOOL srt3dGetOption (char * str, OperationT * op, DimensionT * dim)
// Returns whether valid.
{
  char c = *str;

  switch (c) {
    case 's':
    case 'S':
      *op = opScale;
      break;
    case 'r':
    case 'R':
      *op = opRotate;
      break;
    case 't':
    case 'T':
      *op = opTranslate;
      break;
    default:
      return FALSE;
  }

  c = str[1];

  switch (c) {
    case 'x':
    case 'X':
      *dim = dimX;
      break;
    case 'y':
    case 'Y':
      *dim = dimY;
      break;
    case 'z':
    case 'Z':
      *dim = dimZ;
      break;
    default:
      return FALSE;
  }

  return TRUE;
}

static BOOL srt3dGetNumber (char ** p, double *pValue, QualifierT * qual)
// Returns whether valid.
{
  int len;
  if (sscanf (*p, "%lf%n", pValue, &len) != 1) return FALSE;
  *p += len;
  char c = **p;
  switch (c) {
    case 'c':
    case 'C':
    case '%':
      *qual = qPercent;
      (*p)++;
      break;
    case 'p':
    case 'P':
      *qual = qProp;
      (*p)++;
      break;
    default:
      *qual = qNone;
  }
  return TRUE;
}

static BOOL srt3fInitArray (srt3dT * psm)
{
  srt3dMatId (psm->transMat);
  if (!psm->fInArray) return TRUE;

  FILE * fh;
  BOOL isStdIn = (strcmp (psm->fInArray, "-") == 0);
  if (isStdIn) fh = stdin;
  else {
    fh = fopen (psm->fInArray, "rt");
    if (!fh) {
      fprintf (stderr, "Can't open %s\n", psm->fInArray);
      return FALSE;
    }
  }

  BOOL okay = srt3dRdMat (fh, psm->transMat);

  if (!isStdIn) fclose (fh);

  return okay;
}

static BOOL srt3dStr2Mat (srt3dT * psm)
/*
    Returns whether str is valid.
    String contains one or more occurences of:
      {option} {number}
    Separator is one or more spaces,
      or one comma with optional spaces on both sides.
    Option is two letters (any case).
    First letter is one of 's', 'r' or 't' (scale, rotate or translate).
    Second letter is one of 'x', 'y' or 'z'.
    Number is floating point.
    For 's', number may be suffixed with '%', 'c'.
    For 't', number may be suffixed with '%', 'c' or 'p'.
*/
{
  if (!srt3fInitArray (psm)) {
    fprintf (stderr, "InitArray failed\n");
    return FALSE;
  }

  MatT mOne;

  char *p = psm->transStr;

  if (!p || !*p) {
    if (psm->verbose) fprintf (stderr, "Warning: No string to process\n");
    return TRUE;
  }

  OperationT op;
  DimensionT dim;

  for (;;) {
    srt3dMatId (mOne);

    if (!srt3dGetOption (p, &op, &dim)) {
      fprintf (stderr, "Bad option at [%s]\n", p);
      return FALSE;
    }
    p += 2;
    srt3dSkipSep (&p);
    double Value;
    QualifierT qual;
    if (!srt3dGetNumber (&p, &Value, &qual)) {
      fprintf (stderr, "Bad number at [%s]\n", p);
      return FALSE;
    }

    switch (op) {
      case opScale: {
        // FIXME: also use qual.
        switch (dim) {
          case dimX: mOne[0][0] = Value; break;
          case dimY: mOne[1][1] = Value; break;
          case dimZ: mOne[2][2] = Value; break;
        }
        break;
      }
      case opRotate: {
        double rad = Value * M_PI / 180.0;
        double sv = sin (rad);
        double cv = cos (rad);
 
#define EPS 1e-15

       if (fabs(sv) < EPS) sv = 0;
       if (fabs(cv) < EPS) cv = 0;

       switch (dim) {
          case dimX:
            mOne[1][1] = cv;
            mOne[2][1] = -sv;
            mOne[1][2] = sv;
            mOne[2][2] = cv;
            break;
          case dimY:
            mOne[2][2] = cv;
            mOne[0][2] = -sv;
            mOne[2][0] = sv;
            mOne[0][0] = cv;
            break;
          case dimZ:
            mOne[0][0] = cv;
            mOne[1][0] = -sv;
            mOne[0][1] = sv;
            mOne[1][1] = cv;
            break;
        }
        break;
      }
      case opTranslate: {
        // Adjust for qual.
        if (qual==qPercent) {
          if (dim==dimX) Value *= (psm->imgWidth-1) / 100.0;
          else if (dim==dimY) Value *= (psm->imgHeight-1) / 100.0;
        } else if (qual==qProp) {
          if (dim==dimX) Value *= (psm->imgWidth-1);
          else if (dim==dimY) Value *= (psm->imgHeight-1);
        }

        switch (dim) {
          case dimX: mOne[3][0] = Value; break;
          case dimY: mOne[3][1] = Value; break;
          case dimZ: mOne[3][2] = Value; break;
        }
        break;
      }
    }

    srt3dMatMult2 (psm->transMat, mOne);

    if (!*p) break;

    if (!srt3dSkipSep (&p)) {
      fprintf (stderr, "No separator at [%s]\n", p);
      return FALSE;
    }
  }

  return TRUE;
}

static void srt3dApplyMatArr (
  CoordT * arrIn, CoordPrimeT * arrOut, int nEntries, MatT m)
// Applies matrix to array of x,y,z coordinates, making array of x,y,z,x',y',z'.
{
  int i;
  for (i = 0; i < nEntries; i++) {
    CoordT *c = &arrIn[i];
    CoordPrimeT *p = &arrOut[i];
    p->x = c->x;
    p->y = c->y;
    p->z = c->z;
    p->xPrime = p->x*m[0][0] + p->y*m[1][0] + p->z*m[2][0] + m[3][0];
    p->yPrime = p->x*m[0][1] + p->y*m[1][1] + p->z*m[2][1] + m[3][1];
    p->zPrime = p->x*m[0][2] + p->y*m[1][2] + p->z*m[2][2] + m[3][2];
  }
}

static void srt3dApplyPerspective (
  CoordPrimeT * arrOut, int nEntries, double focalLen)
{
#define SMALL_DIST 1e-3

  int i;
  for (i = 0; i < nEntries; i++) {
    CoordPrimeT *p = &arrOut[i];
    double div = focalLen - p->zPrime;
    if (fabs(div) < SMALL_DIST) {
      if (div >= 0) div = SMALL_DIST;
      else div = -SMALL_DIST;
    }
    double mult = focalLen / div;
    p->xPrime *= mult;
    p->yPrime *= mult;
    p->zPrime = 0;
  }
}

static BOOL srt3dReadCoords (srt3dT * psm)
{
  // If we don't have a file, try to use the corners,
  // clockwise, starting from top-left.
  //
  if (!psm->fInCoords || !*psm->fInCoords) {
    if (!psm->imgWidth || !psm->imgHeight) {
      fprintf (stderr, "No coords file; no width or height\n");
      return FALSE;
    }
    psm->nEntries = 4;

    psm->coords = (CoordT *)malloc (psm->nEntries * sizeof(CoordT));
    if (psm->coords==NULL) return 1;

    int i;
    for (i=0; i < 4; i++) {
      CoordT *c = &psm->coords[i];
      c->x = c->y = c->z = 0;
    }
    psm->coords[1].x = psm->imgWidth-1;
    psm->coords[2].x = psm->imgWidth-1;
    psm->coords[2].y = psm->imgHeight-1;
    psm->coords[3].y = psm->imgHeight-1;

    return TRUE;
  }

  FILE * fh = fopen (psm->fInCoords, "rt");
  if (!fh) {
    fprintf (stderr, "Can't open %s\n", psm->fInCoords);
    return FALSE;
  }

  // Count entries. Allocate arrays.
  if (fseek (fh, 0, SEEK_SET) != 0) return FALSE;

  psm->nEntries = 0;
  char sLine [LINE_LEN];

  while (fgets (sLine, LINE_LEN, fh) != NULL) psm->nEntries++;

  if (psm->coords) {
    fprintf (stderr, "coords already alloced\n");
    return FALSE;
  }

  psm->coords = (CoordT *)malloc (psm->nEntries * sizeof(CoordT));
  if (psm->coords==NULL) return 1;

  // Populate coords.
  if (fseek (fh, 0, SEEK_SET) != 0) return FALSE;
  int i = 0;
  int maxX=0, maxY=0;
  while (fgets (sLine, LINE_LEN, fh) != NULL) {
    //printf ("%i: %s", i, sLine);
    CoordT *c = &psm->coords[i];

    int len;
    c->x = c->y = c->z = 0;
    sscanf (sLine, "%lf,%lf,%lf%n", &c->x, &c->y, &c->z, &len);
    //printf ("nArgs=%i len=%i %g %g %g\n", nArgs, len, c->x, c->y, c->z);
    if (maxX < c->x) maxX = c->x;
    if (maxY < c->y) maxY = c->y;
    i++;
  }

  if (psm->imgWidth==0) psm->imgWidth = maxX+1;
  if (psm->imgHeight==0) psm->imgHeight = maxY+1;

  fclose (fh);

  return TRUE;
}


static void srt3dWrCoordPrimes (srt3dT * psm, FILE * fhout)
{
  int i;
  for (i=0; i < psm->nEntries; i++) {
    CoordPrimeT *p = &psm->coordPrimes[i];
    switch (psm->numCoordsOut) {
      case 1:
        fprintf (fhout, "%.*g, ", psm->precision, p->x);
        fprintf (fhout, "%.*g\n", psm->precision, p->xPrime);
        break;
      case 2:
        fprintf (fhout, "%.*g,%.*g, ", psm->precision, p->x, psm->precision, p->y);
        fprintf (fhout, "%.*g,%.*g\n", psm->precision, p->xPrime, psm->precision, p->yPrime);
        break;
      default:
        fprintf (fhout, "%.*g,%.*g,%.*g, ", psm->precision, p->x, psm->precision, p->y, psm->precision, p->z);
        fprintf (fhout, "%.*g,%.*g,%.*g\n", psm->precision, p->xPrime, psm->precision, p->yPrime, psm->precision, p->zPrime);
        break;
    }
  }
}


static BOOL srt3dApplyMatFile (srt3dT * psm)
// Applies matrix to file of x,y,z coordinates, making file of x,y,z,x',y',z'.
{
  if (psm->coordPrimes) {
    fprintf (stderr, "coordPrimes already alloced\n");
    return FALSE;
  }

  psm->coordPrimes = (CoordPrimeT *)malloc (psm->nEntries * sizeof(CoordPrimeT));
  if (psm->coordPrimes==NULL) return 1; 

  srt3dApplyMatArr (psm->coords, psm->coordPrimes, psm->nEntries, psm->transMat);

  if (psm->focalLen != 0) {
    srt3dApplyPerspective (psm->coordPrimes, psm->nEntries, psm->focalLen);
  }

  if (psm->fOutArray) {
    if (*psm->fOutArray) {
      FILE * fh;
      BOOL isStdOut = (strcmp (psm->fOutArray, "-") == 0);
      if (isStdOut) fh = stdout;
      else {
        fh = fopen (psm->fOutArray, "wt");
        if (!fh) {
          fprintf (stderr, "Can't open %s\n", psm->fOutArray);
          return FALSE;
        }
      }
      srt3dWrMat (fh, psm->transMat, psm->precision);
      if (!isStdOut) fclose (fh);
    }
  }

  if (psm->fOutCoords && psm->numCoordsOut > 0) {
    if (*psm->fOutCoords) {
      FILE * fh;
      BOOL isStdOut = (strcmp (psm->fOutCoords, "-") == 0);
      if (isStdOut) fh = stdout;
      else {
        fh = fopen (psm->fOutCoords, "wt");
        if (!fh) {
          fprintf (stderr, "Can't open %s\n", psm->fOutCoords);
          return FALSE;
        }
      }
      srt3dWrCoordPrimes (psm, fh);
      if (!isStdOut) fclose (fh);
    }
  }

  return TRUE;
}

static void srt3dCalcHidden (srt3dT * psm)
{
  int i, j;
  double area = 0;
  double areaPrime = 0;

  j = psm->nEntries - 1;
  for (i=0; i < psm->nEntries; i++) {
    CoordPrimeT *pi = &psm->coordPrimes[i];
    CoordPrimeT *pj = &psm->coordPrimes[j];

    area      += (pi->x      + pj->x     ) * (pi->y      - pj->y     );
    areaPrime += (pi->xPrime + pj->xPrime) * (pi->yPrime - pj->yPrime);

    j = i;
  }

  // If we wanted the true areas, we would divide them by 2.0.

#define SMALL_AREA 1e-9

  if (psm->verbose) {
    fprintf (stderr, "area=%.*g areaPrime=%.*g\n",
                     psm->precision, area,
                     psm->precision, areaPrime);
  }

  psm->isHidden = (fabs(areaPrime) < SMALL_AREA) || (area * areaPrime < 0);

  if (psm->verbose && psm->isHidden) {
    fprintf (stderr, "isHidden\n");
  }
}

static BOOL srt3dCalc (srt3dT * psm)
{
  if (!srt3dReadCoords (psm)) {
    fprintf (stderr, "ReadCoords failed\n");
    return FALSE;
  }

  if (!srt3dStr2Mat (psm)) {
    fprintf (stderr, "Str2Mat failed\n");
    return FALSE;
  }

  if (!srt3dApplyMatFile (psm)) {
    fprintf (stderr, "ApplyMatFile failed\n");
    return FALSE;
  }

  if (psm->calcHidden) srt3dCalcHidden (psm);
  else psm->isHidden = FALSE;

  return TRUE;
}

arctan2.c

/* Updated:
     7-September-2017 for v7.
     3-April-2018 for v7.0.7-28
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <ctype.h>
#include "vsn_defines.h"


typedef struct {
  double
    scale,
    bias;
  MagickBooleanType
    do_verbose;
} arctanT;


static void usage (void)
{
  printf ("Usage: -process 'arctan2 [OPTION]...'\n");
  printf ("From two equal-size images, calculates the arctangent.\n");
  printf ("\n");
  printf ("  s, scale N          scale [1/2pi]\n");
  printf ("  b, bias N           bias [0.5]\n");
  printf ("  v, verbose          write text information to stdout\n");
  printf ("\n");
}


static MagickBooleanType IsArg (char * pa, char * ShtOpt, char * LongOpt)
{
  if ((LocaleCompare(pa, ShtOpt)==0) || (LocaleCompare(pa, LongOpt)==0))
    return MagickTrue;

  return MagickFalse;
}


#define NEXTARG \
  if (i < argc-1) i++; \
  else { \
    fprintf (stderr, "baryc: ERROR: [%s] needs argument\n", pa); \
    status = MagickFalse; \
  }

static MagickBooleanType menu(
  const int argc,
  const char **argv,
  arctanT * pat
)
// Returns MagickTrue if okay.
{
  int
    i;

  MagickBooleanType
    status;

  status = MagickTrue;

  pat->do_verbose = MagickFalse;

  pat->scale = 1 / (2.0 * M_PI);
  pat->bias = 0.5;

  for (i=0; i < argc; i++) {
    char * pa = (char *)argv[i];

    if (IsArg (pa, "s", "scale")==MagickTrue) {
      NEXTARG;
      pat->scale = atof(argv[i]);
    } else if (IsArg (pa, "b", "bias")==MagickTrue) {
      NEXTARG;
      pat->bias = atof(argv[i]);
    } else if (IsArg (pa, "v", "verbose")==MagickTrue) {
      pat->do_verbose = MagickTrue;
    } else {
      fprintf (stderr, "arctan2: ERROR: unknown option [%s]\n", pa);
      status = MagickFalse;
    }
  }

  if (pat->do_verbose) {
    fprintf (stderr, "arctan2 options:");

    if (pat->do_verbose) fprintf (stderr, "  verbose");

    // FIXME: thoughout, %g should respect "-precision"
    fprintf (stderr, "  scale %g", pat->scale);
    fprintf (stderr, "  bias %g", pat->bias);

    fprintf (stderr, "\n");
  }

  if (status == MagickFalse)
    usage ();

  return (status);
}


// The next function corresponds in style to functions in transform.c
// It takes one image, and returns an image.
//
static Image *arctan2(
  Image *image1,
  Image *image2,
  arctanT * pat,
  ExceptionInfo *exception)
{
  Image
    *new_image;

  CacheView
    *in_view1,
    *in_view2,
    *out_view;

  ssize_t
    y;

  MagickBooleanType
    status;


  assert(image1 != (Image *) NULL);
  assert(image1->signature == MAGICK_CORE_SIG);

  if (image1->debug != MagickFalse)
    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image1->filename);

  if (image1->columns != image2->columns || image1->rows != image2->rows ) {
    fprintf (stderr, "arctan2: needs same-sized images");
  }

  if (SetNoPalette (image1, exception) == MagickFalse)
    return (Image *)NULL;

  if (SetNoPalette (image2, exception) == MagickFalse)
    return (Image *)NULL;

  // Make a clone of this image, same size but undefined pixel values:
  //
  new_image=CloneImage(image1, image1->columns, image1->rows, MagickTrue, exception);
  if (new_image == (Image *) NULL)
    return(new_image);

  if (SetNoPalette (new_image, exception) == MagickFalse)
    return (Image *)NULL;

  in_view1 = AcquireVirtualCacheView (image1,exception);
  in_view2 = AcquireVirtualCacheView (image2,exception);

  out_view = AcquireAuthenticCacheView (new_image,exception);

  status = MagickTrue;

#if defined(MAGICKCORE_OPENMP_SUPPORT)
  #pragma omp parallel for schedule(static,4) shared(status) \
    MAGICK_THREADS(new_image,new_image,new_image->rows,1)
#endif
  for (y=0; y < (ssize_t) new_image->rows; y++)
  {
    register VIEW_PIX_PTR
      *p1, *p2, *q;

    register ssize_t
      x;

    if (status == MagickFalse)
      continue;

    p1=GetCacheViewAuthenticPixels(in_view1,0,y,new_image->columns,1,exception);
    if (p1 == (const VIEW_PIX_PTR *) NULL)
    {
      status=MagickFalse;
      continue;
    }

    p2=GetCacheViewAuthenticPixels(in_view2,0,y,new_image->columns,1,exception);
    if (p2 == (const VIEW_PIX_PTR *) NULL)
    {
      status=MagickFalse;
      continue;
    }

    q=GetCacheViewAuthenticPixels(out_view,0,y,new_image->columns,1,exception);
    if (q == (const VIEW_PIX_PTR *) NULL)
    {
      status=MagickFalse;
      continue;
    }

    for (x=0; x < (ssize_t) new_image->columns; x++)
    {
      SET_PIXEL_RED   (new_image,
                       QuantumRange *
                         (atan2 (GET_PIXEL_RED(image1,p1),
                                 GET_PIXEL_RED(image2,p2)
                                ) * pat->scale + pat->bias),
                       q);

      SET_PIXEL_GREEN (new_image,
                       QuantumRange *
                         (atan2 (GET_PIXEL_GREEN(image1,p1),
                                 GET_PIXEL_GREEN(image2,p2)
                                ) * pat->scale + pat->bias),
                       q);

      SET_PIXEL_BLUE  (new_image,
                       QuantumRange *
                         (atan2 (GET_PIXEL_BLUE(image1,p1),
                                 GET_PIXEL_BLUE(image2,p2)
                                ) * pat->scale + pat->bias),
                       q);

      p1 += Inc_ViewPixPtr (image1);
      p2 += Inc_ViewPixPtr (image2);
      q  += Inc_ViewPixPtr (new_image);
    }
    if (SyncCacheViewAuthenticPixels(out_view,exception) == MagickFalse)
      status=MagickFalse;
  }
  out_view = DestroyCacheView (out_view);
  in_view2 = DestroyCacheView (in_view2);
  in_view1 = DestroyCacheView (in_view1);

  if (status == MagickFalse) return NULL;

  return (new_image);
}



ModuleExport size_t arctan2Image(Image **images,const int argc,
  const char **argv,ExceptionInfo *exception)
{
  Image
    *image1,
    *image2,
    *new_image;

  arctanT
    at;

  assert(images != (Image **) NULL);
  assert(*images != (Image *) NULL);
  assert((*images)->signature == MAGICK_CORE_SIG);

  // Replace each pair of images with atan2(u,v).
  // The images must be the same size.

  int ListLen = (int)GetImageListLength(*images);
  if (ListLen !=2) {
    fprintf (stderr, "arctan2 needs 2 images\n");
    return (-1);
  }

  MagickBooleanType status = menu (argc, argv, &at);
  if (status == MagickFalse)
    return (-1);

  image1 = (*images);
  image2 = GetNextImageInList (image1);

  new_image = arctan2 (image1, image2, &at, exception);
  if (new_image == (Image *) NULL)
    return(-1);

  DeleteImageFromList (&image2);

  ReplaceImageInList (&image1, new_image);
  // Replace messes up the images pointer. Make it good:
  *images = GetFirstImageInList (image1);

  return (MagickImageFilterSignature);
}

rhotheta.c

/* Updated:
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <ctype.h>
#include "vsn_defines.h"


typedef struct {
  double
    scale,
    bias,
    offsetX,
    offsetY;
  MagickBooleanType
    inverse,
    do_verbose;
} rhothetaT;


static void usage (void)
{
  printf ("Usage: -process 'rhotheta [OPTION]...'\n");
  printf ("From x and y channels, calculates the arctangent and polar distance.\n");
  printf ("\n");
  printf ("  s,   scale N          scale [1/2pi]\n");
  printf ("  b,   bias N           bias [0.5]\n");
  printf ("  o,   offset N,N       offset [0,0]\n");
  printf ("  inv, inverse          inverse (rho,theta to x,y)\n");
  printf ("  v,   verbose          write text information to stdout\n");
  printf ("[And xy of origin?]\n");
  printf ("\n");
}


static MagickBooleanType IsArg (char * pa, char * ShtOpt, char * LongOpt)
{
  if ((LocaleCompare(pa, ShtOpt)==0) || (LocaleCompare(pa, LongOpt)==0))
    return MagickTrue;

  return MagickFalse;
}


static MagickBooleanType ParseCoord (
  const char * s, double * px, double * py)
{
  int n;
  char * p = (char *)s;

  sscanf (p, "%lg%n", px, &n);
  if (!n) return MagickFalse;
  p += n;
  if (*p != ',') return MagickFalse;
  p++;

  sscanf (p, "%lg%n", py, &n);
  if (!n) return MagickFalse;
  p += n;
  if (*p != '\0') return MagickFalse;

  return MagickTrue;
}


#define NEXTARG \
  if (i < argc-1) i++; \
  else { \
    fprintf (stderr, "baryc: ERROR: [%s] needs argument\n", pa); \
    status = MagickFalse; \
  }

static MagickBooleanType menu(
  const int argc,
  const char **argv,
  rhothetaT * prt
)
// Returns MagickTrue if okay.
{
  int
    i;

  MagickBooleanType
    status;

  status = MagickTrue;

  prt->do_verbose = MagickFalse;

  prt->scale = 1 / (2.0 * M_PI);
  prt->bias = 0.5;
  prt->offsetX = 0;
  prt->offsetY = 0;
  prt->inverse = MagickFalse;

  for (i=0; i < argc; i++) {
    char * pa = (char *)argv[i];

    if (IsArg (pa, "s", "scale")==MagickTrue) {
      NEXTARG;
      prt->scale = atof(argv[i]);
    } else if (IsArg (pa, "b", "bias")==MagickTrue) {
      NEXTARG;
      prt->bias = atof(argv[i]);
    } else if (IsArg (pa, "o", "offset")==MagickTrue) {
      NEXTARG;
      if (!ParseCoord (argv[i], &prt->offsetX, &prt->offsetY)) {
        status = MagickFalse;
      }
    } else if (IsArg (pa, "inv", "inverse")==MagickTrue) {
      prt->inverse = MagickTrue;
    } else if (IsArg (pa, "v", "verbose")==MagickTrue) {
      prt->do_verbose = MagickTrue;
    } else {
      fprintf (stderr, "rhotheta: ERROR: unknown option [%s]\n", pa);
      status = MagickFalse;
    }
  }

  if (prt->do_verbose) {
    fprintf (stderr, "rhotheta options:");

    // FIXME: thoughout, %g should respect "-precision"
    fprintf (stderr, "  scale %g", prt->scale);
    fprintf (stderr, "  bias %g", prt->bias);

    if (prt->offsetX != 0 || prt->offsetY != 0) {
      fprintf (stderr, "  offset %g,%g", prt->offsetX, prt->offsetY);
    }
    if (prt->inverse) fprintf (stderr, "  inverse");

    if (prt->do_verbose) fprintf (stderr, "  verbose");

    fprintf (stderr, "\n");
  }

  if (status == MagickFalse)
    usage ();

  return (status);
}


// The next function takes an image, and returns it.
//
static Image *rhotheta(
  Image *image,
  rhothetaT * pat,
  ExceptionInfo *exception)
{
  // This function reads and writes the R and G channels.
  // It leaves the B and alpha channels unchanged.

  ssize_t
    y;

  MagickBooleanType
    status;


  assert(image != (Image *) NULL);
  assert(image->signature == MAGICK_CORE_SIG);

  if (image->debug != MagickFalse)
    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);

  if (SetNoPalette (image, exception) == MagickFalse)
    return (Image *)NULL;

  CacheView * image_view = AcquireAuthenticCacheView (image,exception);

  status = MagickTrue;

#if defined(MAGICKCORE_OPENMP_SUPPORT)
  #pragma omp parallel for schedule(static,4) shared(status) \
    MAGICK_THREADS(image,image,image->rows,1)
#endif
  for (y=0; y < (ssize_t) image->rows; y++)
  {
    register VIEW_PIX_PTR
      *p;

    ssize_t
      x;

    if (status == MagickFalse)
      continue;

    p=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
    if (!p)
    {
      status=MagickFalse;
      continue;
    }

    double vx, vy, rho, theta;
    for (x=0; x < (ssize_t) image->columns; x++)
    {
      if (pat->inverse) {
        rho = GET_PIXEL_RED   (image, p) / (MagickRealType)QuantumRange;
        theta = (GET_PIXEL_GREEN (image, p) / (MagickRealType)QuantumRange
                 - pat->bias)
                / pat->scale;

        vx = rho * sin (theta) + pat->offsetX;
        vy = rho * cos (theta) + pat->offsetY;

        SET_PIXEL_RED   (image,
                         QuantumRange * vx,
                         p);

        SET_PIXEL_GREEN (image,
                         QuantumRange * vy,
                         p);
      } else {
        vx = GET_PIXEL_RED   (image, p) / (MagickRealType)QuantumRange - pat->offsetX;
        vy = GET_PIXEL_GREEN (image, p) / (MagickRealType)QuantumRange - pat->offsetY;

        rho = hypot (vx, vy);

        theta = atan2 (vx, vy) * pat->scale + pat->bias;

        SET_PIXEL_RED   (image,
                         QuantumRange * rho,
                         p);

        SET_PIXEL_GREEN (image,
                         QuantumRange * theta,
                         p);
      }
      p += Inc_ViewPixPtr (image);
    }
    if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
      status=MagickFalse;
  }
  image_view = DestroyCacheView (image_view);

  if (!status) return NULL;

  return (image);
}



ModuleExport size_t rhothetaImage(Image **images,const int argc,
  const char **argv,ExceptionInfo *exception)
{
  rhothetaT
    rt;

  assert(images != (Image **) NULL);
  assert(*images != (Image *) NULL);
  assert((*images)->signature == MAGICK_CORE_SIG);

  MagickBooleanType status = menu (argc, argv, &rt);
  if (status == MagickFalse)
    return (-1);

  Image * image;
  for (image=(*images); image != (Image *) NULL; image=GetNextImageInList(image))
  {
    Image * newImg = rhotheta (image, &rt, exception);
    if (!newImg) return (-1);

    if (newImg != image) {
      ReplaceImageInList (&image, newImg);
      *images=GetFirstImageInList (newImg);
    }
  }


  return (MagickImageFilterSignature);
}

rmsealpha.c

/* Updated:
     3-April-2018 for v7.0.7-28
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include "vsn_defines.h"

#include "rmsealpha.inc"


/* Takes two images.
   Does a subimage search of the second image within the first:
     finds the position of smallest score,
     and returns the score at that location.

   The weighting is such that a fully-transparent pixel effectively matches
   any other pixel exactly, whether that pixel is opaque or not,
   and contributes nothing to the scrore.

   At a given position:
   imageScore = sqrt (sigma(pixelScore) / sigma(pixelmAlpha) / 3)

   pixelScore = (dRed^2 + dGreen^2 + dBlue^2 ) * pixelmAlpha

   dRed = GET_PIXEL_RED(image,a) - GET_PIXEL_RED(image,b)
     etc for green and blue.

   pixelmAlpha = GET_PIXEL_ALPHA(image,a) * GET_PIXEL_ALPHA(image,b)
    (Alternative pixelmAlpha = (min (GET_PIXEL_ALPHA(image,a), GET_PIXEL_ALPHA(image,b)))^2

  Updated:
    12-August-2017 for v7.
    22-August-2017 added multi-scale and adjustLC; moved code to rmsealpha.inc
*/

// FIXME? Also option to process all images after first as searchimages?


static void usage (void)
{
  printf ("Usage: -process 'rmsealpha [OPTION]...'\n");
  printf ("Searches for lowest alpha-weighted RMSE score.\n");
  printf ("\n");
  printf ("  ai,  avoid_identical        patches with identical RGBA score 1.0\n");
  printf ("  ddo, dont_decrease_opacity  patches with decreased opacity in any pixel score 1.0\n");
  printf ("  ms,  multi_scale            multi-scale search\n");
  printf ("  adj, adjustLC number        adjust lightness and contrast\n");
  printf ("  j,   just_score             write the score only, with no \\n\n");
  printf ("  cnv, canvasOffset           add canvas offset to result\n");
  printf ("  so,  stdout                 write data to stdout\n");
  printf ("  se,  stderr                 write data to stderr (default)\n");
  printf ("  sis, saveInpScales          save input scales\n");
  printf ("  sss, saveSubScales          save subimage scales\n");
  printf ("  z,   sizeDiv number         size divider for ms\n");
  printf ("  md,  minDim integer         minimum dimension for ms\n");
  printf ("  v,   verbose                write text information to stdout\n");
  printf ("\n");
}


static MagickBooleanType IsArg (char * pa, char * ShtOpt, char * LongOpt)
{
  if ((LocaleCompare(pa, ShtOpt)==0) || (LocaleCompare(pa, LongOpt)==0))
    return MagickTrue;

  return MagickFalse;
}


#define NEXTARG \
  if (i < argc-1) i++; \
  else { \
    fprintf (stderr, "paintpatches: ERROR: [%s] needs argument\n", pa); \
    status = MagickFalse; \
  }

static MagickBooleanType menu(
  const int argc,
  const char **argv,
  RmseAlphaT * pra
)
// Returns MagickTrue if okay.
{
  int
    i;

  MagickBooleanType
    status;

  status = MagickTrue;

  for (i=0; i < argc; i++) {
    char * pa = (char *)argv[i];

    if (IsArg (pa, "ai", "avoid_identical")==MagickTrue) {
      pra->avoidIdentical = MagickTrue;
    } else if (IsArg (pa, "ddo", "dont_decrease_opacity")==MagickTrue) {
      pra->dontDecreaseOpacity = MagickTrue;
    } else if (IsArg (pa, "j", "just_score")==MagickTrue) {
      pra->outJustScore = MagickTrue;
    } else if (IsArg (pa, "cnv", "canvasOffset")==MagickTrue) {
      pra->addCanvOffs = MagickTrue;
    } else if (IsArg (pa, "ms", "multi_scale")==MagickTrue) {
      pra->multiScale = MagickTrue;
    } else if (IsArg (pa, "adj", "adjustLC")==MagickTrue) {
      NEXTARG;
      pra->adjustMeanSd = atof(argv[i]);
    } else if (IsArg (pa, "z", "sizeDiv")==MagickTrue) {
      NEXTARG;
      pra->sizeDivider = atof(argv[i]);
    } else if (IsArg (pa, "md", "minDim")==MagickTrue) {
      NEXTARG;
      pra->minDim = atoi(argv[i]);
    } else if (IsArg (pa, "so", "stdout")==MagickTrue) {
      pra->outhandle = stdout;
    } else if (IsArg (pa, "se", "stderr")==MagickTrue) {
      pra->outhandle = stderr;
    } else if (IsArg (pa, "sis", "saveInpScales")==MagickTrue) {
      pra->saveInpScales = MagickTrue;
    } else if (IsArg (pa, "sss", "saveSubScales")==MagickTrue) {
      pra->saveSubScales = MagickTrue;
    } else if (IsArg (pa, "v", "verbose")==MagickTrue) {
      pra->do_verbose = MagickTrue;
    } else {
      fprintf (stderr, "rmsealpha: ERROR: unknown option [%s]\n", pa);
      status = MagickFalse;
    }
  }

  if (pra->do_verbose) {
    fprintf (stderr, "rmsealpha options:");

    if (pra->avoidIdentical) fprintf (stderr, "  avoid_identical");
    if (pra->dontDecreaseOpacity) fprintf (stderr, "  dont_decrease_opacity");
    if (pra->outJustScore) fprintf (stderr, "  just_score");
    if (pra->addCanvOffs) fprintf (stderr, "  canvasOffset");
    if (pra->multiScale) fprintf (stderr, "  multi_scale");
    if (pra->adjustMeanSd > 0) fprintf (stderr, "  adjustLC %g", pra->adjustMeanSd);
    fprintf (stderr, "  sizeDiv %g", pra->sizeDivider);
    fprintf (stderr, "  minDim %i", pra->minDim);
    if (pra->outhandle == stdout) fprintf (stderr, "  stdout");
    if (pra->outhandle == stderr) fprintf (stderr, "  stderr");
    if (pra->saveInpScales) fprintf (stderr, "  saveInpScales");
    if (pra->saveSubScales) fprintf (stderr, "  saveSubScales");

    if (pra->do_verbose) fprintf (stderr, "  verbose");

    fprintf (stderr, "\n");
  }

  if (status == MagickFalse)
    usage ();

  return (status);
}



// The next function corresponds in style to functions in enhance.c
// It takes one image, and returns a status.
//
static MagickBooleanType rmsealpha (
  RmseAlphaT *pra,
  Image *inp_image, Image *sub_image,
  ExceptionInfo *exception)
{
  int
    precision;

  assert(inp_image != (Image *) NULL);
  assert(inp_image->signature == MAGICK_CORE_SIG);
  assert(sub_image != (Image *) NULL);
  assert(sub_image->signature == MAGICK_CORE_SIG);
  if (inp_image->debug != MagickFalse)
    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",inp_image->filename);

  precision = GetMagickPrecision();

/*==
  pra->solnX = pra->solnY = 0;
  pra->score = 0;

  nPosY = inp_image->rows - ht_sub + 1;
  nPosX = inp_image->columns - wi_sub + 1;

  for (y=0; y < nPosY; y++)
  {
    register ssize_t
      x;

    double
      score;

    if (status == MagickFalse)
      continue;

    for (x=0; x < nPosX; x++)
    {
      score = CompareWindow (
        inp_image, sub_image,
        pra, inp_view, sub_view, wi_sub, ht_sub, x, y, exception);

      if (score < 0) status = MagickFalse;

      if (pra->score > score) {
        pra->score = score;
        pra->solnX = x;
        pra->solnY = y;
      }
      if (pra->score == 0.0) break;
    }
    if (pra->score == 0.0) break;
  }
==*/

  pra->warnSubSd = MagickFalse;

  if (pra->multiScale) {
    if (!subRmseAlphaMS (pra, inp_image, sub_image, exception)) {
      fprintf (stderr, "subRmseAlphaMS failed\n");
      return MagickFalse;
    }
  } else {
    if (!subRmseAlpha (pra, inp_image, sub_image, exception)) {
      fprintf (stderr, "subRmseAlpha failed\n");
      return MagickFalse;
    }
  }

  if (pra->warnSubSd)
    fprintf (stderr, "rmsealpha: Warning: subimage SD==0, so adjustLC matches all\n");


  if (pra->outJustScore) {
    fprintf (pra->outhandle, "%.*g",
             precision, pra->score);
  } else {

    if (pra->addCanvOffs) {
      pra->solnX += inp_image->page.x;
      pra->solnY += inp_image->page.y;
    }

    fprintf (pra->outhandle, "rmsealpha: %.*g @ %lu,%lu\n",
             precision, pra->score, pra->solnX, pra->solnY);
    fprintf (pra->outhandle, "rmsealphaCrop: %lix%li+%lu+%lu\n",
             sub_image->columns, sub_image->rows, pra->solnX, pra->solnY);
  }

  return MagickTrue;
}


ModuleExport size_t rmsealphaImage(Image **images,const int argc,
  const char **argv,ExceptionInfo *exception)
{
  Image
    *image,
    *inp_image,
    *sub_image;

  MagickBooleanType
    status;

  RmseAlphaT
    ra;

  (void) argc;
  (void) argv;

  assert(images != (Image **) NULL);
  assert(*images != (Image *) NULL);
  assert((*images)->signature == MAGICK_CORE_SIG);

  InitRmseAlpha (&ra);

  status = menu (argc, argv, &ra);
  if (status == MagickFalse)
    return (-1);

  // Compare the images in pairs.
  // If we don't have even number of images, fatal error.

  for (image=(*images); image != (Image *) NULL; image=GetNextImageInList(image))
  {
    inp_image = image;
    image=GetNextImageInList(image);
    sub_image = image;
    if (sub_image == (Image *) NULL) {
      status = MagickFalse;
    } else {
      status = rmsealpha(&ra, inp_image, sub_image, exception);

      if (ra.saveInpScales || ra.saveSubScales) {
        // Do it again, to test the saving mechanism.
        status = rmsealpha(&ra, inp_image, sub_image, exception);
        // Do it again, to test the saving mechanism.
        status = rmsealpha(&ra, inp_image, sub_image, exception);
      }
    }

    ReInitRmseAlpha (&ra);

    if (status == MagickFalse)
      continue;
  }

  DeInitRmseAlpha (&ra);

  assert (status == MagickTrue);

  return(MagickImageFilterSignature);
}

rmsealpha.inc

/*
  Updated:
    17-September-2017: if a dimension difference is zero,
      exclude it as a limiter.

    19-September-2017: sub-image larger than main image is no longer fatal.
      If sub-image is larger in either dimension,
      only one search will be made in that dimension.

    22-September-2017: added doSlideX and doSlideY. When false,
      even if sub-image smaller than main, windows will not slide in
      that direction.

    3-April-2018 for v7.0.7-28

    14-April-2018 added offset processing to subRmseAlphaMS
*/

#ifndef RMSEALPHA_INC
#define RMSEALPHA_INC

#include "calcmnsd.inc"

/* Possible optimisations:

   - When searching a large image multiple times,
     use integral images to speed up mean and SD calcs.
*/

#define RMSE_ALPHA_SIGNATURE 54321

typedef struct {
  MagickBooleanType
    do_verbose,
    avoidIdentical,
    dontDecreaseOpacity,
    outJustScore,
    doSlideX,
    doSlideY,
    addCanvOffs,    // whether caller will add canvas offset to result
    multiScale,     // Not used.
    saveInpScales,  // Whether to save and reuse scaled versions of inp_image
    saveSubScales;  // Whether to save and reuse scaled versions of sub_image

  FILE * outhandle;

  double
    adjustMeanSd,
    sizeDivider;

  int
    minDim;

  int
    supSampFact;

  // Returned values:
  ssize_t
    solnX,
    solnY;

  double
    solnXf,
    solnYf;

  double
    score; // RMSE, 0.0 to 1.0.

  MagickBooleanType
    warnSubSd;

  // Used internally:
  MeanSdT
    meanSd_inp,
    meanSd_sub;

  MagickBooleanType
    inpScalesSaved, // whether inp scale have been scaled, so can be reused
    subScalesSaved, // whether sub scale have been scaled, so can be reused
    mainHasAlpha,
    subHasAlpha;

  Image
    *inp_list,
    *sub_list;

  int
    signature;

} RmseAlphaT;

typedef struct {
  double gainR;
  double gainG;
  double gainB;
  double biasR;
  double biasG;
  double biasB;
} GainBiasT;

static void InitRmseAlpha (RmseAlphaT * pra)
{
  pra->do_verbose = pra->avoidIdentical = pra->dontDecreaseOpacity = MagickFalse;
  pra->outJustScore = MagickFalse;
  pra->multiScale = MagickFalse;
  pra->saveInpScales = MagickFalse;
  pra->saveSubScales = MagickFalse;
  pra->doSlideX = pra->doSlideY = MagickTrue;
  pra->addCanvOffs = MagickFalse;
  pra->outhandle = stderr;
  pra->adjustMeanSd = 0.0;

  pra->minDim = 20;
  pra->supSampFact = 1;
  pra->sizeDivider = 2.0;

  pra->inpScalesSaved = MagickFalse;
  pra->subScalesSaved = MagickFalse;
  pra->inp_list = pra->sub_list = NULL;

  pra->warnSubSd = MagickFalse;
  pra->solnX = pra->solnY = -1;
  pra->solnXf = pra->solnYf = -1;
  pra->score = -1;

  pra->mainHasAlpha = MagickFalse;
  pra->subHasAlpha = MagickFalse;

  pra->signature = RMSE_ALPHA_SIGNATURE;
}

#define CHK_RA_SIG(pra) { \
  if ((pra)->signature != RMSE_ALPHA_SIGNATURE) \
    fprintf (stderr, "\n** bad RMSE_ALPHA_SIGNATURE **\n"); \
}

static void ReInitRmseAlpha (RmseAlphaT * pra)
// After this, the returned values are still available.
{
  // When we record resizes, this will free them,
  // allowing for fresh searches.

  CHK_RA_SIG(pra);

  if (pra->signature != RMSE_ALPHA_SIGNATURE)
    fprintf (stderr, "ReInitRmseAlpha: bad sig\n");

  if (pra->inp_list) {
    if (pra->do_verbose) fprintf (stderr, "rmsealpha: DestroyImageList\n");
    pra->inp_list = DestroyImageList (pra->inp_list);
  }

  if (pra->sub_list) {
    if (pra->do_verbose) fprintf (stderr, "rmsealpha: DestroyImageList\n");
    pra->sub_list = DestroyImageList (pra->sub_list);
  }

  pra->inpScalesSaved = MagickFalse;
  pra->subScalesSaved = MagickFalse;
}

static void DeInitRmseAlpha (RmseAlphaT * pra)
// After this, the returned values are still available.
{
  CHK_RA_SIG(pra);

  ReInitRmseAlpha (pra);

  pra->signature = 0;
}

static void inline MeanSdToGainBiasOne (
  double adjustMeanSd,
  double mnA,
  double sdA,
  double mnB,
  double sdB,
  double * gain,
  double * bias
)
{
  double gn, bs;

  if (sdA > 0) {
    gn = sdB / sdA;
  } else {
    gn = 1.0;
  }
  bs = mnB - mnA * gn;
  *gain = (1 - adjustMeanSd + adjustMeanSd*gn);
  *bias = adjustMeanSd * bs * QuantumRange;
}

static void inline MeanSdToGainBias (
  double adjustMeanSd,
  MeanSdT * mnsdA,
  MeanSdT * mnsdB,
  GainBiasT * gb)
// Sets gb to required gain and bias to make A look like B.
{

  MeanSdToGainBiasOne (adjustMeanSd,
    mnsdA->mnR, mnsdA->sdR, mnsdB->mnR, mnsdB->sdR,
    &gb->gainR, &gb->biasR);

  MeanSdToGainBiasOne (adjustMeanSd,
    mnsdA->mnG, mnsdA->sdG, mnsdB->mnG, mnsdB->sdG,
    &gb->gainG, &gb->biasG);

  MeanSdToGainBiasOne (adjustMeanSd,
    mnsdA->mnB, mnsdA->sdB, mnsdB->mnB, mnsdB->sdB,
    &gb->gainB, &gb->biasB);
}

static double CompareWindow (
  const Image *inp_image,
  const Image *sub_image,
  RmseAlphaT *pra,
  CacheView * inp_view,
  CacheView * subimage_view,
  ssize_t width, ssize_t height,
  ssize_t offsX, ssize_t offsY,
  ExceptionInfo *exception)
//
// Compares width x height of subimage with same-size window in inp,
// starting at top-left location (offsX,offsY) of inp.
// Returns number between 0.0 and 1.0 inclusive.
//   0.0 means exact match
//   (but could be because sigma(alpha) is 0.0.
//
{
  GainBiasT gb = {1.0,1.0,1.0,0.0,0.0,0.0};

  if (pra->adjustMeanSd != 0.0) {
    if (!CalcMeanSdVP (
          inp_image, inp_view, width, height, offsX, offsY,
          &pra->meanSd_inp, exception))
    {
      return MagickFalse;  // FIXME: flag error?
    }

    if (pra->meanSd_inp.isMeanSdDefined) {

      if (pra->meanSd_inp.sdR == 0
       && pra->meanSd_inp.sdG == 0
       && pra->meanSd_inp.sdB == 0)
      {
        pra->warnSubSd = MagickTrue;
      }

      // We make the window on the input look more like the subimage.
      // We set gainX relative to 1.0, but biasX relative to QuantumRange.

      MeanSdToGainBias (pra->adjustMeanSd,
        &pra->meanSd_inp, &pra->meanSd_sub, &gb);
    }
  }

  double
    sigScore = 0,
    sigmAlpha = 0;

  MagickBooleanType
    isIdentical = MagickTrue,
    okay = MagickTrue;

  ssize_t y;

#if defined(MAGICKCORE_OPENMP_SUPPORT)
  #pragma omp parallel for schedule(static,4) \
    MAGICK_THREADS(inp_image,sub_image,height,1)
#endif

  for (y = 0; y < height; y++) {
    MagickBooleanType okayY = MagickTrue;
    //if (!okay) continue;
    const VIEW_PIX_PTR *inpy = GetCacheViewVirtualPixels (
      inp_view,offsX,y+offsY,width,1,exception);
    const VIEW_PIX_PTR *suby = GetCacheViewVirtualPixels (
      subimage_view,0,y,width,1,exception);

    if (inpy == (const VIEW_PIX_PTR *) NULL || suby == (const VIEW_PIX_PTR *) NULL) {
      fprintf (stderr, "CompareWindow: badgcvvp\n");
      okayY = MagickFalse;
      continue;
    }
    double sigScoreY=0, sigmAlphaY=0;
    double inpa = 1.0, suba = 1.0;
    ssize_t x;
    for (x = 0; x < width; x++) {
      if (pra->mainHasAlpha) inpa = GET_PIXEL_ALPHA(inp_image, inpy)/(double)QuantumRange;
      if (pra->subHasAlpha)  suba = GET_PIXEL_ALPHA(sub_image, suby)/(double)QuantumRange;

      double mAlpha = inpa * suba;

      double dRed   =
         ((GET_PIXEL_RED(inp_image,inpy)*gb.gainR+gb.biasR)
        - GET_PIXEL_RED(sub_image,suby))/QuantumRange;
      double dGreen =
         ((GET_PIXEL_GREEN(inp_image,inpy)*gb.gainG+gb.biasG)
        - GET_PIXEL_GREEN(sub_image,suby))/QuantumRange;
      double dBlue  =
         ((GET_PIXEL_BLUE(inp_image,inpy)*gb.gainB+gb.biasB)
        - GET_PIXEL_BLUE(sub_image,suby))/QuantumRange;

      if (pra->dontDecreaseOpacity && inpa < suba) {
        okayY = MagickFalse;
      }

      if (dRed!=0 || dGreen!=0 || dBlue!=0 || inpa!=suba) {
        isIdentical = MagickFalse;
      }

      if (mAlpha > 0) {
        sigScoreY  += mAlpha * (dRed*dRed + dGreen*dGreen + dBlue*dBlue);
        sigmAlphaY += mAlpha;
      }

      inpy += Inc_ViewPixPtr (inp_image);
      suby += Inc_ViewPixPtr (sub_image);
    }
#if defined(MAGICKCORE_OPENMP_SUPPORT)
#pragma omp atomic
#endif
    sigScore  += sigScoreY;
#if defined(MAGICKCORE_OPENMP_SUPPORT)
#pragma omp atomic
#endif
    sigmAlpha += sigmAlphaY;
#if defined(MAGICKCORE_OPENMP_SUPPORT)
#pragma omp atomic
#endif
    okay &= okayY;
  }
  if (!okay) return 1.0;

  if (pra->avoidIdentical && isIdentical) return 1.0;

  if (sigmAlpha==0) {
    if (pra->do_verbose) fprintf (stderr, " CompareWindow: All transparent ");
    return 0.0;
  }

  return sqrt (sigScore / sigmAlpha / 3.0);
}


static MagickBooleanType subRmseAlpha (
  RmseAlphaT *pra,
  const Image *inp_image, const Image *sub_image,
  ExceptionInfo *exception)
{
  /* If sub_image is larger than inp_image in either direction,
     the extra pixels at right/bottom of sub_image are ignored,
     so there is only one search in that direction.

     Effectively, sub_image is cropped at right/bottom.
  */

  assert(inp_image != (Image *) NULL);
  assert(inp_image->signature == MAGICK_CORE_SIG);
  assert(sub_image != (Image *) NULL);
  assert(sub_image->signature == MAGICK_CORE_SIG);

  if (pra->signature != RMSE_ALPHA_SIGNATURE) return MagickFalse;

  ssize_t wi_sub = sub_image->columns;
  ssize_t ht_sub = sub_image->rows;

/*== Test replaced 19-September-2017.
  if (wi_sub > inp_image->columns || ht_sub > inp_image->rows) {
    fprintf (stderr, "subRmseAlpha: Subimage %lix%li larger than input %lix%li.\n",
       wi_sub, ht_sub,
       inp_image->columns, inp_image->rows);
    return MagickFalse;
  }
==*/

  if (wi_sub > inp_image->columns) wi_sub = inp_image->columns;
  if (ht_sub > inp_image->rows)    ht_sub = inp_image->rows;

  CacheView * inp_view = AcquireVirtualCacheView(inp_image,exception);
  CacheView * sub_view = AcquireVirtualCacheView(sub_image,exception);

  pra->solnX = pra->solnY = 0;
  pra->score = 99e9;

  if (pra->adjustMeanSd != 0.0) {
    if (!CalcMeanSdVP (
          sub_image, sub_view, wi_sub, ht_sub, 0, 0,
          &pra->meanSd_sub, exception))
    {
      return MagickFalse;
    }
    if (pra->do_verbose) WrMeanSd (&pra->meanSd_sub, stderr);
    if (!pra->meanSd_sub.isMeanSdDefined) {
      pra->adjustMeanSd = 0.0;
    }
  }

  ssize_t nPosX = inp_image->columns - wi_sub + 1;
  ssize_t nPosY = inp_image->rows    - ht_sub + 1;

  if (!pra->doSlideX) nPosX = 1;
  if (!pra->doSlideY) nPosY = 1;

  pra->mainHasAlpha = IS_ALPHA_CH(inp_image);
  pra->subHasAlpha  = IS_ALPHA_CH(sub_image);

  pra->warnSubSd = MagickFalse;

  if (pra->do_verbose) {
    fprintf (stderr, "subRmseAlpha: inp %lix%li (%s alpha)  sub %lix%li (%s alpha)  nPos %lix%li",
      inp_image->columns, inp_image->rows, (pra->mainHasAlpha) ? "has" : "no",
      wi_sub, ht_sub, (pra->subHasAlpha) ? "has" : "no",
      nPosX, nPosY);
  }

  ssize_t y;
  MagickBooleanType okay = MagickTrue;
  for (y=0; y < nPosY; y++)
  {
    ssize_t
      x;

    double
      score;

    if (okay == MagickFalse)
      continue;

    for (x=0; x < nPosX; x++)
    {
      score = CompareWindow (
        inp_image, sub_image,
        pra, inp_view, sub_view, wi_sub, ht_sub, x, y, exception);

      if (score < 0) okay = MagickFalse;
      else if (pra->score > score) {
        pra->score = score;
        pra->solnX = x;
        pra->solnY = y;

        if (pra->score == 0.0) break;
      }
    }
    if (pra->score == 0.0) break;
  }

  if (pra->do_verbose) {
    fprintf (stderr, " %g @ %li,%li\n",
      pra->score, pra->solnX, pra->solnY);
  }

  pra->solnXf = pra->solnX;
  pra->solnYf = pra->solnY;

  sub_view = DestroyCacheView (sub_view);
  inp_view = DestroyCacheView (inp_view);

  return okay;
}


static void WrList (Image * img, FILE * fh)
{
  if (!img) {
    fprintf (fh, "List empty\n");
  } else {
    while (img) {
      fprintf (fh, "%lix%li\n", img->columns, img->rows);
      img = GetNextImageInList (img);
    }
  }
}


// Each image can be in only one list.
// We don't want to mess with the main list of 5,
// so clone images for rmse lists.

static MagickBooleanType subRmseAlphaMSOne (
  RmseAlphaT *pra,
  const Image *inp_image, const Image *sub_image,
  ExceptionInfo *exception)
// Multi-scale subimage search.
// This calls itself recursively.
{
  assert(inp_image != (Image *) NULL);
  assert(inp_image->signature == MAGICK_CORE_SIG);
  assert(sub_image != (Image *) NULL);
  assert(sub_image->signature == MAGICK_CORE_SIG);

  if (pra->signature != RMSE_ALPHA_SIGNATURE) return MagickFalse;

  ssize_t smDim = inp_image->columns;
  if (smDim > inp_image->rows)    smDim = inp_image->rows;
  if (smDim > sub_image->columns) smDim = sub_image->columns;
  if (smDim > sub_image->rows)    smDim = sub_image->rows;

  // 17-September-2017: if a diff is zero, exclude it as a limiter.

  ssize_t dimDiff = inp_image->columns-sub_image->columns;
  if (dimDiff && smDim > dimDiff) smDim = dimDiff;

  dimDiff = inp_image->rows-sub_image->rows;
  if (dimDiff && smDim > dimDiff) smDim = dimDiff;

  if (pra->do_verbose) {
    fprintf (stderr, "subRmseAlphaMSOne: %lix%li+%li+%li %lix%li+%li+%li smDim %li\n",
      inp_image->columns, inp_image->rows,
      inp_image->page.x, inp_image->page.y,
      sub_image->columns, sub_image->rows,
      sub_image->page.x, sub_image->page.y,
      smDim);
  }

  if (pra->saveInpScales && !pra->inpScalesSaved) {
    // Save clone of this image to end of inp_list.
    Image * copy_img = CloneImage(inp_image, 0, 0, MagickTrue, exception);
    if (!copy_img) return MagickFalse;

    AppendImageToList (&pra->inp_list, copy_img);
    if (pra->do_verbose) {
      fprintf (stderr, "AppendImage inp %lix%li to %lix%li\n",
        copy_img->columns, copy_img->rows,
        pra->inp_list->columns, pra->inp_list->rows);
      WrList (pra->inp_list, stderr);
    }
  }

  if (pra->saveSubScales && !pra->subScalesSaved) {
    // Save clone of this image to end of sub_list.
    Image * copy_img = CloneImage(sub_image, 0, 0, MagickTrue, exception);
    if (!copy_img) return MagickFalse;

    AppendImageToList (&pra->sub_list, copy_img);
    if (pra->do_verbose) {
      fprintf (stderr, "AppendImage sub %lix%li to %lix%li\n",
        copy_img->columns, copy_img->rows,
        pra->sub_list->columns, pra->sub_list->rows);
      WrList (pra->sub_list, stderr);
    }
  }

  if (smDim < pra->minDim)
    return subRmseAlpha (pra, inp_image, sub_image, exception);

  Image *inp_sm, *sub_sm;

  if (pra->saveInpScales && pra->inpScalesSaved) {
    // Get small version from list.
    // It is the next one after inp_image.
    if (pra->do_verbose) {
      fprintf (stderr, "GetNextImage inp after %lix%li\n",
        inp_image->columns, inp_image->rows);
    }

    inp_sm = GetNextImageInList (inp_image);

    // If we don't have a small image,
    // create it. (This can happen if subimages are different sizes.)
    if (!inp_sm) {
      fprintf (stderr, "subRmseAlphaMSOne: no small inp\n");
      WrList (pra->inp_list, stderr);
      // FIXME: temp:
      inp_sm = RESIZEIMG (inp_image,
        inp_image->columns/pra->sizeDivider, inp_image->rows/pra->sizeDivider,
        exception);
      // FIXME: Append this to the list?
      Image * copy_img = CloneImage(inp_sm, 0, 0, MagickTrue, exception);
      if (!copy_img) return MagickFalse;

      AppendImageToList (&pra->inp_list, copy_img);
    }

    if (pra->do_verbose) {
      fprintf (stderr, "Got Image inp %lix%li\n",
        inp_sm->columns, inp_sm->rows);
    }
  } else {
    if (pra->do_verbose) {
      fprintf (stderr, "Resizing Image inp\n");
    }
    inp_sm = RESIZEIMG (inp_image,
      inp_image->columns/pra->sizeDivider, inp_image->rows/pra->sizeDivider,
      exception);
  }
  if (!inp_sm) return MagickFalse;

  if (pra->saveSubScales && pra->subScalesSaved) {
    // Get small version from list.
    // It is the next one after sub_image.
    if (pra->do_verbose) {
      fprintf (stderr, "GetNextImage sub after %lix%li\n",
        sub_image->columns, sub_image->rows);
    }

    sub_sm = GetNextImageInList (sub_image);

    // If we don't have a small image,
    // create it. (This can happen if subimages are different sizes.)
    if (!sub_sm) {
      fprintf (stderr, "subRmseAlphaMSOne: no small sub\n");
      WrList (pra->sub_list, stderr);
    }

    if (pra->do_verbose) {
      fprintf (stderr, "Got Image sub %lix%li\n",
        sub_sm->columns, sub_sm->rows);
    }
  } else {
    if (pra->do_verbose) {
      fprintf (stderr, "Resizing Image sub\n");
    }
    sub_sm = RESIZEIMG (sub_image,
      sub_image->columns/pra->sizeDivider, sub_image->rows/pra->sizeDivider,
      exception);
  }
  if (!sub_sm) return MagickFalse;


  if (!subRmseAlphaMSOne (pra, inp_sm, sub_sm, exception))
    return MagickFalse;

  if (!pra->subScalesSaved) sub_sm = DestroyImage (sub_sm);
  if (!pra->inpScalesSaved) inp_sm = DestroyImage (inp_sm);

  int plusMinus = floor (pra->sizeDivider + 2.5);

  RectangleInfo geom;
  geom.x = pra->solnX * pra->sizeDivider - plusMinus;
  geom.y = pra->solnY * pra->sizeDivider - plusMinus;
  if (geom.x < 0) geom.x = 0;
  if (geom.y < 0) geom.y = 0;
  geom.width  = sub_image->columns + 2*plusMinus;
  geom.height = sub_image->rows    + 2*plusMinus;

  if (geom.x + geom.width > inp_image->columns)
    geom.width = inp_image->columns - geom.x;

  if (geom.y + geom.height > inp_image->rows)
    geom.height = inp_image->rows - geom.y;

  if (pra->do_verbose) {
    fprintf (stderr,
      "subRmseAlphaMSOne: crop geom %lix%li+%li+%li out of %lix%li %lix%li+%li+%li \n",
      geom.width, geom.height, geom.x, geom.y,
      inp_image->columns, inp_image->rows,
      inp_image->page.width, inp_image->page.height,
      inp_image->page.x, inp_image->page.y);
  }

  Image * inp_crp = CropImage (inp_image, &geom, exception);
  if (!inp_crp) return MagickFalse;

  inp_crp->page.width = inp_crp->page.height
    = inp_crp->page.x = inp_crp->page.y = 0;

  if (!subRmseAlpha (pra, inp_crp, sub_image, exception))
    return MagickFalse;

  inp_crp = DestroyImage (inp_crp);

  if (pra->do_verbose) {
    WrList (pra->inp_list, stderr);
    WrList (pra->sub_list, stderr);
  }

  // Add the geometry offsets back
  pra->solnX += geom.x;
  pra->solnY += geom.y;

  pra->solnXf = pra->solnX;
  pra->solnYf = pra->solnY;

  return MagickTrue;
}

static MagickBooleanType subRmseSuper (
  RmseAlphaT *pra,
  Image *inp_image, Image *sub_image,
  ExceptionInfo *exception)
// Supersampling subimage search.
// Assumes pra->solnX and pra->solnY are the best integral solutions.
// Sets pra->solnXf and pra->solnYf.
{
  // Clone cropped inp_image, and resize it.
  // Clone sub_image, and resize it.
  // Search.
  // Adjust the numbers.
  int
    dl=0,
    dt=0,
    dr=0,
    db=0;

  if (pra->solnX > 0) dl = 1;
  if (pra->solnY > 0) dt = 1;

  if (dl + pra->solnX + sub_image->columns < inp_image->columns) dr = 1;
  if (dt + pra->solnY + sub_image->rows    < inp_image->rows   ) db = 1;

  RectangleInfo geom;
  geom.x = pra->solnX - dl;
  geom.y = pra->solnY - dt;
  geom.width = sub_image->columns + dl + dr;
  geom.height = sub_image->rows + dt + db;

  if (pra->do_verbose)
    fprintf (stderr, "subRmseSuper geom: %lix%li+%li+%li\n",
             geom.width, geom.height, geom.x, geom.y);

  Image * inp_crp = CropImage (inp_image, &geom, exception);
  if (!inp_crp) return MagickFalse;

  inp_crp->page.width = inp_crp->page.height
    = inp_crp->page.x = inp_crp->page.y = 0;

  // Resize both.

  Image * inp_res = RESIZEIMG (inp_crp,
    inp_crp->columns*pra->supSampFact, inp_crp->rows*pra->supSampFact,
        exception);

  if (!inp_res) {
    return MagickFalse;
  }

  inp_crp = DestroyImage (inp_crp);

  Image * sub_res = RESIZEIMG (sub_image,
    sub_image->columns*pra->supSampFact, sub_image->rows*pra->supSampFact,
        exception);

  if (!sub_res) {
    return MagickFalse;
  }

  if (!subRmseAlphaMSOne (pra, inp_res, sub_res, exception))
    return MagickFalse;

  sub_res = DestroyImage (sub_res);
  inp_res = DestroyImage (inp_res);

  pra->solnXf = geom.x + pra->solnX / (double)pra->supSampFact;
  pra->solnYf = geom.y + pra->solnY / (double)pra->supSampFact;

  pra->solnX = floor (pra->solnXf + 0.5);
  pra->solnY = floor (pra->solnYf + 0.5);

  return MagickTrue;
}

static MagickBooleanType subRmseAlphaMS (
  RmseAlphaT *pra,
  Image *inp_image, Image *sub_image,
  ExceptionInfo *exception)
// Multi-scale subimage search.
{
  CHK_RA_SIG(pra);

  if (pra->signature != RMSE_ALPHA_SIGNATURE) {
    fprintf (stderr, "rmsealpha: bad signature\n");
    return MagickFalse;
  }

  Image * inp_i = inp_image;
  if (pra->inpScalesSaved) {
    if (pra->do_verbose) fprintf (stderr, "subRmseAlphaMS: inpScalesSaved\n");
    inp_i = pra->inp_list;
  }

  Image * sub_i = sub_image;
  if (pra->subScalesSaved) {
    if (pra->do_verbose) fprintf (stderr, "subRmseAlphaMS: subScalesSaved\n");
    sub_i = pra->sub_list;
  }

  ssize_t ix = inp_i->page.x;
  ssize_t iy = inp_i->page.y;

  ssize_t sx = sub_i->page.x;
  ssize_t sy = sub_i->page.y;

  inp_i->page.x = sub_i->page.x =
  inp_i->page.y = sub_i->page.y = 0;

  MagickBooleanType r = subRmseAlphaMSOne (
    pra, inp_i, sub_i, exception);

  pra->solnX += ix;
  pra->solnY += iy;

  pra->solnXf = pra->solnX;
  pra->solnYf = pra->solnY;

  if (pra->supSampFact > 1) {
    if (!subRmseSuper (pra, inp_i, sub_i, exception)) {
      fprintf (stderr, "rmsealpha: bad subRmseSuper\n");
      return MagickFalse;
    }
  }

  inp_i->page.x = ix;
  inp_i->page.y = iy;
  sub_i->page.x = sx;
  sub_i->page.y = sy;

  if (pra->saveInpScales) {
    pra->inpScalesSaved = MagickTrue;
    if (pra->do_verbose) WrList (pra->inp_list, stderr);
  }

  if (pra->saveSubScales) {
    pra->subScalesSaved = MagickTrue;
    if (pra->do_verbose) WrList (pra->sub_list, stderr);
  }

  return r;
}

#endif

calcmnsd.inc

/* Calculate the mean and standard deviation
   of the three colour channels,
   of an image or a VIEW_PIX_PTR.

   Updated:
     3-April-2018 for v7.0.7-28
*/


#ifndef CALCMNSD_INC
#define CALCMNSD_INC 1

typedef struct {
  // Returns values in range 0.0 to 1.0.
  double mnR;
  double mnG;
  double mnB;
  double sdR;
  double sdG;
  double sdB;
  MagickBooleanType isMeanSdDefined;
} MeanSdT;


static void WrMeanSd (MeanSdT * pms, FILE * fh)
{
  fprintf (fh, "isMeanSdDefined: %i\n", pms->isMeanSdDefined);
  if (pms->isMeanSdDefined) {
    fprintf (fh, "           mean: %g %g %g\n", pms->mnR, pms->mnG, pms->mnB);
    fprintf (fh, "             SD: %g %g %g\n", pms->sdR, pms->sdG, pms->sdB);
  }
}


static double inline sqrtEps (double v)
{
  return (v < 1e-10) ? 0 : sqrt(v);
}

static MagickBooleanType CalcMeanSdVP (
  const Image *image,
  const CacheView * in_view,
  ssize_t columns,
  ssize_t rows,
  ssize_t offsX,
  ssize_t offsY,
  MeanSdT * pms,
  ExceptionInfo *exception
)
{
  double sigR=0, sigG=0, sigB=0, sigA=0;
  double sigR2=0, sigG2=0, sigB2=0;
  MagickBooleanType okay = MagickTrue;

  ssize_t y;

#if defined(MAGICKCORE_OPENMP_SUPPORT)
  #pragma omp parallel for schedule(static,4) \
    MAGICK_THREADS(image,image,rows,1)
#endif

  for (y=0; y < rows; y++) {
    MagickBooleanType okayY = MagickTrue;
    const VIEW_PIX_PTR *p=GetCacheViewVirtualPixels (
      in_view,offsX,y+offsY,columns,1,exception);
    if (p == (const VIEW_PIX_PTR *) NULL) {
      okayY = MagickFalse;
      continue;
    }

    double sigRY=0, sigGY=0, sigBY=0, sigAY=0;
    double sigR2Y=0, sigG2Y=0, sigB2Y=0;

    ssize_t x;
    double r, g, b, a;
    for (x=0; x < columns; x++) {
      a = GET_PIXEL_ALPHA(image,p) / QuantumRange;
      r = a * GET_PIXEL_RED(image,p) / QuantumRange;
      g = a * GET_PIXEL_GREEN(image,p) / QuantumRange;
      b = a * GET_PIXEL_BLUE(image,p) / QuantumRange;
      sigRY += r;
      sigGY += g;
      sigBY += b;
      sigAY += a;
      sigR2Y += r*r;
      sigG2Y += g*g;
      sigB2Y += b*b;

      p += Inc_ViewPixPtr (image);
    }

#if defined(MAGICKCORE_OPENMP_SUPPORT)
#pragma omp atomic
    sigR += sigRY;
#pragma omp atomic
    sigG += sigGY;
#pragma omp atomic
    sigB += sigBY;
#pragma omp atomic
    sigA += sigAY;
#pragma omp atomic
    sigR2 += sigR2Y;
#pragma omp atomic
    sigG2 += sigG2Y;
#pragma omp atomic
    sigB2 += sigG2Y;
#pragma omp atomic
    okay &= okayY;
#else
    sigR += sigRY;
    sigG += sigGY;
    sigB += sigBY;
    sigA += sigAY;
    sigR2 += sigR2Y;
    sigG2 += sigG2Y;
    sigB2 += sigG2Y;
    okay &= okayY;
#endif
  }

  if (sigA == 0) {
    pms->isMeanSdDefined = MagickFalse;
    return MagickTrue;
  }

  pms->isMeanSdDefined = MagickTrue;
  pms->mnR = sigR / sigA;
  pms->mnG = sigG / sigA;
  pms->mnB = sigB / sigA;
  pms->sdR = sqrtEps (sigR2/sigA - (pms->mnR)*(pms->mnR));
  pms->sdG = sqrtEps (sigG2/sigA - (pms->mnG)*(pms->mnG));
  pms->sdB = sqrtEps (sigB2/sigA - (pms->mnB)*(pms->mnB));

  return MagickTrue;
}


static MagickBooleanType CalcMeanSdImg (

  const Image *image,
  MeanSdT * pms,
  ExceptionInfo *exception
)
// Calculate mean and standard deviation,
// accounting for alpha (eg ignoring pixels that are entirely transparent).
// Returns values in range 0.0 to 1.0.
// Returns false if major problem.
// Returns isMeanSdDefined = MagickFalse iff statistics are not defined
//   (image is entirely transparent).
{
  // mnR = sig(R) / sig(alpha)
  // sdR = sqrt ( sig(R^2) / sig(alpha) - mnR^2 )
  //
  // Likewise for G and B.

  CacheView *in_view = AcquireVirtualCacheView (image, exception);

  MagickBooleanType r = CalcMeanSdVP (
    image, in_view, image->columns, image->rows, 0, 0, pms, exception);

  in_view = DestroyCacheView (in_view);

  return r;
}

#endif

srchimg.c

/*
   Reference: http://im.snibgo.com/srchimg.htm
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <math.h>
#include <ctype.h>
#include "vsn_defines.h"

#include "rmsealpha.inc"

#define VERSION "srchimg v1.0  Copyright (c) 2017 Alan Gibson"

typedef struct {
  int
    precision;

  MagickBooleanType
    doCrop,
    addCanvOffs,    // whether to add canvas offset to result
    verbose;

  double
    sizeDivider;

  int
    minDim;

  int
    supSampFact;

  FILE *
    fh_data;
} srchimgT;

static void usage (void)
{
  printf ("Usage: -process 'srchimg [OPTION]...'\n");
  printf ("Searches for second image in first.\n");
  printf ("\n");
  printf ("  n,   noCrop              don't replace images with crop\n");
  printf ("  z,   sizeDiv number      size divider for each level\n");
  printf ("  md,  minDim integer      minimum dimension for ms\n");
  printf ("  ss,  superSample number  factor for supersampling\n");
  printf ("  cnv, canvasOffset        add canvas offset to result\n");
  printf ("  f,   file string         write to file stream stdout or stderr\n");
  printf ("  v,   verbose             write text information to stdout\n");
  printf ("       version             write version information to stdout\n");
  printf ("\n");
}

/* TODO:
   Action on input offsets:
   - ignore (effectively "+repage" at start)
   - regard ("+repage" but add in at the end)
*/

static MagickBooleanType IsArg (char * pa, char * ShtOpt, char * LongOpt)
{
  // LocaleCompare is not case-sensitive,
  // so we use strcmp for the short option.

  if ((strcmp(pa, ShtOpt)==0) || (LocaleCompare(pa, LongOpt)==0))
    return MagickTrue;

  return MagickFalse;
}


#define NEXTARG \
  if (i < argc-1) i++; \
  else { \
    fprintf (stderr, "srchimg: ERROR: [%s] needs argument\n", pa); \
    status = MagickFalse; \
  }

static MagickBooleanType menu(
  const int argc,
  const char **argv,
  srchimgT * psi
)
// Returns MagickTrue if okay.
{
  int
    i;

  MagickBooleanType
    status = MagickTrue;

  psi->verbose = MagickFalse;
  psi->doCrop = MagickTrue;
  psi->addCanvOffs = MagickFalse;
  psi->sizeDivider = 2.0;
  psi->minDim = 20;
  psi->supSampFact = 1;
  psi->fh_data = stderr;

  for (i=0; i < argc; i++) {
    char * pa = (char *)argv[i];
    //printf ("Arg %i [%s]\n", i, pa);

    if (IsArg (pa, "n", "noCrop")==MagickTrue) {
      psi->doCrop = MagickFalse;
    } else if (IsArg (pa, "z", "sizeDiv")==MagickTrue) {
      NEXTARG;
      psi->sizeDivider = atof(argv[i]);
    } else if (IsArg (pa, "md", "minDim")==MagickTrue) {
      NEXTARG;
      psi->minDim = atoi(argv[i]);
    } else if (IsArg (pa, "ss", "superSample")==MagickTrue) {
      NEXTARG;
      psi->supSampFact = atoi(argv[i]);
    } else if (IsArg (pa, "cnv", "canvasOffset")==MagickTrue) {
      psi->addCanvOffs = MagickTrue;
    } else if (IsArg (pa, "f", "file")==MagickTrue) {
      NEXTARG;
      if (strcasecmp (argv[i], "stdout")==0) psi->fh_data = stdout;
      else if (strcasecmp (argv[i], "stderr")==0) psi->fh_data = stderr;
      else status = MagickFalse;
    } else if (IsArg (pa, "v", "verbose")==MagickTrue) {
      psi->verbose = MagickTrue;
    } else if (IsArg (pa, "version", "version")==MagickTrue) {
      fprintf (stdout, "%s\n", VERSION);
    } else {
      fprintf (stderr, "srchimg: ERROR: unknown option [%s]\n", pa);
      status = MagickFalse;
    }
  }

  if (psi->verbose) {
    fprintf (stderr, "srchimg options:");

    if (!psi->doCrop) fprintf (stderr, "  noCrop");
    fprintf (stderr, "  sizeDiv %g", psi->sizeDivider);
    fprintf (stderr, "  minDim %i", psi->minDim);
    if (psi->supSampFact > 1) fprintf (stderr, "  superSample %i", psi->supSampFact);
    if (psi->addCanvOffs) fprintf (stderr, "  canvasOffset");

    if (psi->fh_data == stdout) fprintf (stderr, "  file stdout");

    if (psi->verbose)    fprintf (stderr, "  verbose");
    fprintf (stderr, "\n");
  }

  if (status == MagickFalse)
    usage ();

  return (status);
}


// The next function takes an image list,
// may modify it, and returns a status.
//
static MagickBooleanType srchimg (
  Image **images,
  srchimgT * psi,
  ExceptionInfo *exception)
{
  if ((*images)->debug != MagickFalse)
    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",(*images)->filename);

  int ListLen = (int)GetImageListLength(*images);
  if (ListLen !=2) {
    fprintf (stderr, "srchimg needs exactly 2 images\n");
    return MagickFalse;
  }

  Image * inp_image = *images;
  Image * sub_image = GetNextImageInList (inp_image);

  if (psi->verbose) {
    fprintf (stderr, "srchimg: Input image [%s] %ix%i depth is %i\n",
             inp_image->filename,
             (int)inp_image->columns, (int)inp_image->rows,
             (int)inp_image->depth);
  }

  RmseAlphaT ra;
  InitRmseAlpha (&ra);
  ra.do_verbose  = psi->verbose;
  ra.multiScale  = MagickTrue;
  ra.sizeDivider = psi->sizeDivider;
  ra.minDim      = psi->minDim;
  ra.supSampFact = psi->supSampFact;

  if (!subRmseAlphaMS (&ra, inp_image, sub_image, exception)) {
    fprintf (stderr, "subRmseAlphaMS failed\n");
    return MagickFalse;
  }

  DeInitRmseAlpha (&ra);

  ssize_t offsX=0, offsY=0;

  if (psi->addCanvOffs) {
    offsX = inp_image->page.x;
    offsY = inp_image->page.y;
  }

  double dx = ra.solnXf + offsX;
  double dy = ra.solnYf + offsY;

  // fprintf (psi->fh_data, "%.*g @ %li,%li\n",
  //          psi->precision, ra.score,
  //          ra.solnX + offsX, ra.solnY + offsY);

  fprintf (psi->fh_data, "%.*g @ %g,%g\n",
           psi->precision, ra.score,
           dx, dy);

  if (psi->doCrop) {
    Image * inp_crp;
    if (psi->supSampFact == 1) {
      RectangleInfo geom;
      geom.x = ra.solnX;
      geom.y = ra.solnY;
      geom.width = sub_image->columns;
      geom.height = sub_image->rows;
      inp_crp = CropImage (inp_image, &geom, exception);
      if (!inp_crp) return MagickFalse;
    } else {
      // fixme: also set viewport

      char text[MaxTextExtent];

      sprintf (text, "%lux%lu+0+0",
               sub_image->columns, sub_image->rows);

#if IMV6OR7==6
      SetImageProperty (inp_image, "viewport", text);
#else
      SetImageProperty (inp_image, "viewport", text, exception);
#endif


      MagickBooleanType bestfit = MagickTrue;
      double srtArray[6];
      srtArray[0] = dx;
      srtArray[1] = dx;
      srtArray[2] = 1;
      srtArray[3] = 0;
      srtArray[4] = 0;
      srtArray[5] = 0;

      inp_crp = DistortImage (inp_image, ScaleRotateTranslateDistortion,
          6, srtArray, bestfit, exception);
     if (!inp_crp) { fprintf (stderr, "DistortImage failed\n"); return MagickFalse; }
    }

    DeleteImageFromList (&sub_image);

    ReplaceImageInList (&inp_image, inp_crp);
    // Replace messes up the images pointer. Make it good:
    *images = GetFirstImageInList (inp_image);
  }

  if (psi->verbose) {
    fprintf (stderr, "Finished srchimg\n");
  }

  return MagickTrue;
}


ModuleExport size_t srchimgImage (
  Image **images,
  const int argc,
  const char **argv,
  ExceptionInfo *exception)
{
  srchimgT si;

  assert(images != (Image **) NULL);
  assert(*images != (Image *) NULL);
  assert((*images)->signature == MAGICK_CORE_SIG);

  MagickBooleanType status = menu (argc, argv, &si);
  if (!status) return -1;

  si.precision = GetMagickPrecision();

  status = srchimg (images, &si, exception);
  if (!status) return (-1);

  return(MagickImageFilterSignature);
}

aggrsrch.c

/* Updated:
     6-April-2018 Corrected bug in verbose.
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include "vsn_defines.h"

#include "aggrsrch.inc"

#define VERSION "aggrsrch v1.0  Copyright (c) 2017 Alan Gibson"

typedef struct {
  MagickBooleanType
    verbose,
    makeMask;

  double
    aggression,     // 0.0 < aggression <= 1.0.
    maskThreshold;  // typically 0.0 to 1.0.

} SubSrchT;


static void InitSubSrch (SubSrchT * pss)
{
  pss->makeMask = MagickFalse;
  pss->verbose = MagickFalse;
  pss->maskThreshold = 0.75;
  pss->aggression = 1.0;
}

static void DeInitSubSrch (SubSrchT * pss)
{
  ; // Nothing
}


static void usage (void)
{
  printf ("Usage: -process 'aggrsrch [OPTION]...'\n");
  printf ("Search with aggressive resizing.\n");
  printf ("\n");
  printf ("  a,  aggression number  0.0 to 1.0\n");
  printf ("  m,  makeMask           make mask from result\n");
  printf ("  t,  threshold number   0.0 to 1.0\n");
  printf ("  v,  verbose            write text information to stdout\n");
  printf ("      version            write version information to stdout\n");
  printf ("\n");
}


static MagickBooleanType IsArg (char * pa, char * ShtOpt, char * LongOpt)
{
  if ((LocaleCompare(pa, ShtOpt)==0) || (LocaleCompare(pa, LongOpt)==0))
    return MagickTrue;

  return MagickFalse;
}


#define NEXTARG \
  if (i < argc-1) i++; \
  else { \
    fprintf (stderr, "aggrsrch: ERROR: [%s] needs argument\n", pa); \
    status = MagickFalse; \
  }

static MagickBooleanType menu(
  const int argc,
  const char **argv,
  SubSrchT * pss
)
// Returns MagickTrue if okay.
{
  int
    i;

  MagickBooleanType
    status;

  status = MagickTrue;

  for (i=0; i < argc; i++) {
    char * pa = (char *)argv[i];

    if (IsArg (pa, "m", "makeMask")==MagickTrue) {
      pss->makeMask = MagickTrue;
    } else if (IsArg (pa, "a", "aggression")==MagickTrue) {
      NEXTARG;
      pss->aggression = atof(argv[i]);
    } else if (IsArg (pa, "t", "threshold")==MagickTrue) {
      NEXTARG;
      pss->maskThreshold = atof(argv[i]);
    } else if (IsArg (pa, "v", "verbose")==MagickTrue) {
      pss->verbose = MagickTrue;
    } else if (IsArg (pa, "version", "version")==MagickTrue) {
      fprintf (stdout, "%s\n", VERSION);
    } else {
      fprintf (stderr, "aggrsrch: ERROR: unknown option\n");
      status = MagickFalse;
    }
  }

  if (pss->verbose) {
    fprintf (stderr, "aggrsrch options:");
    fprintf (stderr, "  aggression %g", pss->aggression);

    if (pss->makeMask) {
      fprintf (stderr, "  makeMask");
      fprintf (stderr, "  threshold %g", pss->maskThreshold);
    }
    if (pss->verbose) fprintf (stderr, "  verbose");
    fprintf (stderr, "\n");
  }

  if (status == MagickFalse)
    usage ();

  return (status);
}


// The next function takes an image list,
// may modify it, and returns a status.
//
static MagickBooleanType aggrsrch (
  Image **images,
  SubSrchT * pss,
  ExceptionInfo *exception)
{
  Image * main_img = *images;

  Image * sub_img = GetNextImageInList (main_img);
  if (!sub_img) return MagickFalse;


  if (sub_img->columns > main_img->columns ||
      sub_img->rows    > main_img->rows)
  {
    fprintf (stderr, "aggrsrch: subimage is larger than main\n");
    return MagickFalse;
  }

  Image * r = aggrSrchDiff (
    main_img, sub_img, pss->makeMask,
    pss->aggression, pss->maskThreshold, pss->verbose, exception);
  if (!r) return MagickFalse;

  // FIXME: This makes a mask, which is useful.
  // But how about finding the search coordinates?
  // For each of the sinks, crop the appropriate rectangle from main,
  // and search it for sub.

  DeleteImageFromList (&sub_img);
  DeleteImageFromList (&main_img);

  *images = GetFirstImageInList (r);

  return MagickTrue;
}



ModuleExport size_t aggrsrchImage(Image **images,const int argc,
  const char **argv,ExceptionInfo *exception)
{
  MagickBooleanType
    status;

  SubSrchT
    ss;

  assert(images != (Image **) NULL);
  assert(*images != (Image *) NULL);
  assert((*images)->signature == MAGICK_CORE_SIG);

  InitSubSrch (&ss);

  status = menu (argc, argv, &ss);
  if (status == MagickFalse)
    return (-1);

  int ListLen = (int)GetImageListLength(*images);
  if (ListLen != 2) {
    fprintf (stderr, "aggrsrch: needs 2 images\n");
    return (-1);
  }

  if (!aggrsrch (images, &ss, exception)) return (-1);

  DeInitSubSrch (&ss);

  return(MagickImageFilterSignature);
}

aggrsrch.inc

#ifndef AGGRSRCH_INC
#define AGGRSRCH_INC

#include "resetpage.inc"
#include "findsinks.inc"
#include "cropchk.inc"


static MagickBooleanType SetImgRMS (
  Image *image,
  MagickRealType *vLo,
  ssize_t *fndX,
  ssize_t *fndY,
  ExceptionInfo *exception
)
// Sets R,G and B channels to the RMS of R, G and B.
// Sets alpha opaque.
// Return the darkest coordinate.
// Returns whether okay.
{
  MagickBooleanType okay = MagickTrue;
  ssize_t y;

  CacheView * in_view = AcquireAuthenticCacheView (image, exception);

  *vLo = QuantumRange;
  ssize_t xLo = 0, yLo = 0;

  for (y = 0; y < image->rows; y++) {
    if (!okay) continue;

    VIEW_PIX_PTR *p = GetCacheViewAuthenticPixels(
      in_view,0,y,image->columns,1,exception);
    if (!p) {okay = MagickFalse; continue; }

    ssize_t x;
    MagickRealType r, g, b, v;
    for (x = 0; x < image->columns; x++) {

      r = GET_PIXEL_RED   (image, p);
      g = GET_PIXEL_GREEN (image, p);
      b = GET_PIXEL_BLUE  (image, p);

      v = sqrt ((r*r + g*g + b*b) / 3.0);

      if (*vLo > v) {
        *vLo = v;
        xLo = x;
        yLo = y;
      }

      SET_PIXEL_RED   (image, v, p);
      SET_PIXEL_GREEN (image, v, p);
      SET_PIXEL_BLUE  (image, v, p);
      SET_PIXEL_ALPHA (image, QuantumRange, p);

      p += Inc_ViewPixPtr (image);
    }
    if (SyncCacheViewAuthenticPixels(in_view,exception) == MagickFalse)
      okay = MagickFalse;
  }
  in_view  = DestroyCacheView (in_view);

  *vLo /= QuantumRange;
  *fndX = xLo;
  *fndY = yLo;

  return okay;
}


static Image * aggrSrchDiff (
  Image * main_img,
  Image * sub_img,
  MagickBooleanType MakeMask,
  double aggression,     // 0.0 < aggression <= 1.0.
  double maskThreshold,  // 0.0 to 1.0. >= 1.0 for no threshold.
  MagickBooleanType verbose,
  ExceptionInfo *exception)
{
  /* This should account for an expected minimum size.
     Eg if the sub_img appears in main_img at half the size,
     then we can't be so aggressive.
  */

  if (aggression <= 0) {
    fprintf (stderr, "aggrSrch Diff: aggression=%g\n", aggression);
    return NULL;
  }

  ssize_t mainW = main_img->columns;
  ssize_t mainH = main_img->rows;
  ssize_t newW = floor (mainW / (aggression * sub_img->columns ) + 0.5);
  ssize_t newH = floor (mainH / (aggression * sub_img->rows    ) + 0.5);

  if (verbose) fprintf (stderr,
    "aggrSrch Diff: main %lix%li  sub %lix%li  newMain %lix%li threshold %g\n",
    mainW, mainH,
    sub_img->columns, sub_img->rows,
    newW, newH,
    maskThreshold);

  if (newW < 1 || newH < 1) {
    fprintf (stderr, "aggrSrchDiff: sub_img too small\n");
  }

  Image * main_res = ScaleImage (main_img, newW, newH, exception);
  if (!main_res) return NULL;

  Image * sub_res1 = ScaleImage (sub_img, 1, 1, exception);
  if (!sub_res1) return NULL;

#if IMV6OR7==6
  SetImageAlphaChannel (sub_res1, DeactivateAlphaChannel);
#else
  SetImageAlphaChannel (sub_res1, DeactivateAlphaChannel, exception);
#endif

  Image * sub_res2 = ScaleImage (sub_res1, newW, newH, exception);
  if (!sub_res2) return NULL;
  sub_res1 = DestroyImage (sub_res1);

  if (!COMPOSITE(main_res, DifferenceCompositeOp, sub_res2, 0,0, exception))
    return NULL;
  sub_res2 = DestroyImage (sub_res2);

//#if IMV6OR7==6
//  if (!GrayscaleImage (main_res, RMSPixelIntensityMethod)) return NULL;
//
//  TransformImageColorspace (main_res, sRGBColorspace);
//
//  SetImageAlphaChannel (main_res, DeactivateAlphaChannel);
//#else
//  if (!GrayscaleImage (main_res, RMSPixelIntensityMethod, exception)) return NULL;
//
//  TransformImageColorspace (main_res, sRGBColorspace, exception);
//
//  SetImageAlphaChannel (main_res, DeactivateAlphaChannel, exception);
//#endif

  MagickRealType vLo;
  ssize_t fndX, fndY;
  if (! SetImgRMS (main_res, &vLo, &fndX, &fndY, exception)) return NULL;

  if (verbose) {
    fprintf (stderr,
      "aggrsrch Res: %g @ %li,%li\n",
      vLo, (long int)fndX, (long int)fndY);
  }

  if (MakeMask) {
    Image * main_lns = LightenNonSinks (main_res, verbose, exception);
    if (!main_lns) return NULL;
    DestroyImage (main_res);
    main_res = main_lns;
  }

  Image * main_res2 = RESIZEIMG (main_res,
    mainW, mainH, exception);

  if (!main_res2) return NULL;
  DestroyImage (main_res);
  main_res = main_res2;

  if (MakeMask) {

    // Crop to size for the mask, offset by half subimage dimensions.

    RectangleInfo fndRect;
    fndRect.width  = mainW - sub_img->columns + 1;
    fndRect.height = mainH - sub_img->rows + 1;

    if (fndRect.width < 1 || fndRect.height < 1) {
      fprintf (stderr, "aggrSrch Diff: zero or neg mask\n");
      return NULL;
    }

    fndRect.x = sub_img->columns / 2;
    fndRect.y = sub_img->rows / 2;

    if (verbose) fprintf (stderr, "aggrSrch Mask: crop to %lix%li+%li+%li\n",
      fndRect.width, fndRect.height, fndRect.x, fndRect.y);

    Image * main_res3 = CropCheck (main_res, &fndRect, exception);
    if (!main_res3) return NULL;
    ResetPage (main_res3);
    DestroyImage (main_res);
    main_res = main_res3;

    // Set red and green channels to zero.
    // Where blue is more than a threshold, set it to 100%.

    double limitBlue = maskThreshold * QuantumRange;

    int nRemoved = 0;
    CacheView * img_view = AcquireAuthenticCacheView (main_res, exception);
    MagickBooleanType okay = MagickTrue;
    ssize_t y;
    for (y = 0; y < main_res->rows; y++) {
      if (!okay) continue;

      VIEW_PIX_PTR *q = GetCacheViewAuthenticPixels(
        img_view,0,y,main_res->columns,1,exception);
      if (!q) {okay = MagickFalse; continue; }

      ssize_t x;
      for (x = 0; x < main_res->columns; x++) {
        SET_PIXEL_RED (main_res, 0, q);
        SET_PIXEL_GREEN (main_res, 0, q);
        double b = GET_PIXEL_BLUE (main_res, q);
        if (b > limitBlue) {
          SET_PIXEL_BLUE (main_res, QuantumRange, q);
          nRemoved++;
        }
        q += Inc_ViewPixPtr (main_res);
      }
      if (SyncCacheViewAuthenticPixels(img_view,exception) == MagickFalse)
        okay = MagickFalse;
    }
    if (!okay) return NULL;

    if (verbose) fprintf (stderr, "aggrSrch Mask: removed %g%%\n",
      nRemoved * 100.0 / (double)(main_res->columns * main_res->rows));

    img_view = DestroyCacheView (img_view);
  }

  return main_res;
}

#endif

pixmatch.c

/*
    Created 30-Nov-2015

    Updated:
      3-April-2018 for v7.0.7-28
*/


/* Takes two images: reference and source, any size, possibly with transparency.

   Returns image same size as source,
   with red and green channels as relative or absolute displacement map,
   and blue channel as RMSE score.

   The map points to pixels in the reference that centre on windows that match source window.

   Output pixels corresponding to fully-transparent source pixels have
   null displacement and zero score.
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <ctype.h>
#include "vsn_defines.h"

#include "chklist.h"

#include "compwind.h"
#include "match.h"

// If you want search windows clamped to the bounds of the reference image,
// as suggested in the PatchMatch paper, define CLAMP_SEARCH_WINDOW as 1.
//
// If you want my modification that avoids generating random offsets
// that would exceed the bounds,
// define CLAMP_SEARCH_WINDOW as 0.
//
#define CLAMP_SEARCH_WINDOW 0

typedef double pmValueT;

typedef enum {
  psToMatch, // Find a match for this pixel.
  psTrans,   // Pixel is transparent; don't try to match it.
  psMatched, // A good-enough match has been found.
} pixStateT;

// Valid state transitions:


typedef enum {
  dtAbsolute,
  dtRelative
} DispTypeT;

typedef struct {
  pixStateT
    State;

  ssize_t
    ref_x,
    ref_y;

  pmValueT
    score; // MSE (_not_ RMSE)

} PixT;


typedef struct {
  CompWindT
    CompWind;

  MatchT
    Match;

  MagickBooleanType
    do_verbose,
    AutoRepeat,
    WindowRad_IsPc,
    tanc,
    do_search,
    do_part,
    do_sweep,
    debug;

  double
    WindowRad;

  int
    WindowRadPix,
    MaxPartIter;

  DispTypeT
    dispType;

  char
    write_filename[MaxTextExtent];

  /* Calculated. */
  int
    sqDim;

  ssize_t
    refWidth,
    refHeight,
    srcWidth,
    srcHeight;

  PixT
    ** pix;

  RandomInfo
    *restrict random_info;

  Image
    *src_image;

  // Also: relative or absolute

} PixMatchT;


#include "compwind.inc"
#include "match.inc"

#include "writeframe.inc"




static void usage (void)
{
  printf ("Usage: -process 'pixmatch [OPTION]...'\n");
  printf ("Matches pixels from source in reference.\n");
  printf ("\n");
  printf ("  wr,  window_radius N        radius of search window, >= 0\n");
  printf ("  lsr, limit_search_radius N  limit radius to search, >= 0\n");
  printf ("                                default = 0 = no limit\n");
  printf ("  st,  similarity_threshold N\n");
  printf ("                              stop searching when RMSE <= N (eg 0.01)\n");
  printf ("                                default 0\n");
  printf ("  hc,  hom_chk X              homogeneity check, X is off or a small number\n");
  printf ("                                default 0.1\n");
  printf ("  e,   search_to_edges X      search for matches to image edges, on or off\n");
  printf ("                                default on\n");

  printf ("  s,   search X               X=none or entire or random or skip\n");
  printf ("                                default entire\n");
  printf ("  rs,  rand_searches N        number of random searches (eg 100)\n");
  printf ("                                default 0\n");
  printf ("  sn,  skip_num N             number of searches to skip in each direction\n");
  printf ("                                (eg 10)\n");
  printf ("                                default 0\n");
  printf ("  ref, refine X               whether to refine random and skip searches,\n");
  printf ("                                on or off\n");
  printf ("                                default on\n");

  printf ("  part, propagate_and_random_trial X\n");
  printf ("                              on or off\n");
  printf ("                                default on\n");
  printf ("  mpi,  max_part_iter N       maximum number of PART iterations, >= 1\n");
  printf ("                                0 = no maximum\n");
  printf ("                                default 10\n");
  printf ("  tanc, terminate_after_no_change X\n");
  printf ("                              on or off\n");
  printf ("                                default on\n");

  printf ("  swp, sweep X                on or off\n");
  printf ("                                default off\n");

  printf ("  dpt, displacement_type X    displacement type for input and output,\n");
  printf ("                                absolute or relative\n");
  printf ("                                default absolute\n");

  printf ("  ac,  auto_correct N         when RMSE score > N, attempt to correct (eg 0.02)\n");
  printf ("                                default 0 = no auto correction\n");

  //printf ("  a,   auto_repeat            if pixels changed but any unfilled, repeat\n");
  //printf ("  w,   write filename         write frames to files\n");
  printf ("  v,   verbose                write text information to stdout\n");
  printf ("\n"); 
}


static MagickBooleanType IsArg (char * pa, char * ShtOpt, char * LongOpt)
{
  if ((LocaleCompare(pa, ShtOpt)==0) || (LocaleCompare(pa, LongOpt)==0))
    return MagickTrue;

  return MagickFalse;
}


static inline MagickBooleanType EndsPc (const char *s)
{
  char c = *(s+strlen(s)-1);
  if (c == '%' || c == 'c')
    return MagickTrue;
  return MagickFalse;
}


static MagickBooleanType menu(
  const int argc,
  const char **argv,
  PixMatchT * pmh
)
// Returns MagickTrue if okay.
{
  int
    i;

  MagickBooleanType
    status;

  status = MagickTrue;

  pmh->do_verbose = MagickFalse;
  pmh->WindowRad = 1.0;
  pmh->WindowRad_IsPc = MagickFalse;
  pmh->WindowRadPix = 1;
  pmh->debug = MagickFalse;
  pmh->AutoRepeat = MagickFalse;
  pmh->MaxPartIter = 10;
  pmh->do_search = pmh->do_part = pmh->tanc = MagickTrue;
  pmh->do_sweep = MagickFalse;
  pmh->dispType = dtAbsolute;
  pmh->write_filename[0] = '\0';

  pmh->CompWind.HomChkOn = MagickTrue;
  pmh->CompWind.HomChk = 0.1;
  pmh->CompWind.nCompares = 0;

  SetDefaultMatch (&pmh->Match);
  pmh->Match.DoSubTransTest = MagickTrue;
  pmh->Match.AutoLs = MagickFalse;

  for (i=0; i < argc; i++) {
    char * pa = (char *)argv[i];

    if (IsArg (pa, "wr", "window_radius")==MagickTrue) {
      i++;
      if (!isdigit ((int)*argv[i]))
      {
        fprintf (stderr, "Non-numeric argument to 'window_radius'.\n");
        status = MagickFalse;
      }
      else {
        pmh->WindowRad = atof(argv[i]);
        pmh->WindowRad_IsPc = EndsPc (argv[i]);
        if (!pmh->WindowRad_IsPc)
          pmh->WindowRadPix = pmh->WindowRad + 0.5;
      }
      if (pmh->WindowRad < 0) {
        fprintf (stderr, "Bad 'window_radius' value.\n");
        status = MagickFalse;
      }
    } else if (IsArg (pa, "lsr", "limit_search_radius")==MagickTrue) {
      // FIXME: also allow percentage of smaller image dimension.
      i++;
      if (!isdigit ((int)*argv[i])) {
        fprintf (stderr, "Non-numeric argument to 'limit_search_radius'.\n");
        status = MagickFalse;
      } else {
        pmh->Match.LimSrchRad = atof(argv[i]);
        pmh->Match.LimSrchRad_IsPc = EndsPc (argv[i]);
        if (!pmh->Match.LimSrchRad_IsPc)
          pmh->Match.limSrchRadX = pmh->Match.limSrchRadY = pmh->Match.LimSrchRad + 0.5;
      }
      if (pmh->Match.LimSrchRad < 0) {
        fprintf (stderr, "Bad 'limit_search_radius' value.\n");
        status = MagickFalse;
      }
    } else if (IsArg (pa, "ref", "refine")==MagickTrue) {
      i++;
      if (LocaleCompare(argv[i], "on")==0)
        pmh->Match.Refine = MagickTrue;
      else if (LocaleCompare(argv[i], "off")==0)
        pmh->Match.Refine = MagickFalse;
      else {
        fprintf (stderr, "Invalid 'refine' [%s]\n", argv[i]);
        status = MagickFalse;
      }
    } else if (IsArg (pa, "dpt", "displacement_type")==MagickTrue) {
      i++;
      if (LocaleCompare(argv[i], "absolute")==0)
        pmh->dispType = dtAbsolute;
      else if (LocaleCompare(argv[i], "relative")==0)
        pmh->dispType = dtRelative;
      else {
        fprintf (stderr, "Invalid 'displacement_type' [%s]\n", argv[i]);
        status = MagickFalse;
      }
    } else if (IsArg (pa, "s", "search")==MagickTrue) {
      i++;
      if (LocaleCompare(argv[i], "entire")==0) {
        pmh->Match.searchWhat = swEntire;
        pmh->do_search = MagickTrue;
      } else if (LocaleCompare(argv[i], "random")==0) {
        pmh->Match.searchWhat = swRandom;
        pmh->do_search = MagickTrue;
      } else if (LocaleCompare(argv[i], "skip")==0) {
        pmh->Match.searchWhat = swSkip;
        pmh->do_search = MagickTrue;
      } else if (LocaleCompare(argv[i], "none")==0) {
        pmh->Match.searchWhat = swEntire;
        pmh->do_search = MagickFalse;
      } else {
        fprintf (stderr, "Invalid 'search' [%s]\n", argv[i]);
        status = MagickFalse;
      }
    } else if (IsArg (pa, "rs", "rand_searches")==MagickTrue) {
      i++;
      if (!isdigit ((int)*argv[i])) {
        fprintf (stderr, "Non-numeric argument to 'rand_searches'.\n");
        status = MagickFalse;
      } else {
        pmh->Match.RandSearchesF = atof (argv[i]);
        pmh->Match.RandSearches_IsPc = EndsPc (argv[i]);
        if (!pmh->Match.RandSearches_IsPc)
          pmh->Match.RandSearchesI = pmh->Match.RandSearchesF + 0.5;
      }
      if (pmh->Match.RandSearchesF <= 0) {
        fprintf (stderr, "Bad 'rand_searches' value.\n");
        status = MagickFalse;
      }
    } else if (IsArg (pa, "sn", "skip_num")==MagickTrue) {
      i++;
      if (!isdigit ((int)*argv[i])) {
        fprintf (stderr, "Non-numeric argument to 'skip_num'.\n");
        status = MagickFalse;
      } else {
        pmh->Match.SkipNumF = atof (argv[i]);
        pmh->Match.SkipNum_IsPc = EndsPc (argv[i]);
        if (!pmh->Match.SkipNum_IsPc)
          pmh->Match.SkipNumI = pmh->Match.SkipNumF + 0.5;
      }
      if (pmh->Match.SkipNumF <= 0) {
        fprintf (stderr, "Bad 'skip_num' value.\n");
        status = MagickFalse;
      }
    } else if (IsArg (pa, "hc", "hom_chk")==MagickTrue) {
      i++;
      if (LocaleCompare(argv[i], "off")==0) {
        pmh->CompWind.HomChkOn = MagickFalse;
      } else {
        pmh->CompWind.HomChkOn = MagickTrue;
        if (!isdigit ((int)*argv[i])) {
          fprintf (stderr, "'hom_chk' argument must be number or 'off'.\n");
          status = MagickFalse;
        } else {
          pmh->CompWind.HomChk = atof (argv[i]);
        }
      }
    } else if (IsArg (pa, "part", "propagate_and_random_trial")==MagickTrue) {
      i++;
      if (LocaleCompare(argv[i], "off")==0) {
        pmh->do_part = MagickFalse;
      } else if (LocaleCompare(argv[i], "on")==0) {
        pmh->do_part = MagickTrue;
      } else {
        fprintf (stderr, "'part' argument must be 'on' or 'off'.\n");
        status = MagickFalse;
      }
    } else if (IsArg (pa, "swp", "sweep")==MagickTrue) {
      i++;
      if (LocaleCompare(argv[i], "off")==0) {
        pmh->do_sweep = MagickFalse;
      } else if (LocaleCompare(argv[i], "on")==0) {
        pmh->do_sweep = MagickTrue;
      } else {
        fprintf (stderr, "'sweep' argument must be 'on' or 'off'.\n");
        status = MagickFalse;
      }
    } else if (IsArg (pa, "tanc", "terminate_after_no_change")==MagickTrue) {
      i++;
      if (LocaleCompare(argv[i], "off")==0) {
        pmh->tanc = MagickFalse;
      } else if (LocaleCompare(argv[i], "on")==0) {
        pmh->tanc = MagickTrue;
      } else {
        fprintf (stderr, "'tanc' argument must be 'on' or 'off'.\n");
        status = MagickFalse;
      }
    } else if (IsArg (pa, "e", "search_to_edges")==MagickTrue) {
      i++;
      if (LocaleCompare(argv[i], "on")==0)
        pmh->Match.SearchToEdges = MagickTrue;
      else if (LocaleCompare(argv[i], "off")==0)
        pmh->Match.SearchToEdges = MagickFalse;
      else {
        fprintf (stderr, "Invalid 'search_to_edges' [%s]\n", argv[i]);
        status = MagickFalse;
      }
    } else if (IsArg (pa, "a", "auto_repeat")==MagickTrue) {
      pmh->AutoRepeat = MagickTrue;
    } else if (IsArg (pa, "st", "similarity_threshold")==MagickTrue) {
      i++;
      if (!isdigit ((int)*argv[i])) {
        fprintf (stderr, "Non-numeric argument to 'similarity_threshold'.\n");
        status = MagickFalse;
      } else {
        pmh->Match.simThreshold = atof (argv[i]);
      }
    } else if (IsArg (pa, "ac", "auto_correct")==MagickTrue) {
      i++;
      if (!isdigit ((int)*argv[i])) {
        fprintf (stderr, "Non-numeric argument to 'auto_correct'.\n");
        status = MagickFalse;
      } else {
        pmh->Match.autoCorrectThreshold = atof (argv[i]);
      }
    } else if (IsArg (pa, "mpi", "max_part_iter")==MagickTrue) {
      i++;
      if (!isdigit ((int)*argv[i])) {
        fprintf (stderr, "Non-numeric argument to 'max_part_iter'.\n");
        status = MagickFalse;
      } else {
        pmh->MaxPartIter = atoi (argv[i]);
      }
    } else if (IsArg (pa, "w", "write")==MagickTrue) {
      i++;
      CopyMagickString (pmh->write_filename, argv[i], MaxTextExtent);
      if (!*pmh->write_filename) {
        fprintf (stderr, "Invalid 'write' [%s]\n", argv[i]);
        status = MagickFalse;
      }
    } else if (IsArg (pa, "v", "verbose")==MagickTrue) {
      pmh->do_verbose = MagickTrue;
    } else if (IsArg (pa, "d", "debug")==MagickTrue) {
      pmh->debug = MagickTrue;
    } else {
      fprintf (stderr, "pixmatch: ERROR: unknown option [%s]\n", pa);
      status = MagickFalse;
    }
  }

  pmh->Match.simThresholdSq = pmh->Match.simThreshold * pmh->Match.simThreshold;

  pmh->Match.autoCorrectThresholdSq = pmh->Match.autoCorrectThreshold * pmh->Match.autoCorrectThreshold;

  if (pmh->do_verbose) {
    fprintf (stderr, "pixmatch options:\n");

    fprintf (stderr, "  window_radius %g", pmh->WindowRad);
    if (pmh->WindowRad_IsPc) fprintf (stderr, "%c", '%');

    fprintf (stderr, "  limit_search_radius %g", pmh->Match.LimSrchRad);
    if (pmh->Match.LimSrchRad_IsPc) fprintf (stderr, "%c", '%');

    fprintf (stderr, "  similarity_threshold %g", pmh->Match.simThreshold);

    fprintf (stderr, "  hom_chk ");
    if (pmh->CompWind.HomChkOn) {
      fprintf (stderr, "%g", pmh->CompWind.HomChk);
    } else {
      fprintf (stderr, "off");
    }

    if (!pmh->Match.SearchToEdges) fprintf (stderr, "  search_to_edges off");


    fprintf (stderr, "\n  search ");
    if (pmh->do_search == MagickFalse) {
      fprintf (stderr, "none");
    } else {
      switch (pmh->Match.searchWhat) {
        case swEntire: fprintf (stderr, "entire"); break;
        case swSkip:
          fprintf (stderr, "skip  skip_num %g", pmh->Match.SkipNumF);
          if (pmh->Match.SkipNum_IsPc) fprintf (stderr, "%c", '%');
          break;
        case swRandom:
          fprintf (stderr, "random  rand_searches %g", pmh->Match.RandSearchesF);
          if (pmh->Match.RandSearches_IsPc) fprintf (stderr, "%c", '%');
          break;
        default: fprintf (stderr, "??");
      }
    }

    if (pmh->do_search == MagickTrue) {
      fprintf (stderr, "  refine %s", pmh->Match.Refine ? "on" : "off");

      if (pmh->Match.autoCorrectThreshold > 0)
        fprintf (stderr, "  auto_correct %g", pmh->Match.autoCorrectThreshold);
    }


    fprintf (stderr, "\n  propagate_and_random_trial %s", pmh->do_part ? "on" : "off");

    if (pmh->do_part) {
      fprintf (stderr, "  max_part_iter %i", pmh->MaxPartIter);

      fprintf (stderr, "  tanc %s", pmh->tanc ? "on" : "off");
    }

    fprintf (stderr, "\n  sweep %s", pmh->do_sweep ? "on" : "off");


    fprintf (stderr, "\n  displacement_type ");
    switch (pmh->dispType) {
      case dtAbsolute: fprintf (stderr, "absolute"); break;
      case dtRelative: fprintf (stderr, "relative"); break;
      default: fprintf (stderr, " ??");
    }

    if (pmh->AutoRepeat) fprintf (stderr, "  auto_repeat");

    if (pmh->debug) fprintf (stderr, "  debug");

    if (pmh->do_verbose) fprintf (stderr, "  verbose");

    fprintf (stderr, "\n");
  }

  if (status == MagickFalse)
    usage ();

  return (status);
}


static void InitRand (PixMatchT * pmh)
{
  pmh->random_info=AcquireRandomInfo();

  // There seems to be a problem: the first few values show coherency,
  // so skip over them.

  int i;
  for (i=0; i < 20; i++) {
    GetPseudoRandomValue(pmh->random_info);
  }
}

static void DeInitRand (PixMatchT * pmh)
{
  pmh->random_info=DestroyRandomInfo(pmh->random_info);
}

static MagickBooleanType Initialise (const Image *image,
  PixMatchT * pmh,
  ExceptionInfo *exception
)
{
  ssize_t
    x, y;

  MagickBooleanType
    status = MagickTrue;

  //CacheView
  //  *image_view;

  // Allocate memory

  pmh->pix = (PixT **) AcquireQuantumMemory(pmh->srcHeight, sizeof(*pmh->pix));
  if (pmh->pix == (PixT **) NULL) {
    return MagickFalse;
  }
  for (y = 0; y < pmh->srcHeight; y++) {
    pmh->pix[y] = (PixT *) AcquireQuantumMemory(pmh->srcWidth, sizeof(**pmh->pix));
    if (pmh->pix[y] == (PixT *) NULL) break;
  }
  if (y < pmh->srcHeight) {
    for (y--; y >= 0; y--) {
      if (pmh->pix[y] != (PixT *) NULL)
        pmh->pix[y] = (PixT *) RelinquishMagickMemory(pmh->pix[y]);
    }
    pmh->pix = (PixT **) RelinquishMagickMemory(pmh->pix);
    return MagickFalse;
  }

  // Populate values

//#if defined(MAGICKCORE_OPENMP_SUPPORT)
//    #pragma omp parallel for schedule(static,4) shared(status)
//      MAGICK_THREADS(image,image,image->rows,1)
//#endif

  for (y = 0; y < pmh->srcHeight; y++) {
    VIEW_PIX_PTR const
      *p;

    if (status == MagickFalse)
      continue;

    p=GetCacheViewVirtualPixels(pmh->CompWind.sub_view,0,y,image->columns,1,exception);
    if (p == (const VIEW_PIX_PTR *) NULL)
      status=MagickFalse;

    for (x = 0; x < pmh->srcWidth; x++) {

      PixT * pn = &(pmh->pix[y][x]);

      pn->State = (GET_PIXEL_ALPHA (image, p) <= 0) ? psTrans: psToMatch;
      pn->ref_x = GetPseudoRandomValue(pmh->random_info) * pmh->refWidth;
      pn->ref_y = GetPseudoRandomValue(pmh->random_info) * pmh->refHeight;
      // fprintf (stderr, "%li,%li ", pn->ref_x, pn->ref_y);
      pn->score = 123;
assert (pn->ref_x >= 0 && pn->ref_x < pmh->refWidth && pn->ref_y >= 0 && pn->ref_y < pmh->refHeight);

      p += Inc_ViewPixPtr (image);
    }
  }
  return (status);
}

static void deInitialise (PixMatchT * pmh)
{
  ssize_t
    y;

  for (y = 0; y < pmh->srcHeight; y++) {
    pmh->pix[y] = (PixT *) RelinquishMagickMemory(pmh->pix[y]);
  }
  pmh->pix = (PixT **) RelinquishMagickMemory(pmh->pix);
}


static void CalcScores (PixMatchT * pmh,
  ExceptionInfo *exception)
{
  ssize_t
    y;

  if (pmh->debug) fprintf (stderr, "CalcScores\n");

  for (y = 0; y < pmh->srcHeight; y++) {
    ssize_t
      x;

    for (x = 0; x < pmh->srcWidth; x++) {
      PixT * pn = &(pmh->pix[y][x]);
      if (pn->State == psToMatch) {
assert (pn->ref_x >= 0 && pn->ref_x < pmh->refWidth && pn->ref_y >= 0 && pn->ref_y < pmh->refHeight);
        pmh->CompWind.refX = pn->ref_x - pmh->WindowRadPix;
        pmh->CompWind.refY = pn->ref_y - pmh->WindowRadPix;
        pmh->CompWind.subX = x - pmh->WindowRadPix;
        pmh->CompWind.subY = y - pmh->WindowRadPix;

        pmValueT v = CompareWindow (&pmh->CompWind, exception);
        if (pmh->debug) fprintf (stderr, "%li,%li v=%g ", x, y, v);
        pn->score = v;
        if (v < pmh->Match.simThresholdSq) pn->State = psMatched;
      }
    }
    if (pmh->debug) fprintf (stderr, "\n");
  }
}


static MagickBooleanType inline WithinRefX (PixMatchT * pmh, ssize_t x)
{
  return (x >= 0 && x < pmh->refWidth);
}

static MagickBooleanType inline WithinRefY (PixMatchT * pmh, ssize_t y)
{
  return (y >= 0 && y < pmh->refHeight);
}

static MagickBooleanType inline WithinSrcX (PixMatchT * pmh, ssize_t x)
{
  return (x >= 0 && x < pmh->srcWidth);
}

static MagickBooleanType inline WithinSrcY (PixMatchT * pmh, ssize_t y)
{
  return (y >= 0 && y < pmh->srcHeight);
}

static MagickBooleanType WhichProp (PixMatchT * pmh,
  ssize_t x,
  ssize_t y,
  int dxy,
  int * dx,
  int * dy)
// Enter with dxy=-1 for upper/left,
// or dxy=+1 for lower/right.
// If either has score lower than this,
// returns MagickTrue and sets dx, dy.
// Otherwise returns MagickFalse.
// (Could also do diagonal.))
{
  PixT *pn = &(pmh->pix[y][x]);
  pmValueT v = pn->score;

assert (pn->ref_x >= 0 && pn->ref_x < pmh->refWidth && pn->ref_y >= 0 && pn->ref_y < pmh->refHeight);
  PixT *pno;

  *dx = *dy = 0;

  if (pmh->debug) fprintf (stderr, "WhichProp %li,%li, %i\n", x, y, dxy);

  if (WithinSrcY(pmh, y+dxy)) {
    pno = &(pmh->pix[y+dxy][x]);
    if (v > pno->score && WithinRefY (pmh, pno->ref_y - dxy) )
    {
      *dy = dxy;
      v = pno->score;
    }
  }
  if (WithinSrcX(pmh, x+dxy)) {
    pno = &(pmh->pix[y][x+dxy]);
    if (v > pno->score && WithinRefX (pmh, pno->ref_x - dxy) )
    {
      *dy = 0;
      *dx = dxy;
      v = pno->score;
    }
  }

  if (pmh->debug) fprintf (stderr, "  dx,dy=%i,%i, v=%g\n", *dx, *dy, v);

  return (*dx != 0 || *dy != 0);
}

static int Propagate (PixMatchT * pmh,
  ssize_t x,
  ssize_t y,
  int dxy,
  ExceptionInfo *exception)
// Returns 1 if result changed; otherwise 0.
{
  int dx, dy;

  PixT * pn = &(pmh->pix[y][x]);
assert (pn->ref_x >= 0 && pn->ref_x < pmh->refWidth && pn->ref_y >= 0 && pn->ref_y < pmh->refHeight);

  int nChanges = 0;

  if (pmh->debug) fprintf (stderr, "prop %li,%li: v=%g ", x, y, pn->score);
  if (WhichProp (pmh, x, y, dxy, &dx, &dy)) {
    PixT * pno = &(pmh->pix[y+dy][x+dx]);
    ssize_t candX = pno->ref_x - dx;
    ssize_t candY = pno->ref_y - dy;
    if (pmh->debug) fprintf (stderr, "Cand %li,%li ", candX, candY);

    // Now try match.

    pmh->CompWind.subX = x - pmh->WindowRadPix;
    pmh->CompWind.subY = y - pmh->WindowRadPix;

    ssize_t limX = pmh->Match.limSrchRadX;
    ssize_t limY = pmh->Match.limSrchRadY;
    if (limX == 0) limX = 3;
    if (limY == 0) limY = 3;
    RangeT rng;

    CalcMatchRange (candX, candY, limX, limY, &pmh->Match, &rng);

    for (pmh->CompWind.refY = rng.i0; pmh->CompWind.refY < rng.i1; pmh->CompWind.refY++) {
      for (pmh->CompWind.refX = rng.j0; pmh->CompWind.refX < rng.j1; pmh->CompWind.refX++) {

        pmValueT v = CompareWindow (&pmh->CompWind, exception);
        if (pmh->debug) fprintf (stderr, "v=%g ", v);
        if (pn->score > v) {
          if (pmh->debug) fprintf (stderr, "better ");
          pn->score = v;
          pn->ref_x = pmh->CompWind.refX + pmh->WindowRadPix;
          pn->ref_y = pmh->CompWind.refY + pmh->WindowRadPix;
assert (pn->ref_x >= 0 && pn->ref_x < pmh->refWidth && pn->ref_y >= 0 && pn->ref_y < pmh->refHeight);
          if (v < pmh->Match.simThresholdSq) {
            pn->State = psMatched;
            break;
          }
          nChanges = 1;
        }
        if (pn->State == psMatched) break;
      }
    }
  }
  if (pmh->debug) fprintf (stderr, "\n");
  return nChanges;
}


static int RandTrial (PixMatchT * pmh,
  ssize_t x,
  ssize_t y,
  ExceptionInfo *exception)
// Returns 1 if result changed; otherwise 0.
{
  #define ALPHA (0.5)

  pmValueT fact = 1.0;

  ssize_t maxRad = pmh->refWidth;
  if (maxRad < pmh->refHeight) maxRad = pmh->refHeight;

  pmValueT rad = maxRad * fact;

  pmh->CompWind.subX = x - pmh->WindowRadPix;
  pmh->CompWind.subY = y - pmh->WindowRadPix;

  PixT * pn = &(pmh->pix[y][x]);

  int changes = 0;

  do {
    ssize_t tx, ty;

#if CLAMP_SEARCH_WINDOW==1
    tx = pn->ref_x + rad * (GetPseudoRandomValue(pmh->random_info)*2-1);
    ty = pn->ref_y + rad * (GetPseudoRandomValue(pmh->random_info)*2-1);

    if (tx < 0) tx = 0;
    if (ty < 0) ty = 0;
    if (tx >= pmh->refWidth)  tx = pmh->refWidth-1;
    if (ty >= pmh->refHeight) ty = pmh->refHeight-1;
#else
    do {
      tx = pn->ref_x + rad * (GetPseudoRandomValue(pmh->random_info)*2-1);
    } while (tx < 0 || tx >= pmh->refWidth);

    do {
      ty = pn->ref_y + rad * (GetPseudoRandomValue(pmh->random_info)*2-1);
    } while (ty < 0 || ty >= pmh->refHeight);
#endif

    if (pmh->debug) fprintf (stderr, "trial %li,%li ", tx, ty);

    pmh->CompWind.refX = tx - pmh->WindowRadPix;
    pmh->CompWind.refY = ty - pmh->WindowRadPix;

    pmValueT v = CompareWindow (&pmh->CompWind, exception);
    //if (pmh->debug) fprintf (stderr, "v=%g ", v);
    if (pn->score > v) {
      if (pmh->debug) fprintf (stderr, "better %li,%li,%g=>%li,%li,%g ", x, y, pn->score, tx, ty, v);
      pn->score = v;
      pn->ref_x = tx;
      pn->ref_y = ty;

assert (pn->ref_x >= 0 && pn->ref_x < pmh->refWidth && pn->ref_y >= 0 && pn->ref_y < pmh->refHeight);

      if (v < pmh->Match.simThresholdSq) pn->State = psMatched;
      changes = 1;
    }

    fact *= ALPHA;
    rad = maxRad * fact;
  } while (rad > 1.0 && pn->State != psMatched);

  return (changes);
}

static int OnePart (PixMatchT * pmh,
  ExceptionInfo *exception)
// Returns the number of changes made.
{
  ssize_t
    y;

  int nChanges = 0;
  int nToMatch = 0;

  for (y = 0; y < pmh->srcHeight; y++) {
    ssize_t
      x;

    for (x = 0; x < pmh->srcWidth; x++) {
      PixT * pn = &(pmh->pix[y][x]);
      if (pn->State == psToMatch)
        nChanges += Propagate (pmh, x, y, -1, exception);
      if (pn->State == psToMatch)
        nChanges += RandTrial (pmh, x, y, exception);
    }
  }

  for (y = pmh->srcHeight-1; y >= 0; y--) {
    ssize_t
      x;

    for (x = pmh->srcWidth-1; x >= 0; x--) {
      PixT * pn = &(pmh->pix[y][x]);
      if (pn->State == psToMatch)
        nChanges += Propagate (pmh, x, y, +1, exception);
      if (pn->State == psToMatch)
        nChanges += RandTrial (pmh, x, y, exception);
      if (pn->State == psToMatch) nToMatch++;
    }
  }

  if (pmh->do_verbose) fprintf (stderr, "OnePart: nChanges=%i nToMatch=%i\n", nChanges, nToMatch);

  return nChanges;
}


static int Sweep (PixMatchT * pmh,
  ExceptionInfo *exception)
// Returns the number of changes made.
{
  ssize_t
    y;

  RangeT
    rng;

  CalcMatchRange (0, 0, 0, 0, &pmh->Match, &rng);

  int nChanges = 0;

  ssize_t
    besti=0, bestj=0;

  double v;

  CompWindT * cmpwin = &pmh->CompWind;

  for (y = 0; y < pmh->srcHeight; y++) {
    ssize_t
      x;

    for (x = 0; x < pmh->srcWidth; x++) {
      PixT * pn = &(pmh->pix[y][x]);
      if (pn->State == psToMatch) {

        double bestScore = pn->score;

        cmpwin->subX = x - pmh->Match.matchRadX;
        cmpwin->subY = y - pmh->Match.matchRadY;

        for (cmpwin->refY = rng.i0; cmpwin->refY < rng.i1; cmpwin->refY++) {
          for (cmpwin->refX = rng.j0; cmpwin->refX < rng.j1; cmpwin->refX++) {
            v = CompareWindow (cmpwin, exception);
            if (bestScore > v) {
              bestScore = v;
              besti = cmpwin->refY;
              bestj = cmpwin->refX;
              if (bestScore <= pmh->Match.simThresholdSq) break;
            }
          }
          if (bestScore <= pmh->Match.simThresholdSq) break;
        }

        if (pn->score > bestScore) {
          pn->ref_x = bestj + pmh->Match.matchRadX;
          pn->ref_y = besti + pmh->Match.matchRadY;
          pn->score = bestScore;
          nChanges++;
        }
      }

    }  // for x
  } // for y

  if (pmh->do_verbose) fprintf (stderr, "Sweep: nChanges=%i\n", nChanges);

  return nChanges;
}


static Image *MkDispImage (
  PixMatchT * pmh,
  ExceptionInfo *exception)
// Make an image from the pixel data.
{
  Image
    *new_image;

  MagickBooleanType
    status = MagickTrue;

  CacheView
    *new_view;

  ssize_t
    y;

  const long double
    factX = QuantumRange / (long double)(pmh->refWidth-1),
    factY = QuantumRange / (long double)(pmh->refHeight-1);

  if (pmh->debug) fprintf (stderr, "MkDispImage\n");

  // Clone the image, same size, copied pixels:
  //
  new_image=CloneImage(pmh->src_image, 0, 0, MagickTrue, exception);
  if (new_image == (Image *) NULL)
    return(new_image);

  if (SetNoPalette (new_image, exception) == MagickFalse)
    return (Image *)NULL;

  new_view = AcquireAuthenticCacheView (new_image, exception);

  chkentry("mdi",&new_image);

#if defined(MAGICKCORE_OPENMP_SUPPORT)
    #pragma omp parallel for schedule(static,4) shared(status) \
      MAGICK_THREADS(new_image,new_image,new_image->rows,1)
#endif
  for (y = 0; y < pmh->srcHeight; y++) {
    VIEW_PIX_PTR
      *p;

    ssize_t
      x;

    p = GetCacheViewAuthenticPixels(new_view,0,y,new_image->columns,1,exception);
    if (p == (const VIEW_PIX_PTR *) NULL) status = MagickFalse;

    if (status == MagickFalse)
      continue;

    for (x = 0; x < pmh->srcWidth; x++) {

      // FIXME: plus half if not HDRI.
      PixT * pn = &(pmh->pix[y][x]);
assert (pn->ref_x >= 0 && pn->ref_x < pmh->refWidth && pn->ref_y >= 0 && pn->ref_y < pmh->refHeight);
      if (pmh->dispType == dtAbsolute) {
        SET_PIXEL_RED   (new_image, pn->ref_x * factX, p);
        SET_PIXEL_GREEN (new_image, pn->ref_y * factY, p);
      } else {
        SET_PIXEL_RED   (new_image, ((pn->ref_x - x) / (pmValueT)(2*pmh->refWidth) + 0.5) * QuantumRange, p);
        SET_PIXEL_GREEN (new_image, ((pn->ref_y - y) / (pmValueT)(2*pmh->refHeight) + 0.5) * QuantumRange, p);
      }
      SET_PIXEL_BLUE  (new_image, sqrt(pn->score) * QuantumRange, p);
      SET_PIXEL_ALPHA (new_image, (pn->score > 4) ? 0 : QuantumRange, p);

      p += Inc_ViewPixPtr (new_image);
    }
  }

  if (SyncCacheViewAuthenticPixels (new_view,exception) == MagickFalse)
    return (Image *)NULL;

  new_view=DestroyCacheView (new_view);

  chkentry("mdi2",&new_image);

  return (new_image);
}


static MagickBooleanType ReadPrevResult (
  PixMatchT * pmh,
  Image * prevResult,
  ExceptionInfo *exception)
{
  CacheView
    *view;

  MagickBooleanType
    status = MagickTrue;

  ssize_t
    y;

  const long double
    factX = QuantumRange / (long double)(pmh->refWidth-1),
    factY = QuantumRange / (long double)(pmh->refHeight-1);

  if (pmh->do_verbose) fprintf (stderr, "ReadPrevResult\n");

  if (SetNoPalette (prevResult, exception) == MagickFalse)
    return MagickFalse;

  if (prevResult->rows != pmh->srcHeight || prevResult->columns != pmh->srcWidth) {
    fprintf (stderr, "Sizes of source and previous result do not match.\n");
    return MagickFalse;
  }

  view = AcquireVirtualCacheView (prevResult, exception);

#if defined(MAGICKCORE_OPENMP_SUPPORT)
    #pragma omp parallel for schedule(static,4) shared(status) \
      MAGICK_THREADS(prevResult,prevResult,prevResult->rows,1)
#endif
  for (y = 0; y < pmh->srcHeight; y++) {
    const VIEW_PIX_PTR
      *p;

    ssize_t
      x;

    p = GetCacheViewVirtualPixels(view,0,y,prevResult->columns,1,exception);
    if (p == (const VIEW_PIX_PTR *) NULL) status = MagickFalse;

    if (status == MagickFalse)
      continue;

    for (x = 0; x < pmh->srcWidth; x++) {
      PixT * pn = &(pmh->pix[y][x]);

      if (pmh->dispType == dtAbsolute) {
        pn->ref_x = GET_PIXEL_RED   (prevResult, p) / factX;
        pn->ref_y = GET_PIXEL_GREEN (prevResult, p) / factY;
      } else {
        pn->ref_x = (GET_PIXEL_RED   (prevResult, p) / QuantumRange - 0.5)
                    * 2*pmh->refWidth;
        pn->ref_y = (GET_PIXEL_GREEN (prevResult, p) / QuantumRange - 0.5)
                    * 2*pmh->refHeight;
      }

      // The read image may have been resized, maybe with ringing,
      // possibly causing values to go out of range,
      // so we need to clamp.

      if (pn->ref_x < 0) pn->ref_x = 0;
      else if (pn->ref_x >= pmh->refWidth) pn->ref_x = pmh->refWidth-1;

      if (pn->ref_y < 0) pn->ref_y = 0;
      else if (pn->ref_y >= pmh->refHeight) pn->ref_y = pmh->refHeight-1;

assert (pn->ref_x >= 0 && pn->ref_x < pmh->refWidth && pn->ref_y >= 0 && pn->ref_y < pmh->refHeight);

      // The score may have been upscaled, so might not be accurate.

      pn->score = GET_PIXEL_BLUE (prevResult, p) / QuantumRange;
      pn->score *= pn->score;
      pn->State = psToMatch;   // Until we know better.
      p += Inc_ViewPixPtr (prevResult);
    }
  }

  view=DestroyCacheView (view);

  return MagickTrue;
}

static void IterParts (PixMatchT * pmh,
  ExceptionInfo *exception)
// Returns the number of changes made.
{
  int nChanges = 0;
  int nIter = 0;

  for (;;) {
    nChanges = OnePart (pmh, exception);

    if (*pmh->write_filename) {
      Image *tmp_image = MkDispImage (pmh, exception);
      WriteFrame (pmh->write_filename, tmp_image, nIter, exception);
      DestroyImage (tmp_image);
    }

    if (nChanges == 0 && pmh->tanc) break;
    nIter++;
    if (pmh->MaxPartIter > 0 && nIter >= pmh->MaxPartIter) break;
  }

  if (pmh->do_verbose) fprintf (stderr, "IterParts: nIter=%i\n", nIter);

  // FIXME: Also terminate if all pixels are state=matched
}


#if 0
static void DumpPixels (PixMatchT * pmh)
{
  ssize_t
    x, y;

  fprintf (stderr, "DumpPixels\n");

  for (y = 0; y < pmh->srcHeight; y++) {
    for (x = 0; x < pmh->srcWidth; x++) {
      PixT * pn = &(pmh->pix[y][x]);
      char cState;
      switch (pn->State) {
        case psToMatch: cState='m'; break;
        case psTrans:   cState='t'; break;
        case psMatched: cState='d'; break;
        default: cState = '?';
      }
      fprintf (stderr, "%li,%li %c %li,%li %g\n",
        x, y, cState,pn->ref_x, pn->ref_y, pn->score );
    }
  }
}
#endif


static void SearchPixels (
  PixMatchT * pmh,
  MagickBooleanType UsePrev,
  ExceptionInfo *exception)
{
  ssize_t
    y;

  //printf ("SearchPixels %i\n", (int)UsePrev);

  ssize_t
    nChanges = 0;

  if (UsePrev) {
    for (y = 0; y < pmh->srcHeight; y++) {
      ssize_t
        x;

      for (x = 0; x < pmh->srcWidth; x++) {
        PixT * pn = &(pmh->pix[y][x]);

        if (pn->State != psMatched) {
          // We could attempt propagation here.

          Match (x, y, pn->ref_x, pn->ref_y,
            &pmh->Match, &pmh->CompWind, pmh->random_info, exception);

          if (pn->score > pmh->Match.bestScore) {
            pn->ref_x = pmh->Match.bestX;
            pn->ref_y = pmh->Match.bestY;
assert (pn->ref_x >= 0 && pn->ref_x < pmh->refWidth && pn->ref_y >= 0 && pn->ref_y < pmh->refHeight);
            pn->score = pmh->Match.bestScore;
            nChanges++;
            if (pn->score < pmh->Match.simThresholdSq) pn->State = psMatched;
          }
        }
      }
    }
  } else {
    for (y = 0; y < pmh->srcHeight; y++) {
      ssize_t
        x;

      for (x = 0; x < pmh->srcWidth; x++) {
        PixT * pn = &(pmh->pix[y][x]);
        if (pn->State != psMatched) {
          Match (x, y, x, y,
            &pmh->Match, &pmh->CompWind, pmh->random_info, exception);
          pn->ref_x = pmh->Match.bestX;
          pn->ref_y = pmh->Match.bestY;
assert (pn->ref_x >= 0 && pn->ref_x < pmh->refWidth && pn->ref_y >= 0 && pn->ref_y < pmh->refHeight);
          pn->score = pmh->Match.bestScore;
          nChanges++;
          if (pn->score < pmh->Match.simThresholdSq) pn->State = psMatched;
        }
      }
    }
  }
  if (pmh->do_verbose) fprintf (stderr, "Search: nChanges=%li\n", nChanges);
}


// The next function takes two or three images, and returns one image.
//
static Image *pixmatch (
  PixMatchT * pmh,
  Image *ref_image,
  Image *src_image,
  Image *prev_image,
  ExceptionInfo *exception)
{
  Image
    *new_image;

  MagickBooleanType
    UsePrev = MagickFalse;

  if (ref_image->debug != MagickFalse)
    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",ref_image->filename);

  pmh->refWidth  = ref_image->columns;
  pmh->refHeight = ref_image->rows;
  pmh->srcWidth  = src_image->columns;
  pmh->srcHeight = src_image->rows;

  if (pmh->do_verbose) {
    fprintf (stderr, "---------------------------------------\npixmatch: ref %lix%li  src %lix%li\n",
      pmh->refWidth, pmh->refHeight, pmh->srcWidth, pmh->srcHeight);
  }

  pmh->CompWind.ref_image = ref_image;
  pmh->CompWind.sub_image = src_image;
  pmh->CompWind.ref_view = AcquireVirtualCacheView (ref_image,exception);
  pmh->CompWind.sub_view = AcquireVirtualCacheView (src_image,exception);

  pmh->src_image = src_image;

  if (Initialise (src_image, pmh, exception) == MagickFalse)
    return (Image *) NULL;

  if (prev_image != (Image *) NULL) {
    MagickBooleanType r = ReadPrevResult (pmh, prev_image, exception);
    if (r == MagickFalse) return (Image *) NULL;
    UsePrev = MagickTrue;
  }

  pmh->CompWind.win_wi = pmh->WindowRadPix*2+1;
  pmh->CompWind.win_ht = pmh->WindowRadPix*2+1;
  pmh->CompWind.WorstCompare = 9e11;
  pmh->CompWind.AllowEquCoord = MagickTrue;

  pmh->Match.ref_rows = ref_image->rows;
  pmh->Match.ref_columns = ref_image->columns;
  pmh->Match.matchRadX = pmh->WindowRadPix;
  pmh->Match.matchRadY = pmh->WindowRadPix;

  pmh->Match.AutoLs = MagickFalse;
  pmh->Match.DissimOn = MagickFalse;

  ssize_t nTotalCompares = 0;

  pmh->CompWind.nCompares = 0;
  CalcScores (pmh, exception);
  if (pmh->do_verbose) fprintf (stderr, "CalcScores: nCompares=%li\n", pmh->CompWind.nCompares);
  nTotalCompares += pmh->CompWind.nCompares;

  if (pmh->do_search) {
    pmh->CompWind.nCompares = 0;
    SearchPixels (pmh, UsePrev, exception);
    if (pmh->do_verbose) fprintf (stderr, "Search: nCompares=%li\n", pmh->CompWind.nCompares);
    nTotalCompares += pmh->CompWind.nCompares;
  }

  //if (pmh->debug) DumpPixels (pmh);

  if (pmh->do_part) {
    pmh->CompWind.nCompares = 0;
    IterParts (pmh, exception);
    if (pmh->do_verbose) fprintf (stderr, "IterParts: nCompares=%li\n", pmh->CompWind.nCompares);
    nTotalCompares += pmh->CompWind.nCompares;
  }

  if (pmh->do_sweep) {
    pmh->CompWind.nCompares = 0;
    Sweep (pmh, exception);
    if (pmh->do_verbose) fprintf (stderr, "Sweep: nCompares=%li\n", pmh->CompWind.nCompares);
    nTotalCompares += pmh->CompWind.nCompares;
  }

  if (pmh->do_verbose) fprintf (stderr, "nTotalCompares=%li\n", nTotalCompares);

  pmh->CompWind.sub_view=DestroyCacheView (pmh->CompWind.sub_view);
  pmh->CompWind.ref_view=DestroyCacheView (pmh->CompWind.ref_view);

  new_image = MkDispImage (pmh, exception);
  if (new_image == (Image *) NULL) {
    fprintf (stderr, "MkDispImage failed\n");
    return (new_image);
  }

  if (pmh->debug) fprintf (stderr, "deInitialise\n");
  deInitialise (pmh);

  chkentry("be",&new_image);

  return (new_image);
}


ModuleExport size_t pixmatchImage (
  Image **images,
  const int argc,
  const char **argv,
  ExceptionInfo *exception)
{
  Image
    *image,
    *ref_image,
    *src_image,
    *prev_image,
    *new_image;

  MagickBooleanType
    status;

  PixMatchT
    pm;

  int
    ListLen;

  (void) argc;
  (void) argv;

  assert(images != (Image **) NULL);
  assert(*images != (Image *) NULL);
  assert((*images)->signature == MAGICK_CORE_SIG);

  status = menu (argc, argv, &pm);
  if (status == MagickFalse)
    return (-1);

  InitRand (&pm);

  chklist("chkL A", images);

  ListLen = (int)GetImageListLength(*images);
  if (ListLen < 2 || ListLen > 3) {
    fprintf (stderr, "pixmatch needs 2 or 3 images\n");
    return (-1);
  }

  image=(*images);
  ref_image = image;
  image=GetNextImageInList(image);
  src_image = image;
  if (ListLen == 3) {
    image=GetNextImageInList(image);
    prev_image = image;
  } else {
    prev_image = (Image *)NULL;
  }

  new_image = pixmatch(&pm, ref_image, src_image, prev_image, exception);
  if (new_image == (Image *) NULL) {
    fprintf (stderr, "pixmatch failed.\n");
    status = MagickFalse;
  }

  DeleteImageFromList (&src_image);
  if (ListLen == 3) {
    DeleteImageFromList (&prev_image);
  }
  image = ref_image;

  if (status == MagickTrue) {
    ReplaceImageInList(&image,new_image);
  }

  chklist("after rem", &image);

  *images=GetFirstImageInList(image);
  chklist("after rem list", images);

  DeInitRand (&pm);

  if (status == MagickFalse) return -1;

  return(MagickImageFilterSignature);
}

pause.c

#include <stdio.h>
#include <stdlib.h>
#include "vsn_defines.h"


ModuleExport size_t pauseImage(Image **images,const int argc,
  const char **argv,ExceptionInfo *exception)
{
  printf ("Press 'return' to continue.\n");

  fgetc (stdin);

  return(MagickImageFilterSignature);
}

shell.c

#include <stdio.h>
#include <stdlib.h>
#include "vsn_defines.h"


ModuleExport size_t shellImage(Image **images,const int argc,
  const char **argv,ExceptionInfo *exception)
{
  char * sCmd = NULL;

  int i;
  for (i=0; i < argc; i++) {
    if (i) {
      if (!ConcatenateString (&sCmd, " ")) return -1;
    }

    if (!ConcatenateString (&sCmd, argv[i])) return -1;
  }

  MagickBooleanType verbose =
    (GetImageArtifact (*images,"verbose") != (const char *) NULL);

  if (verbose)
    fprintf (stderr, "shell: sCmd = [%s]\n", sCmd);

  if (sCmd && *sCmd) {
    int r = system (sCmd);
    if (r==-1) {
      fprintf (stderr, "shell: Failed to create shell process.\n");
    } else {
      if (verbose) {
        fprintf (stderr, "shell: status %i.\n", r);
      }
    }

    sCmd = DestroyString (sCmd);
  }

  return(MagickImageFilterSignature);
}

sphaldcl.c

/* Updated:
     27-October-2017 added version; introduced QoverLev2m1.
     3-April-2018 for v7.0.7-28
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <ctype.h>
#include "vsn_defines.h"

#define DEBUG 0

#define VERSION "sphaldcl v1.0  Copyright (c) 2017 Alan Gibson"

typedef enum {
  mIdentity,
  mShepards,
  mVoronoi
} methodT;

typedef struct {
  int index;
  double distance;
} distanceT;

typedef struct {
  MagickBooleanType
    do_verbose;

  methodT
    method;

  int
    haldLevel,
    nearest;

  double
    powParam;

  distanceT
    *distances;
} sphaldclT;


static void usage (void)
{
  printf ("Usage: -process 'sphaldcl [OPTION]...'\n");
  printf ("Replace last image (Nx2) with hald clut.\n");
  printf ("\n");
  printf ("  h, haldLevel N      hald level [8]\n");
  printf ("  m, method String    one of: identity, shepards, voronoi [identity]\n");
  printf ("  p, power N          power [2]\n");
  printf ("  n, nearest N        use just nearest (0=all) [0]\n");
  printf ("  v, verbose          write text information to stdout\n");
  printf ("      version          write version information to stdout\n");
  printf ("\n");
}


static MagickBooleanType IsArg (char * pa, char * ShtOpt, char * LongOpt)
{
  if ((LocaleCompare(pa, ShtOpt)==0) || (LocaleCompare(pa, LongOpt)==0))
    return MagickTrue;

  return MagickFalse;
}

#define NEXTARG \
  if (i < argc-1) i++; \
  else { \
    fprintf (stderr, "sphaldcl: ERROR: [%s] needs argument\n", pa); \
    status = MagickFalse; \
  }

static MagickBooleanType menu(
  const int argc,
  const char **argv,
  sphaldclT * pat
)
// Returns MagickTrue if okay.
{
  int
    i;

  MagickBooleanType
    status;

  status = MagickTrue;

  pat->method = mIdentity;
  pat->haldLevel = 8;
  pat->powParam = 2;
  pat->nearest = 0;
  pat->do_verbose = MagickFalse;
  pat->distances = NULL;

  for (i=0; i < argc; i++) {
    char * pa = (char *)argv[i];

    if (IsArg (pa, "h", "haldlevel")==MagickTrue) {
      NEXTARG;
      pat->haldLevel = atoi(argv[i]);
    } else if (IsArg (pa, "p", "power")==MagickTrue) {
      NEXTARG;
      pat->powParam = atof(argv[i]);
    } else if (IsArg (pa, "m", "method")==MagickTrue) {
      NEXTARG;
      if (LocaleCompare(argv[i], "identity")==0) pat->method = mIdentity;
      else if (LocaleCompare(argv[i], "shepards")==0) pat->method = mShepards;
      else if (LocaleCompare(argv[i], "voronoi")==0) pat->method = mVoronoi;
      else {
        fprintf (stderr, "sphaldcl: ERROR: unknown method [%s]\n", argv[i]);
        status = MagickFalse;
      }
    } else if (IsArg (pa, "n", "nearest")==MagickTrue) {
      NEXTARG;
      pat->nearest = atoi(argv[i]);
    } else if (IsArg (pa, "v", "verbose")==MagickTrue) {
      pat->do_verbose = MagickTrue;
    } else if (IsArg (pa, "version", "version")==MagickTrue) {
      fprintf (stdout, "%s\n", VERSION);
    } else {
      fprintf (stderr, "sphaldcl: ERROR: unknown option [%s]\n", pa);
      status = MagickFalse;
    }
  }

  if (pat->do_verbose) {
    fprintf (stderr, "sphaldcl options:");

    if (pat->do_verbose) fprintf (stderr, "  verbose");

    fprintf (stderr, "  haldLevel %i", pat->haldLevel);
    fprintf (stderr, "  method ");
    switch (pat->method) {
      case mIdentity:
        fprintf (stderr, "identity");
        break;
      case mShepards:
        fprintf (stderr, "shepards");
        fprintf (stderr, "  power %g", pat->powParam);
        break;
      case mVoronoi:
        fprintf (stderr, "voronoi");
        break;
    }
    fprintf (stderr, "  nearest %i", pat->nearest);

    fprintf (stderr, "\n");
  }

  if (status == MagickFalse)
    usage ();

  return (status);
}

static double inline colDist (
  Image *image,
  const VIEW_PIX_PTR *p0,
  const VIEW_PIX_PTR *p1
)
// Returns distance between colours, scale 0.0 to 1.0.
{
  double r = GET_PIXEL_RED(image,p0)   - GET_PIXEL_RED(image,p1);
  double g = GET_PIXEL_GREEN(image,p0) - GET_PIXEL_GREEN(image,p1);
  double b = GET_PIXEL_BLUE(image,p0)  - GET_PIXEL_BLUE(image,p1);

  return sqrt ((r*r + g*g + b*b) / 3.0) / QuantumRange;
}

static double inline colDistRel (
  Image *image,
  const VIEW_PIX_PTR *p0,
  const VIEW_PIX_PTR *p1
)
// Returns relative distance between colours.
{
  double r = GET_PIXEL_RED(image,p0)   - GET_PIXEL_RED(image,p1);
  double g = GET_PIXEL_GREEN(image,p0) - GET_PIXEL_GREEN(image,p1);
  double b = GET_PIXEL_BLUE(image,p0)  - GET_PIXEL_BLUE(image,p1);

  return (r*r + g*g + b*b);
}

static void inline calcWeight (
  sphaldclT * pat,
  Image *image,
  const VIEW_PIX_PTR *p0,
  const VIEW_PIX_PTR *p1,
  double * weight,
  MagickBooleanType *DistIsZero
)
{
  double dist = colDist (image, p0, p1);
//printf ("dist=%g\n", dist);
  *DistIsZero = (dist < 1e-8);
  if (*DistIsZero) *weight = 0;
  else *weight = 1.0 / pow (dist, pat->powParam);
}


static void inline calcShepard (
  sphaldclT * pat,
  Image *image,
  const VIEW_PIX_PTR *p0,  // row of input RGB
  const VIEW_PIX_PTR *p1,  // row of differences
  ssize_t inRed,  // 0 to QuantumRange
  ssize_t inGreen,
  ssize_t inBlue,
  // Ouputs are differences, to be added to identity.
  MagickRealType * outRed,
  MagickRealType * outGreen,
  MagickRealType * outBlue
)
// Voronoi: return the colour of the sample nearest input coords.
{
  VIEW_PIX_PTR pIn;

  const VIEW_PIX_PTR *ppOut;

  SET_PIXEL_RED   (image, inRed, &pIn);
  SET_PIXEL_GREEN (image, inGreen, &pIn);
  SET_PIXEL_BLUE  (image, inBlue, &pIn);

  double weight;
  MagickBooleanType DistIsZero;

  int i;

  double sumWeight = 0.0, sumR = 0.0, sumG = 0.0, sumB = 0.0;

  for (i=0; i < (ssize_t) image->columns; i++) {

    calcWeight (
      pat, image, 
      &pIn, p0 + i * Inc_ViewPixPtr (image), 
      &weight, &DistIsZero);

    ppOut = p1 + i * Inc_ViewPixPtr (image);

    if (DistIsZero) {
      *outRed   = GET_PIXEL_RED (image, ppOut);
      *outGreen = GET_PIXEL_GREEN (image, ppOut);
      *outBlue  = GET_PIXEL_BLUE (image, ppOut);
      sumWeight = 0;
      break;
    }

    sumWeight += weight;
    sumR += weight * GET_PIXEL_RED (image, ppOut);
    sumG += weight * GET_PIXEL_GREEN (image, ppOut);
    sumB += weight * GET_PIXEL_BLUE (image, ppOut);
  }
  if (sumWeight > 0) {
    *outRed   = sumR / sumWeight;
    *outGreen = sumG / sumWeight;
    *outBlue  = sumB / sumWeight;
  }

//printf ("outRGB = %g %g %g\n", *outRed, *outGreen, *outBlue);
}


static void inline calcVoronoi (
  sphaldclT * pat,
  Image *image,
  const VIEW_PIX_PTR *p0,  // row of input RGB
  const VIEW_PIX_PTR *p1,  // row of differences
  ssize_t inRed,  // 0 to QuantumRange
  ssize_t inGreen,
  ssize_t inBlue,
  // Ouputs are differences, to be added to identity.
  MagickRealType * outRed,
  MagickRealType * outGreen,
  MagickRealType * outBlue
)
{
  VIEW_PIX_PTR pIn;

  SET_PIXEL_RED   (image, inRed, &pIn);
  SET_PIXEL_GREEN (image, inGreen, &pIn);
  SET_PIXEL_BLUE  (image, inBlue, &pIn);

  double minDist = 99e99;

  int i, iAtMin = 0;

  for (i=0; i < (ssize_t) image->columns; i++) {

    double dist = colDistRel (image, &pIn, p0 + i * Inc_ViewPixPtr (image));

    if (minDist > dist) {
      minDist = dist;
      iAtMin = i;
      if (minDist == 0) break;
    }
  }
  const VIEW_PIX_PTR *ppOut = p1 + iAtMin * Inc_ViewPixPtr (image);

  *outRed = GET_PIXEL_RED (image, ppOut);
  *outGreen = GET_PIXEL_GREEN (image, ppOut);
  *outBlue = GET_PIXEL_BLUE (image, ppOut);

//printf ("outRGB = %g %g %g\n", *outRed, *outGreen, *outBlue);
}


// The next function corresponds in style to functions in transform.c
// It takes one image, and returns an image.
//
static Image *sphaldcl (
  Image *image,
  sphaldclT * pat,
  ExceptionInfo *exception)
{
  Image
    *new_image;

  CacheView
    *in_view,
    *out_view;

  ssize_t
    y;

  MagickBooleanType
    status;


  assert(image != (Image *) NULL);
  assert(image->signature == MAGICK_CORE_SIG);

  if (image->debug != MagickFalse)
    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);

  if (pat->do_verbose) {
    fprintf (stderr,
          "sphaldcl: Input image [%s]  depth %i  size %ix%i\n",
          image->filename, (int)image->depth,
          (int)image->columns, (int)image->rows);
  }

  if (image->rows != 2 ) {
    fprintf (stderr, "sphaldcl: image needs height 2\n");
    return (Image *)NULL;
  }

  if (SetNoPalette (image, exception) == MagickFalse)
    return (Image *)NULL;

  // Make a clone of this image, undefined pixel values:
  //
  int sqSize = pat->haldLevel * pat->haldLevel * pat->haldLevel;

  new_image = CloneImage (image, sqSize, sqSize, MagickTrue, exception);
  if (new_image == (Image *) NULL) {
    fprintf (stderr, "sphaldcl clone failed %i\n", sqSize);
    return(new_image);
  }

  if (SetNoPalette (new_image, exception) == MagickFalse)
    return (Image *)NULL;

  in_view = AcquireVirtualCacheView (image,exception);

  out_view = AcquireAuthenticCacheView (new_image,exception);

  status = MagickTrue;

  const VIEW_PIX_PTR *p0=GetCacheViewVirtualPixels(in_view,0,0,image->columns,1,exception);
  if (p0 == (const VIEW_PIX_PTR *) NULL) {
    return (Image *)NULL;
  }

  const VIEW_PIX_PTR *p1=GetCacheViewVirtualPixels(in_view,0,1,image->columns,1,exception);
  if (p1 == (const VIEW_PIX_PTR *) NULL) {
    return (Image *)NULL;
  }

  pat->distances = (distanceT *) AcquireQuantumMemory (image->columns, sizeof(distanceT));
  if (!pat->distances) {
    fprintf (stderr, "sphaldcl: oom\n");
    return (Image *)NULL;
  }

  const int lev = pat->haldLevel;
  const int lev2 = pat->haldLevel * pat->haldLevel;
  const int lev2m1 = lev2 - 1;

  const double QoverLev2m1 = QuantumRange / (double)lev2m1;

#if defined(MAGICKCORE_OPENMP_SUPPORT)
  #pragma omp parallel for schedule(static,4) shared(status) \
    MAGICK_THREADS(new_image,new_image,new_image->rows,1)
#endif
  for (y=0; y < (ssize_t) new_image->rows; y++)
  {
    VIEW_PIX_PTR
      *q;

    ssize_t
      x;

    MagickRealType
      fr=0, fg=0, fb=0;

    if (status == MagickFalse)
      continue;

    q=GetCacheViewAuthenticPixels(out_view,0,y,new_image->columns,1,exception);
    if (q == (const VIEW_PIX_PTR *) NULL)
    {
      status=MagickFalse;
      continue;
    }

    ssize_t
      inRed, inGreen, inBlue;

    for (x=0; x < (ssize_t) new_image->columns; x++)
    {
      inRed   = x % lev2;
      inGreen = x / lev2 + (y % lev) * lev;
      inBlue  = y / lev;

      switch (pat->method) {
        case mIdentity: {
          SET_PIXEL_RED   (new_image, QoverLev2m1 * inRed  , q);
          SET_PIXEL_GREEN (new_image, QoverLev2m1 * inGreen, q);
          SET_PIXEL_BLUE  (new_image, QoverLev2m1 * inBlue , q);

#if DEBUG==1
          ssize_t x2 = inRed % lev2 + (inGreen % lev) * lev2;
          ssize_t y2 = inBlue * lev + inGreen / lev;
          if ((x != x2) || (y != y2))
            printf ("xy=%li,%li xy2=%li,%li\n", x, y, x2, y2);
#endif
          break;
        }

        case mShepards:
        default: {
          calcShepard (pat, image, p0, p1,
            inRed   * QoverLev2m1,
            inGreen * QoverLev2m1,
            inBlue  * QoverLev2m1,
            &fr, &fg, &fb);

          SET_PIXEL_RED   (new_image, fr + QoverLev2m1 * inRed  , q);
          SET_PIXEL_GREEN (new_image, fg + QoverLev2m1 * inGreen, q);
          SET_PIXEL_BLUE  (new_image, fb + QoverLev2m1 * inBlue , q);
          break;
        }
        case mVoronoi: {
          calcVoronoi (pat, image, p0, p1,
            inRed   * QoverLev2m1,
            inGreen * QoverLev2m1,
            inBlue  * QoverLev2m1,
            &fr, &fg, &fb);

          SET_PIXEL_RED   (new_image, fr + QoverLev2m1 * inRed  , q);
          SET_PIXEL_GREEN (new_image, fg + QoverLev2m1 * inGreen, q);
          SET_PIXEL_BLUE  (new_image, fb + QoverLev2m1 * inBlue , q);
          break;
        }
      }

      q += Inc_ViewPixPtr (new_image);
    }
    if (SyncCacheViewAuthenticPixels(out_view,exception) == MagickFalse)
      status=MagickFalse;
  }
  out_view = DestroyCacheView (out_view);
  in_view = DestroyCacheView (in_view);

  if (pat->distances)
    pat->distances = RelinquishMagickMemory (pat->distances);

  if (status == MagickFalse) return NULL;

  return (new_image);
}


ModuleExport size_t sphaldclImage(Image **images,const int argc,
  const char **argv,ExceptionInfo *exception)
{
  Image
    *image1,
    *new_image;

  MagickBooleanType
    status;

  sphaldclT
    at;

  assert(images != (Image **) NULL);
  assert(*images != (Image *) NULL);
  assert((*images)->signature == MAGICK_CORE_SIG);

  status = menu (argc, argv, &at);
  if (status == MagickFalse)
    return (-1);

  image1=GetLastImageInList (*images);

  new_image = sphaldcl (image1, &at, exception);
  if (new_image == (Image *) NULL)
    return(-1);

  ReplaceImageInList (images,new_image);
  // Replace messes up the images pointer. Make it good:
  *images = GetFirstImageInList (*images);

  return(MagickImageFilterSignature);
}

setmnsd.c

/* Last updated:
     5-September-2017 use NEXTARG.
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include "vsn_defines.h"

// CAUTION: When an image is a constant colour,
//   IM's GetImageMean() returns a standard_deviation of NaN,
//   "not a number" instead of zero.

typedef enum {
  fullframe,
  circular
} FormatT;

typedef enum {
  dIncOnly,
  dDecOnly,
  dBoth
} directionT;

typedef struct {
  double
    mid_pt,
    goal_sd,
    goal_mn,
    tolerance,
    initCon0,
    initCon2;

  int
    precision;

  directionT
    direction;

  MagickBooleanType
    has_sd_goal,
    has_mn_goal,
    pin_sd,
    pin_mn,
    mid_is_mean;

  int
    do_verbose;

  FILE *
    fh_data;

  MagickBooleanType
    sharpen;

  double
    upper_goal_sd,
    lower_goal_sd,
    mid_pt_q,
    fnd_mean,
    fnd_sd,
    error,
    pow;

  int
    nIter;
} setmnsdT;


static void usage (void)
{
  printf ("Usage: -process 'setmnsd [OPTION]...'\n");
  printf ("Adjusts mean and standard deviation.\n");
  printf ("\n");
  printf ("  sd, sdGoal N        goal for standard deviation [none]\n");
  printf ("  mn, meanGoal N      goal for mean [none]\n");
  printf ("  t, tolerance N      tolerance for results [0.00001]\n");
  printf ("  m, mid N            mid-point (percentage of quantum) [mean]\n");
  printf ("  i0, initCon0 N      lower guess for contrast [0]\n");
  printf ("  i2, initCon2 N      upper guess for contrast [1000]\n");
  printf ("  d, direction string one of 'incOnly', 'decOnly', 'both' [both]\n");
  printf ("  f, file string      write to file stream stdout or stderr\n");
  printf ("  v, verbose          write text information\n");
  printf ("  v2, verbose2        write more text information\n");
  printf ("\n");
}


static MagickBooleanType IsArg (char * pa, char * ShtOpt, char * LongOpt)
{
  if ((LocaleCompare(pa, ShtOpt)==0) || (LocaleCompare(pa, LongOpt)==0))
    return MagickTrue;

  return MagickFalse;
}

static inline MagickBooleanType EndsPc (const char *s)
{
  char c = *(s+strlen(s)-1);
  if (c == '%' || c == 'c')
    return MagickTrue;
  return MagickFalse;
}


#define NEXTARG \
  if (i < argc-1) i++; \
  else { \
    fprintf (stderr, "srchimg: ERROR: [%s] needs argument\n", pa); \
    status = MagickFalse; \
  }

static MagickBooleanType menu(
  const int argc,
  const char **argv,
  setmnsdT * psss
)
// Returns MagickTrue if okay.
{
  int
    i;

  MagickBooleanType
    status;

  status = MagickTrue;

  psss->do_verbose = 0;

  psss->goal_sd = 0.166667;
  psss->goal_mn = 0.5;
  psss->has_sd_goal = MagickFalse;
  psss->has_mn_goal = MagickFalse;
  psss->pin_sd = MagickFalse;
  psss->pin_mn = MagickFalse;
  psss->tolerance = 0.00001;
  psss->error = psss->tolerance;
  psss->mid_pt = 50;
  psss->mid_is_mean = MagickTrue;
  psss->direction = dBoth;
  psss->fh_data = stderr;
  psss->initCon0 = 0.0000001;
  psss->initCon2 = 30.0;
  psss->pow = 1.0;

  for (i=0; i < argc; i++) {
    char * pa = (char *)argv[i];

    if (IsArg (pa, "sd", "sdGoal")==MagickTrue) {
      NEXTARG;
      if (LocaleCompare(argv[i], "pin")==0) {
        psss->pin_sd = MagickTrue;
      } else {
        psss->goal_sd = atof(argv[i]);
        if (EndsPc (argv[i])) psss->goal_sd /= 100.0;
      }
      psss->has_sd_goal = MagickTrue;
    } else if (IsArg (pa, "mn", "meanGoal")==MagickTrue) {
      NEXTARG;
      if (LocaleCompare(argv[i], "pin")==0) {
        psss->pin_mn = MagickTrue;
      } else {
        psss->goal_mn = atof(argv[i]);
        if (EndsPc (argv[i])) psss->goal_mn /= 100.0;
      }
      psss->has_mn_goal = MagickTrue;
    } else if (IsArg (pa, "t", "tolerance")==MagickTrue) {
      NEXTARG;
      psss->tolerance = atof(argv[i]);
      if (EndsPc (argv[i])) psss->tolerance /= 100.0;
    } else if (IsArg (pa, "m", "mid")==MagickTrue) {
      NEXTARG;
      if (LocaleCompare(argv[i], "mean")==0) {
        psss->mid_is_mean = MagickTrue;
      } else {
        psss->mid_is_mean = MagickFalse;
        psss->mid_pt = atof(argv[i]);
      }
    } else if (IsArg (pa, "i0", "initCon0")==MagickTrue) {
      NEXTARG;
      psss->initCon0 = atof(argv[i]);
    } else if (IsArg (pa, "i2", "initCon2")==MagickTrue) {
      NEXTARG;
      psss->initCon2 = atof(argv[i]);
    } else if (IsArg (pa, "d", "direction")==MagickTrue) {
      NEXTARG;
      if (LocaleCompare(argv[i], "incOnly")==0) {
        psss->direction = dIncOnly;
      } else if (LocaleCompare(argv[i], "decOnly")==0) {
        psss->direction = dDecOnly;
      } else if (LocaleCompare(argv[i], "both")==0) {
        psss->direction = dBoth;
      } else {
        fprintf (stderr, "setmnsd: ERROR: direction: invalid option [%s]\n", argv[i]);
        status = MagickFalse;
      }
    } else if (IsArg (pa, "f", "file")==MagickTrue) {
      NEXTARG;
      if (strcasecmp (argv[i], "stdout")==0) psss->fh_data = stdout;
      else if (strcasecmp (argv[i], "stderr")==0) psss->fh_data = stderr;
      else status = MagickFalse;
    } else if (IsArg (pa, "v", "verbose")==MagickTrue) {
      psss->do_verbose = 1;
    } else if (IsArg (pa, "v2", "verbose2")==MagickTrue) {
      psss->do_verbose = 2;
    } else {
      fprintf (stderr, "setmnsd: ERROR: unknown option [%s]\n", pa);
      status = MagickFalse;
    }
  }

  if (psss->do_verbose) {
    fprintf (stderr, "setmnsd options:");

    if (psss->do_verbose > 1) fprintf (stderr, "  verbose2");
    else fprintf (stderr, "  verbose");

    if (psss->has_mn_goal) {
      if (psss->pin_mn) fprintf (stderr, "  meanGoal pin");
      else fprintf (stderr, "  meanGoal %.*g", psss->precision, psss->goal_mn);
    }
    if (psss->has_sd_goal) {
      if (psss->pin_sd) fprintf (stderr, "  sdGoal pin");
      else fprintf (stderr, "  sdGoal %.*g", psss->precision, psss->goal_sd);
    }

    fprintf (stderr, "  tolerance %.*g", psss->precision, psss->tolerance);
    if (psss->mid_is_mean) {
      fprintf (stderr, "  mid mean");
    } else {
      fprintf (stderr, "  mid %.*g", psss->precision, psss->mid_pt);
    }

    fprintf (stderr, "  initCon0 %.*g", psss->precision, psss->initCon0);
    fprintf (stderr, "  initCon2 %.*g", psss->precision, psss->initCon2);
    fprintf (stderr, "  direction ");
    switch (psss->direction) {
      case dIncOnly: fprintf (stderr, "incOnly");
      case dDecOnly: fprintf (stderr, "decOnly");
      case dBoth:    fprintf (stderr, "both");
    }

    if (psss->fh_data == stdout) fprintf (stderr, " file stdout");

    fprintf (stderr, "\n");
  }

  if (status == MagickFalse)
    usage ();

  return (status);
}


static MagickBooleanType ApplySigmoid (
  Image *image,
  MagickBooleanType sharpen,
  double contrast,
  double mid,
  ExceptionInfo *exception)
{
#if IMV6OR7==6
  return SigmoidalContrastImageChannel (
    image, DefaultChannels, sharpen, contrast, mid);
#else
  return SigmoidalContrastImage (
    image, sharpen, contrast, mid, exception);
#endif

}


static Image *TryContrast (
  Image *image,
  setmnsdT * psss,
  double contrast,
  ExceptionInfo *exception)
{
  // Make a clone of this image, copied pixel values:
  //
  Image *temp_image = CloneImage(image, 0, 0, MagickTrue, exception);
  if (temp_image == (Image *) NULL)
    return(temp_image);

//  if (SetNoPalette (temp_image, exception) == MagickFalse)
//    return (Image *)NULL;

  psss->nIter++;
  if (!ApplySigmoid (temp_image, psss->sharpen, contrast, psss->mid_pt_q, exception))
    return (Image *)NULL;

  // FIXME: Beware: next doesn't treat alpha as I would like.
  if (!GetImageMean (temp_image, &psss->fnd_mean, &psss->fnd_sd, exception))
    return (Image *)NULL;
  if (isnan(psss->fnd_sd)) psss->fnd_sd = 0;

  psss->pow = 1.0;
  if (psss->has_mn_goal) {

    if (psss->do_verbose > 1) fprintf (psss->fh_data, "goal_mn=%.*g  ",
      psss->precision, psss->goal_mn);

    // We special-case 0.0 and 1.0 to avoid not-a-number, infinity etc.

    if (psss->goal_mn == 0.0) {
      psss->pow = 9e99;
      SetAllBlack (temp_image, exception);
    } else if (psss->goal_mn == 1.0) {
      psss->pow = 0.0;
      SetAllOneCol (image, "white", exception);
    } else {
      // We have a "real" goal for mean.
      // Iterate until fabs(onePow-1) < tolerance.
      int i;
      for (i=0; i < 10; i++) {
        double onePow = log(psss->goal_mn) / log(psss->fnd_mean/(double)QuantumRange);
        psss->pow *= onePow;

        if (fabs(onePow-1) < psss->error) break;

        if (psss->do_verbose > 1) fprintf (psss->fh_data, "before pow: onePow=%.*g pow=%.*g: mean=%.*g sd=%.*g  ",
          psss->precision, onePow,
          psss->precision, psss->pow,
          psss->precision, psss->fnd_mean / (double)QuantumRange,
          psss->precision, psss->fnd_sd / (double)QuantumRange);

        psss->nIter++;

        if (!EvaluateImage (temp_image, PowEvaluateOperator, onePow, exception))
          return (Image *)NULL;

        if (!GetImageMean (temp_image, &psss->fnd_mean, &psss->fnd_sd, exception))
          return (Image *)NULL;
        if (isnan(psss->fnd_sd)) psss->fnd_sd = 0;
      }
    }
  }

  if (psss->do_verbose > 1) fprintf (psss->fh_data, "contrast=%.*g: mean=%.*g sd=%.*g  ",
    psss->precision, contrast,
    psss->precision, psss->fnd_mean / (double)QuantumRange,
    psss->precision, psss->fnd_sd / (double)QuantumRange);

  double err = fabs (psss->fnd_sd/(double)QuantumRange - psss->goal_sd);
  psss->error = (err > psss->tolerance) ? err : psss->tolerance;

  return (temp_image);
}


static void AnnounceResult (
  Image *image,
  setmnsdT * psss,
  double contrast,
  ExceptionInfo *exception)
{
  if (psss->do_verbose) {
    fprintf (psss->fh_data, "nIter=%i\n", psss->nIter);

    fprintf (psss->fh_data, "result: mean=%.*g  sd=%.*g\n",
      psss->precision, psss->fnd_mean / (double)QuantumRange,
      psss->precision, psss->fnd_sd / (double)QuantumRange);
  }

  char msg[200] = "";

  if (contrast != 0) {
    sprintf (msg, "%csigmoidal-contrast %.*g,%.*g%%",
      psss->sharpen ? '-':'+',
      psss->precision, contrast,
      psss->precision, psss->mid_pt_q * 100.0 / (double)QuantumRange);
  }

  if (psss->has_mn_goal) {
    char msg2[100];
    sprintf (msg2, " -evaluate pow %.*g",
      psss->precision, psss->pow);
    strcat (msg, msg2);
  }

  // If no change, make a "no-op" operation.
  if (!*msg) strcpy (msg, "-evaluate Add 0");

  if (psss->do_verbose) {
    fprintf (psss->fh_data, "setmnsd command: %s\n", msg);
  }

#if IMV6OR7==6
  SetImageProperty (image, "filter:setmnsd", msg);
#else
  SetImageProperty (image, "filter:setmnsd", msg, exception);
#endif
}


// The next function corresponds in style to functions in transform.c
// It takes one image, and returns an image (or the input image, unchanged).
//
static Image *setmnsd (
  Image *image,
  setmnsdT * psss,
  ExceptionInfo *exception)
{
  assert(image != (Image *) NULL);
  assert(image->signature == MAGICK_CORE_SIG);

  if (image->debug != MagickFalse)
    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);

//  if (SetNoPalette (image, exception) == MagickFalse)
//    return (Image *)NULL;

  psss->nIter = 0;

  double tol_q = psss->tolerance * QuantumRange;

  psss->mid_pt_q = psss->mid_pt/100.0 * QuantumRange;

  {
    double goal_sd_q = psss->goal_sd * QuantumRange;
    psss->upper_goal_sd = goal_sd_q + tol_q;
    psss->lower_goal_sd = goal_sd_q - tol_q;
  }

  double contrast0 = psss->initCon0;
  double contrast1;
  double contrast2 = psss->initCon2;

  if (psss->do_verbose > 1) fprintf (psss->fh_data, "setmnsd: contrasts %.*g %.*g\n",
    psss->precision, contrast0,
    psss->precision, contrast2);

  double mean, stddev;
  if (!GetImageMean (image, &mean, &stddev, exception))
    return (Image *)NULL;
  if (isnan(stddev)) stddev = 0;

  if (psss->do_verbose) fprintf (psss->fh_data, "input: mean=%.*g sd=%.*g\n",
    psss->precision, mean / (double)QuantumRange,
    psss->precision, stddev / (double)QuantumRange);

  if (stddev==0 && psss->has_sd_goal) {
    if (psss->do_verbose) fprintf (psss->fh_data, "setmnsd Warning: Input SD is zero, so ignoring SD goal\n");
    psss->has_sd_goal = MagickFalse;
    psss->pin_sd = MagickFalse;
  }

  if ((mean<=0 || mean==QuantumRange) && psss->has_mn_goal) {
    if (psss->do_verbose) fprintf (psss->fh_data, "setmnsd Warning: Input mean is <=zero or 100%%, so ignoring mean goal\n");
    psss->has_mn_goal = MagickFalse;
    psss->pin_mn = MagickFalse;
  }

  if (psss->pin_sd) {
    psss->goal_sd = stddev / (double)QuantumRange;
    double goal_sd_q = psss->goal_sd * QuantumRange;
    psss->upper_goal_sd = goal_sd_q + tol_q;
    psss->lower_goal_sd = goal_sd_q - tol_q;
  }

  if (psss->pin_mn) psss->goal_mn = mean / (double)QuantumRange;

  if (psss->mid_is_mean) {
    psss->mid_pt_q = mean;
  }

  Image * iInit = TryContrast (image, psss, 0, exception);
  if (iInit == (Image *)NULL) return (iInit);

  if (!psss->has_sd_goal) {
    // That's all we have to do.
    AnnounceResult (iInit, psss, 0, exception);
    return (iInit);
  }

  iInit = DestroyImage (iInit);

  mean = psss->fnd_mean;
  stddev = psss->fnd_sd;

  psss->sharpen = MagickTrue; // whether to increase contrast in middle

  if (stddev > psss->upper_goal_sd) {
    if (psss->do_verbose > 1) fprintf (psss->fh_data, "above upper\n");
    psss->sharpen = MagickFalse;
    if (psss->direction == dIncOnly) return (image);
    double t = contrast0;
    contrast0 = contrast2;
    contrast2 = t;
  } else if (stddev < psss->lower_goal_sd) {
    if (psss->do_verbose > 1) fprintf (psss->fh_data, "below lower\n");
    if (psss->direction == dDecOnly) return (image);
    psss->sharpen = MagickTrue;
  } else {
    if (psss->do_verbose > 1) fprintf (psss->fh_data, "within tolerance\n");
    AnnounceResult (image, psss, 0, exception);
    return (image);
  }


  Image * i0 = TryContrast (image, psss, contrast0, exception);
  if (i0 == (Image *)NULL) return (i0);

  if (psss->fnd_sd > psss->upper_goal_sd) {
    if (psss->do_verbose) fprintf (psss->fh_data, "i0 above upper -- fail\n");
    fprintf (stderr, "i0 C=%.*g stddev too high: %.*g\n",
      psss->precision, contrast0,
      psss->precision, psss->fnd_sd / (double)QuantumRange);
    i0 = DestroyImage (i0);
    return (i0);
  } else if (psss->fnd_sd < psss->lower_goal_sd) {
    if (psss->do_verbose > 1) fprintf (psss->fh_data, "i0 below lower -- good\n");
  } else {
    if (psss->do_verbose > 1) fprintf (psss->fh_data, "i0 within tolerance\n");
    AnnounceResult (i0, psss, contrast0, exception);
    return (i0);
  }

  i0 = DestroyImage (i0);


  Image * i2 = TryContrast (image, psss, contrast2, exception);
  if (i2 == (Image *)NULL) return (i2);

  if (psss->fnd_sd > psss->upper_goal_sd) {
    if (psss->do_verbose > 1) fprintf (psss->fh_data, "i2 above upper -- good\n");
  } else if (psss->fnd_sd < psss->lower_goal_sd) {
    if (psss->do_verbose) fprintf (psss->fh_data, "i2 below lower -- fail\n");
    fprintf (stderr, "i2 C=%.*g stddev too low: %.*g\n",
      psss->precision, contrast2,
      psss->precision, psss->fnd_sd / (double)QuantumRange);
    i2 = DestroyImage (i2);
    return (i2);
  } else {
    if (psss->do_verbose > 1) fprintf (psss->fh_data, "i2 within tolerance\n");
    AnnounceResult (i2, psss, contrast2, exception);
    return (i2);
  }

  i2 = DestroyImage (i2);

  // Limit the number of iterations, just in case...
  int i;
  for (i=0; i < 100; i++) {
    double prod = contrast0 * contrast2;
    if (prod > 0) contrast1 = sqrt (contrast0 * contrast2);
    else contrast1 = (contrast0 + contrast2) / 2.0;

    Image * i1 = TryContrast (image, psss, contrast1, exception);
    if (i1 == (Image *)NULL) return (i1);

    if (psss->fnd_sd > psss->upper_goal_sd) {
      if (psss->do_verbose > 1) fprintf (psss->fh_data, "i1 above upper\n");
      contrast2 = contrast1;
    } else if (psss->fnd_sd < psss->lower_goal_sd) {
      if (psss->do_verbose > 1) fprintf (psss->fh_data, "i1 below lower\n");
      contrast0 = contrast1;
    } else {
      if (psss->do_verbose > 1) fprintf (psss->fh_data, "i1 within tolerance\n");

      // If mean is a goal, do it again with tighter tolerance on mean.
      if (psss->has_mn_goal) {
        i1 = DestroyImage (i1);
        i1 = TryContrast (image, psss, contrast1, exception);
        if (i1 == (Image *)NULL) return (i1);
      }

      AnnounceResult (i1, psss, contrast1, exception);
      return (i1);
    }

    i1 = DestroyImage (i1);
  }

  if (psss->do_verbose) fprintf (psss->fh_data, "Bust. Using contrast1 = %.*g\n", psss->precision, contrast1);

  Image * i1 = TryContrast (image, psss, contrast1, exception);
  if (i1 == (Image *)NULL) return (i1);

  AnnounceResult (i1, psss, contrast1, exception);
  return (i1);
}



ModuleExport size_t setmnsdImage(Image **images,const int argc,
  const char **argv,ExceptionInfo *exception)
{
  Image
    *image,
    *new_image;

  MagickBooleanType
    status;

  setmnsdT
    sss;

  assert(images != (Image **) NULL);
  assert(*images != (Image *) NULL);
  assert((*images)->signature == MAGICK_CORE_SIG);

  sss.precision = GetMagickPrecision();

  status = menu (argc, argv, &sss);
  if (status == MagickFalse)
    return (-1);

  for (image=(*images); image != (Image *) NULL; image=GetNextImageInList(image))
  {
    new_image = setmnsd (image, &sss, exception);
    if (new_image == (Image *) NULL)
      return(-1);

    if (new_image != image) ReplaceImageInList(&image,new_image);

    *images=GetFirstImageInList(image);
  }

  return(MagickImageFilterSignature);
}

integim.c

/*
    Reference:
      http://im.snibgo.com/customim.htm
      http://im.snibgo.com/integim.htm

    Latest update:
      24-July-2017
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include "vsn_defines.h"

/*
   Latest update:
*/

typedef enum {
  aaAuto,
  aaDisregard,
  aaRegard
} alphaActionT;

typedef struct {
  FILE *
    fh_data;

  MagickBooleanType
    do_regardalpha,
    do_verbose;

  alphaActionT
    alphaAction;

} integimT;

static void usage (void)
{
  printf ("Usage: -process 'integim [OPTION]...'\n");
  printf ("Create an HDRI integral image (summed area table).\n");
  printf ("\n");
  printf ("  pm, premult         'yes' or 'no' or 'auto'\n");
  printf ("  f, file string      Write verbose text to stderr or stdout\n");
  printf ("  v, verbose          write text information to stdout\n");
  printf ("\n");
}


static MagickBooleanType IsArg (char * pa, char * ShtOpt, char * LongOpt)
{
  if ((LocaleCompare(pa, ShtOpt)==0) || (LocaleCompare(pa, LongOpt)==0))
    return MagickTrue;

  return MagickFalse;
}


static MagickBooleanType menu(
  const int argc,
  const char **argv,
  integimT * pch
)
// Returns MagickTrue if okay.
{
  int
    i;

  MagickBooleanType
    status;

  status = MagickTrue;

  pch->fh_data = stderr;
  pch->do_regardalpha =
    pch->do_verbose = MagickFalse;
  pch->alphaAction = aaRegard;

  for (i=0; i < argc; i++) {
    char * pa = (char *)argv[i];

    if (IsArg (pa, "pm", "premult")==MagickTrue) {
      i++;
      if (strcasecmp (argv[i], "auto")==0) pch->alphaAction = aaAuto;
      else if (strcasecmp (argv[i], "yes")==0) pch->alphaAction = aaRegard;
      else if (strcasecmp (argv[i], "no")==0) pch->alphaAction = aaDisregard;
      else status = MagickFalse;
    } else if (IsArg (pa, "f", "file")==MagickTrue) {
      i++;
      if (strcasecmp (argv[i], "stdout")==0) pch->fh_data = stdout;
      else if (strcasecmp (argv[i], "stderr")==0) pch->fh_data = stderr;
      else status = MagickFalse;
    } else if (IsArg (pa, "v", "verbose")==MagickTrue) {
      pch->do_verbose = MagickTrue;
    } else {
      fprintf (stderr, "integim: ERROR: unknown option [%s]\n", pa);
      status = MagickFalse;
    }
  }

  if (pch->do_verbose) {
    fprintf (stderr, "integim options:");
//    if (pch->do_regardalpha) fprintf (stderr, "  regardalpha");
    if (pch->alphaAction != aaAuto) {
      fprintf (stderr, "  premult ");
      if (pch->alphaAction==aaRegard) fprintf (stderr, "yes");
      else if (pch->alphaAction==aaDisregard) fprintf (stderr, "no");
      else fprintf (stderr, "??");
    }
    if (pch->fh_data == stdout) fprintf (stderr, "  file stdout");
    if (pch->do_verbose) fprintf (stderr, "  verbose");
    fprintf (stderr, "\n");
  }

  if (status == MagickFalse)
    usage ();

  return (status);
}


#if defined(MAGICKCORE_HDRI_SUPPORT)
#define ADD_HALF
#else
#define ADD_HALF +0.5
#endif


// The next function corresponds in style to functions in transform.c
// It takes one image, and returns an image.
//
static Image * integim (
  const Image *image,
  integimT * pch,
  ExceptionInfo *exception)
{
  Image
    *new_image;

  CacheView
    *image_view,
    *out_view;

  ssize_t
    y,
    inXmult,
    outXmult;

  MagickBooleanType
    IsAlphaOn = IS_ALPHA_CH(image),
    status;

  if (image->debug != MagickFalse)
    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);

  if (pch->do_verbose) {
    fprintf (pch->fh_data,
             "integim: Input image [%s] depth is %i IsAlpha %s\n",
             image->filename,
             (int)image->depth,
             IsAlphaOn ? "ON":"OFF"
            );
  }

  switch (pch->alphaAction) {
    case aaAuto:
      pch->do_regardalpha = IsAlphaOn;
      if (pch->do_verbose)
        fprintf (pch->fh_data, "integim: alphaaction auto: regardalpha %s\n",
          pch->do_regardalpha ? "TRUE" : "FALSE");
      break;
    case aaRegard:
      pch->do_regardalpha = MagickTrue;
      break;
    case aaDisregard:
      pch->do_regardalpha = MagickFalse;
      break;
  }

  MagickBooleanType UseAlpha = (IsAlphaOn && pch->do_regardalpha);

  // FIXME: Can we do this in-place?

  new_image=CloneImage(image, image->columns, image->rows, MagickTrue, exception);
  if (new_image == (Image *) NULL)
    return(new_image);

  // Just in case image had a palette.
  if (SetNoPalette (new_image, exception) == MagickFalse)
    return (Image *)NULL;

  inXmult = Inc_ViewPixPtr (image);
  outXmult = Inc_ViewPixPtr (new_image);

  status=MagickTrue;

  out_view=AcquireAuthenticCacheView(new_image,exception);

  image_view=AcquireVirtualCacheView(image,exception);

  // We can't easily parallelise
  // because each output row needs the previous row.

  for (y=0; y < (ssize_t) image->rows; y++)
  {
    const VIEW_PIX_PTR
      *p,
      *q_prev = NULL;

    VIEW_PIX_PTR
      *q_out;

    ssize_t
      x;

    long double
      alpha;

    if (status==MagickFalse) continue;

    p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
    if (p == (const VIEW_PIX_PTR *) NULL)
    {
      fprintf (stderr, "bad GetCacheViewVirtualPixels image_view\n");
      status=MagickFalse;
    }

    q_out=GetCacheViewAuthenticPixels(out_view,0,y,new_image->columns,1,exception);
    if (q_out == (const VIEW_PIX_PTR *) NULL) {
      fprintf (stderr, "integim: bad GetCacheViewAuthenticPixels out_view\n");
      status=MagickFalse;
    }

    if (y > 0) {
      q_prev=GetCacheViewVirtualPixels(out_view,0,y-1,new_image->columns,1,exception);
      if (q_prev == (const VIEW_PIX_PTR *) NULL) {
        fprintf (stderr, "integim: bad GetCacheViewAuthenticPixels q_prev\n");
        status=MagickFalse;
      }
    }

    alpha = 1.0;

    if (status==MagickFalse) continue;

    long double
      left_r = 0, up_r = 0, upleft_r = 0,
      left_g = 0, up_g = 0, upleft_g = 0,
      left_b = 0, up_b = 0, upleft_b = 0,
      left_a = 0, up_a = 0, upleft_a = 0;

    for (x=0; x < (ssize_t) image->columns; x++)
    {

      if (x > 0) {
        left_r = GET_PIXEL_RED(new_image, q_out-outXmult);
        left_g = GET_PIXEL_GREEN(new_image, q_out-outXmult);
        left_b = GET_PIXEL_BLUE(new_image, q_out-outXmult);
        left_a = GET_PIXEL_ALPHA(new_image, q_out-outXmult);
      }

      if (y > 0) {
        up_r = GET_PIXEL_RED(new_image, q_prev);
        up_g = GET_PIXEL_GREEN(new_image, q_prev);
        up_b = GET_PIXEL_BLUE(new_image, q_prev);
        up_a = GET_PIXEL_ALPHA(new_image, q_prev);
        if (x > 0) {
          upleft_r = GET_PIXEL_RED(new_image, q_prev-outXmult);
          upleft_g = GET_PIXEL_GREEN(new_image, q_prev-outXmult);
          upleft_b = GET_PIXEL_BLUE(new_image, q_prev-outXmult);
          upleft_a = GET_PIXEL_ALPHA(new_image, q_prev-outXmult);
        }
      }

      if (UseAlpha) {
        alpha = GET_PIXEL_ALPHA(image,p) / (long double)QuantumRange;
      }

      SET_PIXEL_RED (new_image,
        alpha * GET_PIXEL_RED(image,p) + left_r + up_r - upleft_r ADD_HALF,
        q_out);

      SET_PIXEL_GREEN (new_image,
        alpha * GET_PIXEL_GREEN(image,p) + left_g + up_g - upleft_g ADD_HALF,
        q_out);

      SET_PIXEL_BLUE (new_image,
        alpha * GET_PIXEL_BLUE(image,p) + left_b + up_b - upleft_b ADD_HALF,
        q_out);

      if (IsAlphaOn) {
        SET_PIXEL_ALPHA (new_image,
          GET_PIXEL_ALPHA(image,p) + left_a + up_a - upleft_a ADD_HALF,
          q_out);
      }

      p += inXmult;
      q_out += outXmult;
      q_prev += outXmult;
    }

    if (SyncCacheViewAuthenticPixels(out_view,exception) == MagickFalse) {
      fprintf (stderr, "bad sync\n");
      status=MagickFalse;
    }
  }

  image_view=DestroyCacheView(image_view);
  out_view=DestroyCacheView(out_view);

  return (new_image);
}


ModuleExport size_t integimImage(Image **images,const int argc,
  const char **argv,ExceptionInfo *exception)
{
  Image
    *image,
    *new_image;

  MagickBooleanType
    status;

  integimT
    cumul_histo;

  assert(images != (Image **) NULL);
  assert(*images != (Image *) NULL);
  assert((*images)->signature == MAGICK_CORE_SIG);

  status = menu (argc, argv, &cumul_histo);
  if (status == MagickFalse)
    return (-1);

  for (image=(*images); image != (Image *) NULL; image=GetNextImageInList(image))
  {
    new_image = integim (image, &cumul_histo, exception);
    if (!new_image) return(-1);

    ReplaceImageInList(&image,new_image);
    *images=GetFirstImageInList(image);
  }

  return(MagickImageFilterSignature);
}

deintegim.c

/*
    Reference:
      http://im.snibgo.com/customim.htm
      http://im.snibgo.com/integim.htm

    Latest update:
      24-July-2017 Fixed bug on top line and left column.
                   (x1 and y1 can now be -1.)
      3-April-2018 for v7.0.7-28
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include "vsn_defines.h"

typedef enum {
  aaAuto,
  aaDisregard,
  aaRegard
} alphaActionT;

typedef struct {
  FILE *
    fh_data;

  char
    *sWindow;

  MagickBooleanType
    do_regardalpha,
    do_verbose,
    dontDivide;

  alphaActionT
    alphaAction;

  ssize_t
    offsetx,
    offsety,
    winWidth,
    winHeight;
} deintegimT;

static void usage (void)
{
  printf ("Usage: -process 'deintegim [OPTION]...'\n");
  printf ("De-integrate an HDRI integral image (summed area table).\n");
  printf ("\n");
  printf ("  w, window string    window size, WxH\n");
  printf ("  pd, postdiv         'yes' or 'no' or 'auto'\n");
  printf ("  dd, dontdivide      don't divide colour channels\n");
  printf ("  ox, offsetX int     offset for window centre in X-direction\n");
  printf ("  oy, offsetY int     offset for window centre in Y-direction\n");
  printf ("  f, file string      Write verbose text to stderr or stdout\n");
  printf ("  v, verbose          write text information to stdout\n");
  printf ("\n");
}


static MagickBooleanType IsArg (char * pa, char * ShtOpt, char * LongOpt)
{
  if ((LocaleCompare(pa, ShtOpt)==0) || (LocaleCompare(pa, LongOpt)==0))
    return MagickTrue;

  return MagickFalse;
}


static MagickBooleanType menu(
  const int argc,
  const char **argv,
  deintegimT * pch
)
// Returns MagickTrue if okay.
{
  int
    i;

  MagickBooleanType
    status;

  status = MagickTrue;

  pch->fh_data = stderr;
  pch->sWindow = NULL;
  pch->alphaAction = aaRegard;

  pch->do_regardalpha =
    pch->do_verbose = MagickFalse;
  pch->dontDivide = MagickFalse;
  pch->offsetx = 0;
  pch->offsety = 0;

  for (i=0; i < argc; i++) {
    char * pa = (char *)argv[i];

    if (IsArg (pa, "w", "window")==MagickTrue) {
      i++;
      pch->sWindow = (char *)argv[i];
    } else if (IsArg (pa, "pd", "postdiv")==MagickTrue) {
      i++;
      if (strcasecmp (argv[i], "auto")==0) pch->alphaAction = aaAuto;
      else if (strcasecmp (argv[i], "yes")==0) pch->alphaAction = aaRegard;
      else if (strcasecmp (argv[i], "no")==0) pch->alphaAction = aaDisregard;
      else status = MagickFalse;
    } else if (IsArg (pa, "f", "file")==MagickTrue) {
      i++;
      if (strcasecmp (argv[i], "stdout")==0) pch->fh_data = stdout;
      else if (strcasecmp (argv[i], "stderr")==0) pch->fh_data = stderr;
      else status = MagickFalse;
    } else if (IsArg (pa, "ox", "offsetX")==MagickTrue) {
      i++;
      pch->offsetx = atoi(argv[i]);
    } else if (IsArg (pa, "oy", "offsetY")==MagickTrue) {
      i++;
      pch->offsety = atoi(argv[i]);
    } else if (IsArg (pa, "dd", "dontdivide")==MagickTrue) {
      pch->dontDivide = MagickTrue;
    } else if (IsArg (pa, "v", "verbose")==MagickTrue) {
      pch->do_verbose = MagickTrue;
    } else {
      fprintf (stderr, "deintegim: ERROR: unknown option [%s]\n", pa);
      status = MagickFalse;
    }
  }

  if (pch->do_verbose) {
    fprintf (stderr, "deintegim options:");
    if (pch->sWindow) fprintf (stderr, "  window %s", pch->sWindow);
    if (pch->offsetx) fprintf (stderr, "  offsetX %li", pch->offsetx);
    if (pch->offsety) fprintf (stderr, "  offsetY %li", pch->offsety);
    if (pch->alphaAction != aaAuto) {
      fprintf (stderr, "  postdiv ");
      if (pch->alphaAction==aaRegard) fprintf (stderr, "yes");
      else if (pch->alphaAction==aaDisregard) fprintf (stderr, "no");
      else fprintf (stderr, "??");
    }
    if (pch->fh_data == stdout) fprintf (stderr, "  file stdout");
    if (pch->dontDivide) fprintf (stderr, "  dontdivide");
    if (pch->do_verbose) fprintf (stderr, "  verbose");
    fprintf (stderr, "\n");
  }

  if (status == MagickFalse)
    usage ();

  return (status);
}



static MagickBooleanType GetNumber (
  char ** p,
  double *pValue,
  ssize_t dim)
// Returns whether valid.
{
  int len;
  if (sscanf (*p, "%lf%n", pValue, &len) != 1) return MagickFalse;
  *p += len;
  char c = **p;
  switch (c) {
    case 'c':
    case 'C':
    case '%':
      *pValue = floor (*pValue/100.0 * dim + 0.5);
      if (*pValue < 1) *pValue = 1;
      (*p)++;
      break;
    case 'p':
    case 'P':
      *pValue = floor (*pValue * dim + 0.5);
      if (*pValue < 1) *pValue = 1;
      (*p)++;
      break;
    case '\0':
    case 'x':
    case 'X':
      /* nothing */ ;
      break;
    default:
      printf ("Bad terminator? [%c]\n", c);
  }
  return MagickTrue;
}


static MagickBooleanType ParseWindow (
  deintegimT * pch,
  ssize_t imgWidth,
  ssize_t imgHeight
)
// Expected format: WxH
// where W and H are numbers optionally followed by '%' or 'c' or 'p'.
// Returns whether okay.
{
  double w=0, h=0;

  char * p = pch->sWindow;

  if (!*p) return MagickFalse;

  if (!GetNumber (&p, &w, imgWidth)) {
    fprintf (stderr, "deintegim error: Window needs two dimensions.\n");
    return MagickFalse;
  }

  if (*p!='x') {
    fprintf (stderr, "deintegim error: Window needs 'x' betweem two dimensions.\n");
    return MagickFalse;
  }

  p++;
  if (!GetNumber (&p, &h, imgHeight)) {
    fprintf (stderr, "deintegim error: Window needs two dimensions.\n");
    return MagickFalse;
  }

  if (w < 1 || h < 1) {
    fprintf (stderr, "deintegim error: Window dimensions must be >=1.\n");
    return MagickFalse;
  }

  if (*p) {
    fprintf (stderr, "deintegim error: Window dimensions have extra characters [%s].\n", p);
    return MagickFalse;
  }

  pch->winWidth = w;
  pch->winHeight = h;

  return MagickTrue;
}


#if defined(MAGICKCORE_HDRI_SUPPORT)
#define ADD_HALF
#else
#define ADD_HALF +0.5
#endif


// The next function corresponds in style to functions in transform.c
// It takes one image, and returns an image.
//
static Image * deintegim (
  const Image *image,
  deintegimT * pch,
  ExceptionInfo *exception)
{
  Image
    *new_image;

  CacheView
    *image_view,
    *out_view;

  ssize_t
    y,
    inXmult,
    outXmult;

  MagickBooleanType
    IsAlphaOn = IS_ALPHA_CH(image),
    status;

  if (image->debug != MagickFalse)
    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);

  if (pch->do_verbose) {
    fprintf (pch->fh_data,
             "deintegim: Input image [%s] depth is %i IsAlpha %s\n",
             image->filename,
             (int)image->depth,
             IsAlphaOn ? "ON":"OFF"
            );
  }

  pch->winWidth = pch->winHeight = 1;

  if (pch->sWindow) {
    if (! ParseWindow (pch, image->columns, image->rows))
      return (Image *)NULL;
  }

  if (pch->do_verbose)
    fprintf (pch->fh_data, "deintegim Window: %lix%li\n",
      pch->winWidth, pch->winHeight);

  ssize_t winSemiHt = (pch->winHeight+1) / 2;
  ssize_t winSemiWi = (pch->winWidth+1) / 2;

  switch (pch->alphaAction) {
    case aaAuto:
      pch->do_regardalpha = IsAlphaOn;
      if (pch->do_verbose)
        fprintf (pch->fh_data, "deintegim: alphaaction auto: regardalpha %s\n",
          pch->do_regardalpha ? "TRUE" : "FALSE");
      break;
    case aaRegard:
      pch->do_regardalpha = MagickTrue;
      break;
    case aaDisregard:
      pch->do_regardalpha = MagickFalse;
      break;
  }

  MagickBooleanType UseAlpha = (IsAlphaOn && pch->do_regardalpha);

  // Note: We can't do this in-place,
  // because an output pixel generally depends on a _later_ input pixel.

  new_image=CloneImage(image, image->columns, image->rows, MagickTrue, exception);
  if (new_image == (Image *) NULL)
    return(new_image);

  // Just in case image had a palette.
  if (SetNoPalette (new_image, exception) == MagickFalse)
    return (Image *)NULL;

  inXmult = Inc_ViewPixPtr (image);
  outXmult = Inc_ViewPixPtr (new_image);

  status=MagickTrue;

  image_view=AcquireVirtualCacheView(image,exception);

  out_view=AcquireAuthenticCacheView(new_image,exception);

#if defined(MAGICKCORE_OPENMP_SUPPORT)
  #pragma omp parallel for schedule(static,4) shared(status) \
    MAGICK_THREADS(new_image,new_image,new_image->rows,1)
#endif
  for (y=0; y < (ssize_t) new_image->rows; y++)
  {
    const VIEW_PIX_PTR
      *p,
      *p_prev = NULL;

    VIEW_PIX_PTR
      *q_out;

    ssize_t
      y1, y2,
      x;

    long double
      alpha;

    y1 = y - winSemiHt + pch->offsety;
    y2 = y1 + pch->winHeight;

    if (y1 < 0) y1 = -1;
    if (y2 > image->rows-1) y2 = image->rows-1;
    ssize_t winHt = y2 - y1;
    if (winHt==0) winHt = 1;

    if (status==MagickFalse) continue;

    p=GetCacheViewVirtualPixels(image_view,0,y2,image->columns,1,exception);
    if (p == (const VIEW_PIX_PTR *) NULL) {
      fprintf (stderr, "bad GetCacheViewVirtualPixels image_view\n");
      status=MagickFalse;
    }

    if (y1 >= 0) {
      p_prev=GetCacheViewVirtualPixels(image_view,0,y1,image->columns,1,exception);
      if (p_prev == (const VIEW_PIX_PTR *) NULL) {
        fprintf (stderr, "deintegim: bad GetCacheViewAuthenticPixels p_prev\n");
        status=MagickFalse;
      }
    } else {
      p_prev = NULL;
    }

    q_out=GetCacheViewAuthenticPixels(out_view,0,y,new_image->columns,1,exception);
    if (q_out == (const VIEW_PIX_PTR *) NULL) {
      fprintf (stderr, "deintegim: bad GetCacheViewAuthenticPixels out_view\n");
      status=MagickFalse;
    }

    alpha = 1.0;

    if (status==MagickFalse) continue;

    long double
      left_r = 0, up_r = 0, upleft_r = 0,
      left_g = 0, up_g = 0, upleft_g = 0,
      left_b = 0, up_b = 0, upleft_b = 0,
      left_a = 0, up_a = 0, upleft_a = 0;

    ssize_t x1, x2;

    for (x=0; x < (ssize_t) new_image->columns; x++)
    {
      x1 = x - winSemiWi + pch->offsetx;
      x2 = x1 + pch->winWidth;

      if (x1 < 0) x1 = -1;
      if (x2 > image->columns-1) x2 = image->columns-1;
      ssize_t winWi = x2 - x1;
      if (winWi==0) winWi = 1;

      int winArea = winHt * winWi;

      if (x1 >= 0) {
        const VIEW_PIX_PTR * pv = p+x1*inXmult;
        left_r = GET_PIXEL_RED(image, pv);
        left_g = GET_PIXEL_GREEN(image, pv);
        left_b = GET_PIXEL_BLUE(image, pv);
        left_a = GET_PIXEL_ALPHA(image, pv);
      }

      if (p_prev) {
        const VIEW_PIX_PTR * pv = p_prev+x2*inXmult;
        up_r = GET_PIXEL_RED(image, pv);
        up_g = GET_PIXEL_GREEN(image, pv);
        up_b = GET_PIXEL_BLUE(image, pv);
        up_a = GET_PIXEL_ALPHA(image, pv);
        if (x1 >= 0) {
          const VIEW_PIX_PTR * pv = p_prev + x1*inXmult;
          upleft_r = GET_PIXEL_RED(image, pv);
          upleft_g = GET_PIXEL_GREEN(image, pv);
          upleft_b = GET_PIXEL_BLUE(image, pv);
          upleft_a = GET_PIXEL_ALPHA(image, pv);
        }
      }

      // point to bottom-right of window
      const VIEW_PIX_PTR * pbr = p + x2*inXmult;

      if (IsAlphaOn) {
        // If alpha isn't on, image alpha values are 100%.
        alpha = GET_PIXEL_ALPHA(image,pbr) - left_a - up_a + upleft_a;

        SET_PIXEL_ALPHA (new_image, alpha / winArea ADD_HALF, q_out);
      }

      if (UseAlpha && alpha != 0) {
        alpha /= (long double)QuantumRange;
      } else {
        alpha = winArea;
      }

      if (pch->dontDivide) alpha = 1.0; // temp hack

      if (alpha == 0) {
        SET_PIXEL_RED (new_image, 0, q_out);
        SET_PIXEL_GREEN (new_image, 0, q_out);
        SET_PIXEL_BLUE (new_image, 0, q_out);
      } else {
        SET_PIXEL_RED (new_image,
          (GET_PIXEL_RED(image,pbr) - left_r - up_r + upleft_r)/alpha ADD_HALF,
          q_out);

        SET_PIXEL_GREEN (new_image,
          (GET_PIXEL_GREEN(image,pbr) - left_g - up_g + upleft_g)/alpha ADD_HALF,
          q_out);

        SET_PIXEL_BLUE (new_image,
          (GET_PIXEL_BLUE(image,pbr) - left_b - up_b + upleft_b)/alpha ADD_HALF,
          q_out);
      }

      q_out += outXmult;
    }

    if (SyncCacheViewAuthenticPixels(out_view,exception) == MagickFalse) {
      fprintf (stderr, "bad sync\n");
      status=MagickFalse;
    }
  }

  image_view=DestroyCacheView(image_view);
  out_view=DestroyCacheView(out_view);

  return (new_image);
}


ModuleExport size_t deintegimImage(Image **images,const int argc,
  const char **argv,ExceptionInfo *exception)
{
  Image
    *image,
    *new_image;

  MagickBooleanType
    status;

  deintegimT
    di;

  assert(images != (Image **) NULL);
  assert(*images != (Image *) NULL);
  assert((*images)->signature == MAGICK_CORE_SIG);

  status = menu (argc, argv, &di);
  if (status == MagickFalse)
    return (-1);

  for (image=(*images); image != (Image *) NULL; image=GetNextImageInList(image))
  {
    new_image = deintegim (image, &di, exception);
    if (!new_image) return(-1);

    ReplaceImageInList(&image,new_image);
    *images=GetFirstImageInList(image);
  }

  return(MagickImageFilterSignature);
}

deintegim2.c

/*
    Reference:
      http://im.snibgo.com/customim.htm
      http://im.snibgo.com/integim.htm

    Latest update:
      24-July-2017 Fixed bug on top line and left column.
                   (x1 and y1 can now be -1.)
      3-April-2018 for v7.0.7-28
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include "vsn_defines.h"

typedef enum {
  aaAuto,
  aaDisregard,
  aaRegard
} alphaActionT;

typedef struct {
  FILE *
    fh_data;

  char
    *sWindow;

  MagickBooleanType
    do_regardalpha,
    do_verbose,
    dontDivide;

  alphaActionT
    alphaAction;

  ssize_t
    offsetx,
    offsety,
    winWidth,
    winHeight;
} deintegimT;

static void usage (void)
{
  printf ("Usage: -process 'deintegim [OPTION]...'\n");
  printf ("De-integrate an HDRI integral image (summed area table).\n");
  printf ("\n");
  printf ("  w, window string    window size, WxH\n");
  printf ("  pd, postdiv         'yes' or 'no' or 'auto'\n");
  printf ("  dd, dontdivide      don't divide colour channels\n");
  printf ("  ox, offsetX int     offset for window centre in X-direction\n");
  printf ("  oy, offsetY int     offset for window centre in Y-direction\n");
  printf ("  f, file string      Write verbose text to stderr or stdout\n");
  printf ("  v, verbose          write text information to stdout\n");
  printf ("\n");
}


static MagickBooleanType IsArg (char * pa, char * ShtOpt, char * LongOpt)
{
  if ((LocaleCompare(pa, ShtOpt)==0) || (LocaleCompare(pa, LongOpt)==0))
    return MagickTrue;

  return MagickFalse;
}


static MagickBooleanType menu(
  const int argc,
  const char **argv,
  deintegimT * pch
)
// Returns MagickTrue if okay.
{
  int
    i;

  MagickBooleanType
    status;

  status = MagickTrue;

  pch->fh_data = stderr;
  pch->sWindow = NULL;
  pch->alphaAction = aaRegard;

  pch->do_regardalpha =
    pch->do_verbose = MagickFalse;
  pch->dontDivide = MagickFalse;
  pch->offsetx = 0;
  pch->offsety = 0;

  for (i=0; i < argc; i++) {
    char * pa = (char *)argv[i];

    if (IsArg (pa, "w", "window")==MagickTrue) {
      i++;
      pch->sWindow = (char *)argv[i];
    } else if (IsArg (pa, "pd", "postdiv")==MagickTrue) {
      i++;
      if (strcasecmp (argv[i], "auto")==0) pch->alphaAction = aaAuto;
      else if (strcasecmp (argv[i], "yes")==0) pch->alphaAction = aaRegard;
      else if (strcasecmp (argv[i], "no")==0) pch->alphaAction = aaDisregard;
      else status = MagickFalse;
    } else if (IsArg (pa, "f", "file")==MagickTrue) {
      i++;
      if (strcasecmp (argv[i], "stdout")==0) pch->fh_data = stdout;
      else if (strcasecmp (argv[i], "stderr")==0) pch->fh_data = stderr;
      else status = MagickFalse;
    } else if (IsArg (pa, "ox", "offsetX")==MagickTrue) {
      i++;
      pch->offsetx = atoi(argv[i]);
    } else if (IsArg (pa, "oy", "offsetY")==MagickTrue) {
      i++;
      pch->offsety = atoi(argv[i]);
    } else if (IsArg (pa, "dd", "dontdivide")==MagickTrue) {
      pch->dontDivide = MagickTrue;
    } else if (IsArg (pa, "v", "verbose")==MagickTrue) {
      pch->do_verbose = MagickTrue;
    } else {
      fprintf (stderr, "deintegim: ERROR: unknown option [%s]\n", pa);
      status = MagickFalse;
    }
  }

  if (pch->do_verbose) {
    fprintf (stderr, "deintegim options:");
    if (pch->sWindow) fprintf (stderr, "  window %s", pch->sWindow);
    if (pch->offsetx) fprintf (stderr, "  offsetX %li", pch->offsetx);
    if (pch->offsety) fprintf (stderr, "  offsetY %li", pch->offsety);
    if (pch->alphaAction != aaAuto) {
      fprintf (stderr, "  postdiv ");
      if (pch->alphaAction==aaRegard) fprintf (stderr, "yes");
      else if (pch->alphaAction==aaDisregard) fprintf (stderr, "no");
      else fprintf (stderr, "??");
    }
    if (pch->fh_data == stdout) fprintf (stderr, "  file stdout");
    if (pch->dontDivide) fprintf (stderr, "  dontdivide");
    if (pch->do_verbose) fprintf (stderr, "  verbose");
    fprintf (stderr, "\n");
  }

  if (status == MagickFalse)
    usage ();

  return (status);
}



static MagickBooleanType GetNumber (
  char ** p,
  double *pValue,
  ssize_t dim)
// Returns whether valid.
{
  int len;
  if (sscanf (*p, "%lf%n", pValue, &len) != 1) return MagickFalse;
  *p += len;
  char c = **p;
  switch (c) {
    case 'c':
    case 'C':
    case '%':
      *pValue = floor (*pValue/100.0 * dim + 0.5);
      if (*pValue < 1) *pValue = 1;
      (*p)++;
      break;
    case 'p':
    case 'P':
      *pValue = floor (*pValue * dim + 0.5);
      if (*pValue < 1) *pValue = 1;
      (*p)++;
      break;
    case '\0':
    case 'x':
    case 'X':
      /* nothing */ ;
      break;
    default:
      printf ("Bad terminator? [%c]\n", c);
  }
  return MagickTrue;
}


static MagickBooleanType ParseWindow (
  deintegimT * pch,
  ssize_t imgWidth,
  ssize_t imgHeight
)
// Expected format: WxH
// where W and H are numbers optionally followed by '%' or 'c' or 'p'.
// Returns whether okay.
{
  double w=0, h=0;

  char * p = pch->sWindow;

  if (!*p) return MagickFalse;

  if (!GetNumber (&p, &w, imgWidth)) {
    fprintf (stderr, "deintegim error: Window needs two dimensions.\n");
    return MagickFalse;
  }

  if (*p!='x') {
    fprintf (stderr, "deintegim error: Window needs 'x' betweem two dimensions.\n");
    return MagickFalse;
  }

  p++;
  if (!GetNumber (&p, &h, imgHeight)) {
    fprintf (stderr, "deintegim error: Window needs two dimensions.\n");
    return MagickFalse;
  }

  if (w < 1 || h < 1) {
    fprintf (stderr, "deintegim error: Window dimensions must be >=1.\n");
    return MagickFalse;
  }

  if (*p) {
    fprintf (stderr, "deintegim error: Window dimensions have extra characters [%s].\n", p);
    return MagickFalse;
  }

  pch->winWidth = w;
  pch->winHeight = h;

  return MagickTrue;
}


#if defined(MAGICKCORE_HDRI_SUPPORT)
#define ADD_HALF
#else
#define ADD_HALF +0.5
#endif


// The next function corresponds in style to functions in transform.c
// It takes one image, and returns an image.
//
static Image * deintegim2 (
  Image **images,
  deintegimT * pch,
  ExceptionInfo *exception)
{
  Image
    *new_image;

  CacheView
    *imageA_view,
    *imageB_view,
    *out_view;

  ssize_t
    y,
    inBmult,
    outXmult;

  Image * imageA = *images;

  MagickBooleanType
    IsAlphaOn = IS_ALPHA_CH(imageA),
    status;

  if (imageA->debug != MagickFalse)
    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",imageA->filename);

  assert(imageA != (Image *) NULL);
  assert(imageA->signature == MAGICK_CORE_SIG);
  if (imageA->debug != MagickFalse)
    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",imageA->filename);

  Image * imageB = GetNextImageInList (imageA);
  if (!imageB) {
    fprintf (stderr, "deintegim2: needs two images, the same size\n");
    return MagickFalse;
  }

  if (imageA->columns != imageB->columns ||
      imageA->rows    != imageB->rows)
  {
    fprintf (stderr, "deintegim2: the two images must be the same size\n");
    return MagickFalse;
  }

  if (SetNoPalette (imageA, exception) == MagickFalse)
    return MagickFalse;

  if (SetNoPalette (imageB, exception) == MagickFalse)
    return MagickFalse;

  //const ssize_t incA = Inc_ViewPixPtr (imageA);
  //const ssize_t incB = Inc_ViewPixPtr (imageB);

  if (pch->do_verbose) {
    fprintf (pch->fh_data,
             "deintegim2: Input image [%s] depth is %i IsAlpha %s\n",
             imageA->filename,
             (int)imageA->depth,
             IsAlphaOn ? "ON":"OFF"
            );
  }

  pch->winWidth = pch->winHeight = 1;

  if (pch->sWindow) {
    if (! ParseWindow (pch, imageA->columns, imageA->rows))
      return (Image *)NULL;
  }

  if (pch->do_verbose)
    fprintf (pch->fh_data, "deintegim Window: %lix%li\n",
      pch->winWidth, pch->winHeight);

  ssize_t winSemiHt = (pch->winHeight+1) / 2;
  ssize_t winSemiWi = (pch->winWidth+1) / 2;

  switch (pch->alphaAction) {
    case aaAuto:
      pch->do_regardalpha = IsAlphaOn;
      if (pch->do_verbose)
        fprintf (pch->fh_data, "deintegim2: alphaaction auto: regardalpha %s\n",
          pch->do_regardalpha ? "TRUE" : "FALSE");
      break;
    case aaRegard:
      pch->do_regardalpha = MagickTrue;
      break;
    case aaDisregard:
      pch->do_regardalpha = MagickFalse;
      break;
  }

  MagickBooleanType UseAlpha = (IsAlphaOn && pch->do_regardalpha);

  // Note: We can't do this in-place,
  // because an output pixel generally depends on a _later_ input pixel.

  new_image=CloneImage(imageA, imageA->columns, imageA->rows, MagickTrue, exception);
  if (new_image == (Image *) NULL)
    return(new_image);

  // Just in case image had a palette.
  if (SetNoPalette (new_image, exception) == MagickFalse)
    return (Image *)NULL;

  //inXmult = Inc_ViewPixPtr (imageA);
  inBmult = Inc_ViewPixPtr (imageB);
  outXmult = Inc_ViewPixPtr (new_image);

  status=MagickTrue;

  imageA_view=AcquireVirtualCacheView(imageA,exception);
  imageB_view=AcquireVirtualCacheView(imageB,exception);

  out_view=AcquireAuthenticCacheView(new_image,exception);

#if defined(MAGICKCORE_OPENMP_SUPPORT)
  #pragma omp parallel for schedule(static,4) shared(status) \
    MAGICK_THREADS(new_image,new_image,new_image->rows,1)
#endif
  for (y=0; y < (ssize_t) new_image->rows; y++)
  {
    const VIEW_PIX_PTR
      *pB;

    VIEW_PIX_PTR
      *q_out;

    ssize_t
      y1, y2,
      x;

    long double
      alpha;

    if (status==MagickFalse) continue;

/*===
    p=GetCacheViewVirtualPixels(imageA_view,0,y2,imageA->columns,1,exception);
    if (!p) {
      fprintf (stderr, "deintegim2: bad GetCacheViewVirtualPixels imageA_view\n");
      status=MagickFalse;
    }

    if (y1 >= 0) {
      p_prev=GetCacheViewVirtualPixels(imageA_view,0,y1,imageA->columns,1,exception);
      if (!p_prev) {
        fprintf (stderr, "deintegim2: bad GetCacheViewAuthenticPixels p_prev\n");
        status=MagickFalse;
      }
    } else {
      p_prev = NULL;
    }
===*/

    pB=GetCacheViewVirtualPixels(imageB_view,0,y,imageB->columns,1,exception);
    if (!pB) {
      fprintf (stderr, "deintegim2: bad GetCacheViewVirtualPixels imageB_view\n");
      status=MagickFalse;
    }

    q_out=GetCacheViewAuthenticPixels(out_view,0,y,new_image->columns,1,exception);
    if (!q_out) {
      fprintf (stderr, "deintegim2: bad GetCacheViewAuthenticPixels out_view\n");
      status=MagickFalse;
    }

    alpha = 1.0;

    if (status==MagickFalse) continue;

    long double
      left_r = 0, up_r = 0, upleft_r = 0,
      left_g = 0, up_g = 0, upleft_g = 0,
      left_b = 0, up_b = 0, upleft_b = 0,
      left_a = 0, up_a = 0, upleft_a = 0;

    for (x=0; x < (ssize_t) new_image->columns; x++)
    {
      double factX = GET_PIXEL_RED(imageB, pB) / (long double)QuantumRange;
      double factY = GET_PIXEL_GREEN(imageB, pB) / (long double)QuantumRange;

      ssize_t winW = floor ((pch->winWidth-1) * factX + 1.5);
      winSemiWi = floor (winW/2.0 + 0.5);

      ssize_t x1 = x - winSemiWi + pch->offsetx;
      ssize_t x2 = x1 + winW;

      if (x1 < 0) x1 = -1;
      if (x2 > imageA->columns-1) x2 = imageA->columns-1;
      ssize_t winWi = x2 - x1;
      if (winWi==0) winWi = 1;

      ssize_t winH = floor ((pch->winHeight-1) * factY + 1.5);
      winSemiHt = floor (winH/2.0 + 0.5);

      y1 = y - winSemiHt + pch->offsety;
      y2 = y1 + winH;

      if (y1 < 0) y1 = -1;
      if (y2 > imageA->rows-1) y2 = imageA->rows-1;
      ssize_t winHt = y2 - y1;
      if (winHt==0) winHt = 1;

      int winArea = winHt * winWi;

      if (x1 >= 0) {
        //const VIEW_PIX_PTR * pv = p+x1*inXmult;
        const VIEW_PIX_PTR * pv = GetCacheViewVirtualPixels(imageA_view,x1,y2,1,1,exception);
        if (!pv) {
          fprintf (stderr, "deintegim2: bad GetCacheViewVirtualPixels imageA_view\n");
          status=MagickFalse;
        }
        left_r = GET_PIXEL_RED(imageA, pv);
        left_g = GET_PIXEL_GREEN(imageA, pv);
        left_b = GET_PIXEL_BLUE(imageA, pv);
        left_a = GET_PIXEL_ALPHA(imageA, pv);
      }

      if (y1 >= 0) {
        //const VIEW_PIX_PTR * pv = p_prev+x2*inXmult;
        const VIEW_PIX_PTR * pv = GetCacheViewVirtualPixels(imageA_view,x2,y1,1,1,exception);
        if (!pv) {
          fprintf (stderr, "deintegim2: bad GetCacheViewVirtualPixels imageA_view\n");
          status=MagickFalse;
        }
        up_r = GET_PIXEL_RED(imageA, pv);
        up_g = GET_PIXEL_GREEN(imageA, pv);
        up_b = GET_PIXEL_BLUE(imageA, pv);
        up_a = GET_PIXEL_ALPHA(imageA, pv);
        if (x1 >= 0) {
          //const VIEW_PIX_PTR * pv = p_prev + x1*inXmult;
          const VIEW_PIX_PTR * pv = GetCacheViewVirtualPixels(imageA_view,x1,y1,1,1,exception);
          if (!pv) {
            fprintf (stderr, "deintegim2: bad GetCacheViewVirtualPixels imageA_view\n");
            status=MagickFalse;
          }
          upleft_r = GET_PIXEL_RED(imageA, pv);
          upleft_g = GET_PIXEL_GREEN(imageA, pv);
          upleft_b = GET_PIXEL_BLUE(imageA, pv);
          upleft_a = GET_PIXEL_ALPHA(imageA, pv);
        }
      }

      // point to bottom-right of window
      //const VIEW_PIX_PTR * pbr = p + x2*inXmult;
      const VIEW_PIX_PTR * pbr = GetCacheViewVirtualPixels(imageA_view,x2,y2,1,1,exception);
      if (!pbr) {
        fprintf (stderr, "deintegim2: bad GetCacheViewVirtualPixels imageA_view\n");
        status=MagickFalse;
      }

      if (IsAlphaOn) {
        // If alpha isn't on, image alpha values are 100%.
        alpha = GET_PIXEL_ALPHA(imageA,pbr) - left_a - up_a + upleft_a;

        SET_PIXEL_ALPHA (new_image, alpha / winArea ADD_HALF, q_out);
      }

      if (UseAlpha && alpha != 0) {
        alpha /= (long double)QuantumRange;
      } else {
        alpha = winArea;
      }

      if (pch->dontDivide) alpha = 1.0; // temp hack

      if (alpha == 0) {
        SET_PIXEL_RED (new_image, 0, q_out);
        SET_PIXEL_GREEN (new_image, 0, q_out);
        SET_PIXEL_BLUE (new_image, 0, q_out);
      } else {
        SET_PIXEL_RED (new_image,
          (GET_PIXEL_RED(imageA,pbr) - left_r - up_r + upleft_r)/alpha ADD_HALF,
          q_out);

        SET_PIXEL_GREEN (new_image,
          (GET_PIXEL_GREEN(imageA,pbr) - left_g - up_g + upleft_g)/alpha ADD_HALF,
          q_out);

        SET_PIXEL_BLUE (new_image,
          (GET_PIXEL_BLUE(imageA,pbr) - left_b - up_b + upleft_b)/alpha ADD_HALF,
          q_out);
      }

      pB += inBmult;
      q_out += outXmult;
    }

    if (SyncCacheViewAuthenticPixels(out_view,exception) == MagickFalse) {
      fprintf (stderr, "bad sync\n");
      status=MagickFalse;
    }
  }

  imageA_view=DestroyCacheView(imageA_view);
  imageB_view=DestroyCacheView(imageB_view);
  out_view=DestroyCacheView(out_view);

  DeleteImageFromList (&imageB);

  return (new_image);
}


ModuleExport size_t deintegim2Image(Image **images,const int argc,
  const char **argv,ExceptionInfo *exception)
{
  MagickBooleanType
    status;

  deintegimT
    di;

  assert(images != (Image **) NULL);
  assert(*images != (Image *) NULL);
  assert((*images)->signature == MAGICK_CORE_SIG);

  status = menu (argc, argv, &di);
  if (status == MagickFalse)
    return (-1);

  int ListLen = (int)GetImageListLength(*images);
  if (ListLen != 2) {
    fprintf (stderr, "deintegim2: needs 2 images\n");
    return (-1);
  }

  Image * new_image = deintegim2 (images, &di, exception);
  if (!new_image) return(-1);

  ReplaceImageInList (images, new_image);
  *images=GetFirstImageInList(new_image);

  return(MagickImageFilterSignature);
}

kcluster.c

/*
   Reference: http://im.snibgo.com/kcluster.htm

   Last update: 9-August-2017.
     18-August-2017 added SetNoPalette ()
     21-August-2017 removed 'j' option; 'k' can now be range.
     7-September-2017 for v7.
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <math.h>
#include "vsn_defines.h"

#define VERSION "kcluster v1.0  Copyright (c) 2017 Alan Gibson"

typedef enum {
  mNull,
  mMeans,
  mFuzzy,
  mHarmonic
} methodT;

typedef enum {
  iColors,
  iForgy,
  iRandPart,
  iSdLine,
  iFromList
} initT;

typedef struct {
  double r;
  double g;
  double b;
} normaliseT;

typedef struct {
  Quantum red;
  Quantum green;
  Quantum blue;
  Quantum alpha;
  double  count;
} clusterT;

typedef struct {
  int
    verbose;

  methodT
    method;

  initT
    init;

  int
    precision,
    jump0,
    jump1,
    kCols,
    maxIter;

  MagickBooleanType
    hasJump,
    hasSetY,
    regardAlpha,
    sdNorm,
    dither,
    debug;

  normaliseT
    normalise;

  double
    fuzzyR,
    harmonicP,
    tolerance,
    jumpY;

  double
    powParam1,
    powParam2,
    powParam3;

  FILE *
    fh_data;

  RandomInfo
    *random_info;

  ssize_t
    *nearClust;

  clusterT
    *clusters;

  char
    *frameName;

  int
    frameNum;

  double
    *membDenoms;
} kclusterT;



static void usage (void)
{
  printf ("Usage: -process 'kcluster [OPTION]...'\n");
  printf ("Apply a k-clustering method.\n");
  printf ("\n");
  printf ("  m, method string        'Null', 'Means', 'Fuzzy' or 'Harmonic'\n");
  printf ("  k, kCols int[-int]      number [range] of clusters to find\n");
//  printf ("  j, jump integer,integer find k\n");
  printf ("  s, sdNorm               normalise channel SD\n");
  printf ("  o, dither               dither output\n");
  printf ("  a, regardAlpha          process opacity\n");
  printf ("  i, initialize string    'Colors', 'Forgy', 'RandPart', 'SdLine' or 'FromList'\n");
  printf ("  x, maxIter integer      maximum number of iterations\n");
  printf ("  y, jumpY number         y for jump power\n");
  printf ("  r, fuzzyR number        r for fuzzy method\n");
  printf ("  p, harmonicP number     p for harmonic method\n");
  printf ("  t, tolerance number     tolerance\n");
  printf ("  w, write filename       write iterations eg frame_%%06d.png\n");

  printf ("  d, debug                write debugging information to stderr\n");
  printf ("  f, file string          write verbose to file stream stdout or stderr\n");
  printf ("  v, verbose              write text information\n");
  printf ("  v2, verbose2            write more text information\n");
  printf ("     version              write version information to stdout\n");
  printf ("\n");
}


static MagickBooleanType IsArg (char * pa, char * ShtOpt, char * LongOpt)
{
  // LocaleCompare is not case-sensitive,
  // so we use strcmp for the short option.

  if ((strcmp(pa, ShtOpt)==0) || (LocaleCompare(pa, LongOpt)==0))
    return MagickTrue;

  return MagickFalse;
}


static void InitNormalise (
  kclusterT * pkc
)
{
  pkc->normalise.r = 1.0;
  pkc->normalise.g = 1.0;
  pkc->normalise.b = 1.0;
}


static MagickBooleanType GetInteger (
  char ** p,
  int    *pValue)
// Returns whether valid.
{
  int len;
  if (sscanf (*p, "%i%n", pValue, &len) != 1) return MagickFalse;
  *p += len;
  char c = **p;
  switch (c) {
    case '\0':
    case ',':
      /* nothing */ ;
      break;
    default:
      printf ("Bad terminator? [%c]\n", c);
  }
  return MagickTrue;
}

static MagickBooleanType ParseTwoJumps (
  kclusterT * pkc,
  char * p
)
// Expected format: A,B
// where A and B are numbers.
// Returns whether okay.
{
  int v0=0, v1=0;

  //char * p = pch->sWindow;

  if (!*p) return MagickFalse;

  if (!GetInteger (&p, &v0)) {
    fprintf (stderr, "kcluster error: needs two integers.\n");
    return MagickFalse;
  }

  if (*p!=',') {
    fprintf (stderr, "kcluster error: needs 'x' betweem two integers.\n");
    return MagickFalse;
  }

  p++;
  if (!GetInteger (&p, &v1)) {
    fprintf (stderr, "kcluster error: needs two integers.\n");
    return MagickFalse;
  }

  if (v0 < 1 || v1 < v0) {
    fprintf (stderr, "kcluster error: integers must be >=1, ascending.\n");
    return MagickFalse;
  }

  if (*p) {
    fprintf (stderr, "kcluster error: extra characters [%s].\n", p);
    return MagickFalse;
  }

  pkc->jump0 = v0;
  pkc->jump1 = v1;

  return MagickTrue;
}


static MagickBooleanType ParseRange (
  kclusterT * pkc,
  char * p,
  int * valA,
  int * valB,
  int * numVals
)
// Expected format: A or A-B where A and B are integers.
// Returns whether okay.
{
  *numVals = 0;
  int len;
  if (sscanf (p, "%i%n", valA, &len) != 1) return MagickFalse;
  p += len;
  *numVals = 1;

  if (*p == '-') {
    p++;

    if (sscanf (p, "%i%n", valB, &len) != 1) return MagickFalse;
    p += len;
    *numVals = 2;
  }
  if (*p) {
    fprintf (stderr, "kcluster ParseRange error: extra characters [%s].\n", p);
    return MagickFalse;
  }
  return MagickTrue;
}




#define NEXTARG \
  if (i < argc-1) i++; \
  else { \
    fprintf (stderr, "kcluster: ERROR: [%s] needs argument\n", pa); \
    status = MagickFalse; \
  }

static MagickBooleanType menu(
  const int argc,
  const char **argv,
  kclusterT * pkc
)
// Returns MagickTrue if okay.
{
  int
    i;

  MagickBooleanType
    status = MagickTrue;

  char ** pargv = (char **)argv;

  pkc->verbose = 0;
  pkc->init = iColors;
  pkc->regardAlpha = MagickFalse;
  pkc->debug = MagickFalse;
  pkc->sdNorm = MagickFalse;
  pkc->dither = MagickFalse;
  pkc->method = mMeans;
  pkc->hasJump = MagickFalse;
  pkc->hasSetY = MagickFalse;
  pkc->jumpY = 0;
  pkc->jump0 = 1;
  pkc->jump1 = 10;
  pkc->kCols = 10;
  pkc->maxIter = 100;
  pkc->fuzzyR = 1.5;
  pkc->harmonicP = 3.5;
  pkc->tolerance = 0.01;
  pkc->fh_data = stderr;
  pkc->random_info = NULL;
  pkc->frameName = NULL;
  pkc->frameNum = 0;
  pkc->clusters = NULL;
  pkc->membDenoms = NULL;
  InitNormalise (pkc);

  for (i=0; i < argc; i++) {
    char * pa = (char *)argv[i];

    if (IsArg (pa, "m", "method")==MagickTrue) {
      NEXTARG;
      if (LocaleCompare(argv[i], "means")==0) pkc->method = mMeans;
      else if (LocaleCompare(argv[i], "fuzzy")==0) pkc->method = mFuzzy;
      else if (LocaleCompare(argv[i], "harmonic")==0) pkc->method = mHarmonic;
      else if (LocaleCompare(argv[i], "null")==0) pkc->method = mNull;
      else {
        fprintf (stderr, "kcluster: ERROR: unknown method [%s]\n", argv[i]);
        status = MagickFalse;
      }
    } else if (IsArg (pa, "k", "kCols")==MagickTrue) {
      NEXTARG;
      pkc->hasJump = MagickFalse;
      pkc->kCols = atoi(pargv[i]);

      int valA, valB, numVals;
      if (ParseRange (pkc, pargv[i], &valA, &valB, &numVals)) {
        if (numVals==1) {
          pkc->hasJump = MagickFalse;
          pkc->kCols = valA;
        } else if (numVals==2) {
          pkc->hasJump = MagickTrue;
          pkc->jump0 = valA;
          pkc->jump1 = valB;
        } else {
          fprintf (stderr, "kcluster: ERROR: bad kCols [%s]\n", argv[i]);
          status = MagickFalse;
        }
      } else {
        fprintf (stderr, "kcluster: ERROR: bad kCols [%s]\n", argv[i]);
        status = MagickFalse;
      }

    } else if (IsArg (pa, "j", "jump")==MagickTrue) {
      NEXTARG;
      pkc->hasJump = MagickTrue;
      if (!ParseTwoJumps (pkc, pargv[i])) status = MagickFalse;
    } else if (IsArg (pa, "i", "initialize")==MagickTrue) {
      NEXTARG;
      if (LocaleCompare(argv[i], "colors")==0) pkc->init = iColors;
      else if (LocaleCompare(argv[i], "colours")==0) pkc->init = iColors;
      else if (LocaleCompare(argv[i], "forgy")==0) pkc->init = iForgy;
      else if (LocaleCompare(argv[i], "randPart")==0) pkc->init = iRandPart;
      else if (LocaleCompare(argv[i], "sdLine")==0) pkc->init = iSdLine;
      else if (LocaleCompare(argv[i], "fromList")==0) pkc->init = iFromList;
      else {
        fprintf (stderr, "kcluster: ERROR: unknown initialize [%s]\n", argv[i]);
        status = MagickFalse;
      }
    } else if (IsArg (pa, "x", "maxIter")==MagickTrue) {
      NEXTARG;
      pkc->maxIter = atoi(pargv[i]);
    } else if (IsArg (pa, "y", "jumpY")==MagickTrue) {
      NEXTARG;
      pkc->hasSetY = MagickTrue;
      pkc->jumpY = atof(pargv[i]);
    } else if (IsArg (pa, "r", "fuzzyR")==MagickTrue) {
      NEXTARG;
      pkc->fuzzyR = atof(pargv[i]);
    } else if (IsArg (pa, "p", "harmonicP")==MagickTrue) {
      NEXTARG;
      pkc->harmonicP = atof(pargv[i]);
    } else if (IsArg (pa, "t", "tolerance")==MagickTrue) {
      NEXTARG;
      pkc->tolerance = atof(pargv[i]);
    } else if (IsArg (pa, "s", "sdNorm")==MagickTrue) {
      pkc->sdNorm = MagickTrue;
    } else if (IsArg (pa, "o", "dither")==MagickTrue) {
      pkc->dither = MagickTrue;
    } else if (IsArg (pa, "a", "regardAlpha")==MagickTrue) {
      pkc->regardAlpha = MagickTrue;
    } else if (IsArg (pa, "w", "write")==MagickTrue) {
      NEXTARG;
      pkc->frameName = pargv[i];
    } else if (IsArg (pa, "d", "debug")==MagickTrue) {
      pkc->debug = MagickTrue;
    } else if (IsArg (pa, "f", "file")==MagickTrue) {
      NEXTARG;
      if (strcasecmp (argv[i], "stdout")==0) pkc->fh_data = stdout;
      else if (strcasecmp (argv[i], "stderr")==0) pkc->fh_data = stderr;
      else status = MagickFalse;
    } else if (IsArg (pa, "v", "verbose")==MagickTrue) {
      pkc->verbose = 1;
    } else if (IsArg (pa, "v2", "verbose2")==MagickTrue) {
      pkc->verbose = 2;
    } else if (IsArg (pa, "version", "version")==MagickTrue) {
      fprintf (stdout, "%s\n", VERSION);
    } else {
      fprintf (stderr, "kcluster: ERROR: unknown option [%s]\n", pa);
      status = MagickFalse;
    }
  }

  if (pkc->dither && pkc->method != mFuzzy) {
    fprintf (stderr, "kcluster: WARNING dither is available only for fuzzy.");
    pkc->dither = MagickFalse;
  }

  if (pkc->verbose) {
    fprintf (stderr, "kcluster options: ");

    fprintf (stderr, "  method ");
    switch (pkc->method) {
      case mNull:
        fprintf (stderr, "Null");
        break;
      case mMeans:
        fprintf (stderr, "Means");
        break;
      case mFuzzy:
        fprintf (stderr, "Fuzzy");
        fprintf (stderr, "  fuzzyR %.*g", pkc->precision, pkc->fuzzyR);
        break;
      case mHarmonic:
        fprintf (stderr, "Harmonic");
        fprintf (stderr, "  harmonicP %.*g", pkc->precision, pkc->harmonicP);
        break;
    }
    if (pkc->hasJump) {
      fprintf (stderr, "  kCols %i-%i", pkc->jump0, pkc->jump1);
      if (pkc->hasSetY) fprintf (stderr, "  jumpY %.*g", pkc->precision, pkc->jumpY);
    }

    if (pkc->init != iFromList && !pkc->hasJump)
      fprintf (stderr, "  kCols %i", pkc->kCols);

    fprintf (stderr, "  initialize ");
    switch (pkc->init) {
      case iColors:
        fprintf (stderr, "Colors");
        break;
      case iForgy:
        fprintf (stderr, "Forgy");
        break;
      case iRandPart:
        fprintf (stderr, "RandPart");
        break;
      case iSdLine:
        fprintf (stderr, "SdLine");
        break;
      case iFromList:
        fprintf (stderr, "FromList");
        break;
    }
    fprintf (stderr, "  tolerance %.*g", pkc->precision, pkc->tolerance);
    fprintf (stderr, "  maxIter %i", pkc->maxIter);

    if (pkc->sdNorm)          fprintf (stderr, "  sdNorm");
    if (pkc->dither)          fprintf (stderr, "  dither");
    if (pkc->regardAlpha)     fprintf (stderr, "  regardAlpha");

    if (pkc->frameName)       fprintf (stderr, "  write %s", pkc->frameName);

    if (pkc->debug)           fprintf (stderr, "  debug");
    if (pkc->fh_data == stdout) fprintf (stderr, " file stdout");
    if (pkc->verbose==1)      fprintf (stderr, "  verbose");
    else if (pkc->verbose==2) fprintf (stderr, "  verbose2");
    fprintf (stderr, "\n");
  }

  if (status == MagickFalse)
    usage ();

  return (status);
}




static void InitRand (kclusterT * pkc)
{
  pkc->random_info=AcquireRandomInfo();

  // There seems to be a problem: the first few values show coherency,
  // so skip over them.

  int i;
  for (i=0; i < 20; i++) {
    GetPseudoRandomValue(pkc->random_info);
  }
}

static void DeInitRand (kclusterT * pkc)
{
  if (pkc->random_info)
    pkc->random_info=DestroyRandomInfo(pkc->random_info);
}

static void DumpImage (
  kclusterT * pkc,
  Image *image,
  ExceptionInfo *exception)
{
  CacheView *in_view = AcquireVirtualCacheView (image, exception);

  ssize_t x, y;
  for (y=0; y < (ssize_t) image->rows; y++) {
    const VIEW_PIX_PTR *p=GetCacheViewVirtualPixels(in_view,0,y,image->columns,1,exception);
    if (p == (const VIEW_PIX_PTR *) NULL) return;

    for (x=0; x < (ssize_t) image->columns; x++) {
      fprintf (pkc->fh_data, "%li,%li: %g %g %g  %g\n",
        x, y,
        GET_PIXEL_RED(image,p),
        GET_PIXEL_GREEN(image,p),
        GET_PIXEL_BLUE(image,p),
        GET_PIXEL_ALPHA(image,p));

      p += Inc_ViewPixPtr (image);
    }
  }

  in_view = DestroyCacheView (in_view);
}

static void getRandomPixel (
  kclusterT * pkc,
  Image *image,
  Image *forgy_img,
  CacheView *in_view,
  VIEW_PIX_PTR * pf,  // points to pixel in forgy_img
  ExceptionInfo *exception
)
{
  // If we are regarding alpha,
  // try to get non-transparent colour.

  int i;
  for (i=0; i < 10; i++) {
    ssize_t x = image->columns * GetPseudoRandomValue(pkc->random_info);
    ssize_t y = image->rows * GetPseudoRandomValue(pkc->random_info);
    const VIEW_PIX_PTR *pi = GetCacheViewVirtualPixels(in_view,x,y,1,1,exception);

    SET_PIXEL_RED   (forgy_img, GET_PIXEL_RED(image,pi), pf);
    SET_PIXEL_GREEN (forgy_img, GET_PIXEL_GREEN(image,pi), pf);
    SET_PIXEL_BLUE  (forgy_img, GET_PIXEL_BLUE(image,pi), pf);
    double a = GET_PIXEL_ALPHA(image,pi);
    if (a < 0) a = 0;
    SET_PIXEL_ALPHA (forgy_img, a, pf);

    if (!pkc->regardAlpha) break;
    if (a > 0) break;
  }
}

static Image * InitialiseForgy (
  kclusterT * pkc,
  Image *image,
  ExceptionInfo *exception
)
{
  if (!pkc->random_info) InitRand (pkc);

  Image *forgy_img = CloneImage (image, pkc->kCols,1, MagickFalse, exception);
  if (!forgy_img) return NULL;

  CacheView *in_view = AcquireVirtualCacheView (image, exception);
  CacheView *forgy_view = AcquireAuthenticCacheView (forgy_img, exception);

  VIEW_PIX_PTR * pf = GetCacheViewAuthenticPixels (
    forgy_view,0,0,forgy_img->columns,1,exception);

  int i;
  for (i=0; i < pkc->kCols; i++) {
    getRandomPixel (pkc, image, forgy_img, in_view, pf, exception);

    pf += Inc_ViewPixPtr (forgy_img);
  }
  if (!SyncCacheViewAuthenticPixels(forgy_view,exception))
    return NULL;

  forgy_view = DestroyCacheView (forgy_view);
  in_view    = DestroyCacheView (in_view);

  // FIXME: then ensure they are unique.

  return forgy_img;
}

static Image * MakeUnique (
  kclusterT * pkc,
  Image *image,
  Image *sml_image,
  ExceptionInfo *exception
)
// Returns image of unique colours, or NULL if failure.
{
  Image *uniq_img = UniqueImageColors (sml_image, exception);
  if (!uniq_img) return NULL;

  if (pkc->debug) {
    fprintf (stderr, "AfterUnique:\n");
    DumpImage (pkc, uniq_img, exception);
  }

  if (uniq_img->columns < pkc->kCols) {
    if (pkc->verbose) {
      fprintf (pkc->fh_data, "kcluster: wanted %i but found %li unique\n",
        pkc->kCols, uniq_img->columns);
    }
    ssize_t skipOver = uniq_img->columns;

    uniq_img->gravity = WestGravity;
    // Background?
#if IMV6OR7==6
    SetImageExtent (uniq_img, pkc->kCols, 1);
#else
    SetImageExtent (uniq_img, pkc->kCols, 1, exception);
#endif
    if (pkc->verbose) {
      fprintf (pkc->fh_data, "kcluster: -- now have %li\n", uniq_img->columns);
    }

    if (!pkc->random_info) InitRand (pkc);

    CacheView *in_view = AcquireVirtualCacheView (image, exception);
    CacheView *uniq_view = AcquireAuthenticCacheView (uniq_img, exception);

    VIEW_PIX_PTR * pu = GetCacheViewAuthenticPixels (
      uniq_view, 0, 0, uniq_img->columns, 1, exception);
    pu += skipOver * Inc_ViewPixPtr (uniq_img);
    int i;
    for (i = skipOver; i < uniq_img->columns; i++) {
      getRandomPixel (pkc, image, uniq_img, in_view, pu, exception);
      pu += Inc_ViewPixPtr (uniq_img);
    }
    if (!SyncCacheViewAuthenticPixels(uniq_view,exception))
      return NULL;
    uniq_view = DestroyCacheView (uniq_view);
    in_view = DestroyCacheView (in_view);
  }

  if (pkc->debug) {
    fprintf (stderr, "After padding:\n");
    DumpImage (pkc, uniq_img, exception);
  }

  if (pkc->regardAlpha) {
    // If we have caught any fully-transparent colours,
    // try for better ones.

    if (!pkc->random_info) InitRand (pkc);

    CacheView *in_view = AcquireVirtualCacheView (image, exception);
    CacheView *uniq_view = AcquireAuthenticCacheView (uniq_img, exception);

    VIEW_PIX_PTR * pu = GetCacheViewAuthenticPixels(uniq_view,0,0,uniq_img->columns,1,exception);
    int i;
    for (i=0; i < uniq_img->columns; i++) {
      if (GET_PIXEL_ALPHA(uniq_img,pu) <= 0) {
        getRandomPixel (pkc, image, uniq_img, in_view, pu, exception);
      }
      pu += Inc_ViewPixPtr (uniq_img);
    }
    if (!SyncCacheViewAuthenticPixels(uniq_view,exception))
      return NULL;
    uniq_view = DestroyCacheView (uniq_view);
    in_view = DestroyCacheView (in_view);
  }
  return uniq_img;
}

static Image * InitialiseColors (
  kclusterT * pkc,
  Image *image,
  ExceptionInfo *exception
)
// Returns image of unique colours, or NULL if failure.
{
  Image *quant_img = CloneImage (image, 0,0, MagickFalse, exception);
  if (!quant_img) return NULL;

  QuantizeInfo *qi = AcquireQuantizeInfo (NULL);
  qi->number_colors = pkc->kCols;
  qi->dither_method = NoDitherMethod;

#if IMV6OR7==6
  qi->dither = MagickFalse;
  if (!QuantizeImage (qi, quant_img)) return NULL;
#else
  if (!QuantizeImage (qi, quant_img, exception)) return NULL;
#endif

  qi = DestroyQuantizeInfo (qi);

  Image * uniq_img = MakeUnique (pkc, image, quant_img, exception);

  Image * uniq_img2 = MakeUnique (pkc, image, uniq_img, exception);

  // FIXME: free images

  return uniq_img2;
}

static Image * InitialiseRandomPartition (
  kclusterT * pkc,
  Image *image,
  ExceptionInfo *exception
)
// Returns image of unique colours, or NULL if failure.
{
  // Assign each image pixel to a random partition.
  // Set each partition to the average of the pixels (each weighted by alpha).

  if (!pkc->random_info) InitRand (pkc);

  Image *rp_img = CloneImage (image, pkc->kCols,1, MagickFalse, exception);
  if (!rp_img) return NULL;

  if (!pkc->clusters) {
    fprintf (stderr, "kcluster: BUG: irp no clusters");
    return NULL;
  }

  CacheView *in_view = AcquireVirtualCacheView (image, exception);
  CacheView *rp_view = AcquireAuthenticCacheView (rp_img, exception);

  ssize_t x, y, qx;

  for (qx = 0; qx < rp_img->columns; qx++) {
    clusterT * pClust = &pkc->clusters[qx];
    pClust->red =
      pClust->green =
      pClust->blue =
      pClust->alpha =
      pClust->count = 0;
  }

  double r, g, b, a;

  for (y=0; y < (ssize_t) image->rows; y++) {
    const VIEW_PIX_PTR *p=GetCacheViewVirtualPixels(in_view,0,y,image->columns,1,exception);
    if (p == (const VIEW_PIX_PTR *) NULL) return MagickFalse;

    for (x=0; x < (ssize_t) image->columns; x++) {
      a = GET_PIXEL_ALPHA(image,p) / (double)QuantumRange;
      r = a * GET_PIXEL_RED(image,p) / (double)QuantumRange;
      g = a * GET_PIXEL_GREEN(image,p) / (double)QuantumRange;
      b = a * GET_PIXEL_BLUE(image,p) / (double)QuantumRange;

      qx = rp_img->columns * GetPseudoRandomValue(pkc->random_info);

      clusterT * pClust = &pkc->clusters[qx];

      pClust->red += r;
      pClust->green += g;
      pClust->blue += b;
      pClust->alpha += a;
      pClust->count++;

      p += Inc_ViewPixPtr (image);
    }
  }

  // We may have clusters with count=0,
  //   no image pixels have been assigned to it.

  VIEW_PIX_PTR *rp=GetCacheViewAuthenticPixels(rp_view,0,0,rp_img->columns,1,exception);
  for (qx = 0; qx < rp_img->columns; qx++) {
    clusterT * pClust = &pkc->clusters[qx];

    if (pClust->count==0) {
      SET_PIXEL_ALPHA (rp_img, QuantumRange, rp);
      SET_PIXEL_RED (rp_img, 0, rp);
      SET_PIXEL_GREEN (rp_img, 0, rp);
      SET_PIXEL_BLUE (rp_img, 0, rp);
    } else {
      SET_PIXEL_ALPHA (rp_img, QuantumRange * pClust->alpha / pClust->count, rp);
      if (pClust->alpha == 0) pClust->alpha = 1.0;
      SET_PIXEL_RED   (rp_img, QuantumRange * pClust->red / pClust->alpha, rp);
      SET_PIXEL_GREEN (rp_img, QuantumRange * pClust->green / pClust->alpha, rp);
      SET_PIXEL_BLUE  (rp_img, QuantumRange * pClust->blue / pClust->alpha, rp);
    }
    rp += Inc_ViewPixPtr (rp_img);
  }

  if (!SyncCacheViewAuthenticPixels(rp_view,exception))
    return NULL;

  rp_view = DestroyCacheView (rp_view);
  in_view = DestroyCacheView (in_view);

  Image * uniq_img = MakeUnique (pkc, image, rp_img, exception);

  Image * uniq_img2 = MakeUnique (pkc, image, uniq_img, exception);

  // FIXME: free images

  return uniq_img2;
}



static MagickBooleanType CalcMeanSd (
  kclusterT * pkc,
  Image *image,
  double *mnR,
  double *mnG,
  double *mnB,
  double *sdR,
  double *sdG,
  double *sdB,
  MagickBooleanType *isMeanSdDefined,
  ExceptionInfo *exception
)
// Calculate mean and standard deviation,
// accounting for alpha (eg ignoring pixels that are entirely transparent).
// Returns values in range 0.0 to 1.0.
// Returns false if major problem.
// Returns isMeanSdDefined = MagickFalse iff statistics are not defined
//   (image is entirely transparent).
{
  // mnR = sig(R) / sig(alpha)
  // sdR = sqrt ( sig(R^2) / sig(alpha) - mnR^2 )
  //
  // Likewise for G and B.

  CacheView *in_view = AcquireVirtualCacheView (image, exception);

  double r, g, b, a;
  double sigR=0, sigG=0, sigB=0, sigA=0;
  double sigR2=0, sigG2=0, sigB2=0;

  ssize_t x, y;
  for (y=0; y < (ssize_t) image->rows; y++) {
    const VIEW_PIX_PTR *p=GetCacheViewVirtualPixels(in_view,0,y,image->columns,1,exception);
    if (p == (const VIEW_PIX_PTR *) NULL) return MagickFalse;

    for (x=0; x < (ssize_t) image->columns; x++) {
      a = GET_PIXEL_ALPHA(image,p) / QuantumRange;
      r = a * GET_PIXEL_RED(image,p) / QuantumRange;
      g = a * GET_PIXEL_GREEN(image,p) / QuantumRange;
      b = a * GET_PIXEL_BLUE(image,p) / QuantumRange;
      sigR += r;
      sigG += g;
      sigB += b;
      sigA += a;
      sigR2 += r*r;
      sigG2 += g*g;
      sigB2 += b*b;

      p += Inc_ViewPixPtr (image);
    }
  }

  in_view = DestroyCacheView (in_view);

  if (sigA == 0) {
    *isMeanSdDefined = MagickFalse;
    return MagickTrue;
  }

  *isMeanSdDefined = MagickTrue;
  *mnR = sigR / sigA;
  *mnG = sigG / sigA;
  *mnB = sigB / sigA;
  *sdR = sqrt (sigR2/sigA - (*mnR)*(*mnR));
  *sdG = sqrt (sigG2/sigA - (*mnG)*(*mnG));
  *sdB = sqrt (sigB2/sigA - (*mnB)*(*mnB));

  return MagickTrue;
}

static Image * InitialiseSdLine (
  kclusterT * pkc,
  Image *image,
  ExceptionInfo *exception
)
// Returns image of unique colours, or NULL if failure.
{
  double mnR=0, mnG=0, mnB=0;
  double sdR=0, sdG=0, sdB=0;

  MagickBooleanType isMeanSdDefined;

  if (!CalcMeanSd (pkc, image,
          &mnR, &mnG, &mnB, &sdR, &sdG, &sdB,
          &isMeanSdDefined, exception))
    return MagickFalse;

  if (sdR==0 && sdG==0 && sdB==0) {
    fprintf (stderr, "kcluster: initialize SdLine with flat-colour image: only one cluster.");
    pkc->kCols = 1;
  }

  double loR = mnR - sdR;
  double loG = mnG - sdG;
  double loB = mnB - sdB;

  double semiR = sdR / (double)pkc->kCols;
  double semiG = sdG / (double)pkc->kCols;
  double semiB = sdB / (double)pkc->kCols;

  if (pkc->debug) fprintf (stderr,
    "InitialiseSdLine: mnR=%g sdR=%g loR=%g semiR=%g\n", 
    mnR, sdR, loR, semiR);

  Image *sd_img = CloneImage (image, pkc->kCols,1, MagickFalse, exception);
  if (!sd_img) return NULL;

  CacheView *sd_view = AcquireAuthenticCacheView (sd_img, exception);

  VIEW_PIX_PTR *q=GetCacheViewAuthenticPixels(sd_view,0,0,sd_img->columns,1,exception);

  int qx;
  for (qx = 0; qx < sd_img->columns; qx++) {
    SET_PIXEL_ALPHA (sd_img, QuantumRange, q);

    if (pkc->debug) fprintf (stderr,
      "InitialiseSdLine: qx=%i R=%g\n", 
      qx, loR + semiR*(2*qx+1));

    SET_PIXEL_RED   (sd_img, QuantumRange * (loR + semiR*(2*qx+1)), q);
    SET_PIXEL_GREEN (sd_img, QuantumRange * (loG + semiG*(2*qx+1)), q);
    SET_PIXEL_BLUE  (sd_img, QuantumRange * (loB + semiB*(2*qx+1)), q);

    q += Inc_ViewPixPtr (sd_img);
 }

  if (!SyncCacheViewAuthenticPixels(sd_view,exception))
    return NULL;

  sd_view = DestroyCacheView (sd_view);

  return sd_img;
}

static Image * InitialiseFromList (
  kclusterT * pkc,
  Image *image,
  ExceptionInfo *exception
)
// Returns image of unique colours, or NULL if failure.
{
  Image *last_img = GetLastImageInList(image);

  if (last_img == NULL || last_img == image) {
    fprintf (stderr, "kcluster: FromList: can't find last\n");
    return NULL;
  }

  if (pkc->verbose) {
    fprintf (pkc->fh_data, "kcluster: FromList [%s] %ix%i depth %i\n",
             last_img->filename,
             (int)last_img->columns, (int)last_img->rows,
             (int)last_img->depth);
  }

  Image *uniq_img = UniqueImageColors (last_img, exception);
  if (!uniq_img) return NULL;

  // FIXME: transparency?

  pkc->kCols = uniq_img->columns;

  if (pkc->verbose) fprintf (pkc->fh_data, "kcluster: FromList k=%i\n", pkc->kCols);

  return uniq_img;
}


static MagickBooleanType CalcNormalise (
  kclusterT * pkc,
  Image *image,
  ExceptionInfo *exception
)
{
  InitNormalise (pkc);

  double mnR=0, mnG=0, mnB=0;
  double sdR=0, sdG=0, sdB=0;

  MagickBooleanType isMeanSdDefined;

  if (!CalcMeanSd (pkc, image,
          &mnR, &mnG, &mnB, &sdR, &sdG, &sdB,
          &isMeanSdDefined, exception))
    return MagickFalse;

  if (pkc->debug) {
    fprintf (pkc->fh_data, "kcluster: mnsd defined: %i\n", isMeanSdDefined);

    fprintf (pkc->fh_data, "kcluster: mn: %.*g,%.*g,%.*g sd: %.*g,%.*g,%.*g\n",
      pkc->precision, mnR,
      pkc->precision, mnG,
      pkc->precision, mnB,
      pkc->precision, sdR,
      pkc->precision, sdG,
      pkc->precision, sdB);
  }

  if (!isMeanSdDefined) return MagickFalse;

  // FIXME: Can this also set Y for jump???

  double sdMin = sdR;
  if (sdMin > sdG) sdMin = sdG;
  if (sdMin > sdB) sdMin = sdB;

  double sdMax = sdR;
  if (sdMax < sdG) sdMax = sdG;
  if (sdMax < sdB) sdMax = sdB;

  if (sdMin==sdMax) return MagickTrue;

  if (sdR+sdG+sdB==sdMax) {
    if (pkc->verbose) fprintf (pkc->fh_data, "kcluster: only one channel\n");
    return MagickTrue;
  }

  if (sdMin==0) {
    if (pkc->verbose) fprintf (pkc->fh_data, "kcluster: only two channels\n");

    if (sdMax==sdR) {
      pkc->normalise.r = ((sdG==0) ? sdB : sdG) / sdR;
    } else if (sdMax==sdG) {
      pkc->normalise.g = ((sdR==0) ? sdB : sdR) / sdG;
    } else if (sdMax==sdB) {
      pkc->normalise.b = ((sdR==0) ? sdG : sdR) / sdB;
    }

  } else {
    if (pkc->verbose) fprintf (pkc->fh_data, "kcluster: three channels\n");

    if (sdMin==sdR) {
      pkc->normalise.g = sdR / sdG;
      pkc->normalise.b = sdR / sdB;
    } else if (sdMin==sdG) {
      pkc->normalise.r = sdG / sdR;
      pkc->normalise.b = sdG / sdB;
    } else if (sdMin==sdB) {
      pkc->normalise.r = sdB / sdR;
      pkc->normalise.g = sdB / sdG;
    }
  }

  if (pkc->normalise.r == 0) pkc->normalise.r = 1.0;
  if (pkc->normalise.g == 0) pkc->normalise.g = 1.0;
  if (pkc->normalise.b == 0) pkc->normalise.b = 1.0;

  if (pkc->verbose)
    fprintf (pkc->fh_data, "kcluster: normalise %.*g,%.*g,%.*g\n",
      pkc->precision, pkc->normalise.r,
      pkc->precision, pkc->normalise.g,
      pkc->precision, pkc->normalise.b);

  return MagickTrue;
}

static MagickStatusType WriteFrame (
  kclusterT * pkc,
  Image * img,
  ExceptionInfo *exception)
{
  ImageInfo
    *ii;

  MagickStatusType
    status;

  Image
    *copy_img;

  ii = AcquireImageInfo ();

  copy_img = CloneImage(img, 0, 0, MagickTrue, exception);
  if (copy_img == (Image *) NULL)
    return MagickFalse;

  copy_img->scene = pkc->frameNum++;

  CopyMagickString (copy_img->filename, pkc->frameName, MaxTextExtent);

#if IMV6OR7==6
  status = WriteImage (ii, copy_img);
#else
  status = WriteImage (ii, copy_img, exception);
#endif

  DestroyImageList(copy_img);

  ii = DestroyImageInfo (ii);

  return status;
}


static MagickBooleanType ApplyNormalise (
  kclusterT * pkc,
  Image *image,
  ExceptionInfo *exception,
  MagickBooleanType IsForwards
)
{
  double mr, mg, mb;
  if (IsForwards) {
    mr = pkc->normalise.r;
    mg = pkc->normalise.g;
    mb = pkc->normalise.b;
  } else {
    mr = 1.0 / pkc->normalise.r;
    mg = 1.0 / pkc->normalise.g;
    mb = 1.0 / pkc->normalise.b;
  }

  CacheView *in_view = AcquireAuthenticCacheView (image, exception);
  ssize_t x, y;
  for (y=0; y < (ssize_t) image->rows; y++) {
    VIEW_PIX_PTR *p=GetCacheViewAuthenticPixels(in_view,0,y,image->columns,1,exception);
    if (p == (const VIEW_PIX_PTR *) NULL) return MagickFalse;

    for (x=0; x < (ssize_t) image->columns; x++) {
      SET_PIXEL_RED (image, mr * GET_PIXEL_RED(image,p), p);
      SET_PIXEL_GREEN (image, mg * GET_PIXEL_GREEN(image,p), p);
      SET_PIXEL_BLUE (image, mb * GET_PIXEL_BLUE(image,p), p);

      p += Inc_ViewPixPtr (image);
    }
    if (!SyncCacheViewAuthenticPixels(in_view,exception))
      return MagickFalse;
  }
  in_view = DestroyCacheView (in_view);

  return MagickTrue;
}

static double inline colDistRel (
  Image *image0,
  Image *image1,
  const VIEW_PIX_PTR *p0,
  const VIEW_PIX_PTR *p1
)
// Returns relative distance between colours.
{
  double r = GET_PIXEL_RED(image0,p0)   - GET_PIXEL_RED(image1,p1);
  double g = GET_PIXEL_GREEN(image0,p0) - GET_PIXEL_GREEN(image1,p1);
  double b = GET_PIXEL_BLUE(image0,p0)  - GET_PIXEL_BLUE(image1,p1);

  return (r*r + g*g + b*b);
}


static double inline colDist (
  Image *image0,
  Image *image1,
  const VIEW_PIX_PTR *p0,
  const VIEW_PIX_PTR *p1
)
// Returns distance between colours, scale 0.0 to 1.0.
{
  return sqrt (colDistRel(image0, image1, p0, p1) / 3.0) / QuantumRange;
}


static double inline powDist (
  Image *image0,
  Image *image1,
  const VIEW_PIX_PTR *p0,
  const VIEW_PIX_PTR *p1,
  double powParam
)
{
  double r = colDist (image0, image1, p0, p1);
  if (r==0) return 0;
  return pow (r, powParam);
}

static double inline sumPowDist (
  Image *image,
  Image *uniq_img,
  const VIEW_PIX_PTR *pImg,  // an image pixel
  const VIEW_PIX_PTR *pUniq, // first entry in uniq_img
  double powParam
)
{
  double r = 0.0, c;
  ssize_t qx;
  const VIEW_PIX_PTR *qu = pUniq;
  for (qx=0; qx < uniq_img->columns; qx++) {
    c = colDist (image, uniq_img, pImg, qu);
    if (c > 0) r += pow (c, powParam);
    qu += Inc_ViewPixPtr (uniq_img);
  }
  return r;
}

static double inline fuzzyMembership (
  kclusterT * pkc,
  Image *image,
  Image *uniq_img,
  const VIEW_PIX_PTR *pImg,  // an image pixel
  const VIEW_PIX_PTR *pUniq, // first entry in uniq_img
  ssize_t qx,                // index into uniq_img
  double denom
)
{
  double numer = powDist (image, uniq_img,
                   pImg, pUniq + qx * Inc_ViewPixPtr (uniq_img),
                   pkc->powParam1);

  if (numer == denom) return 1.0;

  if (pkc->debug) {
    if (isnan(numer)) fprintf (stderr, "numer is nan ");
    if (isnan(denom)) fprintf (stderr, "denom is nan ");

    if (isnan (numer / denom)) {
      fprintf (stderr, "fuzm %g,%g  qx=%li  ", numer, denom, qx);
    }

    if (numer > denom) fprintf (stderr, "fm %g/%g  ", numer, denom);
  }

  return numer / denom;
}


static double inline harmonicMembership (
  kclusterT * pkc,
  Image *image,
  Image *uniq_img,
  const VIEW_PIX_PTR *pImg,  // an image pixel
  const VIEW_PIX_PTR *pUniq, // first entry in uniq_img
  ssize_t qx,                // index into uniq_img
  double denom
)
{
  double numer = powDist (image, uniq_img,
                   pImg, pUniq + qx * Inc_ViewPixPtr (uniq_img),
                   pkc->powParam2);
  //double denom = sumPowDist (image, uniq_img, pImg, pUniq, pkc->powParam2);

  if (numer == denom) return 1.0;

  return numer / denom;
}


static double inline harmonicWeight (
  kclusterT * pkc,
  Image *image,
  Image *uniq_img,
  const VIEW_PIX_PTR *pImg, // an image pixel
  const VIEW_PIX_PTR *pUniq // first entry in uniq_img
)
{
  double numer = sumPowDist (image, uniq_img, pImg, pUniq, pkc->powParam2);
  double denom = sumPowDist (image, uniq_img, pImg, pUniq, pkc->powParam3);
  denom = denom * denom;

  if (numer == denom) return 1.0;

  if (pkc->debug) {
    if (isnan(numer)) fprintf (stderr, "numer is nan ");
    if (isnan(denom)) fprintf (stderr, "denom is nan ");

    if (isnan (numer / denom)) {
      fprintf (stderr, "harm w %g,%g  ", numer, denom);
    }
  }

  return numer / denom;
}

static void fuzzyDither (
  kclusterT * pkc,
  Image *image,
  Image *uniq_img,
  ssize_t x,
  ssize_t y,
  VIEW_PIX_PTR *pImg,        // an image pixel
  const VIEW_PIX_PTR *pUniq  // first entry in uniq_img
)
{
  double rand = GetPseudoRandomValue(pkc->random_info);
  double cumMemb = 0;

  if (!pkc->membDenoms)
    fprintf (stderr, "null membDenoms\n");

  double denom = pkc->membDenoms[y*image->columns+x];

  ssize_t qx;
  for (qx=0; qx < uniq_img->columns; qx++) {

    // stop when cumulative membership >= rand
    cumMemb += fuzzyMembership (pkc, image, uniq_img, pImg, pUniq, qx, denom);

    if (cumMemb >= rand) {
      const VIEW_PIX_PTR *qu = pUniq + qx * Inc_ViewPixPtr (uniq_img);
      SET_PIXEL_RED   (image, GET_PIXEL_RED   (uniq_img, qu), pImg);
      SET_PIXEL_GREEN (image, GET_PIXEL_GREEN (uniq_img, qu), pImg);
      SET_PIXEL_BLUE  (image, GET_PIXEL_BLUE  (uniq_img, qu), pImg);
      break;
    }
  }
}


static MagickBooleanType setMembDenoms (
  kclusterT * pkc,
  Image *image,
  Image *uniq_img,
  VIEW_PIX_PTR *pUniq,
  CacheView *in_view,
  double powParam, // pkc->powParam1
  ExceptionInfo *exception)
{
  if (!pkc->membDenoms) return MagickFalse;

  ssize_t x, y;

  for (y=0; y < (ssize_t) image->rows; y++) {
    const VIEW_PIX_PTR *p=GetCacheViewVirtualPixels(in_view,0,y,image->columns,1,exception);
    if (p == (const VIEW_PIX_PTR *) NULL) return MagickFalse;

    for (x=0; x < (ssize_t) image->columns; x++) {
      pkc->membDenoms[y*image->columns+x] = sumPowDist (image, uniq_img, p, pUniq, powParam);
      p += Inc_ViewPixPtr (image);
    }
  }
  return MagickTrue;
}



static MagickBooleanType updateImage (
  kclusterT * pkc,
  Image *image,
  Image *uniq_img,
  VIEW_PIX_PTR *pUniq,
  ExceptionInfo *exception
)
// Returns whether okay.
{
  // Populate image with the updated cluster colours.

  assert(image != (Image *) NULL);
  assert(image->signature == MAGICK_CORE_SIG);

  assert (pkc->clusters);

  CacheView *upd_view = AcquireAuthenticCacheView (image, exception);

  if (pkc->dither) {
    if (!setMembDenoms (
          pkc, image, uniq_img, pUniq, upd_view, pkc->powParam1,
          exception)) return MagickFalse;
  }

  ssize_t x, y, qx;
  for (y=0; y < (ssize_t) image->rows; y++) {
    VIEW_PIX_PTR *p=GetCacheViewAuthenticPixels(upd_view,0,y,image->columns,1,exception);
    if (p == (const VIEW_PIX_PTR *) NULL)
      return MagickFalse;

    for (x=0; x < (ssize_t) image->columns; x++) {
      if (pkc->dither) {
        fuzzyDither (pkc, image, uniq_img, x, y, p, pUniq);
      } else {
        qx = pkc->nearClust[y*image->columns+x];
        clusterT * pClust = &pkc->clusters[qx];

        SET_PIXEL_RED (image, pClust->red, p);
        SET_PIXEL_GREEN (image, pClust->green, p);
        SET_PIXEL_BLUE (image, pClust->blue, p);
      }
      p += Inc_ViewPixPtr (image);
    }
    if (!SyncCacheViewAuthenticPixels(upd_view,exception))
        return MagickFalse;
  }

  upd_view = DestroyCacheView (upd_view);

  return MagickTrue;
}



// The next function corresponds in style to functions in transform.c
// It takes one image, and returns an image.
//
static Image *kclusterOneK (
  Image *image,
  kclusterT * pkc,
  ExceptionInfo *exception)
//
// This may adjust pkc->kCols downwards.
//
{
  if (image->debug != MagickFalse)
    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);

  ssize_t nPixels = image->columns * image->rows;

  // Beware: if FromList, we don't know kCol yet.

  if (pkc->kCols < 1) pkc->kCols = 1;

  if (pkc->kCols > nPixels) pkc->kCols = nPixels;

  if (pkc->verbose) {
    fprintf (pkc->fh_data, "kcluster: Input image [%s] %ix%i depth %i; nPixels %li  k=%i\n",
             image->filename,
             (int)image->columns, (int)image->rows,
             (int)image->depth,
             (long int)nPixels,
             pkc->kCols);
  }

  pkc->nearClust = (ssize_t *) AcquireQuantumMemory (nPixels, sizeof(ssize_t));
  if (!pkc->nearClust) {
    fprintf (stderr, "kcluster: oom2\n");
    return (Image *)NULL;
  }

  // If FromList, we don't know kCol yet.
  // But RandPart needs clusters.

  pkc->clusters = NULL;

  Image *uniq_img = NULL;
  switch (pkc->init) {
    case iColors:
      default:
      uniq_img = InitialiseColors (pkc, image, exception);
      break;
    case iForgy:
      uniq_img = InitialiseForgy (pkc, image, exception);
      break;
    case iRandPart:
      pkc->clusters = (clusterT *) AcquireQuantumMemory (pkc->kCols, sizeof(clusterT));
      if (!pkc->clusters) {
        fprintf (stderr, "kcluster: oom2\n");
        return (Image *)NULL;
      }
      uniq_img = InitialiseRandomPartition (pkc, image, exception);
      break;
    case iSdLine:
      uniq_img = InitialiseSdLine (pkc, image, exception);
      break;
    case iFromList:
      uniq_img = InitialiseFromList (pkc, image, exception);

      if (pkc->kCols > nPixels) pkc->kCols = nPixels;
      break;
  }

  if (!uniq_img) {
    fprintf (stderr, "kcluster: Initialise failed\n");
    return NULL;
  }

  if (!pkc->clusters) {
    pkc->clusters = (clusterT *) AcquireQuantumMemory (pkc->kCols, sizeof(clusterT));
    if (!pkc->clusters) {
      fprintf (stderr, "kcluster: oom2\n");
      return (Image *)NULL;
    }
  }

  if (pkc->debug) DumpImage (pkc, uniq_img, exception);

  if (pkc->verbose)
    fprintf (pkc->fh_data, "kcluster: %li uniq_img\n", uniq_img->columns);

  CacheView *uniq_view = AcquireAuthenticCacheView (uniq_img,exception);

  VIEW_PIX_PTR *q_uniq = GetCacheViewAuthenticPixels(uniq_view,0,0,uniq_img->columns,1,exception);
  if (!q_uniq) return NULL;

  CacheView *in_view = AcquireVirtualCacheView (image, exception);

  pkc->powParam1 = -2 / (pkc->fuzzyR - 1);
  pkc->powParam2 = -pkc->harmonicP - 2;
  pkc->powParam3 = -pkc->harmonicP;

  pkc->membDenoms = (double *) AcquireQuantumMemory (nPixels, sizeof(double));
  if (!pkc->membDenoms) {
    fprintf (stderr, "kcluster: oom membDen\n");
    return (Image *)NULL;
  }

  double opacity = 1.0;

  // Note that an iteration is NOT guaranteed to be better than the previous one.

  int iter;
  for (iter=0; iter < 9999; iter++) {

    ssize_t x, y;
    ssize_t qx;

    // Loop through x, y, uniq, to populate nearClust.

    for (y=0; y < (ssize_t) image->rows; y++) {
      const VIEW_PIX_PTR *p=GetCacheViewVirtualPixels(in_view,0,y,image->columns,1,exception);
      if (p == (const VIEW_PIX_PTR *) NULL) return (Image *)NULL;

      for (x=0; x < (ssize_t) image->columns; x++) {
        double minDist=0;
        VIEW_PIX_PTR *qu = q_uniq;
        for (qx=0; qx < uniq_img->columns; qx++) {
          double dist = colDistRel (image, uniq_img, p, qu);
          if (qx==0 || minDist > dist) {
            minDist = dist;
            pkc->nearClust[y*image->columns+x] = qx;
          }
          qu += Inc_ViewPixPtr (uniq_img);
        }
        p += Inc_ViewPixPtr (image);
      }
    }

    if (pkc->method == mMeans) {
      // Zero cluster accumulators.

      for (qx=0; qx < uniq_img->columns; qx++) {
        clusterT * pClust = &pkc->clusters[qx];
        pClust->red = pClust->green = pClust->blue = 0.0;
        pClust->count = 0;
      }

      // Loop though y,x; add colour to appropriate cluster accumulator.

      for (y=0; y < (ssize_t) image->rows; y++) {
        const VIEW_PIX_PTR *p=GetCacheViewVirtualPixels(in_view,0,y,image->columns,1,exception);
        if (p == (const VIEW_PIX_PTR *) NULL) return (Image *)NULL;

        for (x=0; x < (ssize_t) image->columns; x++) {
          ssize_t qx = pkc->nearClust[y*image->columns+x];
          clusterT * pClust = &pkc->clusters[qx];
          if (pkc->regardAlpha)
            opacity = GET_PIXEL_ALPHA (image, p) / QuantumRange;
          pClust->red   += opacity * GET_PIXEL_RED (image, p);
          pClust->green += opacity * GET_PIXEL_GREEN (image, p);
          pClust->blue  += opacity * GET_PIXEL_BLUE (image, p);
          pClust->count += opacity;

          p += Inc_ViewPixPtr (image);
        }
      }

      // Calc the mean colour per cluster.

      for (qx=0; qx < uniq_img->columns; qx++) {
        clusterT * pClust = &pkc->clusters[qx];
        if (pClust->count) {
          pClust->red   /= pClust->count;
          pClust->green /= pClust->count;
          pClust->blue  /= pClust->count;
        }
      }
    } else if (pkc->method == mFuzzy) {
      if (!setMembDenoms (
            pkc, image, uniq_img, q_uniq, in_view, pkc->powParam1,
            exception)) return NULL;

      for (qx=0; qx < uniq_img->columns; qx++) {
        double sumM = 0.0, sumMr = 0.0, sumMg = 0.0, sumMb = 0.0;
        for (y=0; y < (ssize_t) image->rows; y++) {
          const VIEW_PIX_PTR *p=GetCacheViewVirtualPixels(in_view,0,y,image->columns,1,exception);
          if (p == (const VIEW_PIX_PTR *) NULL) return (Image *)NULL;

          for (x=0; x < (ssize_t) image->columns; x++) {
            double m = fuzzyMembership (pkc,
                         image, uniq_img, p, q_uniq, qx,
                         pkc->membDenoms[y*image->columns+x]);

            if (isnan(m)) fprintf (stderr, "m is nan  xy=%li,%li  ", x, y);

            if (pkc->regardAlpha) {
              opacity = GET_PIXEL_ALPHA (image, p) / QuantumRange;
              m *= opacity;
            }

            sumMr += m * GET_PIXEL_RED (image, p);
            sumMg += m * GET_PIXEL_GREEN (image, p);
            sumMb += m * GET_PIXEL_BLUE (image, p);
            sumM  += m;

            p += Inc_ViewPixPtr (image);
          }
        }
        if (pkc->debug) fprintf (pkc->fh_data, "fuzzy %li %g, %g,%g,%g\n", 
          qx,
          sumM, sumMr, sumMg, sumMb);

        clusterT * pClust = &pkc->clusters[qx];

        if (sumM==0) {
          pClust->red = pClust->green = pClust->blue = 0;
        } else {
          pClust->red   = sumMr / sumM;
          pClust->green = sumMg / sumM;
          pClust->blue  = sumMb / sumM;
        }
      }

    } else if (pkc->method == mHarmonic) {

      double *weights = (double *) AcquireQuantumMemory (nPixels, sizeof(double));
      if (!weights) {
        fprintf (stderr, "kcluster: oomwt\n");
        return (Image *)NULL;
      }
      for (y=0; y < (ssize_t) image->rows; y++) {
        const VIEW_PIX_PTR *p=GetCacheViewVirtualPixels(in_view,0,y,image->columns,1,exception);
        if (p == (const VIEW_PIX_PTR *) NULL) return (Image *)NULL;

        for (x=0; x < (ssize_t) image->columns; x++) {
          weights[y*image->columns+x] = harmonicWeight (pkc, image, uniq_img, p, q_uniq);
          pkc->membDenoms[y*image->columns+x] = sumPowDist (image, uniq_img, p, q_uniq, pkc->powParam2);
          p += Inc_ViewPixPtr (image);
        }
      }

      for (qx=0; qx < uniq_img->columns; qx++) {
        double sumM = 0.0, sumMr = 0.0, sumMg = 0.0, sumMb = 0.0;
        for (y=0; y < (ssize_t) image->rows; y++) {
          const VIEW_PIX_PTR *p=GetCacheViewVirtualPixels(in_view,0,y,image->columns,1,exception);
          if (p == (const VIEW_PIX_PTR *) NULL) return (Image *)NULL;

          for (x=0; x < (ssize_t) image->columns; x++) {
            double m = harmonicMembership (
              pkc, image, uniq_img, p, q_uniq, qx,
              pkc->membDenoms[y*image->columns+x]);
            if (isnan(m)) fprintf (stderr, "m is nan ");

            double w = weights[y*image->columns+x];
            if (isnan(w)) fprintf (stderr, "w is nan ");

            if (pkc->regardAlpha) {
              opacity = GET_PIXEL_ALPHA (image, p) / QuantumRange;
              m *= opacity;
            }
            double mw = m * w;
            sumMr += mw * GET_PIXEL_RED (image, p);
            sumMg += mw * GET_PIXEL_GREEN (image, p);
            sumMb += mw * GET_PIXEL_BLUE (image, p);
            sumM  += mw;

            p += Inc_ViewPixPtr (image);
          }
        }
        clusterT * pClust = &pkc->clusters[qx];

        if (sumM==0) {
          pClust->red = pClust->green = pClust->blue = 0;
        } else {
          pClust->red   = sumMr / sumM;
          pClust->green = sumMg / sumM;
          pClust->blue  = sumMb / sumM;
        }
      }
      weights = RelinquishMagickMemory (weights);

    } else if (pkc->method == mNull) {

      VIEW_PIX_PTR *qu = q_uniq;
      for (qx=0; qx < uniq_img->columns; qx++) {
        clusterT * pClust = &pkc->clusters[qx];
        pClust->red   = GET_PIXEL_RED (uniq_img, qu);
        pClust->green = GET_PIXEL_GREEN (uniq_img, qu);
        pClust->blue  = GET_PIXEL_BLUE (uniq_img, qu);
        qu += Inc_ViewPixPtr (uniq_img);
      }

    } // mNull

    // Calc how much we have changed;
    // update the cluster colours.

    VIEW_PIX_PTR *qu = q_uniq;
    double diff, maxDiff = 0;

    for (qx=0; qx < uniq_img->columns; qx++) {
      clusterT * pClust = &pkc->clusters[qx];
      diff = fabs (pClust->red - GET_PIXEL_RED (uniq_img, qu));
      if (maxDiff < diff) maxDiff = diff;
      diff = fabs (pClust->green - GET_PIXEL_GREEN (uniq_img, qu));
      if (maxDiff < diff) maxDiff = diff;
      diff = fabs (pClust->blue - GET_PIXEL_BLUE (uniq_img, qu));
      if (maxDiff < diff) maxDiff = diff;

      SET_PIXEL_RED   (uniq_img, pClust->red, qu);
      SET_PIXEL_GREEN (uniq_img, pClust->green, qu);
      SET_PIXEL_BLUE  (uniq_img, pClust->blue, qu);

      qu += Inc_ViewPixPtr (uniq_img);
    }

    if (!SyncCacheViewAuthenticPixels(uniq_view,exception))
        return NULL;

    maxDiff /= QuantumRange;

    if (pkc->verbose > 1) {
      fprintf (pkc->fh_data,
        "kcluster: iteration %i  maxDiff %.*g\n",
        iter, pkc->precision, maxDiff);
    }

    if (pkc->debug) DumpImage (pkc, uniq_img, exception);


    if (pkc->frameName) {
      // For performance: don't need to create and destroy every time.
      Image * frame_img = CloneImage(image, 0, 0, MagickTrue, exception);
      if (frame_img == (Image *) NULL)
        return MagickFalse;

      if (!updateImage (pkc, frame_img, uniq_img, q_uniq, exception)) return NULL;
      WriteFrame (pkc, frame_img, exception);
      DestroyImage (frame_img);
    }


    if (maxDiff <= pkc->tolerance && (iter > 2 || pkc->init != iRandPart))
      break;

    if (pkc->maxIter && iter >= pkc->maxIter-1) break;

  } // end iteration loop


  // Populate image with the updated cluster colours.

  if (!updateImage (pkc, image, uniq_img, q_uniq, exception)) return NULL;

  if (pkc->frameName) WriteFrame (pkc, image, exception);


  if (pkc->membDenoms) 
    pkc->membDenoms = RelinquishMagickMemory (pkc->membDenoms);

  in_view = DestroyCacheView (in_view);

  uniq_view = DestroyCacheView (uniq_view);



  if (pkc->verbose) {
    fprintf (pkc->fh_data, "kcluster: %i iterations\n", iter+1);
  }

  DestroyImage (uniq_img);

  pkc->clusters  = RelinquishMagickMemory (pkc->clusters);
  pkc->nearClust  = RelinquishMagickMemory (pkc->nearClust);

  return (image);
}


static MagickBooleanType CompareRmseAlpha (
  Image *image1,
  Image *image2,
  double * score,
  ExceptionInfo *exception)
// Returns RMSE score (aka "distortion") of two equal-size images,
// taking alpha into account.
{
  CacheView *in1_view = AcquireVirtualCacheView (image1, exception);
  CacheView *in2_view = AcquireVirtualCacheView (image2, exception);

  if ((image1->columns != image2->columns) || (image1->rows != image2->rows)) {
    fprintf (stderr, "CompareRmseAlpha: images sizes should match");
    return MagickFalse;
  }

  double sigScore=0, sigmAlpha=0;

  ssize_t y;
  for (y=0; y < (ssize_t) image1->rows; y++) {
    const VIEW_PIX_PTR *p1=GetCacheViewVirtualPixels(in1_view,0,y,image1->columns,1,exception);
    if (p1 == (const VIEW_PIX_PTR *) NULL) return MagickFalse;
    const VIEW_PIX_PTR *p2=GetCacheViewVirtualPixels(in2_view,0,y,image2->columns,1,exception);
    if (p2 == (const VIEW_PIX_PTR *) NULL) return MagickFalse;

    ssize_t x;
    double a1, a2;
    for (x=0; x < (ssize_t) image1->columns; x++) {

      a1 = GET_PIXEL_ALPHA(image1, p1) / QuantumRange;
      a2 = GET_PIXEL_ALPHA(image2, p2) / QuantumRange;

      double mAlpha = a1 * a2;

      double dRed   =
         (GET_PIXEL_RED(image1,p1)
        - GET_PIXEL_RED(image2,p2)) / QuantumRange;
      double dGreen =
         (GET_PIXEL_GREEN(image1,p1)
        - GET_PIXEL_GREEN(image2,p2)) / QuantumRange;
      double dBlue  =
         (GET_PIXEL_BLUE(image1,p1)
        - GET_PIXEL_BLUE(image2,p2)) / QuantumRange;

      if (mAlpha > 0) {
        sigScore += mAlpha * (dRed*dRed + dGreen*dGreen + dBlue*dBlue);

        sigmAlpha += mAlpha;
      }

      p1 += Inc_ViewPixPtr (image1);
      p2 += Inc_ViewPixPtr (image2);
    }
  }

  in2_view = DestroyCacheView (in2_view);
  in1_view = DestroyCacheView (in1_view);

  if (sigmAlpha==0) {
    // Every location has one or other pixel fully transparent.
    *score = 0.0;
  } else {
    *score = sqrt (sigScore / sigmAlpha / 3.0);
  }

  return MagickTrue;
}


static Image *kcluster (
  Image *image,
  kclusterT * pkc,
  ExceptionInfo *exception)
{
  if (image->debug != MagickFalse)
    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);

  if (! SetNoPalette (image, exception))
    return NULL;

  if (pkc->sdNorm) {
    if (!CalcNormalise (pkc, image, exception)) {
      fprintf (stderr, "kcluster: CalcNormalise failed\n");
      return (Image *)NULL;
    }

    if (!ApplyNormalise (pkc, image, exception, MagickTrue)) {
      fprintf (stderr, "kcluster: ApplyNormalise failed\n");
      return (Image *)NULL;
    }
  }

  if (!pkc->hasJump) {
    image = kclusterOneK (image, pkc, exception);
  } else {

    if (pkc->verbose) {
      fprintf (pkc->fh_data, "kcluster: jump %i to %i\n", pkc->jump0, pkc->jump1);
    }

    double jumpY;   // The transform power
    if (pkc->hasSetY) {
      jumpY = pkc->jumpY;
    } else {
      double jumpP;
#if IMV6OR7==6
      jumpP = IsGrayImage (image, exception) ? 1 : 3; // Number of dimensions.
#else
      jumpP = IsImageGray (image) ? 1 : 3; // Number of dimensions.
#endif
      jumpY = jumpP / 2.0;
    }
    if (pkc->verbose) {
      fprintf (pkc->fh_data, "kcluster: jumpY = %.*g\n",
        pkc->precision, jumpY);
    }

    double trans, J, maxJ = 0;
    int kAtMax = 0;
    double transPrev = 0.0;

    // if jump0 > 1,
    // start at jump0-1 but don't count first result for maxJ.

    MagickBooleanType ignoreThis = MagickFalse;
    int initK = pkc->jump0;
    if (initK > 1) {
      ignoreThis = MagickTrue;
      initK--;
    }

    int k;
    for (k = initK; k <= pkc->jump1; k++) {

      // clone it, cluster it, compare it

      pkc->kCols = k;

      Image * jump_img = CloneImage(image, 0, 0, MagickTrue, exception);
      if (jump_img == (Image *) NULL)
        return MagickFalse;

      jump_img = kclusterOneK (jump_img, pkc, exception);

      double dist, distArg;
      // FIXME: following ignores alpha.
      if (!GetImageDistortion (
       image, jump_img, RootMeanSquaredErrorMetric, &dist, exception))
      {
        return MagickFalse;
      }

      if (!CompareRmseAlpha (
       image, jump_img, &distArg, exception))
      {
        return MagickFalse;
      }

      trans = (dist > 0) ? pow (dist, -jumpY) : 0;
      J = trans - transPrev;

      if (!ignoreThis) {
        if (maxJ < J) {
          maxJ = J;
          kAtMax = pkc->kCols;
        }

        if (pkc->verbose) {
          fprintf (pkc->fh_data, "kcluster: k=%i  d=%.*g  dArg=%.*g  trans=%.*g  J=%.*g\n",
            pkc->kCols,
            pkc->precision, dist,
            pkc->precision, distArg,
            pkc->precision, trans,
            pkc->precision, J);
        }
      }

      transPrev = trans;
      ignoreThis = MagickFalse;

      DestroyImage (jump_img);
    }

    if (pkc->verbose) {
      fprintf (pkc->fh_data, "kcluster: maxJ=%.*g  at k=%i\n",
        pkc->precision, maxJ,
        kAtMax);
    }

    if (kAtMax == 0) kAtMax = 1;

    // FIXME: write this to attribute?

    pkc->kCols = kAtMax;
    image = kclusterOneK (image, pkc, exception);
  }

  if (pkc->sdNorm) {
    if (!ApplyNormalise (pkc, image, exception, MagickFalse)) {
      fprintf (stderr, "kcluster: ApplyNormalise failed\n");
      return MagickFalse;
    }
  }

  return image;
}



ModuleExport size_t kclusterImage (
  Image **images,
  const int argc,
  const char **argv,
  ExceptionInfo *exception)
{
  Image
    *image,
    *new_image;

  MagickBooleanType
    status;

  kclusterT pkc;

  assert(images != (Image **) NULL);
  assert(*images != (Image *) NULL);
  assert((*images)->signature == MAGICK_CORE_SIG);

  pkc.precision = GetMagickPrecision();

  status = menu (argc, argv, &pkc);
  if (status == MagickFalse)
    return (-1);

  if (!pkc.random_info) InitRand (&pkc);

  if (pkc.init == iFromList) {
    int ListLen = (int)GetImageListLength(*images);
    if (ListLen < 2) {
      fprintf (stderr, "kcluster: initialize FromList needs at least 2 images\n");
      return (-1);
    }
  }

  for (image=(*images); image != (Image *) NULL; image=GetNextImageInList(image))
  {
    Image * next = GetNextImageInList(image);
    if (next == NULL && pkc.init == iFromList) {
      //fprintf (stderr, "discarding map image\n");
      DeleteImageFromList (&image);
    } else {
      new_image = kcluster (image, &pkc, exception);
      if (!new_image) return (-1);

      if (new_image != image) {
        ReplaceImageInList(&image,new_image);
      }
      *images=GetFirstImageInList(image);
    }
  }

  DeInitRand (&pkc);

  return(MagickImageFilterSignature);
}

centsmcrop.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <ctype.h>
#include "vsn_defines.h"
#include "centsmcrop.inc"


typedef struct {
  MagickBooleanType
    verbose;
} cscT;


static void usage (void)
{
  printf ("Usage: -process 'centsmcrop [OPTION]...'\n");
  printf ("Crops both images to dimensions of the smallest.\n");
  printf ("\n");
  printf ("  v, verbose          write text information to stdout\n");
  printf ("\n");
}


static MagickBooleanType IsArg (char * pa, char * ShtOpt, char * LongOpt)
{
  if ((LocaleCompare(pa, ShtOpt)==0) || (LocaleCompare(pa, LongOpt)==0))
    return MagickTrue;

  return MagickFalse;
}


static MagickBooleanType menu(
  const int argc,
  const char **argv,
  cscT * pCsc
)
// Returns MagickTrue if okay.
{
  int
    i;

  MagickBooleanType
    status;

  status = MagickTrue;

  pCsc->verbose = MagickFalse;

  for (i=0; i < argc; i++) {
    char * pa = (char *)argv[i];

    if (IsArg (pa, "v", "verbose")==MagickTrue) {
      pCsc->verbose = MagickTrue;
    } else {
      fprintf (stderr, "centsmcrop: ERROR: unknown option [%s]\n", pa);
      status = MagickFalse;
    }
  }

  if (pCsc->verbose) {
    fprintf (stderr, "centsmcrop options:");

    if (pCsc->verbose) fprintf (stderr, "  verbose");

    fprintf (stderr, "\n");
  }

  if (status == MagickFalse)
    usage ();

  return (status);
}





ModuleExport size_t centsmcropImage(Image **images,const int argc,
  const char **argv,ExceptionInfo *exception)
{
  Image
    *image1,
    *image2;

  cscT
    csc;

  assert(images != (Image **) NULL);
  assert(*images != (Image *) NULL);
  assert((*images)->signature == MAGICK_CORE_SIG);

  // Replace each pair of images with atan2(u,v).
  // The images must be the same size.

  int ListLen = (int)GetImageListLength(*images);
  if (ListLen !=2) {
    fprintf (stderr, "centsmcrop needs 2 images\n");
    return (-1);
  }

  MagickBooleanType status = menu (argc, argv, &csc);
  if (status == MagickFalse)
    return (-1);

  image1 = (*images);
  image2 = GetNextImageInList (image1);

  if (!CentralSmallestCrop (&image1, &image2, csc.verbose, exception))
    return -1;

  // Replace messes up the images pointer. Make it good:
  *images = GetFirstImageInList (image1);

  return (MagickImageFilterSignature);
}

centsmcrop.inc

#ifndef CENTSMCROP_INC
#define CENTSMCROP_INC

#include "resetpage.inc"
#include "cropchk.inc"


static MagickBooleanType CentralSmallestCrop (
  Image ** iA,
  Image ** iB,
  MagickBooleanType verbose,
  ExceptionInfo *exception)
// Returns both images cropped, if required, to same dimensions,
// being the smallest of the inputs in each direction.
{
  ssize_t smX = (*iA)->columns;
  if (smX > (*iB)->columns) smX = (*iB)->columns;

  ssize_t smY = (*iA)->rows;
  if (smY > (*iB)->rows) smY = (*iB)->rows;

  if (verbose) fprintf (stderr,
    "CentralSmallestCrop: %lix%li %lix%li %lix%li\n",
    (*iA)->columns, (*iA)->rows,
    (*iB)->columns, (*iB)->rows,
    smX, smY);

  if ((*iA)->columns > smX || (*iA)->rows > smY) {
    RectangleInfo crpRect;
    crpRect.width  = smX;
    crpRect.height = smY;
    crpRect.x = ((*iA)->columns - smX) / 2;
    crpRect.y = ((*iA)->rows    - smY) / 2;

    ResetPage (*iA);
    Image * smA = CropCheck (*iA, &crpRect, exception);
    if (!smA) return MagickFalse;
    ResetPage (smA);

    ReplaceImageInList (iA, smA);
  }

  if ((*iB)->columns > smX || (*iB)->rows > smY) {
    RectangleInfo crpRect;
    crpRect.width  = smX;
    crpRect.height = smY;
    crpRect.x = ((*iB)->columns - smX) / 2;
    crpRect.y = ((*iB)->rows    - smY) / 2;

    ResetPage (*iB);
    Image * smB = CropCheck (*iB, &crpRect, exception);
    if (!smB) return MagickFalse;
    ResetPage (smB);

    ReplaceImageInList (iB, smB);
  }
  return MagickTrue;
}

#endif

findsinks.c

/* Updated:
     5-April-2018 for v7.0.7-28, and correct intensity bug.
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <math.h>
#include <ctype.h>
#include "vsn_defines.h"

#include "findsinks.inc"


typedef struct {
  MagickBooleanType
    verbose,
    findPeaks;
} findsinksT;

#define VERSION "findsinks v1.0  Copyright (c) 2017 Alan Gibson"

static void usage (void)
{
  printf ("Usage: -process 'findsinks [OPTION]...'\n");
  printf ("Find sinks or peaks.\n");
  printf ("\n");
  printf ("  p, findPeaks            find peaks instead of sinks\n");
  printf ("  v, verbose              write text information to stdout\n");
  printf ("     version              write version information to stdout\n");
  printf ("\n");
}


static MagickBooleanType IsArg (char * pa, char * ShtOpt, char * LongOpt)
{
  // LocaleCompare is not case-sensitive,
  // so we use strcmp for the short option.

  if ((strcmp(pa, ShtOpt)==0) || (LocaleCompare(pa, LongOpt)==0))
    return MagickTrue;

  return MagickFalse;
}


#define NEXTARG \
  if (i < argc-1) i++; \
  else { \
    fprintf (stderr, "findsinks: ERROR: [%s] needs argument\n", pa); \
    status = MagickFalse; \
  }

static MagickBooleanType menu(
  const int argc,
  const char **argv,
  findsinksT * pfs
)
// Returns MagickTrue if okay.
{
  int
    i;

  MagickBooleanType
    status = MagickTrue;

  pfs->findPeaks = MagickFalse;
  pfs->verbose = MagickFalse;

  for (i=0; i < argc; i++) {
    char * pa = (char *)argv[i];

    if (IsArg (pa, "p", "findPeaks")==MagickTrue) {
      pfs->findPeaks = MagickTrue;
    } else if (IsArg (pa, "v", "verbose")==MagickTrue) {
      pfs->verbose = MagickTrue;
    } else if (IsArg (pa, "version", "version")==MagickTrue) {
      fprintf (stdout, "%s\n", VERSION);
    } else {
      fprintf (stderr, "findsinks: ERROR: unknown option [%s]\n", pa);
      status = MagickFalse;
    }
  }

  if (pfs->verbose) {
    fprintf (stderr, "findsinks options: ");
    if (pfs->findPeaks)  fprintf (stderr, " findPeaks");
    if (pfs->verbose)    fprintf (stderr, " verbose");
    fprintf (stderr, "\n");
  }

  if (status == MagickFalse)
    usage ();

  return (status);
}



static MagickBooleanType SetImgGray (
  Image *image,
  ExceptionInfo *exception)
// Sets R,G and B channels to the pixel intensity.
// Does not change alpha.
// Returns whether okay.
{
  MagickBooleanType okay = MagickTrue;
  ssize_t y;

  CacheView * in_view = AcquireAuthenticCacheView (image, exception);

  for (y = 0; y < image->rows; y++) {
    if (!okay) continue;

    VIEW_PIX_PTR *p = GetCacheViewAuthenticPixels(
      in_view,0,y,image->columns,1,exception);
    if (!p) {okay = MagickFalse; continue; }

    ssize_t x;
    for (x = 0; x < image->columns; x++) {

      MagickRealType v = GetPixelIntensity (image, p);

      SET_PIXEL_RED   (image, v, p);
      SET_PIXEL_GREEN (image, v, p);
      SET_PIXEL_BLUE  (image, v, p);

      p += Inc_ViewPixPtr (image);
    }
    if (SyncCacheViewAuthenticPixels(in_view,exception) == MagickFalse)
      okay = MagickFalse;
  }
  in_view  = DestroyCacheView (in_view);

  return okay;
}


// The next function corresponds in style to functions in transform.c
// It takes one image, and returns an image.
//
static Image *findsinks (
  Image *image,
  findsinksT * pfs,
  ExceptionInfo *exception)
{
  if (image->debug != MagickFalse)
    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);

  if (pfs->verbose) {
    fprintf (stderr, "findsinks: Input image [%s] %ix%i depth is %i\n",
             image->filename,
             (int)image->columns, (int)image->rows,
             (int)image->depth);
  }

  // Clone, make grayscale. Perhaps negate.

  Image * new_img = CloneImage (image, 0, 0, MagickTrue, exception);
  if (!new_img) return NULL;

//#if IMV6OR7==6
//  if (!GrayscaleImage (new_img, MSPixelIntensityMethod)) return NULL;
//#else
//  if (!GrayscaleImage (new_img, MSPixelIntensityMethod, exception)) return NULL;
//#endif

  SetImgGray (new_img, exception);

  Quantum outNotFnd;
  if (pfs->findPeaks) {
#if IMV6OR7==6
    if (!NegateImage (new_img, MagickFalse)) return NULL;
#else
    ChannelType channel_mask = SetImageChannelMask (
      new_img,
      RedChannel | GreenChannel | BlueChannel);

    if (!NegateImage (new_img, MagickFalse, exception)) return NULL;

    SetImageChannelMask (new_img, channel_mask);
#endif
    outNotFnd = 0;
  } else {
    outNotFnd = QuantumRange;
  }

  Image * new_img2 = LightenNonSinks (new_img, pfs->verbose, exception);
  if (!new_img2) return NULL;

  DestroyImage (new_img);

  // Where new_img2 has blue at QuantumRange (or zero),
  //   make image pixel white (or black).
  // Put another way: 
  //   where new_img2 has blue at not QuantumRange (or not zero),
  //   copy colours from image.

  CacheView * in_view = AcquireVirtualCacheView (image, exception);
  CacheView * out_view = AcquireAuthenticCacheView (new_img2, exception);

  MagickBooleanType okay = MagickTrue;
  ssize_t y;

  for (y = 0; y < image->rows; y++) {
    if (!okay) continue;

    VIEW_PIX_PTR *q = GetCacheViewAuthenticPixels(
      out_view,0,y,image->columns,1,exception);
    if (!q) {okay = MagickFalse; continue; }

    const VIEW_PIX_PTR *pIn = GetCacheViewVirtualPixels(
      in_view,0,y,image->columns,1,exception);
    if (!pIn) {okay = MagickFalse; continue; }

    ssize_t x;
    for (x = 0; x < image->columns; x++) {

      if ( GET_PIXEL_BLUE (new_img2, q) == QuantumRange) {
        SET_PIXEL_RED   (new_img2, outNotFnd, q);
        SET_PIXEL_GREEN (new_img2, outNotFnd, q);
        SET_PIXEL_BLUE  (new_img2, outNotFnd, q);
      } else {
        SET_PIXEL_RED   (new_img2, GET_PIXEL_RED(image,pIn), q);
        SET_PIXEL_GREEN (new_img2, GET_PIXEL_GREEN(image,pIn), q);
        SET_PIXEL_BLUE  (new_img2, GET_PIXEL_BLUE(image,pIn), q);
      }

      SET_PIXEL_ALPHA (new_img2, GET_PIXEL_ALPHA(image,pIn), q);

      q += Inc_ViewPixPtr (new_img2);
      pIn += Inc_ViewPixPtr (image);
    }
    if (SyncCacheViewAuthenticPixels(out_view,exception) == MagickFalse)
      okay = MagickFalse;
  }
  out_view = DestroyCacheView (out_view);
  in_view  = DestroyCacheView (in_view);

  return (new_img2);
}


ModuleExport size_t findsinksImage (
  Image **images,
  const int argc,
  const char **argv,
  ExceptionInfo *exception)
{
  Image
    *image,
    *new_image;

  MagickBooleanType
    status;

  findsinksT s3d;

  assert(images != (Image **) NULL);
  assert(*images != (Image *) NULL);
  assert((*images)->signature == MAGICK_CORE_SIG);

  status = menu (argc, argv, &s3d);
  if (status == MagickFalse)
    return (-1);

  for (image=(*images); image != (Image *) NULL; image=GetNextImageInList(image))
  {
    new_image = findsinks (image, &s3d, exception);

    ReplaceImageInList(&image,new_image);
    *images=GetFirstImageInList(image);
  }

  return(MagickImageFilterSignature);
}

findsinks.inc

/* Updated:
     3-April-2018 for v7.0.7-28
     5-April-2018 bug: using ip when should be oq
*/

#ifndef FINDSINKS_INC
#define FINDSINKS_INC


static Image * LightenNonSinks (
  Image *image,
  MagickBooleanType verbose,
  ExceptionInfo *exception)
{
  // In blue channel, makes non-sinks QuantumRange.
  // A "sink" is a pixel,
  //   or connected group of pixels with the same value,
  // with all 8 neighbours lighter than it.
  // Thus, every pixel (or connected group) with a darker neighbour
  // is made white.

#if IMV6OR7==6
  //SetPixelCacheVirtualMethod (image, EdgeVirtualPixelMethod);
  SetImageVirtualPixelMethod (image, EdgeVirtualPixelMethod);
#else
  //SetPixelCacheVirtualMethod (image,
  //  EdgeVirtualPixelMethod, exception);
  SetImageVirtualPixelMethod (image, EdgeVirtualPixelMethod, exception);
#endif

  Image * new_img = CloneImage (image, 0, 0, MagickTrue, exception);
  if (!new_img) return NULL;

  CacheView * in_view = AcquireVirtualCacheView (image, exception);
  CacheView * out_view = AcquireAuthenticCacheView (new_img, exception);

  MagickBooleanType okay = MagickTrue;
  const ssize_t dy1 = image->columns+2;
  const ssize_t dy2 = 2 * (image->columns+2);
  ssize_t y;
  const int ip = Inc_ViewPixPtr (image);
  const int oq = Inc_ViewPixPtr (new_img);

  for (y = 0; y < image->rows; y++) {
    if (!okay) continue;

    VIEW_PIX_PTR *q = GetCacheViewAuthenticPixels(
      out_view,0,y,image->columns,1,exception);
    if (!q) {okay = MagickFalse; continue; }

    const VIEW_PIX_PTR *pIn = GetCacheViewVirtualPixels(
      in_view,-1,y-1,image->columns+2,3,exception);
    if (!pIn) {okay = MagickFalse; continue; }

    ssize_t x;
    for (x = 0; x < image->columns; x++) {

      double score = GET_PIXEL_BLUE (new_img, q);

      if (score < QuantumRange) {
        if (
        score > GET_PIXEL_BLUE (image, pIn + (x+0)*ip)
     || score > GET_PIXEL_BLUE (image, pIn + (x+1)*ip)
     || score > GET_PIXEL_BLUE (image, pIn + (x+2)*ip)
     || score > GET_PIXEL_BLUE (image, pIn + (dy1+x+0)*ip)
     || score > GET_PIXEL_BLUE (image, pIn + (dy1+x+1)*ip)
     || score > GET_PIXEL_BLUE (image, pIn + (dy1+x+2)*ip)
     || score > GET_PIXEL_BLUE (image, pIn + (dy2+x+0)*ip)
     || score > GET_PIXEL_BLUE (image, pIn + (dy2+x+1)*ip)
     || score > GET_PIXEL_BLUE (image, pIn + (dy2+x+2)*ip)
           )
        {
          SET_PIXEL_BLUE (new_img, QuantumRange, q);

          // If any pixels to the right have the same score,
          // set them to QuantumRange.
          if (x < image->columns-1) {
            ssize_t x2;
            VIEW_PIX_PTR *q2 = q + ip;
            for (x2=x+1; x2 < image->columns; x2++) {
              double score2 = GET_PIXEL_BLUE (new_img, q2);
              if (score2 != score) break;
              SET_PIXEL_BLUE (new_img, QuantumRange, q2);
              q2 += ip;
            }
          }

        }
      }
      // (pIn is _not_ incremented)
      q += oq;
    }  // for x
    if (SyncCacheViewAuthenticPixels(out_view,exception) == MagickFalse)
      okay = MagickFalse;
  }
  if (!okay) return NULL;

  in_view = DestroyCacheView (in_view);


  // To get all the pixels on a terrace, we iterate:
  // every pixel that is next to a white pixel,
  // where that white pixel was the same colour as this pixel,
  // is made white.

  MagickBooleanType anyChanged = MagickTrue;
  int nIter = 0;

  while (anyChanged) {
    anyChanged = MagickFalse;

    CacheView * out_view_rdm = AcquireVirtualCacheView (new_img, exception);
    CacheView * out_view_rdp = AcquireVirtualCacheView (new_img, exception);

    CacheView * in_view0 = AcquireVirtualCacheView (image, exception);
    CacheView * in_view1 = AcquireVirtualCacheView (image, exception);
    CacheView * in_view2 = AcquireVirtualCacheView (image, exception);

    for (y = 0; y < image->rows; y++) {
      if (!okay) continue;

      VIEW_PIX_PTR *q = GetCacheViewAuthenticPixels(
        out_view,0,y,image->columns,1,exception);
      if (!q) {okay = MagickFalse; continue; }

      const VIEW_PIX_PTR *qm1 = GetCacheViewVirtualPixels(
        out_view_rdm,-1,y-1,image->columns+2,1,exception);
      if (!qm1) {okay = MagickFalse; continue; }

      const VIEW_PIX_PTR *qp1 = GetCacheViewVirtualPixels(
        out_view_rdp,-1,y+1,image->columns+2,1,exception);
      if (!qp1) {okay = MagickFalse; continue; }

      qm1 += oq;
      qp1 += oq;

      const VIEW_PIX_PTR *p0 = GetCacheViewVirtualPixels(
        in_view0,-1,y-1,image->columns+2,1,exception);
      if (!p0) {okay = MagickFalse; continue; }

      const VIEW_PIX_PTR *p1 = GetCacheViewVirtualPixels(
        in_view1,-1,y,image->columns+2,1,exception);
      if (!p1) {okay = MagickFalse; continue; }

      const VIEW_PIX_PTR *p2 = GetCacheViewVirtualPixels(
        in_view2,-1,y+1,image->columns+2,1,exception);
      if (!p2) {okay = MagickFalse; continue; }

      p0 += ip;
      p1 += ip;
      p2 += ip;

      ssize_t x;
      for (x = 0; x < image->columns; x++) {
        double score = GET_PIXEL_BLUE (new_img, q);
        if (score < QuantumRange) {

          // FIXME: for now,
          //  just the pixels directly above, left, right and down.
          if (
                (    score == GET_PIXEL_BLUE (image, p0)
                  && GET_PIXEL_BLUE (new_img, qm1)==QuantumRange)
             || (    x > 0
                  && score == GET_PIXEL_BLUE (image, p1-ip)
                  && GET_PIXEL_BLUE (new_img, q-ip)==QuantumRange)
             || (    x < image->columns-1
                  && score == GET_PIXEL_BLUE (image, p1+ip)
                  && GET_PIXEL_BLUE (new_img, q+ip)==QuantumRange)
             || (    score == GET_PIXEL_BLUE (image, p2)
                  && GET_PIXEL_BLUE (new_img, qp1)==QuantumRange)
             )
          {
            SET_PIXEL_BLUE (new_img, QuantumRange, q);
            anyChanged = MagickTrue;
          }
        }
        q += oq;
        qm1 += oq;
        qp1 += oq;
        p0 += ip;
        p1 += ip;
        p2 += ip;
      }
      if (SyncCacheViewAuthenticPixels(out_view,exception) == MagickFalse)
        okay = MagickFalse;
    }
    if (!okay) break;

    in_view2 = DestroyCacheView (in_view2);
    in_view1 = DestroyCacheView (in_view1);
    in_view0 = DestroyCacheView (in_view0);

    out_view_rdp = DestroyCacheView (out_view_rdp);
    out_view_rdm = DestroyCacheView (out_view_rdm);

    nIter++;
  } // while

  if (!okay) return NULL;

  out_view = DestroyCacheView (out_view);

  if (verbose) fprintf (stderr, "LightenNonSinks: nIter %i\n", nIter);
  return new_img;
}


#endif

avgconcrings.c

/* Updated:
     3-April-2018 for v7.0.7-28
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>

#include "vsn_defines.h"
#include "avgconcrings.inc"


static void usage (void)
{
  printf ("Usage: -process 'avgconcrings [OPTION]...'\n");
  printf ("Finds mean of concentric rings for a range of scales.\n");
  printf ("\n");
  printf ("  s0, minScale number  minimum scale\n");
  printf ("  s1, maxScale number  maximum scale\n");
  printf ("  n,  nScales integer  number of scales\n");
  printf ("  m,  method string    'slow' or 'quick'\n");
  printf ("  v,  verbose          write text information to stdout\n");
  printf ("  v2, verbose2         more text information to stdout\n");
  printf ("\nOutput has radii on x-axis, with centre at left, scales on y-axis, lowest at top.\n");
  printf ("\n");
}


static MagickBooleanType IsArg (char * pa, char * ShtOpt, char * LongOpt)
{
  if ((LocaleCompare(pa, ShtOpt)==0) || (LocaleCompare(pa, LongOpt)==0))
    return MagickTrue;

  return MagickFalse;
}


#define NEXTARG \
  if (i < argc-1) i++; \
  else { \
    fprintf (stderr, "paintpatches: ERROR: [%s] needs argument\n", pa); \
    status = MagickFalse; \
  }

static MagickBooleanType menu(
  const int argc,
  const char **argv,
  ConcRingsT * pcr
)
// Returns MagickTrue if okay.
{
  int
    i;

  MagickBooleanType
    status;

  status = MagickTrue;

  for (i=0; i < argc; i++) {
    char * pa = (char *)argv[i];

    if (IsArg (pa, "s0", "minScale")==MagickTrue) {
      NEXTARG;
      pcr->minScale = atof(argv[i]);
    } else if (IsArg (pa, "s1", "maxScale")==MagickTrue) {
      NEXTARG;
      pcr->maxScale = atof(argv[i]);
    } else if (IsArg (pa, "n", "nScales")==MagickTrue) {
      NEXTARG;
      pcr->nScales = atoi(argv[i]);
    } else if (IsArg (pa, "m", "method")==MagickTrue) {
      NEXTARG;
      if (LocaleCompare(argv[i], "slow")==0) pcr->ConcRingsMethod = crmSlow;
      else if (LocaleCompare(argv[i], "quick")==0) pcr->ConcRingsMethod = crmQuick;
      else {
        fprintf (stderr, "avgconcrings: ERROR: unknown method [%s]\n", argv[i]);
        status = MagickFalse;
      }
    } else if (IsArg (pa, "v", "verbose")==MagickTrue) {
      pcr->verbose = 1;
    } else if (IsArg (pa, "v2", "verbose2")==MagickTrue) {
      pcr->verbose = 2;
    } else {
      fprintf (stderr, "avgconcrings: ERROR: unknown option\n");
      status = MagickFalse;
    }
  }

  if (pcr->verbose) {
    fprintf (stderr, "avgconcrings options:");
    fprintf (stderr, "  maxScale %g", pcr->maxScale);
    fprintf (stderr, "  minScale %g", pcr->minScale);
    fprintf (stderr, "  nScales %i", pcr->nScales);
    fprintf (stderr, "  method ");
    switch (pcr->ConcRingsMethod) {
      case crmSlow:  fprintf (stderr, "slow"); break;
      case crmQuick: fprintf (stderr, "quick"); break;
    }
    if (pcr->verbose > 1) fprintf (stderr, "  verbose%i ", pcr->verbose);
    else if (pcr->verbose) fprintf (stderr, "  verbose ");
    fprintf (stderr, "\n");
  }

  if (status == MagickFalse)
    usage ();

  return (status);
}


ModuleExport size_t avgconcringsImage(Image **images,const int argc,
  const char **argv,ExceptionInfo *exception)
{
  Image
    *image,
    *new_image;

  MagickBooleanType
    status;

  ConcRingsT
    cr;

  assert(images != (Image **) NULL);
  assert(*images != (Image *) NULL);
  assert((*images)->signature == MAGICK_CORE_SIG);

  InitConcRings (&cr);

  status = menu (argc, argv, &cr);
  if (status == MagickFalse)
    return (-1);

  for (image=(*images); image != (Image *) NULL; image=GetNextImageInList(image))
  {
    new_image = avgconcrings (image, &cr, exception);
    if (new_image == (Image *) NULL) return (-1);

    ReplaceImageInList(&image,new_image);
    *images=GetFirstImageInList(image);

    ReInitConcRings (&cr);
  }

  DeInitConcRings (&cr);

  return(MagickImageFilterSignature);
}

avgconcrings.inc

/* Updated:
     3-April-2018 for v7.0.7-28
     5-April-2018 improved error-reporting;
       explicit channel copy from scal_img to new_img.
*/

#ifndef AVGCONCRINGS_INC
#define AVGCONCRINGS_INC

#include "rmsealpha.inc"
#include "resetpage.inc"
#include "trimone.inc"

typedef enum {
  crmSlow,
  crmQuick
} ConcRingsMethodT;

typedef struct {
  int
    verbose;

  double
    minScale,
    maxScale;

  int
    nScales;

  ConcRingsMethodT
    ConcRingsMethod;

  // Used internally:
  RmseAlphaT
    ra;

  // Calculated:
  double
    scaleFact;  // A measure of error.

  double
    nScalesM1;
} ConcRingsT;

static void InitConcRings (ConcRingsT * p)
{
  p->verbose = 0;
  p->minScale = 0.5;
  p->maxScale = 2.0;
  p->nScales = 20;
  p->ConcRingsMethod = crmQuick;
  p->scaleFact = 0;

  p->nScalesM1 = (double)(p->nScales-1);

  InitRmseAlpha (&p->ra);
}

static void ReInitConcRings (ConcRingsT * p)
{
  ReInitRmseAlpha (&p->ra);
}

static void DeInitConcRings (ConcRingsT * p)
{
  DeInitRmseAlpha (&p->ra);
}


static double inline ConcRingsScale (
  ConcRingsT * pcr,
  ssize_t y
)
// Returns the scale at a given y-coordinate.
{
  if (pcr->nScalesM1==0) return pcr->maxScale;

  return pcr->minScale *
         pow (pcr->maxScale/pcr->minScale, y / pcr->nScalesM1);
}


// The next function corresponds in style to functions in transform.c
// It takes one image, and returns an image.
//
static Image *avgconcrings (
  Image *image,
  ConcRingsT * pcr,
  ExceptionInfo *exception)
{
  Image * cln = CloneImage (image, 0, 0, MagickTrue, exception);
  if (!cln) return NULL;

//#if IMV6OR7==6
//  SetPixelCacheVirtualMethod (cln, TransparentVirtualPixelMethod);
//  SetImageAlphaChannel (cln, ActivateAlphaChannel);
//#else
//  SetPixelCacheVirtualMethod (cln,
//    TransparentVirtualPixelMethod, exception);
//  SetImageAlphaChannel (cln, ActivateAlphaChannel, exception);
//#endif

  VIRT_NONE(cln, exception);

  MagickBooleanType bestfit = MagickTrue;
  double minusOne=-1.0;

  Image *tmp_img = DistortImage (cln, DePolarDistortion,
    1, &minusOne, bestfit, exception);
  if (!tmp_img) return NULL;

  if (pcr->verbose) fprintf (stderr,
    "avgconcrings: tmp_img %lix%li\n",
    tmp_img->columns, tmp_img->rows);

  ssize_t unscaledHeight = tmp_img->rows;
  tmp_img = DestroyImage (tmp_img);

  if (pcr->verbose) fprintf (stderr,
    "avgconcrings: %lix%li unscaled %li\n",
    cln->columns, cln->rows, unscaledHeight);


  pcr->nScalesM1 = (double)(pcr->nScales-1);

  if (pcr->nScalesM1 == 0) {
    pcr->scaleFact = 0;
  } else {
    pcr->scaleFact = pow (pcr->maxScale/pcr->minScale, 1.0 / pcr->nScalesM1);
  }

  // Make an image for output: unscaledHeight x nScales, transparent black.

  Image * new_img = CloneImage(cln, unscaledHeight, pcr->nScales,
    MagickTrue, exception);
  if (!new_img) return NULL;

  SetAllBlack (new_img, exception);
#if IMV6OR7==6
  SetImageAlphaChannel (new_img, TransparentAlphaChannel);
#else
  SetImageAlphaChannel (new_img, TransparentAlphaChannel, exception);
#endif

  double scaleArray[2];
  scaleArray[0] = 1.0;  // Scale factor.
  scaleArray[1] = 0.0;  // Rotation angle.

  Image *depol_max2_img = NULL;

  if (pcr->ConcRingsMethod == crmQuick) {

    // For the quick method:
    // Make a single-column image at the maximum scale,
    // and resize it down for the others.

    scaleArray[0] = pcr->maxScale;

    Image *max_img = DistortImage (cln, ScaleRotateTranslateDistortion,
      2, scaleArray, bestfit, exception);
    if (!max_img) {
      fprintf (stderr, "avgconcrings: DistortImage SRT max_img failed\n");
      return NULL;
    }
    ResetPage (max_img);
    if (!TrimOne (&max_img, exception)) return NULL;

    if (pcr->verbose) fprintf (stderr,
      "avgconcrings: max_img %lix%li\n",
      max_img->columns, max_img->rows);

    Image *depol_max_img = DistortImage (max_img, DePolarDistortion,
      1, &minusOne, bestfit, exception);
    if (!depol_max_img) return NULL;

    DestroyImage (max_img);


    // Scale to 1 column.

    depol_max2_img = ScaleImage (depol_max_img, 1, depol_max_img->rows, exception);
    if (!depol_max2_img) return NULL;

    if (pcr->verbose) fprintf (stderr,
      "avgconcrings: depol_max2_img %lix%li\n",
      depol_max2_img->columns, depol_max2_img->rows);

    DestroyImage (depol_max_img);
  }

  MagickBooleanType okay = MagickTrue;
  int i;
  for (i=0; i < pcr->nScales; i++) {
    if (!okay) continue;

    scaleArray[0] = ConcRingsScale (pcr, i);

    if (pcr->verbose > 1) fprintf (stderr,
      "avgconcrings: %i %g\n", i, scaleArray[0]);

    Image *scal_img = NULL;

    if (pcr->ConcRingsMethod == crmSlow) {
      Image *tmp_img = DistortImage (cln, ScaleRotateTranslateDistortion,
        2, scaleArray, bestfit, exception);
      if (!tmp_img) {okay = MagickFalse; continue;}
      ResetPage (tmp_img);
      if (!TrimOne (&tmp_img, exception))
        {okay = MagickFalse; continue;};

      if (pcr->verbose > 1) fprintf (stderr,
        "avgconcrings: tmp_img %lix%li\n",
        tmp_img->columns, tmp_img->rows);

      Image *depol_img = DistortImage (tmp_img, DePolarDistortion,
        1, &minusOne, bestfit, exception);
      if (!depol_img) {okay = MagickFalse; continue;}

      tmp_img = DestroyImage (tmp_img);

      if (pcr->verbose > 1) fprintf (stderr,
        "avgconcrings: depol_img %lix%li\n",
        depol_img->columns, depol_img->rows);

      scal_img = ScaleImage (depol_img, 1, depol_img->rows, exception);
      if (!scal_img) {okay = MagickFalse; continue;}

      depol_img = DestroyImage (depol_img);

    } else if (pcr->ConcRingsMethod == crmQuick) {
      // Alternative: make scal_img by scaling depol_max2_img down.
      scaleArray[0] /= pcr->maxScale;

#if IMV6OR7==6
      SetImageVirtualPixelMethod (depol_max2_img, EdgeVirtualPixelMethod);\
      //SetPixelCacheVirtualMethod (depol_max2_img, EdgeVirtualPixelMethod);
#else
      SetImageVirtualPixelMethod (depol_max2_img, EdgeVirtualPixelMethod, exception);\

      //SetPixelCacheVirtualMethod (depol_max2_img,
      //  EdgeVirtualPixelMethod, exception);
#endif

      scal_img = DistortImage (depol_max2_img, ScaleRotateTranslateDistortion,
        2, scaleArray, bestfit, exception);
      if (!scal_img) {
        fprintf (stderr, "avgconcrings DistortImage scal_img failed\n");
        okay = MagickFalse;
        continue;
      }
      ResetPage (scal_img);
      if (!TrimOne (&scal_img, exception)) {
        fprintf (stderr, "avgconcrings: TrimOne scal_img failed\n");
        okay = MagickFalse;
        continue;
      }
    }

    if (pcr->verbose > 1) fprintf (stderr,
      "avgconcrings: scal_img %lix%li\n",
      scal_img->columns, scal_img->rows);

    // Copy pixels from scal_img to new_img.

    CacheView * scal_view = AcquireVirtualCacheView (scal_img, exception);
    CacheView * new_view = AcquireAuthenticCacheView (new_img, exception);

    const VIEW_PIX_PTR *sp = GetCacheViewVirtualPixels (
      scal_view,0,0,1,scal_img->rows,exception);
    if (!sp) {okay = MagickFalse; continue;}

    VIEW_PIX_PTR *np = GetCacheViewAuthenticPixels (
      new_view,0,i,new_img->columns,1,exception);
    if (!np) {okay = MagickFalse; continue;}

    ssize_t maxN = new_img->columns;
    if (maxN > scal_img->rows) maxN = scal_img->rows;
    ssize_t n;
    for (n=0; n < maxN; n++) {
      //*np = *sp;
      SET_PIXEL_RED (new_img, GET_PIXEL_RED(scal_img, sp), np);
      SET_PIXEL_GREEN (new_img, GET_PIXEL_GREEN(scal_img, sp), np);
      SET_PIXEL_BLUE (new_img, GET_PIXEL_BLUE(scal_img, sp), np);
      SET_PIXEL_ALPHA (new_img, GET_PIXEL_ALPHA(scal_img, sp), np);
      sp += Inc_ViewPixPtr (scal_img);
      np += Inc_ViewPixPtr (new_img);
    }

    new_view  = DestroyCacheView (new_view);
    scal_view = DestroyCacheView (scal_view);

    scal_img = DestroyImage (scal_img);
  }

  if (!okay) {
    fprintf (stderr, "avgconcrings not okay\n");
    return NULL;
  }

  if (depol_max2_img) DestroyImage (depol_max2_img);

  cln = DestroyImage (cln);

  return new_img;
}


#endif // not AVGCONCRINGS_INC

writeframe.inc

#ifndef WRITEFRAME_INC
#define WRITEFRAME_INC


static MagickBooleanType WriteFrame (
  char *FileName,
  Image * img,
  int frame_num,
  ExceptionInfo *exception)
{
  ImageInfo
    *ii;

  MagickBooleanType
    okay;

  Image
    *copy_img;

  ii = AcquireImageInfo ();

  copy_img = CloneImage(img, 0, 0, MagickTrue, exception);
  if (!copy_img) return(MagickFalse); // FIXME: raise error

  copy_img->scene = frame_num;

  CopyMagickString (copy_img->filename, FileName, MaxTextExtent);

  okay = WRITEIMAGE(ii, copy_img, exception);

  DestroyImageList(copy_img);

  ii = DestroyImageInfo (ii);

  return okay;
}


#endif

whatrot.c

/* Updated:
     3-April-2018 for v7.0.7-28
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include "vsn_defines.h"
#include "rmsealpha.inc"

#include "absdispmap.inc"
#include "whatrot.inc"

#define VERSION "whatrot v1.0  Copyright (c) 2017 Alan Gibson"

typedef struct {
  WhatRotT
    wr;

  int
    verbose;

  Image
    *DispMap;

  MagickBooleanType
    doRotate,
    UseDispMap,
    approxOnly,
    debug;

  int
    precision;

  double
    tolerance;

  FILE *
    fh_data;

} SubSrchT;


static void InitSubSrch (SubSrchT * pss)
{
  InitWhatRot (&pss->wr);
  pss->verbose = 0;
  pss->DispMap = NULL;
  pss->doRotate = MagickTrue;
  pss->UseDispMap = MagickTrue;
  pss->approxOnly = MagickFalse;
  pss->precision = 6;
  pss->tolerance = 0.01;
  pss->fh_data = stderr;
  pss->debug = MagickFalse;
}

static void DeInitSubSrch (SubSrchT * pss)
{
  if (pss->DispMap) pss->DispMap = DestroyImage (pss->DispMap);
  DeInitWhatRot (&pss->wr);
}


static void usage (void)
{
  printf ("Usage: -process 'whatrot [OPTION]...'\n");
  printf ("Find rotation by unrolling and golden section methods.\n");
  printf ("\n");
  printf ("  a,  approxOnly       use only the first (approximate) method\n");
  printf ("  t,  tolerance number for golden section method\n");
  printf ("  x,  noRotate         don't replace images with rotation\n");
  printf ("  f,  file string      write data to file stream stdout or stderr\n");
  printf ("  d,  debug            create debugging images\n");
  printf ("  v,  verbose          write text information to stdout\n");
  printf ("  v2, verbose2         write more information to stdout\n");
  printf ("  v3, verbose3         yet more information to stdout\n");
  printf ("      version          write version information to stdout\n");
  printf ("\n");
}

// FIXME: also option to normalise mean and SD.


static MagickBooleanType IsArg (char * pa, char * ShtOpt, char * LongOpt)
{
  if ((LocaleCompare(pa, ShtOpt)==0) || (LocaleCompare(pa, LongOpt)==0))
    return MagickTrue;

  return MagickFalse;
}


#define NEXTARG \
  if (i < argc-1) i++; \
  else { \
    fprintf (stderr, "paintpatches: ERROR: [%s] needs argument\n", pa); \
    status = MagickFalse; \
  }

static MagickBooleanType menu(
  const int argc,
  const char **argv,
  SubSrchT * pss
)
// Returns MagickTrue if okay.
{
  int
    i;

  MagickBooleanType
    status;

  status = MagickTrue;

  for (i=0; i < argc; i++) {
    char * pa = (char *)argv[i];

    if (IsArg (pa, "a", "approxOnly")==MagickTrue) {
      pss->approxOnly = MagickTrue;
    } else if (IsArg (pa, "x", "noRotate")==MagickTrue) {
      pss->doRotate = MagickFalse;
    } else if (IsArg (pa, "t", "tolerance")==MagickTrue) {
      NEXTARG;
      pss->tolerance = atof (argv[i]);
    } else if (IsArg (pa, "f", "file")==MagickTrue) {
      NEXTARG;
      if (strcasecmp (argv[i], "stdout")==0) pss->fh_data = stdout;
      else if (strcasecmp (argv[i], "stderr")==0) pss->fh_data = stderr;
      else status = MagickFalse;
    } else if (IsArg (pa, "d", "debug")==MagickTrue) {
      pss->debug = MagickTrue;
    } else if (IsArg (pa, "v", "verbose")==MagickTrue) {
      pss->verbose = 1;
    } else if (IsArg (pa, "v2", "verbose2")==MagickTrue) {
      pss->verbose = 2;
    } else if (IsArg (pa, "v3", "verbose3")==MagickTrue) {
      pss->verbose = 3;
    } else if (IsArg (pa, "version", "version")==MagickTrue) {
      fprintf (stdout, "%s\n", VERSION);
    } else {
      fprintf (stderr, "whatrot: ERROR: unknown option\n");
      status = MagickFalse;
    }
  }

  if (pss->verbose) {
    fprintf (stderr, "whatrot options:");
    if (pss->approxOnly) fprintf (stderr, "  approxOnly");
    fprintf (stderr, "  tolerance %g", pss->tolerance);
    if (!pss->doRotate) fprintf (stderr, "  noRotate");

    if (pss->fh_data == stdout) fprintf (stderr, " file stdout");
    if (pss->debug) fprintf (stderr, "  debug");
    if (pss->verbose > 1) fprintf (stderr, "  verbose%i", pss->verbose);
    else if (pss->verbose) fprintf (stderr, "  verbose");
    fprintf (stderr, "\n");
  }

  if (status == MagickFalse)
    usage ();

  return (status);
}


// The next function takes an image list,
// may modify it, and returns a status.
//
static MagickBooleanType whatrot (
  Image **images,
  SubSrchT * pss,
  ExceptionInfo *exception)
{
  Image * main_img = *images;

  Image * sub_img = GetNextImageInList (main_img);
  if (!sub_img) return MagickFalse;

/*==
  if (sub_img->columns > main_img->columns ||
      sub_img->rows    > main_img->rows)
  {
    fprintf (stderr, "whatrot: subimage is larger than main\n");
    return MagickFalse;
  }
==*/

#if IMV6OR7==6
  //SetPixelCacheVirtualMethod (main_img, TransparentVirtualPixelMethod);
  SetImageVirtualPixelMethod (main_img, TransparentVirtualPixelMethod);
#else
  //SetPixelCacheVirtualMethod (main_img,
  //  TransparentVirtualPixelMethod, exception);
  SetImageVirtualPixelMethod (main_img, TransparentVirtualPixelMethod, exception);
#endif

  // Make displacement map, and unroll it.
  double minusOne=-1.0;
  MagickBooleanType bestfit = MagickTrue;

  Image * id_map = mIdentAbsDispMap (sub_img, exception);
#if IMV6OR7==6
  SetImageAlphaChannel (id_map, OpaqueAlphaChannel);
#else
  SetImageAlphaChannel (id_map, OpaqueAlphaChannel, exception);
#endif

  pss->DispMap = DistortImage (id_map, DePolarDistortion,
    1, &minusOne, bestfit, exception);
  if (!pss->DispMap) return MagickFalse;

  if (pss->verbose) fprintf (stderr, "whatrot: DispMap %lix%li\n",
    pss->DispMap->columns, pss->DispMap->rows);


  pss->wr.verbose = (pss->verbose <= 1) ? 0 : pss->verbose-1;
  if (pss->debug) pss->wr.debug = "wr_dbg";

  if (!CalcApproxRot (&pss->wr, main_img, sub_img, NULL, NULL, exception)) {
    fprintf (stderr, "CalcApproxRot failed\n");
    return MagickFalse;
  }

  if (pss->verbose) {
    fprintf (stderr, "Approx  ang=%.*g +/-%.*g score=%.*g\n",
      pss->precision, pss->wr.angle,
      pss->precision, pss->wr.plusOrMinus,
      pss->precision, pss->wr.score);
  }

  if (!pss->approxOnly) {
    // Save, in case GoldSect fails.
    double saveAng = pss->wr.angle;
    double savePorm = pss->wr.plusOrMinus;
    double saveScore = pss->wr.score;

    pss->wr.tolerance = pss->tolerance;

    if (GoldSectRot (&pss->wr, main_img, sub_img, exception)) {
      if (pss->verbose) fprintf (stderr,
        "From gss: ang=%.*g +/-%.*g score=%.*g\n",
        pss->precision, pss->wr.angle,
        pss->precision, pss->wr.plusOrMinus,
        pss->precision, pss->wr.score);
    } else {
      fprintf (stderr, "whatrot: GoldSectRot failed\n");
      pss->wr.angle = saveAng;
      pss->wr.plusOrMinus = savePorm;
      pss->wr.score = saveScore;
    }
  }

  fprintf (pss->fh_data,
    "whatrot:  ang=%.*g angPorm=%.*g angScore=%.*g\n",
    pss->precision, pss->wr.angle,
    pss->precision, pss->wr.plusOrMinus,
    pss->precision, pss->wr.score);

  if (pss->doRotate) {
    MagickBooleanType bestfit = MagickTrue;
    double scaleArray[2];
    scaleArray[0] = 1.0;            // Scale factor.
    scaleArray[1] = pss->wr.angle;  // Rotation angle.

//#if IMV6OR7==6
//    SetPixelCacheVirtualMethod (sub_img, TransparentVirtualPixelMethod);
//#else
//    SetPixelCacheVirtualMethod (sub_img,
//      TransparentVirtualPixelMethod, exception);
//#endif

    VIRT_NONE(sub_img, exception);

    Image *rot_img = DistortImage (sub_img, ScaleRotateTranslateDistortion,
      2, scaleArray, bestfit, exception);
    if (!rot_img) return MagickFalse;
    // We don't ResetPage (rot_img);

    if (pss->verbose) fprintf (stderr,
      "WhatRot: created image %lix%li %lix%li%+li%+li\n",
      rot_img->columns, rot_img->rows,
      rot_img->page.width, rot_img->page.height,
      rot_img->page.x, rot_img->page.y);

    DeleteImageFromList (&sub_img);

    ReplaceImageInList (&main_img, rot_img);
    // Replace messes up the images pointer. Make it good:
    *images = GetFirstImageInList (main_img);
  }

  return MagickTrue;
}



ModuleExport size_t whatrotImage(Image **images,const int argc,
  const char **argv,ExceptionInfo *exception)
{
  MagickBooleanType
    status;

  SubSrchT
    ss;

  assert(images != (Image **) NULL);
  assert(*images != (Image *) NULL);
  assert((*images)->signature == MAGICK_CORE_SIG);

  InitSubSrch (&ss);

  ss.precision = GetMagickPrecision();

  status = menu (argc, argv, &ss);
  if (status == MagickFalse)
    return (-1);

  int ListLen = (int)GetImageListLength(*images);
  if (ListLen != 2) {
    fprintf (stderr, "whatrot: needs 2 images\n");
    return (-1);
  }

  if (!whatrot (images, &ss, exception)) return (-1);

  DeInitSubSrch (&ss);

  return(MagickImageFilterSignature);
}

whatrot.inc

/* Updated:
     3-April-2018 for v7.0.7-28
*/

#ifndef WHATROT_INC
#define WHATROT_INC


// FIXME: caller might normalize mean and SD.


#include "rmsealpha.inc"
#include "goldsectsrch.inc"
#include "resetpage.inc"
#include "trimone.inc"
#include "centsmcrop.inc"
//#include "virtnone.inc"
#include "writeframe.inc"

typedef struct {
  int
    verbose;

  MagickBooleanType
    recordA,
    recordB;

  char 
    *debug;

  double
    tolerance;

  // Used internally:

  Image
    *scaledA,
    *scaledB2;

  RmseAlphaT
    ra;

  // Returned results:
  double
    angle,  // degrees clockwise, 0.0 <= angle <= 360.0.
    plusOrMinus,
    score;
} WhatRotT;


static void InitWhatRot (WhatRotT * pwr)
{
  pwr->verbose = 0;
  pwr->recordA = pwr->recordB = MagickFalse;
  pwr->scaledA = pwr->scaledB2 = NULL;
  pwr->tolerance = 0.01;
  pwr->score = pwr->angle = pwr->plusOrMinus = 0.0;
  pwr->debug = NULL;

  InitRmseAlpha (&pwr->ra);
}

static void ReInitWhatRot (WhatRotT * pwr)
{
  if (pwr->scaledA)  pwr->scaledA  = DestroyImage (pwr->scaledA);
  if (pwr->scaledB2) pwr->scaledB2 = DestroyImage (pwr->scaledB2);
  ReInitRmseAlpha (&pwr->ra);
}

static void DeInitWhatRot (WhatRotT * pwr)
{
  ReInitWhatRot (pwr);
  DeInitRmseAlpha (&pwr->ra);
}


static Image * AppendRowSelf (
  Image * img,
  ExceptionInfo *exception)
// Returns image twice as wide, just one row,
// made from top row of img appended to itself.
{
  Image * new_img = CloneImage (img, 2 * img->columns, 1,
    MagickTrue, exception);
  if (!new_img) return NULL;

  CacheView * img_view = AcquireVirtualCacheView (img, exception);
  CacheView * new_view = AcquireAuthenticCacheView (new_img, exception);

  const VIEW_PIX_PTR *p = GetCacheViewVirtualPixels(
      img_view,0,0,img->columns,1,exception);
  if (!p) return NULL;

  VIEW_PIX_PTR *q = GetCacheViewAuthenticPixels(
      new_view,0,0,new_img->columns,1,exception);
  if (!q) return NULL;

  ssize_t dq = img->columns * Inc_ViewPixPtr (new_img);
  ssize_t x;
  for (x=0; x < img->columns; x++) {
    SET_PIXEL_RED   (new_img, GET_PIXEL_RED(img,p),   q);
    SET_PIXEL_GREEN (new_img, GET_PIXEL_GREEN(img,p), q);
    SET_PIXEL_BLUE  (new_img, GET_PIXEL_BLUE(img,p),  q);
    SET_PIXEL_ALPHA (new_img, GET_PIXEL_ALPHA(img,p),  q);

    SET_PIXEL_RED   (new_img, GET_PIXEL_RED(img,p),   q+dq);
    SET_PIXEL_GREEN (new_img, GET_PIXEL_GREEN(img,p), q+dq);
    SET_PIXEL_BLUE  (new_img, GET_PIXEL_BLUE(img,p),  q+dq);
    SET_PIXEL_ALPHA (new_img, GET_PIXEL_ALPHA(img,p),  q+dq);

    p += Inc_ViewPixPtr (img);
    q += Inc_ViewPixPtr (new_img);
  }

  new_view = DestroyCacheView (new_view);
  img_view = DestroyCacheView (img_view);

  return new_img;
}


static MagickBooleanType CalcApproxRot (
  WhatRotT * pwr,
  Image * imageA,
  Image * imageB,
  Image * DispMapA,  // May be null.
  Image * DispMapB,  // May be null.
  ExceptionInfo *exception)
//
// Find the rotation (degrees clockwise) that applied to imageB
// makes it best match imageA.
//
// Finds the approximate angle by radial filter,
// ie unroll and scale to one row.
{
  MagickBooleanType bestfit = MagickTrue;

  double minusOne=-1.0;

  if (!pwr->scaledA) {

    Image * clnA = CloneImage (imageA, 0, 0, MagickTrue, exception);
    if (!clnA) return MagickFalse;

//#if IMV6OR7==6
//    SetPixelCacheVirtualMethod (clnA, TransparentVirtualPixelMethod);
//    SetImageAlphaChannel (clnA, ActivateAlphaChannel);
//#else
//    SetPixelCacheVirtualMethod (clnA,
//      TransparentVirtualPixelMethod, exception);
//    SetImageAlphaChannel (clnA, ActivateAlphaChannel, exception);
//#endif

    VIRT_NONE(clnA, exception);

    Image *unrlA = DistortImage (clnA, DePolarDistortion,
      1, &minusOne, bestfit, exception);
    if (!unrlA) return MagickFalse;
    clnA = DestroyImage (clnA);

    pwr->scaledA = ScaleImage (unrlA, unrlA->columns, 1,
        exception);
    if (!pwr->scaledA) return MagickFalse;
    unrlA = DestroyImage (unrlA);
  }

  if (!pwr->scaledB2) {
    Image * clnB = CloneImage (imageB, 0, 0, MagickTrue, exception);
    if (!clnB) return MagickFalse;

//#if IMV6OR7==6
//    SetPixelCacheVirtualMethod (clnB, TransparentVirtualPixelMethod);
//    SetImageAlphaChannel (clnB, ActivateAlphaChannel);
//#else
//    SetPixelCacheVirtualMethod (clnB,
//      TransparentVirtualPixelMethod, exception);
//    SetImageAlphaChannel (clnB, ActivateAlphaChannel, exception);
//#endif

    VIRT_NONE(clnB, exception);

    Image *unrlB = DistortImage (clnB, DePolarDistortion,
      1, &minusOne, bestfit, exception);
    if (!unrlB) return MagickFalse;
    clnB = DestroyImage (clnB);

    // Scale to one row, and width to match A.
    Image * scaledB = ScaleImage (unrlB, pwr->scaledA->columns, 1,
        exception);
    if (!scaledB) return MagickFalse;
    unrlB = DestroyImage (unrlB);

    pwr->scaledB2 = AppendRowSelf (scaledB, exception);
    if (!pwr->scaledB2) return MagickFalse;
    scaledB = DestroyImage (scaledB);
  }

  ssize_t unrWidth = pwr->scaledA->columns;

  pwr->ra.do_verbose = (pwr->verbose > 0);

//WriteFrame ("wr_scaledB2.png", pwr->scaledB2, 0, exception);
//WriteFrame ("wr_scaledA.png", pwr->scaledA, 0, exception);

  // FIXME: Should MS be an option?
  //   It wouldn't currently improve performance.
  if (!subRmseAlpha (&pwr->ra, pwr->scaledB2, pwr->scaledA, exception))
    return MagickFalse;

  pwr->score = pwr->ra.score;
  pwr->angle = pwr->ra.solnX * 360.0 / (double)unrWidth;
  pwr->plusOrMinus = 180.0 / (double)unrWidth;

  if (pwr->verbose) fprintf (stderr,
    "CalcApproxRot: solnX=%li degrees=%g score=%g\n",
    pwr->ra.solnX, pwr->angle, pwr->score);

  if (!pwr->recordB) pwr->scaledB2 = DestroyImage (pwr->scaledB2);
  if (!pwr->recordA) pwr->scaledA  = DestroyImage (pwr->scaledA);

  return MagickTrue;
}


static MagickBooleanType InCircOnly (
  Image * img,
  ExceptionInfo *exception)
// Makes pixels outside an inscribed circle transparent.
{
  CacheView * img_view = AcquireAuthenticCacheView (img, exception);

  // FIXME: enable alpha for the image?

  // FIXME: No anti-aliasing.

  //if (pat_img->columns <= 2 || pat_img->rows <= 2) return MagickTrue;

  // FIXME: following isn't symmetrical.

  double r = img->columns;
  if (r > img->rows) r = img->rows;
  r /= 2.0;

  double a = r;
  double b = r;

  double a2 = a*a;
  double b2 = b*b;
  double a2b2 = a2*b2;

  MagickBooleanType okay = MagickTrue;

  ssize_t y;
#if defined(MAGICKCORE_OPENMP_SUPPORT)
  #pragma omp parallel for schedule(static,4) \
    shared(okay) \
    MAGICK_THREADS(img,img,img->rows,1)
#endif
  for (y = 0; y < img->rows; y++) {
    ssize_t x;

    VIEW_PIX_PTR *sp = GetCacheViewAuthenticPixels(
      img_view,0,y,img->columns,1,exception);
    if (!sp) okay = MagickFalse;

    if (!okay) continue;

    double a2y2   = a2 * (y-b) * (y-b);
    double a2y2m1 = a2 * (y-b-0.5) * (y-b-0.5);
    double a2y2p1 = a2 * (y-b+0.5) * (y-b+0.5);

    for (x = 0; x < img->columns; x++) {
      double b2x2   = b2 * (x-a) * (x-a);
      double b2x2m1 = b2 * (x-a-0.5) * (x-a-0.5);
      double b2x2p1 = b2 * (x-a+0.5) * (x-a+0.5);
      int nIn = 0;

      if (b2x2m1 + a2y2m1 < a2b2) nIn++;
      if (b2x2   + a2y2m1 < a2b2) nIn++;
      if (b2x2p1 + a2y2m1 < a2b2) nIn++;

      if (b2x2m1 + a2y2   < a2b2) nIn++;
      if (b2x2   + a2y2   < a2b2) nIn++;
      if (b2x2p1 + a2y2   < a2b2) nIn++;

      if (b2x2m1 + a2y2p1 < a2b2) nIn++;
      if (b2x2   + a2y2p1 < a2b2) nIn++;
      if (b2x2p1 + a2y2p1 < a2b2) nIn++;

      if (nIn < 9) {
        SET_PIXEL_ALPHA (img,
                         nIn / 9.0 * GET_PIXEL_ALPHA(img,sp),
                         sp);
      }
      sp += Inc_ViewPixPtr (img);
    }
    if (SyncCacheViewAuthenticPixels(img_view,exception) == MagickFalse)
      okay = MagickFalse;
  }

  if (!okay) return MagickFalse;

  img_view = DestroyCacheView (img_view);

  return MagickTrue;
}


typedef struct {
  Image * imageA;
  Image * imageB;
  int verbose;
  ExceptionInfo *exception;
  char * debug;
} GoldSectRotT;


static double cbGetRot (double ang, void * pData, int * okay)
{
  GoldSectRotT * pGsr = (GoldSectRotT *)pData;

  /* For now, a simple method:
     -distort SRT 1,angle the second image
     Make transparent all pixels outside an inscribed circle.
       (See PaintPatches EllipseOnly().)
     Return the alpha-adjusted rmse with the first image.

     As this loses data where some rotated imageB pixels fall on
     the main image but outside imageA, this can give false positives.

     A better method would lose no data, which needs a larger imageA.
     We would make transparent imageA pixels that are outside rotated imageB.
       (Then possibly normalise mean and SD.)
  */

  MagickBooleanType bestfit = MagickTrue;

  double scaleArray[2];
  scaleArray[0] = 1.0;  // Scale factor.
  scaleArray[1] = ang;  // Rotation angle.

  Image *rot_img = DistortImage (pGsr->imageB, ScaleRotateTranslateDistortion,
    2, scaleArray, bestfit, pGsr->exception);
  if (!rot_img) { *okay=0; return 0; }
  ResetPage (rot_img);
  TrimOne (&rot_img, pGsr->exception);

  Image * clnA = CloneImage (pGsr->imageA, 0, 0, MagickTrue, pGsr->exception);
  if (!clnA) { *okay=0; return 0; }

  if (!CentralSmallestCrop (&clnA, &rot_img, (pGsr->verbose > 1), pGsr->exception))
    { *okay=0; return 0; }

  //printf ("clnA: %lix%li  ",    clnA->columns, clnA->rows);
  //printf ("rot_img: %lix%li\n", rot_img->columns, rot_img->rows);

  // FIXME: Do we need to restrict to inscribed circle?
//  if (!InCircOnly (rot_img, pGsr->exception)) { *okay=0; return 0; }

  RmseAlphaT ra;
  InitRmseAlpha (&ra);  // FIXME: Can we do this outside the loop?
  ra.do_verbose = (pGsr->verbose <= 1) ? 0 : pGsr->verbose-1;

  if (pGsr->debug) {
    WriteFrame ("wrcb_rot.png", rot_img, 0, pGsr->exception);
    WriteFrame ("wrcb_clna.png", clnA, 0, pGsr->exception);
  }

  if (!subRmseAlpha (&ra, rot_img, clnA, pGsr->exception))
    { *okay=0; fprintf (stderr, "cbGetRot sra failed\n"); return 0; }

  DeInitRmseAlpha (&ra);

  DestroyImage (rot_img);
  DestroyImage (clnA);

  if (ra.score < 0) {
    fprintf (stderr, "cbGetRot sra %g\n", ra.score);
    *okay = 0;
  }

  if (pGsr->verbose) fprintf (stderr,
    "cbGetRot: ang=%g, score=%g\n", ang, ra.score);

  return ra.score;
}

static MagickBooleanType GoldSectRot (
  WhatRotT * pwr,
  Image * imageA,
  Image * imageB,
  ExceptionInfo *exception)
// Finds rotation by golden section search.
{
  if (pwr->verbose) fprintf (stderr, "GoldSectRot %g +/- %g\n",
    pwr->angle, pwr->plusOrMinus);

  Image * clnB = CloneImage (imageB, 0, 0, MagickTrue, exception);
  if (!clnB) return MagickFalse;

//#if IMV6OR7==6
//  SetPixelCacheVirtualMethod (clnB, TransparentVirtualPixelMethod);
//#else
//  SetPixelCacheVirtualMethod (clnB,
//    TransparentVirtualPixelMethod, exception);
//#endif

  VIRT_NONE(clnB, exception);

  goldSectSrchT gss;

  InitGoldSectSrch (&gss);

  double porm = pwr->plusOrMinus * 2;
  if (porm < 5) porm = 5;

  gss.xLo = pwr->angle - porm;
  gss.xHi = pwr->angle + porm;
  gss.epsX = pwr->tolerance * 2;
  gss.getYcb = cbGetRot;
  gss.verbose = (pwr->verbose <= 1) ? 0 : pwr->verbose-1 ;

  GoldSectRotT gsr;
  gsr.imageA = imageA;
  gsr.imageB = clnB;
  gsr.verbose = gss.verbose;
  gsr.exception = exception;
  gsr.debug = pwr->debug;

  if (! GoldSectSrch (&gss, &gsr)) {
    clnB = DestroyImage (clnB);
    return MagickFalse;
  }

  pwr->angle = gss.fndX;
  pwr->plusOrMinus = gss.xPlusOrMinus;
  pwr->score = gss.calcY;

  clnB = DestroyImage (clnB);

  return MagickTrue;
}


#endif

whatscale.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include "vsn_defines.h"
#include "rmsealpha.inc"
//#include "chklist.h"

#include "avgconcrings.inc"
#include "writeframe.inc"
#include "absdispmap.inc"
#include "whatscale.inc"

#define VERSION "whatrot v1.0  Copyright (c) 2017 Alan Gibson"

typedef struct {
  ConcRingsT
    cr;

  Image
    *AvgConcRings,
    *DispMap;

  MagickBooleanType
    doScale,
    UseDispMap,
    JustSinks,
    FindRotation,
    approxOnly,
    refineOnly,
    phApprox,
    phRefine,
    debug;

  int
    verbose,
    precision,
    scaleRadMax;

  double
    inPorm,
    tolerance;

  FILE *
    fh_data;

} SubSrchT;

static void InitSubSrch (SubSrchT * pss)
{
  InitConcRings (&pss->cr);
  pss->AvgConcRings = NULL;
  pss->DispMap = NULL;
  pss->doScale = MagickTrue;
  pss->UseDispMap = MagickTrue;
  pss->JustSinks = MagickTrue;
  pss->FindRotation = MagickFalse;
  pss->approxOnly = pss->refineOnly = MagickFalse;
  pss->phApprox = pss->phRefine = MagickTrue;
  pss->debug = MagickFalse;
  pss->verbose = 0;
  pss->precision = 6;
  pss->scaleRadMax = 0;
  pss->inPorm = 1.3;
//  pss->porm = 0.3;
  pss->tolerance = 0.01;
  pss->fh_data = stderr;
}

static void DeInitSubSrch (SubSrchT * pss)
{
  if (pss->AvgConcRings) pss->AvgConcRings = DestroyImage (pss->AvgConcRings);
  if (pss->DispMap) pss->DispMap = DestroyImage (pss->DispMap);
  DeInitConcRings (&pss->cr);
}


static void usage (void)
{
  printf ("Usage: -process 'whatscale [OPTION]...'\n");
  printf ("Find scale by unrolling and golden section methods.\n");
  printf ("\n");
  printf ("  a,  approxOnly       use only the first (approximate) method\n");
  printf ("  r,  refineOnly       use only the second (refining) method\n");
  printf ("  x,  noScale          don't replace images with rescaled\n");
  printf ("  s0, minScale number  minimum scale\n");
  printf ("  s1, maxScale number  maximum scale\n");
  printf ("  ns, nScales integer  number of scales\n");
  printf ("  p,  porm number      plus or minus for scale gss\n");
  printf ("  t,  tolerance number error tolerance for scale gss\n");
  printf ("  r,  scaleRadMax int  scale so radius is this, at max\n");
  printf ("  m,  method string    for concentric rings, 'slow' or 'quick'\n");
  printf ("  f,  file string      write data to file stream stdout or stderr\n");
  printf ("  d,  debug            create debugging images\n");
  printf ("  v,  verbose          write text information to stdout\n");
  printf ("  v2, verbose2         write more information to stdout\n");
  printf ("      version          write version information to stdout\n");
  printf ("\n");
}

// FIXME: also option to not update image list.
// FIXME: also option to normalise mean and SD.


static MagickBooleanType IsArg (char * pa, char * ShtOpt, char * LongOpt)
{
  if ((LocaleCompare(pa, ShtOpt)==0) || (LocaleCompare(pa, LongOpt)==0))
    return MagickTrue;

  return MagickFalse;
}


#define NEXTARG \
  if (i < argc-1) i++; \
  else { \
    fprintf (stderr, "paintpatches: ERROR: [%s] needs argument\n", pa); \
    status = MagickFalse; \
  }

static MagickBooleanType menu(
  const int argc,
  const char **argv,
  SubSrchT * pss
)
// Returns MagickTrue if okay.
{
  int
    i;

  MagickBooleanType
    status;

  status = MagickTrue;

  for (i=0; i < argc; i++) {
    char * pa = (char *)argv[i];

    if (IsArg (pa, "a", "approxOnly")==MagickTrue) {
      pss->approxOnly = MagickTrue;
      pss->phRefine = MagickFalse;
    } else if (IsArg (pa, "r", "refineOnly")==MagickTrue) {
      pss->refineOnly = MagickTrue;
      pss->phApprox = MagickFalse;
    } else if (IsArg (pa, "x", "noScale")==MagickTrue) {
      pss->doScale = MagickFalse;
    } else if (IsArg (pa, "s0", "minScale")==MagickTrue) {
      NEXTARG;
      pss->cr.minScale = atof(argv[i]);
    } else if (IsArg (pa, "s1", "maxScale")==MagickTrue) {
      NEXTARG;
      pss->cr.maxScale = atof(argv[i]);
    } else if (IsArg (pa, "ns", "nScales")==MagickTrue) {
      NEXTARG;
      pss->cr.nScales = atoi(argv[i]);
    } else if (IsArg (pa, "p", "porm")==MagickTrue) {
      NEXTARG;
      pss->inPorm = atof(argv[i]);
    } else if (IsArg (pa, "t", "tolerance")==MagickTrue) {
      NEXTARG;
      pss->tolerance = atof(argv[i]);
    } else if (IsArg (pa, "r", "scaleRadMax")==MagickTrue) {
      NEXTARG;
      pss->scaleRadMax = atoi(argv[i]);
    } else if (IsArg (pa, "m", "method")==MagickTrue) {
      NEXTARG;
      if (LocaleCompare(argv[i], "slow")==0) pss->cr.ConcRingsMethod = crmSlow;
      else if (LocaleCompare(argv[i], "quick")==0) pss->cr.ConcRingsMethod = crmQuick;
      else {
        fprintf (stderr, "whatscale: ERROR: unknown method [%s]\n", argv[i]);
        status = MagickFalse;
      }
    } else if (IsArg (pa, "d", "debug")==MagickTrue) {
      pss->debug = MagickTrue;
    } else if (IsArg (pa, "f", "file")==MagickTrue) {
      NEXTARG;
      if (strcasecmp (argv[i], "stdout")==0) pss->fh_data = stdout;
      else if (strcasecmp (argv[i], "stderr")==0) pss->fh_data = stderr;
      else status = MagickFalse;
    } else if (IsArg (pa, "v", "verbose")==MagickTrue) {
      pss->verbose = 1;
    } else if (IsArg (pa, "v2", "verbose2")==MagickTrue) {
      pss->verbose = 2;
    } else if (IsArg (pa, "version", "version")==MagickTrue) {
      fprintf (stdout, "%s\n", VERSION);
    } else {
      fprintf (stderr, "whatscale: ERROR: unknown option\n");
      status = MagickFalse;
    }
  }

  if (pss->verbose) {
    fprintf (stderr, "whatscale options:");
    if (pss->approxOnly) fprintf (stderr, "  approxOnly");
    if (pss->refineOnly) fprintf (stderr, "  refineOnly");
    fprintf (stderr, "  minScale %g", pss->cr.minScale);
    fprintf (stderr, "  maxScale %g", pss->cr.maxScale);
    fprintf (stderr, "  nScales %i", pss->cr.nScales);
    fprintf (stderr, "  porm %g", pss->inPorm);
    fprintf (stderr, "  tolerance %g", pss->tolerance);
    fprintf (stderr, "  method ");
    switch (pss->cr.ConcRingsMethod) {
      case crmSlow:  fprintf (stderr, "slow"); break;
      case crmQuick: fprintf (stderr, "quick"); break;
    }
    if (!pss->doScale) fprintf (stderr, "  noScale");
    if (pss->debug) fprintf (stderr, "  debug");
    if (pss->fh_data == stdout) fprintf (stderr, " file stdout");
    if (pss->verbose==2) fprintf (stderr, "  verbose2");
    else if (pss->verbose) fprintf (stderr, "  verbose");
    fprintf (stderr, "\n");
  }

  if (status == MagickFalse)
    usage ();

  return (status);
}


// The next function takes an image list,
// may modify it, and returns a status.
//
static MagickBooleanType whatscale (
  Image **images,
  SubSrchT * pss,
  ExceptionInfo *exception)
// Takes two images: can be same size, or either can be larger.
{
  Image * main_img = *images;

  Image * sub_img = GetNextImageInList (main_img);
  if (!sub_img) return MagickFalse;

  pss->cr.verbose = (pss->verbose > 1);


/*--
  // Make the concentric rings version of the subimage.
  pss->AvgConcRings = avgconcrings (sub_img, &pss->cr, exception);
  if (!pss->AvgConcRings) {
    fprintf (stderr, "whatscale: avgconcrings failed\n");
    return MagickFalse;
  }

  // Make displacement map, and unroll it.
  double minusOne=-1.0;
  MagickBooleanType bestfit = MagickTrue;

  Image * id_map = mIdentAbsDispMap (sub_img, exception);
#if IMV6OR7==6
  SetImageAlphaChannel (id_map, OpaqueAlphaChannel);
#else
  SetImageAlphaChannel (id_map, OpaqueAlphaChannel, exception);
#endif

  pss->DispMap = DistortImage (id_map, DePolarDistortion,
    1, &minusOne, bestfit, exception);
  if (!pss->DispMap) return MagickFalse;

  if (pss->verbose) fprintf (stderr, "whatscale: DispMap %lix%li\n",
    pss->DispMap->columns, pss->DispMap->rows);

  //if (!WriteFrameImage (pss->DispMap, "dispmap.png", NULL, exception))
  //  return MagickFalse;

  // Check: width of AvgConcRings should be height of DispMap.
  if (pss->AvgConcRings->columns != pss->DispMap->rows) {
    fprintf (stderr, "pss->AvgConcRings->columns != pss->DispMap->rows\n");
  }

  // Possibly shrink pss->AvgConcRings to X columns,
  // and pss->DispMap in same proportion.

  if (pss->scaleRadMax && pss->AvgConcRings->columns > pss->scaleRadMax) {

    Image * tmp_img1 = RESIZEIMG (pss->AvgConcRings, pss->scaleRadMax, pss->AvgConcRings->rows,
      exception);
    Image * tmp_img2 = RESIZEIMG (pss->DispMap, pss->DispMap->rows, pss->scaleRadMax,
      exception);

    if (!tmp_img1) return MagickFalse;
    ReplaceImageInList (&pss->AvgConcRings, tmp_img1);

    if (!tmp_img2) return MagickFalse;
    ReplaceImageInList (&pss->DispMap, tmp_img2);

  }

  pss->cr.ra.do_verbose = pss->cr.verbose;

--*/

  WhatScaleT ws;
  InitWhatScale (&ws);
  ws.scale = 1.0;
  ws.inPorm = pss->inPorm;
  ws.tolerance = pss->tolerance;
  ws.verbose = pss->verbose;
  ws.score = 1.0;
  if (pss->debug) ws.debug = "ws_dbg";

  // FIXME: next shld be a parameter, so user can prevent the loop.
#define MAX_ITER 10
  int i;
  for (i = 0; i < MAX_ITER; i++) {

    if (! PrepCalcApproxScale (
      &pss->cr, sub_img, &pss->AvgConcRings,
      NULL, exception)) return MagickFalse;

    //  pss->DispMap = DestroyImage (pss->DispMap); // FIXME: temp


    if (pss->debug) {
      WriteFrame ("wsx_main.png", main_img, 0, exception);
      WriteFrame ("wsx_sub.png", sub_img, 0, exception);
      WriteFrame ("wsx_acr.png", pss->AvgConcRings, 0, exception);
    }

    if (pss->phApprox) {
      if (!CalcApproxScale (&ws, &pss->cr, main_img, pss->AvgConcRings,
                         pss->DispMap, exception))
      {
        fprintf (stderr, "CalcApproxScale failed\n");
      }

      if (pss->verbose) {
        fprintf (stderr,
          "whatscale: approx scale=%.*g +/-=%.*g score=%.*g\n",
           pss->precision, ws.scale,
           pss->precision, ws.porm,
           pss->precision, ws.score);
      }

      if (ws.possSmaller) {
        double rangeFact = pss->cr.maxScale / pss->cr.minScale;
        pss->cr.maxScale = sqrt (pss->cr.minScale * pss->cr.maxScale);
        pss->cr.minScale = pss->cr.maxScale / rangeFact;
        if (pss->verbose) fprintf (stderr,
          "Try smaller scale, range %g to %g\n",
          pss->cr.minScale, pss->cr.maxScale);
      } else if (ws.possLarger) {
        double rangeFact = pss->cr.maxScale / pss->cr.minScale;
        pss->cr.minScale = sqrt (pss->cr.minScale * pss->cr.maxScale);
        pss->cr.maxScale = pss->cr.minScale * rangeFact;
        if (pss->verbose) fprintf (stderr,
          "Try larger scale, range %g to %g\n",
          pss->cr.minScale, pss->cr.maxScale);
      } else {
        break;
      }
    }
  }
  if (i==MAX_ITER) fprintf (stderr,
    "whatscale: scale probably wrong, between %g and %g, scale=%g?\n",
    pss->cr.minScale, pss->cr.maxScale,
    ws.scale);


  if (pss->phRefine) {
    // Save, in case GoldSect fails.
    double saveScale = ws.scale;
    double savePorm = ws.porm;
    double saveScore = ws.score;

    ws.porm = pss->tolerance;
    ws.tolerance = pss->tolerance;

    if (GoldSectScale (&ws, main_img, sub_img, exception)) {
      if (pss->verbose) {
        fprintf (stderr,
          "whatscale: gss scale=%.*g +/-=%.*g score=%.*g\n",
           pss->precision, ws.scale,
           pss->precision, ws.porm,
           pss->precision, ws.score);
      }
    } else {
      fprintf (stderr, "whatscale: GoldSectScale failed\n");
      ws.scale = saveScale;
      ws.porm = savePorm;
      ws.score = saveScore;
    }
  }

  fprintf (pss->fh_data,
    "whatscale: scale=%.*g scalePorm=%.*g scaleScore=%.*g\n",
     pss->precision, ws.scale,
     pss->precision, ws.porm,
     pss->precision, ws.score);

  DeInitWhatScale (&ws);

  if (pss->doScale) {

    MagickBooleanType bestfit = MagickTrue;
    double scaleArray[2];
    scaleArray[0] = ws.scale; // Scale factor.
    scaleArray[1] = 0.0;   // Rotation angle.

#if IMV6OR7==6
    //SetPixelCacheVirtualMethod (sub_img, TransparentVirtualPixelMethod);
    SetImageVirtualPixelMethod (sub_img, TransparentVirtualPixelMethod);
#else
    //SetPixelCacheVirtualMethod (sub_img,
    //  TransparentVirtualPixelMethod, exception);
    SetImageVirtualPixelMethod (sub_img, TransparentVirtualPixelMethod, exception);
#endif

    Image *resB = DistortImage (sub_img, ScaleRotateTranslateDistortion,
      2, scaleArray, bestfit, exception);
    if (!resB) return MagickFalse;
    // We don't ResetPage (resB);

    if (pss->verbose) fprintf (stderr,
      "WhatScale: created image %lix%li %lix%li%+li%+li\n",
      resB->columns, resB->rows,
      resB->page.width, resB->page.height,
      resB->page.x, resB->page.y);

    DeleteImageFromList (&sub_img);

    ReplaceImageInList (&main_img, resB);
    // Replace messes up the images pointer. Make it good:
    *images = GetFirstImageInList (main_img);
  }

  return MagickTrue;
}



ModuleExport size_t whatscaleImage(Image **images,const int argc,
  const char **argv,ExceptionInfo *exception)
{
  MagickBooleanType
    status;

  SubSrchT
    ss;

  assert(images != (Image **) NULL);
  assert(*images != (Image *) NULL);
  assert((*images)->signature == MAGICK_CORE_SIG);

  InitSubSrch (&ss);

  ss.precision = GetMagickPrecision();

  status = menu (argc, argv, &ss);
  if (status == MagickFalse)
    return (-1);

  int ListLen = (int)GetImageListLength(*images);
  if (ListLen != 2) {
    fprintf (stderr, "whatscale: needs 2 images\n");
    return (-1);
  }

  if (!whatscale (images, &ss, exception)) return -1;

  DeInitSubSrch (&ss);

  return(MagickImageFilterSignature);
}

whatscale.inc

#ifndef WHATSCALE_INC
#define WHATSCALE_INC


// Returns the angle and score.

// FIXME: caller might normalize mean and SD.

/* Updated:
     5-April-2018 consider smaller or larger only if non-zero score.
*/

#include "rmsealpha.inc"
#include "goldsectsrch.inc"
#include "avgconcrings.inc"
#include "resetpage.inc"
#include "trimone.inc"
#include "centsmcrop.inc"

#include "writeframe.inc"



typedef struct {
  int
    verbose;

  char 
    *debug;

  double
    inPorm,
    tolerance;

  // Results:
  double
    scale,
    porm,      // error margin, plus or minus
    scaleFact, // error margin
    score;

  MagickBooleanType
    possSmaller,
    possLarger;

} WhatScaleT;


static void InitWhatScale (WhatScaleT * pws)
{
  pws->verbose = 0;
  pws->score = pws->scale = pws->scaleFact = 0;
  pws->inPorm = 1.2;  // >= 1.0
  pws->debug = NULL;
  pws->tolerance = 0.01;

  pws->scale = pws->porm = pws->scaleFact = pws->score = 0.0;
}


static void ReInitWhatScale (WhatScaleT * pws)
{
  ; // Nothing.
}

static void DeInitWhatScale (WhatScaleT * pws)
{
  ; // Nothing.
}


static MagickBooleanType PrepCalcApproxScale (
  ConcRingsT * pcr,
  Image * sub_img,
  Image ** pAvgConcRings, // Creates this.
  Image ** pDispMap,      // If not null, creates this.
  ExceptionInfo *exception)
{
  // Make the concentric rings version of the subimage.
  *pAvgConcRings = avgconcrings (sub_img, pcr, exception);
  if (!*pAvgConcRings) {
    fprintf (stderr, "whatscale: avgconcrings failed\n");
    return MagickFalse;
  }

  if (pDispMap) {
    // Make displacement map, and unroll it.
    double minusOne=-1.0;
    MagickBooleanType bestfit = MagickTrue;

    Image * id_map = mIdentAbsDispMap (sub_img, exception);
#if IMV6OR7==6
    SetImageAlphaChannel (id_map, OpaqueAlphaChannel);
#else
    SetImageAlphaChannel (id_map, OpaqueAlphaChannel, exception);
#endif

    *pDispMap = DistortImage (id_map, DePolarDistortion,
      1, &minusOne, bestfit, exception);
    if (!*pDispMap) return MagickFalse;

    if (pcr->verbose) fprintf (stderr, "whatscale: DispMap %lix%li\n",
      (*pDispMap)->columns, (*pDispMap)->rows);

    //if (!WriteFrameImage (pss->DispMap, "dispmap.png", NULL, exception))
    //  return MagickFalse;

    // Check: width of AvgConcRings should be height of DispMap.
    if ((*pAvgConcRings)->columns != (*pDispMap)->rows) {
      fprintf (stderr, "AvgConcRings->columns != DispMap->rows\n");
    }

  }

  // Possibly shrink pss->AvgConcRings to X columns,
  // and pss->DispMap in same proportion.

/*==
  if (pss->scaleRadMax && pss->AvgConcRings->columns > pss->scaleRadMax) {

    Image * tmp_img1 = RESIZEIMG (pss->AvgConcRings, pss->scaleRadMax, pss->AvgConcRings->rows,
      exception);
    Image * tmp_img2 = RESIZEIMG (pss->DispMap, pss->DispMap->rows, pss->scaleRadMax,
      exception);

    if (!tmp_img1) return MagickFalse;
    ReplaceImageInList (&pss->AvgConcRings, tmp_img1);

    if (!tmp_img2) return MagickFalse;
    ReplaceImageInList (&pss->DispMap, tmp_img2);
  }
==*/

  pcr->ra.do_verbose = pcr->verbose;

  return MagickTrue;
}


static MagickBooleanType CalcApproxScale (
  WhatScaleT * pws,
  ConcRingsT * pcr,
  Image * cropped,      // Must not be null.
  Image * AvgConcRings, // Must not be null.
  Image * DispMap,      // May be null.
  ExceptionInfo *exception)
{
  pws->possSmaller = pws->possLarger = MagickFalse;

  MagickBooleanType bestfit = MagickTrue;

  double minusOne=-1.0;

  Image *unrl_img;

  if (DispMap) {

    // For DePolar, faster alternative is to use pre-built displacement map.
    // Scale image to size of the disp map, and displace ("distort") it.

    // Scale instead of resize. Scale is quicker, and no practical difference.
    unrl_img = ScaleImage (cropped, DispMap->columns, DispMap->rows,
      exception);
    if (!unrl_img) return MagickFalse;

    if (!COMPOSITE(unrl_img, DistortCompositeOp, DispMap, 0,0, exception))
    {
      fprintf (stderr, "CalcApproxScale: Composite failed\n");
      return MagickFalse;
    }

    if (!COMPOSITE(unrl_img, COPY_OPACITY, DispMap, 0,0, exception))
    {
      fprintf (stderr, "CalcApproxScale: Composite2 failed\n");
      return MagickFalse;
    }

    if (pws->debug)
      WriteFrame ("wrx_unrl_img.png", unrl_img, 0, exception);

    //if (pcr->do_verbose) fprintf (stderr, "CalcApproxScale: unrl 2 %lix%li\n",
    //  unrl_img->columns, unrl_img->rows);

  } else {
    unrl_img = DistortImage (cropped, DePolarDistortion,
      1, &minusOne, bestfit, exception);
  }

  if (!unrl_img) return MagickFalse;

  // Scale to 1 column, with height of AvgConcRings width
  // (instead of unrl_img->rows).
  // FIXME: put it back.

  Image * one_col_img = ScaleImage (unrl_img, 1, unrl_img->rows, exception);
  if (!one_col_img) return MagickFalse;
  unrl_img = DestroyImage (unrl_img);

  // Rotate by -90 degrees.

  Image * one_row_img = IntegralRotateImage (one_col_img, 3, exception);
  if (!one_row_img) return MagickFalse;
  one_col_img = DestroyImage (one_col_img);
  ResetPage (one_row_img);

  if (pws->debug)
    WriteFrame ("wsx_one_row_img.png", one_row_img, 0, exception);

  //if (pcr->do_verbose) fprintf (stderr, "CalcApproxScale: %lix%li\n",
  //  one_row_img->columns, one_row_img->rows);

  // Search for one_row_img in AvgConcRings.
  // Return score and scale.

  pcr->ra.doSlideX = MagickFalse;
  if (!subRmseAlpha (&pcr->ra, AvgConcRings, one_row_img, exception)) {
    fprintf (stderr, "CalcApproxScale: subRmseAlpha failed\n");
    return MagickFalse;
  }

  one_row_img = DestroyImage (one_row_img);

  if (pcr->ra.score != 0) {
    if (pcr->ra.solnY==0) pws->possSmaller = MagickTrue;
    else if (pcr->ra.solnY==AvgConcRings->rows-1) pws->possLarger = MagickTrue;
  }

  pws->scale = ConcRingsScale (pcr, pcr->ra.solnY);
  pws->porm  = ConcRingsScale (pcr, pcr->ra.solnY + 1) - pws->scale;
  pws->score = pcr->ra.score;

  if (pws->verbose) {
    fprintf (stderr,
    "CalcApproxScale: solnY=%li scale=%g score=%g",
    pcr->ra.solnY, pws->scale, pws->score);
    if (pws->possSmaller) fprintf (stderr, " (possibly smaller)");
    if (pws->possLarger)  fprintf (stderr, " (possibly larger)");
    fprintf (stderr, "\n");
  }

  return MagickTrue;
}



typedef struct {
  Image * imageA;
  Image * imageB;
  int verbose;
  ExceptionInfo *exception;
  char * debug;
} GoldSectScaleT;

static double cbGetScale (double scale, void * pData, int * okay)
{
  GoldSectScaleT * pGsr = (GoldSectScaleT *)pData;

  /* Resize imageB.
     Crop the larger to the smaller.
     Return the alpha-adjusted rmse.
  */

  MagickBooleanType bestfit = MagickTrue;
  double scaleArray[2];
  scaleArray[0] = scale; // Scale factor.
  scaleArray[1] = 0.0;   // Rotation angle.

  Image *resB = DistortImage (pGsr->imageB, ScaleRotateTranslateDistortion,
    2, scaleArray, bestfit, pGsr->exception);
  if (!resB) { fprintf (stderr, "DistortImage failed\n"); *okay=0; return 0; }
  ResetPage (resB);
  TrimOne (&resB, pGsr->exception);

  Image * clnA = CloneImage (pGsr->imageA, 0, 0, MagickTrue, pGsr->exception);
  if (!clnA) {
    fprintf (stderr, "cbGetScale clone failed\n");
    *okay=0;
    return 0;
  }

  if (!CentralSmallestCrop (&clnA, &resB, (pGsr->verbose > 1), pGsr->exception)) {
    fprintf (stderr, "cbGetScale csc failed\n");
    *okay=0;
    return 0;
  }

  if (pGsr->debug) {
    WriteFrame ("wscb_clna.png", clnA, 0, pGsr->exception);
    WriteFrame ("wscb_resb.png", resB, 0, pGsr->exception);
  }


/*===
  MagickBooleanType CropIt = MagickTrue;
  Image *toCrop, *othImg, *crpd_img;
  if (resB->columns > pGsr->imageA->columns) {
    toCrop = resB;
    othImg = pGsr->imageA;
  } else if (resB->columns < pGsr->imageA->columns) {
    toCrop = pGsr->imageA;
    othImg = resB;
  } else {
    CropIt = MagickFalse;
    crpd_img = resB;
    othImg = pGsr->imageA;
  }

  if (CropIt) {
    RectangleInfo crpRect;
    crpRect.width  = othImg->columns;
    crpRect.height = othImg->rows;
    crpRect.x = (toCrop->columns - othImg->columns) / 2;
    crpRect.y = (toCrop->rows    - othImg->rows) / 2;

    crpd_img = CropCheck (toCrop, &crpRect, pGsr->exception);
    if (!crpd_img) { fprintf (stderr, "CropImage failed\n"); *okay=0; return 0; }

    ResetPage (crpd_img);
  }
===*/

  RmseAlphaT ra;
  InitRmseAlpha (&ra);  // FIXME: Can we do this outside the loop?
  ra.do_verbose = (pGsr->verbose <= 1) ? 0 : pGsr->verbose-1;

//  if (!subRmseAlpha (&ra, crpd_img, othImg, pGsr->exception))
  if (!subRmseAlpha (&ra, clnA, resB, pGsr->exception))
  {
    fprintf (stderr, "cbGetScale: subRmseAlpha failed\n");
    *okay=0;
    return 0;
  }

  DeInitRmseAlpha (&ra);

//  if (CropIt) DestroyImage (crpd_img);
  DestroyImage (clnA);
  DestroyImage (resB);

  if (ra.score < 0) {
    fprintf (stderr, "cbGetScale: subRmseAlpha %g\n", ra.score);
    *okay = 0;
  }

  if (pGsr->verbose) fprintf (stderr,
    "cbGetScale: scale=%g, score=%g\n", scale, ra.score);

  return ra.score;
}


static MagickBooleanType GoldSectScale (
  WhatScaleT * pws,
  Image * imageA,
  Image * imageB,
  ExceptionInfo *exception)
// Finds scale by golden section search.
{

  if (pws->verbose) fprintf (stderr,
    "GoldSectScale inScale=%g inPorm=%g tol=%g\n",
    pws->scale, pws->inPorm, pws->tolerance);

  goldSectSrchT gss;

  InitGoldSectSrch (&gss);

  gss.xLo = pws->scale / pws->inPorm; // FIXME: get error margin from pws.
  gss.xHi = pws->scale * pws->inPorm;
  gss.epsX = pws->tolerance;
  gss.getYcb = cbGetScale;
  gss.verbose = (pws->verbose <= 1) ? 0 : pws->verbose-1 ;

  GoldSectScaleT gsr;
  gsr.imageA = imageA;
  gsr.imageB = imageB;
  gsr.verbose = gss.verbose;
  gsr.exception = exception;
  gsr.debug = pws->debug;

  if (! GoldSectSrch (&gss, &gsr)) return MagickFalse;

  pws->scale = gss.fndX;
  pws->scaleFact = gss.xPlusOrMinus * 2;
  pws->score = gss.calcY;
  pws->porm = gss.xPlusOrMinus;

  return MagickTrue;
}


#endif

whatrotscale.c

/* Updated:
     3-April-2018 for v7.0.7-28
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include "vsn_defines.h"
#include "rmsealpha.inc"

#include "absdispmap.inc"
#include "whatrot.inc"
#include "whatscale.inc"
#include "whatrotscale.inc"

#define VERSION "whatrotscale v1.0  Copyright (c) 2017 Alan Gibson"

typedef struct {
  WhatRotScaleT
    wrs;

  int
    verbose;

  Image
    *DispMap;

  MagickBooleanType
    doRotate,
    UseDispMap,
    approxOnly,
    debug;

  int
    precision;

  double
    tolerance;

  FILE *
    fh_data;

} SubSrchT;


static void InitSubSrch (SubSrchT * pss)
{
  InitWhatRotScale (&pss->wrs);
  pss->verbose = 0;
  pss->DispMap = NULL;
  pss->doRotate = MagickTrue;
  pss->UseDispMap = MagickTrue;
  pss->approxOnly = MagickFalse;
  pss->precision = 6;
  pss->tolerance = 0.01;
  pss->fh_data = stderr;
  pss->debug = MagickFalse;
}

static void DeInitSubSrch (SubSrchT * pss)
{
  if (pss->DispMap) pss->DispMap = DestroyImage (pss->DispMap);
  DeInitWhatRotScale (&pss->wrs);
}


static void usage (void)
{
  printf ("Usage: -process 'whatrot [OPTION]...'\n");
  printf ("Find scale and rotation by unrolling and golden section methods.\n");
  printf ("\n");
  printf ("  a,  approxOnly       use only the first (approximate) method\n");
  printf ("  t,  tolerance number for golden section method\n");
  printf ("  x,  noChange         don't replace images\n");
  printf ("  f,  file string      write data to file stream stdout or stderr\n");
  printf ("  d,  debug            create debugging images\n");
  printf ("  v,  verbose          write text information to stdout\n");
  printf ("  v2, verbose2         write more information to stdout\n");
  printf ("  v3, verbose3         yet more information to stdout\n");
  printf ("      version          write version information to stdout\n");
  printf ("\n");
}

// FIXME: also option to normalise mean and SD.


static MagickBooleanType IsArg (char * pa, char * ShtOpt, char * LongOpt)
{
  if ((LocaleCompare(pa, ShtOpt)==0) || (LocaleCompare(pa, LongOpt)==0))
    return MagickTrue;

  return MagickFalse;
}


#define NEXTARG \
  if (i < argc-1) i++; \
  else { \
    fprintf (stderr, "paintpatches: ERROR: [%s] needs argument\n", pa); \
    status = MagickFalse; \
  }

static MagickBooleanType menu(
  const int argc,
  const char **argv,
  SubSrchT * pss
)
// Returns MagickTrue if okay.
{
  int
    i;

  MagickBooleanType
    status;

  status = MagickTrue;

  for (i=0; i < argc; i++) {
    char * pa = (char *)argv[i];

    if (IsArg (pa, "a", "approxOnly")==MagickTrue) {
      pss->approxOnly = MagickTrue;
    } else if (IsArg (pa, "x", "noChange")==MagickTrue) {
      pss->doRotate = MagickFalse;
    } else if (IsArg (pa, "t", "tolerance")==MagickTrue) {
      NEXTARG;
      pss->tolerance = atof (argv[i]);
    } else if (IsArg (pa, "f", "file")==MagickTrue) {
      NEXTARG;
      if (strcasecmp (argv[i], "stdout")==0) pss->fh_data = stdout;
      else if (strcasecmp (argv[i], "stderr")==0) pss->fh_data = stderr;
      else status = MagickFalse;
    } else if (IsArg (pa, "d", "debug")==MagickTrue) {
      pss->debug = MagickTrue;
    } else if (IsArg (pa, "v", "verbose")==MagickTrue) {
      pss->verbose = 1;
    } else if (IsArg (pa, "v2", "verbose2")==MagickTrue) {
      pss->verbose = 2;
    } else if (IsArg (pa, "v3", "verbose3")==MagickTrue) {
      pss->verbose = 3;
    } else if (IsArg (pa, "version", "version")==MagickTrue) {
      fprintf (stdout, "%s\n", VERSION);
    } else {
      fprintf (stderr, "whatrotscale: ERROR: unknown option\n");
      status = MagickFalse;
    }
  }

  if (pss->verbose) {
    fprintf (stderr, "whatrotscale options:");
    if (pss->approxOnly) fprintf (stderr, "  approxOnly");
    fprintf (stderr, "  tolerance %g", pss->tolerance);
    if (!pss->doRotate) fprintf (stderr, "  noChange");

    if (pss->fh_data == stdout) fprintf (stderr, " file stdout");
    if (pss->debug) fprintf (stderr, "  debug");
    if (pss->verbose > 1) fprintf (stderr, "  verbose%i", pss->verbose);
    else if (pss->verbose) fprintf (stderr, "  verbose");
    fprintf (stderr, "\n");
  }

  if (status == MagickFalse)
    usage ();

  return (status);
}


#if 0
// The next function takes an image list,
// may modify it, and returns a status.
//
static MagickBooleanType doWhatrotscale (
  Image **images,
  SubSrchT * pss,
  ExceptionInfo *exception)
{
  Image * main_img = *images;

  Image * sub_img = GetNextImageInList (main_img);
  if (!sub_img) return MagickFalse;

/*==
  if (sub_img->columns > main_img->columns ||
      sub_img->rows    > main_img->rows)
  {
    fprintf (stderr, "whatrotscale: subimage is larger than main\n");
    return MagickFalse;
  }
==*/

//#if IMV6OR7==6
//  SetPixelCacheVirtualMethod (main_img, TransparentVirtualPixelMethod);
//#else
//  SetPixelCacheVirtualMethod (main_img,
//    TransparentVirtualPixelMethod, exception);
//#endif

  VIRT_NONE(main_ing, exception);

  // Make displacement map, and unroll it.
  double minusOne=-1.0;
  MagickBooleanType bestfit = MagickTrue;

  Image * id_map = mIdentAbsDispMap (sub_img, exception);
#if IMV6OR7==6
  SetImageAlphaChannel (id_map, OpaqueAlphaChannel);
#else
  SetImageAlphaChannel (id_map, OpaqueAlphaChannel, exception);
#endif

  pss->DispMap = DistortImage (id_map, DePolarDistortion,
    1, &minusOne, bestfit, exception);
  if (!pss->DispMap) return MagickFalse;

  if (pss->verbose) fprintf (stderr, "srchconcr: DispMap %lix%li\n",
    pss->DispMap->columns, pss->DispMap->rows);

  pss->wrs.wr.verbose = pss->verbose;
  pss->wrs.ws.verbose = pss->verbose;
  if (pss->debug) {
    pss->wrs.debug = "wrs_dbg";
    pss->wr.debug = "wrs_dbg";
    pss->ws.debug = "wrs_dbg";
  }

  if (!CalcApproxRot (&pss->wrs.wr, main_img, sub_img, NULL, NULL, exception)) {
    fprintf (stderr, "CalcApproxRot failed\n");
    return MagickFalse;
  }

  if (pss->verbose) {
    fprintf (stderr, "Approx  ang=%.*g +/-%.*g score=%.*g\n",
      pss->precision, pss->wrs.wr.angle,
      pss->precision, pss->wrs.wr.plusOrMinus,
      pss->precision, pss->wrs.wr.score);
  }

  if (!pss->approxOnly) {
    // Save, in case GoldSect fails.
    double saveAng = pss->wrs.wr.angle;
    double savePorm = pss->wrs.wr.plusOrMinus;
    double saveScore = pss->wrs.wr.score;

    pss->wr.tolerance = pss->tolerance;

    if (GoldSectRot (&pss->wr, main_img, sub_img, exception)) {
      if (pss->verbose) fprintf (stderr,
        "From gss: ang=%.*g +/-%.*g score=%.*g\n",
        pss->precision, pss->wr.angle,
        pss->precision, pss->wr.plusOrMinus,
        pss->precision, pss->wr.score);
    } else {
      fprintf (stderr, "whatrot: GoldSectScale failed\n");
      pss->wr.angle = saveAng;
      pss->wr.plusOrMinus = savePorm;
      pss->wr.score = saveScore;
    }
  }

  fprintf (pss->fh_data, "whatrotscale:  ang=%.*g angPorm=%.*g angScore=%.*g\n",
    pss->precision, pss->wr.angle,
    pss->precision, pss->wr.plusOrMinus,
    pss->precision, pss->wr.score);

  if (pss->doRotate) {
    MagickBooleanType bestfit = MagickTrue;

    double scaleArray[2];
    scaleArray[0] = 1.0;            // Scale factor.
    scaleArray[1] = pss->wr.angle;  // Rotation angle.

//#if IMV6OR7==6
//    SetPixelCacheVirtualMethod (sub_img, TransparentVirtualPixelMethod);
//#else
//    SetPixelCacheVirtualMethod (sub_img,
//      TransparentVirtualPixelMethod, exception);
//#endif

    VIRT_NONE(sub_img, exception);

    Image *rot_img = DistortImage (sub_img, ScaleRotateTranslateDistortion,
      2, scaleArray, bestfit, exception);
    if (!rot_img) return MagickFalse;
    // We don't ResetPage (rot_img);

    DeleteImageFromList (&sub_img);

    ReplaceImageInList (&main_img, rot_img);
    // Replace messes up the images pointer. Make it good:
    *images = GetFirstImageInList (main_img);
  }

  return MagickTrue;
}
#endif


static MagickBooleanType doWhatrotscale (
  Image **images,
  SubSrchT * pss,
  ExceptionInfo *exception)
{
  int ListLen = (int)GetImageListLength(*images);
  if (ListLen != 2) {
    fprintf (stderr, "whatrotscale: needs 2 images\n");
    return (-1);
  }

  Image * main_img = *images;
  Image * sub_img = GetNextImageInList (main_img);

  pss->wrs.verbose = pss->verbose;
  if (pss->debug) {
    pss->wrs.debug = "wrs_dbg";
  }

  if (!ApproxWhatRotScale (&pss->wrs, main_img, sub_img, exception))
    return MagickFalse;

  if (!RefineWhatRotScale (&pss->wrs, main_img, sub_img, exception))
    return MagickFalse;

  fprintf (pss->fh_data, "WhatRotScale scale=%.*g angle=%.*g score=%.*g\n",
    pss->precision, pss->wrs.scale,
    pss->precision, pss->wrs.angle,
    pss->precision, pss->wrs.score);

  if (pss->doRotate) {
    MagickBooleanType bestfit = MagickTrue;
    double scaleArray[2];
    scaleArray[0] = pss->wrs.scale;  // Scale factor.
    scaleArray[1] = pss->wrs.angle;  // Rotation angle.

    VIRT_NONE(sub_img, exception);

    Image *dist_img = DistortImage (sub_img, ScaleRotateTranslateDistortion,
      2, scaleArray, bestfit, exception);
    if (!dist_img) return MagickFalse;
    // We don't ResetPage (dist_img);

    if (pss->verbose) fprintf (stderr,
      "WhatRotScale: created image %lix%li %lix%li%+li%+li\n",
      dist_img->columns, dist_img->rows,
      dist_img->page.width, dist_img->page.height,
      dist_img->page.x, dist_img->page.y);

    DeleteImageFromList (&sub_img);

    ReplaceImageInList (&main_img, dist_img);
    // Replace messes up the images pointer. Make it good:
    *images = GetFirstImageInList (main_img);
  }

  return MagickTrue;
}

ModuleExport size_t whatrotscaleImage(Image **images,const int argc,
  const char **argv,ExceptionInfo *exception)
{
  MagickBooleanType
    status;

  SubSrchT
    ss;

  assert(images != (Image **) NULL);
  assert(*images != (Image *) NULL);
  assert((*images)->signature == MAGICK_CORE_SIG);

  InitSubSrch (&ss);

  ss.precision = GetMagickPrecision();

  status = menu (argc, argv, &ss);
  if (status == MagickFalse)
    return (-1);

  if (!doWhatrotscale (images, &ss, exception))
    fprintf (stderr, "doWhatrotscale failed\n");

  DeInitSubSrch (&ss);

  return(MagickImageFilterSignature);
}

whatrotscale.inc

/* Updated:
     3-April-2018 for v7.0.7-28
*/

#ifndef WHATROTSCALE_INC
#define WHATROTSCALE_INC

#define wrsDEBUG 1

//#include "virtnone.inc"


typedef struct {
  WhatRotT wr;
  WhatScaleT ws;

  MagickBooleanType
    verbose;

  char 
    *debug;

  double
    ApproxScale,
    ApproxAngle;

  // Returned results:
  double
    scale,
    angle,
    score;

} WhatRotScaleT;

static void InitWhatRotScale (WhatRotScaleT *wrs)
{
  InitWhatRot   (&wrs->wr);
  InitWhatScale (&wrs->ws);

  wrs->verbose = MagickFalse;
  wrs->ApproxScale = 1;
  wrs->ApproxAngle = 0;
  wrs->debug = NULL;

  wrs->scale = wrs->angle = wrs->score = 0;
}

static void DeInitWhatRotScale (WhatRotScaleT *wrs)
{
  DeInitWhatScale (&wrs->ws);
  DeInitWhatRot   (&wrs->wr);
}


static MagickBooleanType ApproxWhatRotScale (
  WhatRotScaleT *wrs,
  Image * main_img,
  Image * sub_img,
  ExceptionInfo *exception)
// Finds approximate scale and rotation.
{
  ConcRingsT cr;
  InitConcRings (&cr);
  cr.verbose = (wrs->verbose > 1);

  wrs->ws.verbose = wrs->verbose;
  wrs->wr.verbose = wrs->verbose;
  wrs->ws.debug = wrs->debug;
  wrs->wr.debug = wrs->debug;


  // Make the concentric rings version of the subimage.
  Image * AvgConcRings = avgconcrings (sub_img, &cr, exception);
  if (!AvgConcRings) {
    fprintf (stderr, "ApproxWhatRotScale: avgconcrings failed\n");
    return MagickFalse;
  }

  if (! CalcApproxScale (&wrs->ws, &cr, 
          main_img, AvgConcRings, NULL, exception))
    return MagickFalse;

  if (! CalcApproxRot (&wrs->wr, main_img, sub_img, NULL, NULL, exception))
    return MagickFalse;

  wrs->scale = wrs->ApproxScale = wrs->ws.scale;
  wrs->angle = wrs->ApproxAngle = wrs->wr.angle;

  wrs->score = (wrs->wr.score + wrs->ws.score)  / 2.0;

  if (wrs->verbose) fprintf (stderr,
    "WhatRotScale approx scale=%g porm=%g ang=%g porm=%g score=%g\n",
    wrs->scale, wrs->ws.porm,
    wrs->angle, wrs->wr.plusOrMinus, wrs->score);

  DestroyImage (AvgConcRings);

  DeInitConcRings (&cr);

  return MagickTrue;
}

static MagickBooleanType RefineWhatRotScale (
  WhatRotScaleT *wrs,
  Image * main_img,
  Image * sub_img,
  ExceptionInfo *exception)
// Given approximate scale and rotation,
// refine the scale and rotation by golden section searches.
// FIXME: Can the inputs be different sizes??
{
  wrs->wr.verbose = (wrs->verbose <= 1) ? 0 : wrs->verbose-1;
  wrs->ws.verbose = wrs->wr.verbose;
  wrs->ws.debug = wrs->debug;
  wrs->wr.debug = wrs->debug;

  wrs->scale = wrs->ApproxScale;
  wrs->angle = wrs->ApproxAngle;

  if (wrs->verbose) fprintf (stderr,
    "RefineWhatRotScale ApproxScale=%g ApproxAng=%g\n",
    wrs->scale, wrs->angle);

  Image * clnSub = CloneImage (sub_img, 0, 0, MagickTrue, exception);
  if (!clnSub) return MagickFalse;

//#if IMV6OR7==6
//  SetPixelCacheVirtualMethod (clnSub, TransparentVirtualPixelMethod);
//  SetImageAlphaChannel (clnSub, ActivateAlphaChannel);
//#else
//  SetPixelCacheVirtualMethod (clnSub,
//    TransparentVirtualPixelMethod, exception);
//  SetImageAlphaChannel (clnSub, ActivateAlphaChannel, exception);
//#endif

  VIRT_NONE(clnSub, exception);

  MagickBooleanType bestfit = MagickTrue;
  MagickBooleanType okay = MagickTrue;

  double srtArray[2];
  int i;
  double prevScore = 9.9;
  for (i=0; i < 10; i++) {
    srtArray[0] = wrs->scale;
    srtArray[1] = wrs->angle;

#if wrsDEBUG==1
    if (wrs->verbose > 1) {
      fprintf (stderr, "RefineWhatRotScale sc=%g rot=%g\n", wrs->scale, wrs->angle);
      WriteFrame ("wrsx_main.png", main_img, 0, exception);
      WriteFrame ("wrsx_sub.png", sub_img, 0, exception);
    }
#endif

    Image *sub_trans = DistortImage (clnSub, ScaleRotateTranslateDistortion,
      2, srtArray, bestfit, exception);
    if (!sub_trans) {
      fprintf (stderr, "WhatRotScale: distort sc and ang failed\n");
      return MagickFalse;
    }
    ResetPage (sub_trans);
    TrimOne (&sub_trans, exception);

#if wrsDEBUG==1
    if (wrs->debug) {
      WriteFrame ("wrsx_trans0.png", sub_trans, 0, exception);
    }
#endif

    int nFailed = 0;

    wrs->wr.angle = 0;
    wrs->wr.plusOrMinus = 5;
    wrs->wr.debug = wrs->debug;
    wrs->ws.debug = wrs->debug;
    okay = GoldSectRot (&wrs->wr, main_img, sub_trans, exception);
    DestroyImage (sub_trans);
    if (!okay) {
      if (wrs->verbose > 1) fprintf (stderr, "WhatRotScale: gss angle failed\n");
      nFailed = 1;
      wrs->wr.angle = 0.0;
      // return MagickFalse;
    }

    wrs->angle += wrs->wr.angle;
    if (wrs->verbose > 1) fprintf (stderr,
      "RefineWhatRotScale ang=%g porm=%g score=%g\n",
      wrs->angle, wrs->wr.plusOrMinus, wrs->wr.score);

    srtArray[1] = wrs->angle;

    sub_trans = DistortImage (clnSub, ScaleRotateTranslateDistortion,
      2, srtArray, bestfit, exception);
    if (!sub_trans) {
      fprintf (stderr, "WhatRotScale: distort sc and new ang failed\n");
      return MagickFalse;
    }
    ResetPage (sub_trans);
    TrimOne (&sub_trans, exception);

#if wrsDEBUG==1
    if (wrs->debug) {
      WriteFrame ("wrsx_trans1.png", sub_trans, 0, exception);
    }
#endif

    wrs->ws.scale = 1.0;
    okay = GoldSectScale (&wrs->ws, main_img, sub_trans, exception);
    DestroyImage (sub_trans);
    if (!okay) {
      if (wrs->verbose > 1) fprintf (stderr, "WhatRotScale: gss scale failed\n");
      nFailed++;
      wrs->ws.scale = 1.0;
      // return MagickFalse;
    }
    if (nFailed == 2) return MagickFalse;

    wrs->scale *= wrs->ws.scale;
    if (wrs->verbose > 1) fprintf (stderr,
      "WhatRotScale scale=%g porm=%g score=%g\n",
      wrs->scale, wrs->ws.porm, wrs->ws.score);

    wrs->score = wrs->ws.score;

    if (wrs->score < 0.01) break;

    if (fabs(prevScore - wrs->score) < 0.001) break;

    prevScore = wrs->score;
  } // end for


  if (wrs->verbose) fprintf (stderr,
    "WhatRotScale refined nIter=%i scale=%g porm=%g ang=%g porm=%g score=%g\n",
    i+1,
    wrs->scale, wrs->ws.porm,
    wrs->angle, wrs->wr.plusOrMinus, wrs->score);

  return (wrs->score > 0);
}


#endif

srchmask.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include "vsn_defines.h"

#include "srchmask.inc"

#define VERSION "srchmask v1.0  Copyright (c) 2017 Alan Gibson"

typedef struct {
  int
    verbose;

  MagickBooleanType
    updateMask,
    makeComposite,
    elimClose;

  int
    precision;

  FILE *
    fh_data;

} SubSrchT;

/*
   Optionally dumps text.
   Optionally applies transformation to subimage, merging with main image.
*/

static void InitSubSrch (SubSrchT * pss)
{
  pss->verbose = 0;
  pss->updateMask = MagickFalse;
  pss->makeComposite = MagickFalse;
  pss->elimClose = MagickFalse;
  pss->precision = 6;
  pss->fh_data = stderr;
}

static void DeInitSubSrch (SubSrchT * pss)
{
  ; // Nothing.
}


static void usage (void)
{
  printf ("Usage: -process 'srchmask [OPTION]...'\n");
  printf ("Find rotation by unrolling and golden section methods.\n");
  printf ("\n");
  printf ("  u,  updateMask       update with only the 'good' positions\n");
  printf ("  c,  makeComposite    make composite output image\n");
  printf ("  e,  elimClose        eliminate close solutions\n");
  printf ("  f,  file string      write data to file stream stdout or stderr\n");
  printf ("  v,  verbose          write text information to stdout\n");
  printf ("  v2, verbose2         write more information to stdout\n");
  printf ("      version          write version information to stdout\n");
  printf ("\n");
}

// FIXME: also option to normalise mean and SD.


static MagickBooleanType IsArg (char * pa, char * ShtOpt, char * LongOpt)
{
  if ((LocaleCompare(pa, ShtOpt)==0) || (LocaleCompare(pa, LongOpt)==0))
    return MagickTrue;

  return MagickFalse;
}


#define NEXTARG \
  if (i < argc-1) i++; \
  else { \
    fprintf (stderr, "paintpatches: ERROR: [%s] needs argument\n", pa); \
    status = MagickFalse; \
  }

static MagickBooleanType menu(
  const int argc,
  const char **argv,
  SubSrchT * pss
)
// Returns MagickTrue if okay.
{
  int
    i;

  MagickBooleanType
    status;

  status = MagickTrue;

  for (i=0; i < argc; i++) {
    char * pa = (char *)argv[i];

    if (IsArg (pa, "c", "makeComposite")==MagickTrue) {
      pss->makeComposite = MagickTrue;
    } else if (IsArg (pa, "e", "elimClose")==MagickTrue) {
      pss->elimClose = MagickTrue;
    } else if (IsArg (pa, "u", "updateMask")==MagickTrue) {
      pss->updateMask = MagickTrue;
    } else if (IsArg (pa, "f", "file")==MagickTrue) {
      NEXTARG;
      if (strcasecmp (argv[i], "stdout")==0) pss->fh_data = stdout;
      else if (strcasecmp (argv[i], "stderr")==0) pss->fh_data = stderr;
      else status = MagickFalse;
    } else if (IsArg (pa, "v", "verbose")==MagickTrue) {
      pss->verbose = 1;
    } else if (IsArg (pa, "v2", "verbose2")==MagickTrue) {
      pss->verbose = 2;
    } else if (IsArg (pa, "version", "version")==MagickTrue) {
      fprintf (stdout, "%s\n", VERSION);
    } else {
      fprintf (stderr, "srchmask: ERROR: unknown option\n");
      status = MagickFalse;
    }
  }

  if (pss->verbose) {
    fprintf (stderr, "srchmask options:");
    if (pss->updateMask) fprintf (stderr, "  updateMask");
    if (pss->makeComposite) fprintf (stderr, "  makeComposite");

    if (pss->fh_data == stdout) fprintf (stderr, " file stdout");
    if (pss->verbose==2) fprintf (stderr, "  verbose2");
    else if (pss->verbose) fprintf (stderr, "  verbose");
    fprintf (stderr, "\n");
  }

  if (status == MagickFalse)
    usage ();

  return (status);
}


// The next function takes an image list,
// may modify it, and returns a status.
//
static MagickBooleanType srchmask (
  Image **images,
  SubSrchT * pss,
  ExceptionInfo *exception)
{
  int ListLen = (int)GetImageListLength(*images);

  Image * img1 = *images;
  Image * img2 = GetNextImageInList (img1);
  Image * img3 = NULL;
  if (img2) img3 = GetNextImageInList (img2);

  Image *main_img=NULL, *sub_img=NULL, *mask_img=NULL;

  switch (ListLen) {
    case 1:
      mask_img = img1;
      break;

    case 2:
      sub_img = img1;
      mask_img = img2;
      break;

    case 3:
      main_img = img1;
      sub_img = img2;
      mask_img = img3;
      break;
  }

  if (main_img) fprintf (stderr,
    "main_img %lix%li  ", main_img->columns, main_img->rows);

  if (sub_img) fprintf (stderr,
    "sub_img %lix%li  ", sub_img->columns, sub_img->rows);

  if (mask_img) fprintf (stderr,
    "mask_img %lix%li", mask_img->columns, mask_img->rows);

  fprintf (stderr, "\n");


/*==
  if (sub_img->columns > main_img->columns ||
      sub_img->rows    > main_img->rows)
  {
    fprintf (stderr, "srchconcr: subimage is larger than main\n");
    return MagickFalse;
  }
==*/

//#if IMV6OR7==6
//  SetPixelCacheVirtualMethod (main_img, TransparentVirtualPixelMethod);
//#else
//  SetPixelCacheVirtualMethod (main_img,
//    TransparentVirtualPixelMethod, exception);
//#endif


  srchMaskT sm;
  InitSrchMask (&sm);
  sm.precision = GetMagickPrecision();
  sm.verbose = pss->verbose;

  if (main_img) {
//    sm.main_width = main_img->columns;
//    sm.main_height = main_img->rows;
  }

  if (sub_img) {
    sm.sub_width = sub_img->columns;
    sm.sub_height = sub_img->rows;
  }

  ReadSrchMask (mask_img, &sm, exception);
  DumpSrchMask (stderr, &sm);
  SortSrchMask (&sm);
  DumpSrchMask (stderr, &sm);
  FindGoodSrchMask (&sm);
  WrSrchMaskSrt (stderr, &sm);
  SrchMaskExclHidden (&sm);
  WrSrchMaskSrt (stderr, &sm);

  if (pss->updateMask) {
    Image * new_msk = WrGoodSrchMask (mask_img, &sm, exception);
    if (!new_msk) return MagickFalse;
    ReplaceImageInList (&mask_img, new_msk);
  }

  if (pss->elimClose) {
    SrchMaskExclHidden (&sm);
  }

  if (pss->makeComposite) {
    Image * comp_img = SrchMaskMakeComposite (main_img, sub_img, &sm, exception);
    if (!comp_img) {
      fprintf (stderr, "SrchMaskMakeComposite failed\n");
      return MagickFalse;
    }
    ReplaceImageInList (&mask_img, comp_img);
  }

  DeInitSrchMask (&sm);

  if (sub_img) DeleteImageFromList (&sub_img);
  if (main_img) DeleteImageFromList (&main_img);

  *images = GetFirstImageInList (mask_img);

  return MagickTrue;
}



ModuleExport size_t srchmaskImage(Image **images,const int argc,
  const char **argv,ExceptionInfo *exception)
{
  MagickBooleanType
    status;

  SubSrchT
    ss;

  assert(images != (Image **) NULL);
  assert(*images != (Image *) NULL);
  assert((*images)->signature == MAGICK_CORE_SIG);

  InitSubSrch (&ss);

  ss.precision = GetMagickPrecision();

  status = menu (argc, argv, &ss);
  if (status == MagickFalse)
    return (-1);

  int ListLen = (int)GetImageListLength(*images);
  if (ListLen < 1 || ListLen > 3) {
    fprintf (stderr, "srchconcr: needs 1, 2 or 3 images\n");
    return (-1);
  }

  if (!srchmask (images, &ss, exception)) return (-1);

  DeInitSubSrch (&ss);

  return(MagickImageFilterSignature);
}

writeframe.inc

#ifndef WRITEFRAME_INC
#define WRITEFRAME_INC


static MagickBooleanType WriteFrame (
  char *FileName,
  Image * img,
  int frame_num,
  ExceptionInfo *exception)
{
  ImageInfo
    *ii;

  MagickBooleanType
    okay;

  Image
    *copy_img;

  ii = AcquireImageInfo ();

  copy_img = CloneImage(img, 0, 0, MagickTrue, exception);
  if (!copy_img) return(MagickFalse); // FIXME: raise error

  copy_img->scene = frame_num;

  CopyMagickString (copy_img->filename, FileName, MaxTextExtent);

  okay = WRITEIMAGE(ii, copy_img, exception);

  DestroyImageList(copy_img);

  ii = DestroyImageInfo (ii);

  return okay;
}


#endif

absdispmap.inc

/* Updated:
     5-April-2018 for v7.0.7-28.
*/

// From an image, make a same-size absolute displacement map.

static void SetArgs (
  double * args, int nArg,
  ssize_t x, ssize_t y,
  double r, double g, double b)
{
  int i = nArg*5;
  args[i++] = x;
  args[i++] = y;
  args[i++] = r;
  args[i++] = g;
  args[i++] = b;
}

static Image * mIdentAbsDispMap (
  Image * image,
  ExceptionInfo *exception)
{
#define NUM_SPARSE_ARGS 20
  double args[NUM_SPARSE_ARGS];

  SetArgs (args, 0, 0,                0,             0, 0, 0);
  SetArgs (args, 1, image->columns-1, 0,             1, 0, 0);
  SetArgs (args, 2, 0,                image->rows-1, 0, 1, 0);
  SetArgs (args, 3, image->columns-1, image->rows-1, 1, 1, 0);

  // V7 SparseColorImage is different.
  // Set the traits of image to RGB only.


#if IMV6OR7==6
  Image * new_img = SparseColorImage (
    image,
    RedChannel | GreenChannel | BlueChannel,
    BilinearColorInterpolate,
    NUM_SPARSE_ARGS, args, exception);
#else
  ChannelType channel_mask = SetImageChannelMask (
    image,
    RedChannel | GreenChannel | BlueChannel);

  Image * new_img = SparseColorImage (
    image,
    BilinearColorInterpolate,
    NUM_SPARSE_ARGS, args, exception);

  SetImageChannelMask (image, channel_mask);
#endif

  if (!new_img) {
    fprintf (stderr, "mIdentAbsDispMap: SparseColorImage failed\n");
    return NULL;
  }

  return (new_img);
}

trimone.inc

#ifndef TRIMONE_INC
#define TRIMONE_INC

#include "resetpage.inc"
#include "cropchk.inc"

static MagickBooleanType TrimOne (
  Image ** img,
  ExceptionInfo *exception)
{
  RectangleInfo crpRect;
  crpRect.width  = (*img)->columns - 2;
  crpRect.height = (*img)->rows - 2;
  crpRect.x = 1;
  crpRect.y = 1;

  Image * r = CropCheck (*img, &crpRect, exception);
  if (!r) return MagickFalse;
  ResetPage (r);
  ReplaceImageInList (img, r);

  return MagickTrue;
}

#endif

cropchk.inc

#ifndef CROPCHK_INC
#define CROPCHK_INC

static Image * CropCheck (
  Image * img,
  RectangleInfo *rect,
  ExceptionInfo *exception)
{
  Image * crp_img = CropImage (img, rect, exception);
  if (!crp_img) return NULL;

  // Check the crop really worked.
  if (crp_img->columns != rect->width || crp_img->rows != rect->height) {
    crp_img = DestroyImage (crp_img);
  }

  return crp_img;
}

#endif

resetpage.inc

#ifndef RESETPAGE_INC
#define RESETPAGE_INC

static void inline ResetPage (Image * img)
// Like "+repage".
{
  img->page.width = img->page.height = img->page.x = img->page.y = 0;
}


#endif

virtnone.inc

#ifndef VIRTNONE_INC
#define VIRTNONE_INC

// This is temporary. Should be in vsn_defines.h.

#if IMV6OR7==6
  #define VIRT_NONE(img) \
    SetPixelCacheVirtualMethod (img, TransparentVirtualPixelMethod);\
    SetImageAlphaChannel (img, ActivateAlphaChannel);
#else
  #define VIRT_NONE(img) \
    SetPixelCacheVirtualMethod (img,\
      TransparentVirtualPixelMethod, exception);\
    SetImageAlphaChannel (img, ActivateAlphaChannel, exception);
#endif


#endif

goldsectsrch.inc

#ifndef GOLDSECTSRCH_INC
#define GOLDSECTSRCH_INC


#include <stdio.h>
#include <stdlib.h>
#include <math.h>


/*
  Ref: https://en.wikipedia.org/wiki/Golden_section_search

       +------+------+-----+
       x0     x1     x2    x3
       <--a--><--b--><--c-->

Required:

  b+c   a   a+b+c
  --- = - = -----
   a    b    b+c

Which implies:
  b^2 + bc = a^2
  c = (a^2-b^2) / b

  ab + ac = ab + b^2 + bc
  ac = b^2 + bc
  c(a-b) = b^2
  c = b^2 / (a-b)

So:
  (a^2-b^2) / b = b^2 / (a-b)

  (a^2-b^2)*(a-b) = b^3

  a^3-a.b^2 -a^2.b + b^3 = b^3
  a^3-a.b^2 -a^2.b = 0
  a^2-b^2 -a.b = 0
  a^2 -a.b -b^2 = 0

      +b +-sqrt (b^2 + 4.1.b^2)
  a = -------------------------
                  2

a = b * (1 +- sqrt(5)) / 2

a/b = phi
a = b.phi

But
  c = b^2 / (a-b)
so:
  c = b^2 / (b(phi-1))
  c = b / (phi-1)
but phi-1 = 1/phi so
  c = b.phi
*/



typedef double getYcbT (double x, void * pg, int * okay);

typedef struct {
  double xLo;
  double xHi;
  double epsX;
  double epsY;
  getYcbT *getYcb; // This function will be called to calculate y.
  int verbose;

  // Returns results:
  double fndX;
  double xPlusOrMinus;
  double calcY;
  int dodgy;
  int nIter;
} goldSectSrchT;

// FIXME: perhaps also return if less than xLo or greater than xHi.


/* This is a framework for searching for a minimum
   within a 1D inverse-unimodal function,
   such as finding the resize that makes an image best match another image.
*/

static void InitGoldSectSrch (goldSectSrchT *goldSectSrch)
{
  goldSectSrch->xLo  = 0.5;
  goldSectSrch->xHi  = 1.5;
  goldSectSrch->epsX = 1e-2;
  goldSectSrch->epsY = 0;
  goldSectSrch->dodgy = 0;
  goldSectSrch->nIter = 0;
  goldSectSrch->getYcb = NULL;
  goldSectSrch->verbose = 0;
}


static int GoldSectSrch (
  goldSectSrchT *goldSectSrch,
  void * pData)
// This finds x, xLo <= x <= xHi, such that getY(x) is a minimum.
// Returns 1 if okay, otherwise 0.
{
  int okay = 1;

  goldSectSrch->dodgy = 0;

  double x0 = goldSectSrch->xLo;
  double x3 = goldSectSrch->xHi;
  double x1, x2;

  double origX0 = x0;
  double origX3 = x3;

  double phi = (1.0 + sqrt(5.0))/2.0;

  double y0 = goldSectSrch->getYcb (x0, pData, &okay);
  if (!okay) return 0;
  double y3 = goldSectSrch->getYcb (x3, pData, &okay);
  if (!okay) return 0;

  x1 = x3 - (x3-x0) / phi;
  x2 = x0 + (x3-x0) / phi;

  if (goldSectSrch->verbose > 1) {
    fprintf (stderr, "x1=%g x2=%g phi=%g phi=%g\n",
      x1, x2,
      (x3-x1)/(x1-x0),
      (x3-x0)/(x3-x1));
  }

  goldSectSrch->dodgy = 1;

  double y1 = goldSectSrch->getYcb (x1, pData, &okay);
  if (!okay) return 0;
  double y2 = goldSectSrch->getYcb (x2, pData, &okay);
  if (!okay) return 0;

  if (y0 < y1 && y1 < y2 && y2 < y3) {
    if (goldSectSrch->verbose) {
      fprintf (stderr, "x: %g %g %g %g\n", x0, x1, x2, x3);
      fprintf (stderr, "y: %g %g %g %g\n", y0, y1, y2, y3);
      fprintf (stderr, "gss: Solution possibly < %g\n", x0);
    }
    return 0;
  }

  if (y0 > y1 && y1 > y2 && y2 > y3) {
    if (goldSectSrch->verbose) {
      fprintf (stderr, "x: %g %g %g %g\n", x0, x1, x2, x3);
      fprintf (stderr, "y: %g %g %g %g\n", y0, y1, y2, y3);
      fprintf (stderr, "gss: Solution possibly > %g\n", x3);
    }
    return 0;
  }

  if (y1 > y0 || y2 > y3) {
    if (goldSectSrch->verbose) {
      fprintf (stderr, "x: %g %g %g %g\n", x0, x1, x2, x3);
      fprintf (stderr, "y: %g %g %g %g\n", y0, y1, y2, y3);
      fprintf (stderr, "gss: dodgy initial y1 or y2\n");
    }
    return 0;
  }

  int cnt=0;

  goldSectSrch->dodgy = 0;

  for (;;) {

    if (fabs (x3-x0) <= goldSectSrch->epsX) break;
    if (fabs (y3-y0) <= goldSectSrch->epsY) break;

    if (y1 > y0 && y1 > y3) {
      if (goldSectSrch->verbose) {
        fprintf (stderr, "x: %g %g %g %g\n", x0, x1, x2, x3);
        fprintf (stderr, "y: %g %g %g %g\n", y0, y1, y2, y3);
        fprintf (stderr, "gss: dodgy y1\n");
      }
      goldSectSrch->dodgy = 1;
      break;
    }

    if (y2 > y0 && y2 > y3) {
      if (goldSectSrch->verbose) {
        fprintf (stderr, "x: %g %g %g %g\n", x0, x1, x2, x3);
        fprintf (stderr, "y: %g %g %g %g\n", y0, y1, y2, y3);
        fprintf (stderr, "gss: dodgy y2\n");
      }
      goldSectSrch->dodgy = 1;
      break;
    }

    if (cnt++ > 100) break;

    if (goldSectSrch->verbose > 1) {
      fprintf (stderr, "%i x: %g %g %g %g\n", cnt, x0, x1, x2, x3);
      fprintf (stderr, "y: %g %g %g %g\n", y0, y1, y2, y3);
    }

    if (y1 < y2) {
      x3 = x2;
      y3 = y2;
      x2 = x1;
      y2 = y1;
      x1 = x3 - (x3-x0) / phi;
      y1 = goldSectSrch->getYcb (x1, pData, &okay);
    } else {
      x0 = x1;
      y0 = y1;
      x1 = x2;
      y1 = y2;
      x2 = x0 + (x3-x0) / phi;
      y2 = goldSectSrch->getYcb (x2, pData, &okay);
    }
    if (!okay) return 0;
  }

  if (goldSectSrch->verbose) {
    fprintf (stderr, "\ngss: cnt=%i\nx: %g %g %g %g\n",
      cnt, x0, x1, x2, x3);
    fprintf (stderr, "y: %g %g %g %g\n", y0, y1, y2, y3);
  }

  if (origX0 == x0) {
    fprintf (stderr, "gss: dodgy bottom\n");
    goldSectSrch->dodgy = 1;
  }
  if (origX3 == x3) {
    fprintf (stderr, "gss: dodgy top\n");
    goldSectSrch->dodgy = 1;
  }

  goldSectSrch->fndX  = (x0+x3) / 2.0;
  goldSectSrch->xPlusOrMinus = (x3-x0) / 2.0;
  goldSectSrch->calcY = goldSectSrch->getYcb (goldSectSrch->fndX, pData, &okay);
  goldSectSrch->nIter = cnt;

  return 1;
}


#endif

usercoord.inc

/* Updated:
     1-September-2017 ParseXy now allows ',' or 'x' as separator.
*/

//---------------------------------------------------------------------------
// User-supplied coordinates: data structures and functions.

typedef enum {
  csPix,
  csPc,
  csProp
} CoordSpecT;

typedef struct {
  double
    Num;

  CoordSpecT
    cs;

  ssize_t
    Pix;
} UserCoordT;

typedef struct {
  UserCoordT
    x,
    y;
} UserCoordXyT;


static int ParseXy (const char * s, UserCoordXyT * uc)
// Returns 1 if okay; 0 if failure.
{
  double d;
  int n;
  char * p = (char *)s;

  sscanf (p, "%lg%n", &d, &n);

  uc->x.Num = d;
  uc->x.Pix = (int)(d+0.5);

  p += n;
  switch (*p) {
    case '%':
    case 'c':
      uc->x.cs = csPc;
      p++;
      break;
    case 'p':
      uc->x.cs = csProp;
      p++;
      break;
  }

  if (*p != ',' && *p != 'x') return 0;
  p++;

  sscanf (p, "%lg%n", &d, &n);

  uc->y.Num = d;
  uc->y.Pix = (int)(d+0.5);

  p += n;
  switch (*p) {
    case '%':
    case 'c':
      uc->y.cs = csPc;
      p++;
      break;
    case 'p':
      uc->y.cs = csProp;
      p++;
      break;
  }

  if (*p != '\0') return 0;

  return 1;
}

static int ResolveUserCoords (
  UserCoordXyT * uc,
  ssize_t width,
  ssize_t height,
  double maxFact  // Eg 1.0 or 2.0.
)
// Returns 1 if okay; 0 if failure.
{
  switch (uc->x.cs) {
    case csPc:
      uc->x.Pix = (int)(uc->x.Num * (width-1) / 100.0 + 0.5);
      break;
    case csProp:
      uc->x.Pix = (int)(uc->x.Num * (width-1) + 0.5);
      break;
    default:
      uc->x.Pix = (int)(uc->x.Num + 0.5);
  }
  switch (uc->y.cs) {
    case csPc:
      uc->y.Pix = (int)(uc->y.Num * (height-1) / 100.0 + 0.5);
      break;
    case csProp:
      uc->y.Pix = (int)(uc->y.Num * (height-1) + 0.5);
      break;
    default:
      uc->y.Pix = (int)(uc->y.Num + 0.5);
  }

  int status = 1;
  if (uc->x.Pix < 0 || uc->y.Pix < 0) {
    fprintf (stderr, "Coordinates must be positive.");
    status = 0;
  }

  if (uc->x.Pix >= width * maxFact || uc->y.Pix >= height * maxFact)
  {
    fprintf (stderr,
      "Coordinates (%li,%li) must be less than width and height (%li,%li)",
      uc->x.Pix, uc->y.Pix, width, height);

    if (maxFact != 1.0) {
      fprintf (stderr, " multipled by %g", maxFact);
    }
    fprintf (stderr, ".\n");
    status = 0;
  }

  return status;
}

static int ResolveUserDims (
  UserCoordXyT * uc,
  ssize_t width,
  ssize_t height,
  double maxFact  // Eg 1.0 or 2.0. If 0.0, no check.
)
// Returns 1 if okay; 0 if failure.
{
  switch (uc->x.cs) {
    case csPc:
      uc->x.Pix = (int)(uc->x.Num * (width) / 100.0 + 0.5);
      break;
    case csProp:
      uc->x.Pix = (int)(uc->x.Num * (width) + 0.5);
      break;
    default:
      uc->x.Pix = (int)(uc->x.Num + 0.5);
  }
  switch (uc->y.cs) {
    case csPc:
      uc->y.Pix = (int)(uc->y.Num * (height) / 100.0 + 0.5);
      break;
    case csProp:
      uc->y.Pix = (int)(uc->y.Num * (height) + 0.5);
      break;
    default:
      uc->y.Pix = (int)(uc->y.Num + 0.5);
  }

  int status = 1;
  if (uc->x.Pix < 0 || uc->y.Pix < 0) {
    fprintf (stderr, "Dimensions must be positive.");
    status = 0;
  }

  if (maxFact > 0) {
    if (uc->x.Pix > width * maxFact || uc->y.Pix > height * maxFact)
    {
      fprintf (stderr,
        "Dimensions (%li,%li) must be less than width and height (%li,%li)",
        uc->x.Pix, uc->y.Pix, width, height);

      if (maxFact != 1.0) {
        fprintf (stderr, " multipled by %g", maxFact);
      }
      fprintf (stderr, ".\n");
      status = 0;
    }
  }

  return status;
}

static void WrUserCoord (UserCoordXyT * uc)
{
  switch (uc->x.cs) {
    case csPc:
      fprintf (stderr, "%gc", uc->x.Num);
      break;
    case csProp:
      fprintf (stderr, "%gp", uc->x.Num);
      break;
    default:
      fprintf (stderr, "%li", uc->x.Pix);
  }
  fprintf (stderr, ",");
  switch (uc->y.cs) {
    case csPc:
      fprintf (stderr, "%gc", uc->y.Num);
      break;
    case csProp:
      fprintf (stderr, "%gp", uc->y.Num);
      break;
    default:
      fprintf (stderr, "%li", uc->y.Pix);
  }
}

cols2mat.c

/*
   Reference: http://im.snibgo.com/cols2mat.htm

   Updated:
     3-April-2018 for v7.0.7-28
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include "vsn_defines.h"
//#include "chklist.h"

#if IMV6OR7==7
#include <MagickCore/matrix-private.h>  // for GaussJordanElimination()
#endif

#include "cols2mat.inc"


#define VERSION "cols2mat v1.0  Copyright (c) 2017 Alan Gibson"

typedef struct {
  int
    precision;

  MagickBooleanType
    doTrans,
    weightAlpha,
    verbose;

  c2mTypeT
    c2mType;

  int
    polyDegree;

  double
    BottomLineWeight;

  FILE *
    fh_data;
} cols2matT;

static void usage (void)
{
  printf ("Usage: -process 'cols2mat [OPTION]...'\n");
  printf ("Finds the colour matrix (or polynomials) that transforms first image to second.\n");
  printf ("\n");
  printf ("  m,  method string       'Cross', 'NoCross', 'NoCrossPoly' or 'GainOnly'\n");
  printf ("  d,  degreePoly integer  degree for NoCrossPoly\n");
  printf ("  w,  weightLast number   weight for bottom line of image\n");
  printf ("  wa, weightAlpha         multiply weight by product of alphas\n");
  printf ("  x,  noTrans             don't replace images with transformation\n");
  printf ("  f,  file string         write to file stream stdout or stderr\n");
  printf ("  v,  verbose             write text information to stdout\n");
  printf ("      version             write version information to stdout\n");
  printf ("\n");
}


static MagickBooleanType IsArg (char * pa, char * ShtOpt, char * LongOpt)
{
  // LocaleCompare is not case-sensitive,
  // so we use strcmp for the short option.

  if ((strcmp(pa, ShtOpt)==0) || (LocaleCompare(pa, LongOpt)==0))
    return MagickTrue;

  return MagickFalse;
}


#define NEXTARG \
  if (i < argc-1) i++; \
  else { \
    fprintf (stderr, "cols2mat: ERROR: [%s] needs argument\n", pa); \
    status = MagickFalse; \
  }

static MagickBooleanType menu(
  const int argc,
  const char **argv,
  cols2matT * psi
)
// Returns MagickTrue if okay.
{
  int
    i;

  MagickBooleanType
    status = MagickTrue;

  psi->verbose = MagickFalse;
  psi->weightAlpha = MagickFalse;
  psi->doTrans = MagickTrue;
  psi->fh_data = stderr;
  psi->c2mType = ctCross;
  psi->polyDegree = 2;
  psi->BottomLineWeight = 1.0;

  for (i=0; i < argc; i++) {
    char * pa = (char *)argv[i];
    //printf ("Arg %i [%s]\n", i, pa);

    if (IsArg (pa, "x", "noTrans")==MagickTrue) {
      psi->doTrans = MagickFalse;
    } else if (IsArg (pa, "m", "method")==MagickTrue) {
      NEXTARG;
      if (strcasecmp (argv[i], "Cross")==0) psi->c2mType = ctCross;
      else if (strcasecmp (argv[i], "NoCross")==0) psi->c2mType = ctNoCross;
      else if (strcasecmp (argv[i], "NoCrossPoly")==0) psi->c2mType = ctNoCrossPoly;
      else if (strcasecmp (argv[i], "GainOnly")==0) psi->c2mType = ctGainOnly;
      else status = MagickFalse;
    } else if (IsArg (pa, "d", "degreePoly")==MagickTrue) {
      NEXTARG;
      psi->polyDegree = atoi(argv[i]);
    } else if (IsArg (pa, "w", "weightLast")==MagickTrue) {
      NEXTARG;
      psi->BottomLineWeight = atof(argv[i]);
    } else if (IsArg (pa, "wa", "weightAlpha")==MagickTrue) {
      psi->weightAlpha = MagickTrue;
    } else if (IsArg (pa, "f", "file")==MagickTrue) {
      NEXTARG;
      if (strcasecmp (argv[i], "stdout")==0) psi->fh_data = stdout;
      else if (strcasecmp (argv[i], "stderr")==0) psi->fh_data = stderr;
      else status = MagickFalse;
    } else if (IsArg (pa, "v", "verbose")==MagickTrue) {
      psi->verbose = MagickTrue;
    } else if (IsArg (pa, "version", "version")==MagickTrue) {
      fprintf (stdout, "%s\n", VERSION);
    } else {
      fprintf (stderr, "cols2mat: ERROR: unknown option [%s]\n", pa);
      status = MagickFalse;
    }
  }

  if (psi->polyDegree < 0) {
    fprintf (stdout, "polyDegree is less than zero.\n");
    status = MagickFalse;
  }

  if (psi->verbose) {
    fprintf (stderr, "cols2mat options: ");

    if (!psi->doTrans) fprintf (stderr, " noTrans");

    fprintf (stderr, "  method ");
    switch (psi->c2mType) {
      case ctCross:
        fprintf (stderr, "Cross");
        break;
      case ctNoCross:
        fprintf (stderr, "NoCross");
        break;
      case ctNoCrossPoly:
        fprintf (stderr, "NoCrossPoly degreePoly %i", psi->polyDegree);
        break;
      case ctGainOnly:
        fprintf (stderr, "GainOnly");
        break;
    }

    fprintf (stderr, "  weightLast %g", psi->BottomLineWeight);
    if (psi->weightAlpha) fprintf (stderr, " weightAlpha");

    if (psi->fh_data == stdout) fprintf (stderr, " file stdout");

    if (psi->verbose)    fprintf (stderr, " verbose");
    fprintf (stderr, "\n");
  }

  if (status == MagickFalse)
    usage ();

  return (status);
}


// The next function takes an image list,
// may modify it, and returns a status.
//
static MagickBooleanType cols2mat (
  Image **images,
  cols2matT * psi,
  ExceptionInfo *exception)
{
  if ((*images)->debug != MagickFalse)
    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",(*images)->filename);

  int ListLen = (int)GetImageListLength(*images);
  if (ListLen < 2 || ListLen > 3) {
    fprintf (stderr, "cols2mat needs 2 or 3 images\n");
    return MagickFalse;
  }

  Image * imgA = *images;
  Image * imgB = GetNextImageInList (imgA);
  assert(imgA->signature == MAGICK_CORE_SIG);
  assert(imgB->signature == MAGICK_CORE_SIG);
  Image * src_img = imgA;
  if (ListLen == 3) {
    src_img = GetNextImageInList (imgB);
    assert(src_img->signature == MAGICK_CORE_SIG);
  }

  if (psi->verbose) {
    fprintf (stderr, "cols2mat: imgA [%s] %ix%i depth is %i\n",
             imgA->filename,
             (int)imgA->columns, (int)imgA->rows,
             (int)imgA->depth);
    fprintf (stderr, "cols2mat: ListLen=%i\n", ListLen);
  }

  cols2matsT cols2mats;
  InitCols2mat (&cols2mats);
  cols2mats.verbose = psi->verbose;
  cols2mats.weightAlpha = psi->weightAlpha;
  cols2mats.c2mType = psi->c2mType;
  cols2mats.polyDegree = psi->polyDegree;
  cols2mats.BottomLineWeight = psi->BottomLineWeight;
  cols2mats.kernel = NULL;

  if (!cols2matGen (imgA, imgB, &cols2mats, psi->fh_data, exception))
    return MagickFalse;

  if (!cols2mats.solutionFound) {
    fprintf (stderr, "cols2mat: No solution\n");
    return MagickFalse;
  }

  if (psi->doTrans) {
    Image * new_img = NULL;

    switch (cols2mats.c2mType) {
      case ctNoCrossPoly:
        new_img = CloneImage (src_img, 0, 0, MagickTrue, exception);
        if (!new_img) return MagickFalse;

#if IMV6OR7==6
        if (!FunctionImageChannel(new_img, RedChannel,
              PolynomialFunction, psi->polyDegree+1,
              cols2mats.polyCoeff[0], exception)) return MagickFalse;

        if (!FunctionImageChannel(new_img, GreenChannel,
              PolynomialFunction, psi->polyDegree+1,
              cols2mats.polyCoeff[1], exception)) return MagickFalse;

        if (!FunctionImageChannel(new_img, BlueChannel,
              PolynomialFunction, psi->polyDegree+1,
              cols2mats.polyCoeff[2], exception)) return MagickFalse;
#else
        ChannelType channel_mask = SetImageChannelMask (new_img, RedChannel);

        if (!FunctionImage(new_img,
              PolynomialFunction, psi->polyDegree+1,
              cols2mats.polyCoeff[0], exception)) return MagickFalse;

        SetImageChannelMask (new_img, GreenChannel);

        if (!FunctionImage(new_img,
              PolynomialFunction, psi->polyDegree+1,
              cols2mats.polyCoeff[1], exception)) return MagickFalse;

        SetImageChannelMask (new_img, BlueChannel);

        if (!FunctionImage(new_img,
              PolynomialFunction, psi->polyDegree+1,
              cols2mats.polyCoeff[2], exception)) return MagickFalse;

        SetImageChannelMask(new_img,channel_mask);
#endif

        break;

      default:
        new_img = ColorMatrixImage (src_img, cols2mats.kernel, exception);
        if (!new_img) return MagickFalse;
    }

    DeleteImageFromList (&imgB);
    if (ListLen == 3) DeleteImageFromList (&src_img);

    ReplaceImageInList (images, new_img);
    // Replace messes up the images pointer. Make it good:
    *images = GetFirstImageInList (new_img);
  }

  if (cols2mats.kernel) {
    cols2mats.kernel = DestroyKernelInfo (cols2mats.kernel);
  }

  return MagickTrue;
}


ModuleExport size_t cols2matImage (
  Image **images,
  const int argc,
  const char **argv,
  ExceptionInfo *exception)
{
  cols2matT si;

  assert(images != (Image **) NULL);
  assert(*images != (Image *) NULL);
  assert((*images)->signature == MAGICK_CORE_SIG);

  MagickBooleanType status = menu (argc, argv, &si);
  if (!status) return -1;

  si.precision = GetMagickPrecision();

  status = cols2mat (images, &si, exception);
  if (!status) return (-1);

  return(MagickImageFilterSignature);
}

cols2mat.inc

/* Updated:
     3-April-2018 for v7.0.7-28
*/

#ifndef COLS2MAT_INC
#define COLS2MAT_INC

#include "chklist.h"


typedef enum {
  ctCross,
  ctNoCross,
  ctNoCrossPoly,
  ctGainOnly
} c2mTypeT;

#define NUM_CH 3

typedef struct {
  MagickBooleanType
    weightAlpha,
    verbose,
    doWarn;

  c2mTypeT
    c2mType;

  int
    polyDegree;

  double
    **matrix,
    **vectors,
    *terms,
    *results;

  double
    *polyCoeff[NUM_CH];

  double
    BottomLineWeight;

  // Used internally
  MagickBooleanType
    imgAhasAlpha,
    imgBhasAlpha;

  // Returned results
  KernelInfo
    *kernel;

  MagickBooleanType
    solutionFound;
} cols2matsT;

// Option to add zero and 100% for matrix calculation?


static void InitCols2mat (
  cols2matsT * c2m)
{
  c2m->verbose = MagickFalse;
  c2m->weightAlpha = MagickFalse;
  c2m->doWarn = MagickTrue;
  c2m->c2mType = ctCross;
  c2m->polyDegree = 2;
  c2m->matrix = c2m->vectors = NULL;
  c2m->terms = c2m->results = NULL;
  c2m->kernel = NULL;
  c2m->BottomLineWeight = 1.0;
  int c;
  for (c = 0; c < NUM_CH; c++) c2m->polyCoeff[c] = NULL;
}


static MagickBooleanType AllocCols2mat (
  cols2matsT * c2m,
  int nTerms,
  int nResults
)
{
  c2m->terms   = (double *)AcquireMagickMemory (nTerms   * sizeof(double));
  if (!c2m->terms) return MagickFalse;

  c2m->results = (double *)AcquireMagickMemory (nResults * sizeof(double));
  if (!c2m->results) return MagickFalse;

  c2m->matrix = AcquireMagickMatrix (nTerms, nTerms);
  if (!c2m->matrix) return MagickFalse;

  c2m->vectors = AcquireMagickMatrix (nResults, nTerms);
  if (!c2m->vectors) return MagickFalse;

  return MagickTrue;
}


static void DeAllocCols2mat (
  cols2matsT * c2m,
  int nTerms,
  int nResults
)
{
  c2m->vectors = RelinquishMagickMatrix (c2m->vectors, nResults);
  c2m->matrix  = RelinquishMagickMatrix (c2m->matrix, nTerms);
  c2m->results = RelinquishMagickMemory (c2m->results);
  c2m->terms   = RelinquishMagickMemory (c2m->terms);
}


static void LeastSquaresAddTermsWt (
  double **matrix,
  double **vectors,
  const double *terms,
  const double *results,
  const size_t rank,
  const size_t number_vectors,
  double weight)
{
  register ssize_t
    i,
    j;

  for (j=0; j < (ssize_t) rank; j++)
  {
    for (i=0; i < (ssize_t) rank; i++)
      matrix[i][j] += weight*terms[i]*terms[j];

    for (i=0; i < (ssize_t) number_vectors; i++)
      vectors[i][j] += weight*results[i]*terms[j];
  }
}

static double inline AlphaWeight (
  cols2matsT *c2m,
  Image * imgA,
  Image * imgB,
  const VIEW_PIX_PTR *pA,
  const VIEW_PIX_PTR *pB
)
{
  double wt = 1.0;
  if (c2m->weightAlpha) {
    if (c2m->imgAhasAlpha) {
      wt = GET_PIXEL_ALPHA (imgA, pA) / QuantumRange;
    }
    if (c2m->imgBhasAlpha) {
      wt *= GET_PIXEL_ALPHA (imgB, pB) / QuantumRange;
    }
  }
  return wt;
}


static MagickBooleanType cols2matCross (
  Image * imgA,
  Image * imgB,
  cols2matsT *c2m,
  ExceptionInfo *exception
)
/* From two images, same size,
   creates color matrix with 36 elements
   that if applied to imgA would make it look like imgB.
   "Cross" -- includes parameter for cross-freed between channels.
*/
{
  c2m->solutionFound = MagickFalse;

  if (imgA->columns != imgB->columns || imgA->rows != imgB->rows) {
    fprintf (stderr, "cols2mat: images not the same size\n");
    return MagickFalse;
  }

#define nTermsC 4
#define nResultsC 3

  if (!AllocCols2mat (c2m, nTermsC, nResultsC)) { fprintf (stderr, "alloc fail\n"); return MagickFalse; }

  CacheView * imgA_view = AcquireVirtualCacheView (imgA, exception);
  CacheView * imgB_view = AcquireVirtualCacheView (imgB, exception);

  c2m->terms[3] = 1;

  ssize_t y;
  for (y=0; y < imgA->rows; y++) {
    const VIEW_PIX_PTR *pA = GetCacheViewVirtualPixels (
        imgA_view, 0, y, imgA->columns, 1, exception);
    if (!pA) return MagickFalse;

    const VIEW_PIX_PTR *pB = GetCacheViewVirtualPixels (
        imgB_view, 0, y, imgB->columns, 1, exception);
    if (!pB) return MagickFalse;

    double LnWeight = (y==imgA->rows-1) ? c2m->BottomLineWeight : 1.0;

    ssize_t x;
    for (x=0; x < imgA->columns; x++) {
      c2m->terms[0]   = GET_PIXEL_RED   (imgA, pA) / QuantumRange;
      c2m->terms[1]   = GET_PIXEL_GREEN (imgA, pA) / QuantumRange;
      c2m->terms[2]   = GET_PIXEL_BLUE  (imgA, pA) / QuantumRange;

      c2m->results[0] = GET_PIXEL_RED   (imgB, pB) / QuantumRange;
      c2m->results[1] = GET_PIXEL_GREEN (imgB, pB) / QuantumRange;
      c2m->results[2] = GET_PIXEL_BLUE  (imgB, pB) / QuantumRange;

      LeastSquaresAddTermsWt (
        c2m->matrix, c2m->vectors, c2m->terms, c2m->results,
        nTermsC, nResultsC, LnWeight * AlphaWeight (c2m,imgA,imgB,pA,pB));

      pA += Inc_ViewPixPtr (imgA);
      pB += Inc_ViewPixPtr (imgB);
    }
  }

  if (c2m->verbose > 1) {
    fprintf (stderr, "cols2mat: Matrix is:\n");
    int u, v;
    for (u = 0; u < nTermsC; u++) {
      for (v = 0; v < nTermsC; v++) {
        fprintf (stderr, "  %g", c2m->matrix[u][v]);
      }
      fprintf (stderr, "\n");
    }
  }

  if (!GaussJordanElimination(c2m->matrix,c2m->vectors,nTermsC,nResultsC) ) {
    fprintf (stderr, "No solution.\n");
  } else {
    if (c2m->verbose > 1) {
      int j, k;
      fprintf (stderr, "cols2mat: Vectors is:\n");
      for (j = 0; j < nResultsC; j++) {
        for (k = 0; k < nTermsC; k++) {
          fprintf (stderr, "  %g", c2m->vectors[j][k]);
        }
        fprintf (stderr, "\n");
      }
    }
    c2m->kernel->values[0]  = c2m->vectors[0][0];
    c2m->kernel->values[1]  = c2m->vectors[0][1];
    c2m->kernel->values[2]  = c2m->vectors[0][2];
    c2m->kernel->values[5]  = c2m->vectors[0][3];

    c2m->kernel->values[6]  = c2m->vectors[1][0];
    c2m->kernel->values[7]  = c2m->vectors[1][1];
    c2m->kernel->values[8]  = c2m->vectors[1][2];
    c2m->kernel->values[11] = c2m->vectors[1][3];

    c2m->kernel->values[12] = c2m->vectors[2][0];
    c2m->kernel->values[13] = c2m->vectors[2][1];
    c2m->kernel->values[14] = c2m->vectors[2][2];
    c2m->kernel->values[17] = c2m->vectors[2][3];

    c2m->solutionFound = MagickTrue;
  }

  imgB_view = DestroyCacheView (imgB_view);
  imgA_view = DestroyCacheView (imgA_view);

  DeAllocCols2mat (c2m, nTermsC, nResultsC);

#undef nTermsC
#undef nResultsC

//  if (c2m->verbose) {
//    ShowKernelInfo (c2m->kernel);
//  }

  return MagickTrue;
}


static MagickBooleanType cols2matNoCross (
  Image * imgA,
  Image * imgB,
  cols2matsT * c2m,
  ExceptionInfo *exception
)
/* From two images, same size,
   creates color matrix with 36 elements
   that if applied to imgA would make it look like imgB.
   "NoCross" -- excludes parameters for cross-freed between channels.
*/
{
  c2m->solutionFound = MagickFalse;

#define nTermsNC 2
#define nResultsNC 1

  if (!AllocCols2mat (c2m, nTermsNC, nResultsNC)) { fprintf (stderr, "alloc fail\n"); return MagickFalse; }

  CacheView * imgA_view = AcquireVirtualCacheView (imgA, exception);
  CacheView * imgB_view = AcquireVirtualCacheView (imgB, exception);

  ssize_t y;

  int nFound = 0;

  // Solve for red.
  //
  for (y=0; y < imgA->rows; y++) {
    const VIEW_PIX_PTR *pA = GetCacheViewVirtualPixels (
        imgA_view, 0, y, imgA->columns, 1, exception);
    if (!pA) return MagickFalse;

    const VIEW_PIX_PTR *pB = GetCacheViewVirtualPixels (
        imgB_view, 0, y, imgB->columns, 1, exception);
    if (!pB) return MagickFalse;

    double LnWeight = (y==imgA->rows-1) ? c2m->BottomLineWeight : 1.0;

    ssize_t x;
    for (x=0; x < imgA->columns; x++) {
      c2m->terms[0]   = GET_PIXEL_RED   (imgA, pA) / QuantumRange;
      c2m->terms[1]   = 1;
      c2m->results[0] = GET_PIXEL_RED   (imgB, pB) / QuantumRange;

      LeastSquaresAddTermsWt (
        c2m->matrix, c2m->vectors, c2m->terms, c2m->results,
        nTermsNC, nResultsNC, LnWeight * AlphaWeight (c2m,imgA,imgB,pA,pB));

      pA += Inc_ViewPixPtr (imgA);
      pB += Inc_ViewPixPtr (imgB);
    }
  }

  if (!GaussJordanElimination(c2m->matrix,c2m->vectors,nTermsNC,nResultsNC) ) {
    fprintf (stderr, "No solution R.\n");
  } else {
    c2m->kernel->values[0]  = c2m->vectors[0][0];
    c2m->kernel->values[5]  = c2m->vectors[0][1];
    nFound++;
  }

  DeAllocCols2mat (c2m, nTermsNC, nResultsNC);
  if (!AllocCols2mat (c2m, nTermsNC, nResultsNC)) { fprintf (stderr, "alloc fail\n"); return MagickFalse; }

  // Solve for green.
  //
  for (y=0; y < imgA->rows; y++) {
    const VIEW_PIX_PTR *pA = GetCacheViewVirtualPixels (
        imgA_view, 0, y, imgA->columns, 1, exception);
    if (!pA) return MagickFalse;

    const VIEW_PIX_PTR *pB = GetCacheViewVirtualPixels (
        imgB_view, 0, y, imgB->columns, 1, exception);
    if (!pB) return MagickFalse;

    double LnWeight = (y==imgA->rows-1) ? c2m->BottomLineWeight : 1.0;

    ssize_t x;
    for (x=0; x < imgA->columns; x++) {
      c2m->terms[0]   = GET_PIXEL_GREEN (imgA, pA) / QuantumRange;
      c2m->terms[1]   = 1;
      c2m->results[0] = GET_PIXEL_GREEN (imgB, pB) / QuantumRange;

      LeastSquaresAddTermsWt (
        c2m->matrix, c2m->vectors, c2m->terms, c2m->results,
        nTermsNC, nResultsNC, LnWeight * AlphaWeight (c2m,imgA,imgB,pA,pB));

      pA += Inc_ViewPixPtr (imgA);
      pB += Inc_ViewPixPtr (imgB);
    }
  }

  if (!GaussJordanElimination(c2m->matrix,c2m->vectors,nTermsNC,nResultsNC) ) {
    fprintf (stderr, "No solution G.\n");
  } else {
    c2m->kernel->values[7]  = c2m->vectors[0][0];
    c2m->kernel->values[11] = c2m->vectors[0][1];
    nFound++;
  }

  DeAllocCols2mat (c2m, nTermsNC, nResultsNC);
  if (!AllocCols2mat (c2m, nTermsNC, nResultsNC)) { fprintf (stderr, "alloc fail\n"); return MagickFalse; }

  // Solve for blue.
  //
  for (y=0; y < imgA->rows; y++) {
    const VIEW_PIX_PTR *pA = GetCacheViewVirtualPixels (
        imgA_view, 0, y, imgA->columns, 1, exception);
    if (!pA) return MagickFalse;

    const VIEW_PIX_PTR *pB = GetCacheViewVirtualPixels (
        imgB_view, 0, y, imgB->columns, 1, exception);
    if (!pB) return MagickFalse;

    double LnWeight = (y==imgA->rows-1) ? c2m->BottomLineWeight : 1.0;

    ssize_t x;
    for (x=0; x < imgA->columns; x++) {
      c2m->terms[0]   = GET_PIXEL_BLUE  (imgA, pA) / QuantumRange;
      c2m->terms[1]   = 1;
      c2m->results[0] = GET_PIXEL_BLUE  (imgB, pB) / QuantumRange;

      LeastSquaresAddTermsWt (
        c2m->matrix, c2m->vectors, c2m->terms, c2m->results,
        nTermsNC, nResultsNC, LnWeight * AlphaWeight (c2m,imgA,imgB,pA,pB));

      pA += Inc_ViewPixPtr (imgA);
      pB += Inc_ViewPixPtr (imgB);
    }
  }

  if (!GaussJordanElimination(c2m->matrix,c2m->vectors,nTermsNC,nResultsNC) ) {
    fprintf (stderr, "No solution B.\n");
  } else {
    c2m->kernel->values[14]  = c2m->vectors[0][0];
    c2m->kernel->values[17]  = c2m->vectors[0][1];
    nFound++;
  }

  c2m->solutionFound = (nFound ==3);

  imgB_view = DestroyCacheView (imgB_view);
  imgA_view = DestroyCacheView (imgA_view);

  DeAllocCols2mat (c2m, nTermsNC, nResultsNC);

#undef nTermsNC
#undef nResultsNC

//  if (c2m->verbose) {
//    ShowKernelInfo (c2m->kernel);
//  }

  return MagickTrue;
}


static MagickBooleanType cols2matGainOnly (
  Image * imgA,
  Image * imgB,
  cols2matsT * c2m,
  ExceptionInfo *exception
)
/* From two images, same size,
   creates color matrix with 36 elements
   that if applied to imgA would make it look like imgB.
   "GainOnly" -- no cross-feed, no bias.
*/
{
  c2m->solutionFound = MagickFalse;

#define nTermsGO 1
#define nResultsGO 1

  if (!AllocCols2mat (c2m, nTermsGO, nResultsGO)) { fprintf (stderr, "alloc fail\n"); return MagickFalse; }

  CacheView * imgA_view = AcquireVirtualCacheView (imgA, exception);
  CacheView * imgB_view = AcquireVirtualCacheView (imgB, exception);

  ssize_t y;

  int nFound = 0;

  // Solve for red.
  //
  for (y=0; y < imgA->rows; y++) {
    const VIEW_PIX_PTR *pA = GetCacheViewVirtualPixels (
        imgA_view, 0, y, imgA->columns, 1, exception);
    if (!pA) return MagickFalse;

    const VIEW_PIX_PTR *pB = GetCacheViewVirtualPixels (
        imgB_view, 0, y, imgB->columns, 1, exception);
    if (!pB) return MagickFalse;

    double LnWeight = (y==imgA->rows-1) ? c2m->BottomLineWeight : 1.0;

    ssize_t x;
    for (x=0; x < imgA->columns; x++) {
      c2m->terms[0]   = GET_PIXEL_RED   (imgA, pA) / QuantumRange;
      c2m->results[0] = GET_PIXEL_RED   (imgB, pB) / QuantumRange;

      LeastSquaresAddTermsWt (
        c2m->matrix, c2m->vectors, c2m->terms, c2m->results,
        nTermsGO, nResultsGO, LnWeight * AlphaWeight (c2m,imgA,imgB,pA,pB));

      pA += Inc_ViewPixPtr (imgA);
      pB += Inc_ViewPixPtr (imgB);
    }
  }

  if (!GaussJordanElimination(c2m->matrix,c2m->vectors,nTermsGO,nResultsGO) ) {
    fprintf (stderr, "No solution R.\n");
  } else {
    c2m->kernel->values[0]  = c2m->vectors[0][0];
    nFound++;
  }

  DeAllocCols2mat (c2m, nTermsGO, nResultsGO);
  if (!AllocCols2mat (c2m, nTermsGO, nResultsGO)) { fprintf (stderr, "alloc fail\n"); return MagickFalse; }

  // Solve for green.
  //
  for (y=0; y < imgA->rows; y++) {
    const VIEW_PIX_PTR *pA = GetCacheViewVirtualPixels (
        imgA_view, 0, y, imgA->columns, 1, exception);
    if (!pA) return MagickFalse;

    const VIEW_PIX_PTR *pB = GetCacheViewVirtualPixels (
        imgB_view, 0, y, imgB->columns, 1, exception);
    if (!pB) return MagickFalse;

    double LnWeight = (y==imgA->rows-1) ? c2m->BottomLineWeight : 1.0;

    ssize_t x;
    for (x=0; x < imgA->columns; x++) {
      c2m->terms[0]   = GET_PIXEL_GREEN (imgA, pA) / QuantumRange;
      c2m->results[0] = GET_PIXEL_GREEN (imgB, pB) / QuantumRange;

      LeastSquaresAddTermsWt (
        c2m->matrix, c2m->vectors, c2m->terms, c2m->results,
        nTermsGO, nResultsGO, LnWeight * AlphaWeight (c2m,imgA,imgB,pA,pB));

      pA += Inc_ViewPixPtr (imgA);
      pB += Inc_ViewPixPtr (imgB);
    }
  }

  if (!GaussJordanElimination(c2m->matrix,c2m->vectors,nTermsGO,nResultsGO) ) {
    fprintf (stderr, "No solution G.\n");
  } else {
    c2m->kernel->values[7]  = c2m->vectors[0][0];
    nFound++;
  }

  DeAllocCols2mat (c2m, nTermsGO, nResultsGO);
  if (!AllocCols2mat (c2m, nTermsGO, nResultsGO)) { fprintf (stderr, "alloc fail\n"); return MagickFalse; }

  // Solve for blue.
  //
  for (y=0; y < imgA->rows; y++) {
    const VIEW_PIX_PTR *pA = GetCacheViewVirtualPixels (
        imgA_view, 0, y, imgA->columns, 1, exception);
    if (!pA) return MagickFalse;

    const VIEW_PIX_PTR *pB = GetCacheViewVirtualPixels (
        imgB_view, 0, y, imgB->columns, 1, exception);
    if (!pB) return MagickFalse;

    double LnWeight = (y==imgA->rows-1) ? c2m->BottomLineWeight : 1.0;

    ssize_t x;
    for (x=0; x < imgA->columns; x++) {
      c2m->terms[0]   = GET_PIXEL_BLUE  (imgA, pA) / QuantumRange;
      c2m->results[0] = GET_PIXEL_BLUE  (imgB, pB) / QuantumRange;

      LeastSquaresAddTermsWt (
        c2m->matrix, c2m->vectors, c2m->terms, c2m->results,
        nTermsGO, nResultsGO, LnWeight * AlphaWeight (c2m,imgA,imgB,pA,pB));

      pA += Inc_ViewPixPtr (imgA);
      pB += Inc_ViewPixPtr (imgB);
    }
  }

  if (!GaussJordanElimination(c2m->matrix,c2m->vectors,nTermsGO,nResultsGO) ) {
    fprintf (stderr, "No solution B.\n");
  } else {
    c2m->kernel->values[14]  = c2m->vectors[0][0];
    nFound++;
  }

  c2m->solutionFound = (nFound ==3);


  imgB_view = DestroyCacheView (imgB_view);
  imgA_view = DestroyCacheView (imgA_view);

  DeAllocCols2mat (c2m, nTermsGO, nResultsGO);

#undef nTermsGO
#undef nResultsGO

//  if (c2m->verbose) {
//    ShowKernelInfo (c2m->kernel);
//  }

  return MagickTrue;
}


static MagickBooleanType cols2matNoCrossPoly (
  Image * imgA,
  Image * imgB,
  cols2matsT * c2m,
  ExceptionInfo *exception
)
{
  c2m->solutionFound = MagickFalse;

  int nTerms = c2m->polyDegree + 1;
  int nResults = 1;

  // Note: terms[0] is term with largest degree.

  int c;
  for (c = 0; c < NUM_CH; c++) {
    // FIXME: This is never Relinguished.
    c2m->polyCoeff[c] = (double *)AcquireMagickMemory (nTerms * sizeof(double));
    if (!c2m->polyCoeff[c]) return MagickFalse;
  }

  CacheView * imgA_view = AcquireVirtualCacheView (imgA, exception);
  CacheView * imgB_view = AcquireVirtualCacheView (imgB, exception);

  int i;
  ssize_t y;

  int nFound = 0;

  if (!AllocCols2mat (c2m, nTerms, nResults)) { fprintf (stderr, "alloc fail\n"); return MagickFalse; }

  // Solve for red.
  //
  for (y=0; y < imgA->rows; y++) {
    const VIEW_PIX_PTR *pA = GetCacheViewVirtualPixels (
        imgA_view, 0, y, imgA->columns, 1, exception);
    if (!pA) return MagickFalse;

    const VIEW_PIX_PTR *pB = GetCacheViewVirtualPixels (
        imgB_view, 0, y, imgB->columns, 1, exception);
    if (!pB) return MagickFalse;

    double LnWeight = (y==imgA->rows-1) ? c2m->BottomLineWeight : 1.0;

    ssize_t x;
    for (x=0; x < imgA->columns; x++) {
      double v = GET_PIXEL_RED (imgA, pA) / QuantumRange;
      c2m->terms[nTerms-1]   = 1;
      for (i = 1; i < nTerms; i++) {
        int j = nTerms-1-i;
        c2m->terms[j] = v * c2m->terms[j+1];
//        printf ("term %i is %g\n", j, c2m->terms[j]);
      }
      c2m->results[0] = GET_PIXEL_RED (imgB, pB) / QuantumRange;

      LeastSquaresAddTermsWt (
        c2m->matrix, c2m->vectors, c2m->terms, c2m->results,
        nTerms, nResults, LnWeight * AlphaWeight (c2m,imgA,imgB,pA,pB));

      pA += Inc_ViewPixPtr (imgA);
      pB += Inc_ViewPixPtr (imgB);
    }
  }

  for (i = 0; i < nTerms; i++) {
//    printf ("term %i is %g\n", i, c2m->terms[i]);
  }

  if (!GaussJordanElimination(c2m->matrix,c2m->vectors,nTerms,nResults) ) {
    fprintf (stderr, "No solution R.\n");
  } else {
    for (i = 0; i < nTerms; i++) {
//      printf ("term %i is %g; coeff is ", i, c2m->terms[i]);
//      printf ("  %g\n", c2m->vectors[0][i]);
      c2m->polyCoeff[0][i] = c2m->vectors[0][i];
    }
//    printf ("\n");
    nFound++;
  }

  DeAllocCols2mat (c2m, nTerms, nResults);
  if (!AllocCols2mat (c2m, nTerms, nResults)) { fprintf (stderr, "alloc fail\n"); return MagickFalse; }

  // Solve for green.
  //
  for (y=0; y < imgA->rows; y++) {
    const VIEW_PIX_PTR *pA = GetCacheViewVirtualPixels (
        imgA_view, 0, y, imgA->columns, 1, exception);
    if (!pA) return MagickFalse;

    const VIEW_PIX_PTR *pB = GetCacheViewVirtualPixels (
        imgB_view, 0, y, imgB->columns, 1, exception);
    if (!pB) return MagickFalse;

    double LnWeight = (y==imgA->rows-1) ? c2m->BottomLineWeight : 1.0;

    ssize_t x;
    for (x=0; x < imgA->columns; x++) {
      double v = GET_PIXEL_GREEN (imgA, pA) / QuantumRange;
      c2m->terms[nTerms-1]   = 1;
      for (i = 1; i < nTerms; i++) {
        int j = nTerms-1-i;
        c2m->terms[j] = v * c2m->terms[j+1];
//        printf ("term %i is %g\n", j, c2m->terms[j]);
      }
      c2m->results[0] = GET_PIXEL_GREEN (imgB, pB) / QuantumRange;

      LeastSquaresAddTermsWt (
        c2m->matrix, c2m->vectors, c2m->terms, c2m->results,
        nTerms, nResults, LnWeight * AlphaWeight (c2m,imgA,imgB,pA,pB));

      pA += Inc_ViewPixPtr (imgA);
      pB += Inc_ViewPixPtr (imgB);
    }
  }

  for (i = 0; i < nTerms; i++) {
//    printf ("term %i is %g\n", i, c2m->terms[i]);
  }

  if (!GaussJordanElimination(c2m->matrix,c2m->vectors,nTerms,nResults) ) {
    fprintf (stderr, "No solution G.\n");
  } else {
    for (i = 0; i < nTerms; i++) {
//      printf ("term %i is %g; coeff is ", i, c2m->terms[i]);
//      printf ("  %g\n", c2m->vectors[0][i]);
      c2m->polyCoeff[1][i] = c2m->vectors[0][i];
    }
//    printf ("\n");
    nFound++;
  }

  DeAllocCols2mat (c2m, nTerms, nResults);
  if (!AllocCols2mat (c2m, nTerms, nResults)) { fprintf (stderr, "alloc fail\n"); return MagickFalse; }

  // Solve for blue.
  //
  for (y=0; y < imgA->rows; y++) {
    const VIEW_PIX_PTR *pA = GetCacheViewVirtualPixels (
        imgA_view, 0, y, imgA->columns, 1, exception);
    if (!pA) return MagickFalse;

    const VIEW_PIX_PTR *pB = GetCacheViewVirtualPixels (
        imgB_view, 0, y, imgB->columns, 1, exception);
    if (!pB) return MagickFalse;

    double LnWeight = (y==imgA->rows-1) ? c2m->BottomLineWeight : 1.0;

    ssize_t x;
    for (x=0; x < imgA->columns; x++) {
      double v = GET_PIXEL_BLUE (imgA, pA) / QuantumRange;
      c2m->terms[nTerms-1]   = 1;
      for (i = 1; i < nTerms; i++) {
        int j = nTerms-1-i;
        c2m->terms[j] = v * c2m->terms[j+1];
//        printf ("term %i is %g\n", j, c2m->terms[j]);
      }
      c2m->results[0] = GET_PIXEL_BLUE (imgB, pB) / QuantumRange;

      LeastSquaresAddTermsWt (
        c2m->matrix, c2m->vectors, c2m->terms, c2m->results,
        nTerms, nResults, LnWeight * AlphaWeight (c2m,imgA,imgB,pA,pB));

      pA += Inc_ViewPixPtr (imgA);
      pB += Inc_ViewPixPtr (imgB);
    }
  }

//  for (i = 0; i < nTerms; i++) {
//    printf ("term %i is %g\n", i, c2m->terms[i]);
//  }

  if (!GaussJordanElimination(c2m->matrix,c2m->vectors,nTerms,nResults) ) {
    fprintf (stderr, "No solution B.\n");
  } else {
    for (i = 0; i < nTerms; i++) {
//      printf ("term %i is %g; coeff is ", i, c2m->terms[i]);
//      printf ("  %g\n", c2m->vectors[0][i]);
      c2m->polyCoeff[2][i] = c2m->vectors[0][i];
    }
//    printf ("\n");
    nFound++;
  }

  DeAllocCols2mat (c2m, nTerms, nResults);

  c2m->solutionFound = (nFound == NUM_CH);

  imgB_view = DestroyCacheView (imgB_view);
  imgA_view = DestroyCacheView (imgA_view);

  return MagickTrue;
}

static void WrKernelVals (cols2matsT * c2m, FILE * fh, char * prefix)
{
  int precision = GetMagickPrecision();

  fprintf (fh, "%s", prefix);
  int i;
  for (i = 0; i < c2m->kernel->height * c2m->kernel->width; i++) {
    if (i) fprintf (fh, ",");
    fprintf (fh, "%.*g", precision, c2m->kernel->values[i]);
  }
  fprintf (fh, "\n");

  if (c2m->doWarn) {
    char *chNames[] = {"Red", "Green", "Blue"};

    int c,k;
    i = 0;
    for (c = 0; c < 2; c++) {
      double sumPosRow = 0.0;
      double last = 0.0;
      for (k = 0; k < 6; k++) {
        if (c2m->kernel->values[i] > 0) sumPosRow += c2m->kernel->values[i];
        if (k==5) last = c2m->kernel->values[i];
        i++;
      }
      if (last < 0.0) {
        fprintf (fh, "Warning: may clip %s shadows\n", chNames[c]);
      }
      if (sumPosRow > 1.0) {
        fprintf (fh, "Warning: may clip %s highlights\n", chNames[c]);
      }
    }
  }
}

static void WrPolynomials (cols2matsT * c2m, FILE * fh)
{
  char *chNames[] = {"Red", "Green", "Blue"};

  int precision = GetMagickPrecision();

  int nTerms = c2m->polyDegree + 1;

  int c, i;
  for (c = 0; c < NUM_CH; c++) {
    double sumPoly = 0.0;
    fprintf (fh, "Poly%s=", chNames[c]);
    for (i = 0; i < nTerms; i++) {
      if (i) fprintf (fh, ",");
      fprintf (fh, "%.*g", precision, c2m->polyCoeff[c][i]);
      sumPoly += c2m->polyCoeff[c][i];
    }
    fprintf (fh, "\n");
    if (c2m->doWarn) {
      if (c2m->polyCoeff[c][nTerms-1] < 0.0) {
        fprintf (fh, "Warning: may clip %s shadows\n", chNames[c]);
      }
      if (sumPoly > 1.0) {
        fprintf (fh, "Warning: may clip %s highlights\n", chNames[c]);
      }
    }
  }
}


static MagickBooleanType cols2matGen (
  Image * imgA,
  Image * imgB,
  cols2matsT * c2m,
  FILE *fh_out,
  ExceptionInfo *exception
)
// This may acquire a kernel. Caller should destroy it.
{
  c2m->solutionFound = MagickFalse;

  if (imgA->columns != imgB->columns || imgA->rows != imgB->rows) {
    fprintf (stderr, "cols2mat: images not the same size\n");
    return MagickFalse;
  }

  // FIXME: Following is a kludge.
  // We merely want to ensure we have three colour channels.
#if IMV6OR7==6
  TransformImageColorspace (imgA, sRGBColorspace);
  TransformImageColorspace (imgB, sRGBColorspace);
#else
  TransformImageColorspace (imgA, sRGBColorspace, exception);
  TransformImageColorspace (imgB, sRGBColorspace, exception);
#endif

  c2m->kernel = ACQUIRE_KERNEL(
    "6x6:1,0,0,0,0,0,0,1,0,0,0,0,0,0,1,0,0,0,0,0,0,1,0,0,0,0,0,0,1,0,0,0,0,0,0,1",
     exception);
  if (!c2m->kernel) {
    fprintf (stderr, "AcquireKernelInfo failed\n");
    return MagickFalse;
  }

  c2m->imgAhasAlpha = IS_ALPHA_CH(imgA);
  c2m->imgBhasAlpha = IS_ALPHA_CH(imgB);

  MagickBooleanType okay = MagickFalse;

  switch (c2m->c2mType) {
    case ctCross:
      okay = cols2matCross (imgA, imgB, c2m, exception);
      break;
    case ctNoCross:
      okay = cols2matNoCross (imgA, imgB, c2m, exception);
      break;
    case ctNoCrossPoly:
      okay = cols2matNoCrossPoly (imgA, imgB, c2m, exception);
      break;
    case ctGainOnly:
      okay = cols2matGainOnly (imgA, imgB, c2m, exception);
      break;
  }

  if (okay && fh_out) {
    if (c2m->solutionFound) {
      switch (c2m->c2mType) {
        case ctNoCrossPoly:
          WrPolynomials (c2m, fh_out);
          break;

        default:
          WrKernelVals (c2m, fh_out, "c2matrix=");
      }
    } else {
      fprintf (fh_out, "cols2mat: no solution found\n");
    }
  }

  return okay;
}


static KernelInfo * MultKernels (KernelInfo *kA, KernelInfo *kB)
// From two equal-sized square kernels,
// create a new kernel, which caller should eventually destroy.
{
  int nElements = kA->width;

  if (nElements != kA->height
   || nElements != kB->height
   || nElements != kB->width)
  {
    return NULL;
  }

  KernelInfo * newK = CloneKernelInfo (kA);
  if (!newK) return NULL;

  int x, y, i;

  for (i = 0; i < nElements*nElements; i++) {
    newK->values[i] = 0;
  }

  for (y = 0; y < nElements; y++) {
    for (x = 0; x < nElements; x++) {
      int nNew = y * nElements + x;
      for (i = 0; i < nElements; i++) {
        int nA = y * nElements + i;
        int nB = i * nElements + x;
        newK->values[nNew] = kA->values[nA] * kB->values[nB];
      }
    }
  }

  return newK;
}


#endif

oogbox.c

/* Updated:

     11-December-2018 corrected typos in options.
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <math.h>
#include <ctype.h>
#include <float.h>
#include "vsn_defines.h"

typedef enum {
  omLinear,
  omPower
} oogMethodT;

typedef struct {
  // Settings:
  MagickBooleanType
    verbose,
    doForceMin,
    doForceMax;

  oogMethodT
    oogMethod;

  double
    loLimit,
    hiLimit,
    forceMin,
    forceMax;

  int
    chanNum, // -1 for all channels together.
    precision;

  // Used internally:
  MagickBooleanType
    doLo,
    doHi;

  double
    min, max,
    a, b, c, d,
    A0, B0,
    A1, B1;
} oogboxT;

#define VERSION "oogbox v1.0  Copyright (c) 2017 Alan Gibson"

static void usage (void)
{
  printf ("Usage: -process 'oogbox [OPTION]...'\n");
  printf ("Puts out-of-gamut values back in the box.\n");
  printf ("\n");
  printf ("  m, method string        Linear or Power [Power]\n");
  printf ("  l, loLimit number       low limit for shadow processing [0.1]\n");
  printf ("  h, hiLimit number       high limit for highlight processing [0.9]\n");
  printf ("  fmn, forceMin number    force minimum value (override calculation)\n");
  printf ("  fmx, forceMax number    force maximum value (override calculation)\n");
  printf ("  c, channel integer      channel number, or -1 for all [-1]\n");
  printf ("  v, verbose              write text information to stdout\n");
  printf ("     version              write version information to stdout\n");
  printf ("\n");
}


static MagickBooleanType IsArg (char * pa, char * ShtOpt, char * LongOpt)
{
  // LocaleCompare is not case-sensitive,
  // so we use strcmp for the short option.

  if ((strcmp(pa, ShtOpt)==0) || (LocaleCompare(pa, LongOpt)==0))
    return MagickTrue;

  return MagickFalse;
}


#define NEXTARG \
  if (i < argc-1) i++; \
  else { \
    fprintf (stderr, "oogbox: ERROR: [%s] needs argument\n", pa); \
    status = MagickFalse; \
  }

static MagickBooleanType menu(
  const int argc,
  const char **argv,
  oogboxT * pob
)
// Returns MagickTrue if okay.
{
  int
    i;

  MagickBooleanType
    status = MagickTrue;

  pob->oogMethod = omPower;
  pob->loLimit = 0.1;
  pob->hiLimit = 0.9;
  pob->chanNum = -1;
  pob->doLo = pob->doHi = MagickFalse;
  pob->doForceMin = pob->doForceMax = MagickFalse;
  pob->forceMin = 0.0;
  pob->forceMax = 1.0;
  pob->a = pob->b = pob->c = pob->d = 0;
  pob->A0 = pob->B0 = 0;
  pob->A1 = pob->B1 = 0;

  pob->verbose = MagickFalse;

  for (i=0; i < argc; i++) {
    char * pa = (char *)argv[i];

    if (IsArg (pa, "m", "method")==MagickTrue) {
      NEXTARG;
      if (LocaleCompare(argv[i], "Linear")==0) pob->oogMethod = omLinear;
      else if (LocaleCompare(argv[i], "Power")==0) pob->oogMethod = omPower;
      else {
        fprintf (stderr, "oogbox: ERROR: unknown method [%s]\n", argv[i]);
        status = MagickFalse;
      }
    } else if (IsArg (pa, "l", "loLimit")==MagickTrue) {
      NEXTARG;
      pob->loLimit = atof(argv[i]);
    } else if (IsArg (pa, "h", "hiLimit")==MagickTrue) {
      NEXTARG;
      pob->hiLimit = atof(argv[i]);
    } else if (IsArg (pa, "fmn", "forceMin")==MagickTrue) {
      NEXTARG;
      pob->forceMin = atof(argv[i]);
      pob->doForceMin = MagickTrue;
    } else if (IsArg (pa, "fmx", "forceMax")==MagickTrue) {
      NEXTARG;
      pob->forceMax = atof(argv[i]);
      pob->doForceMax = MagickTrue;
    } else if (IsArg (pa, "c", "channel")==MagickTrue) {
      NEXTARG;
      pob->chanNum = atoi(argv[i]);

    } else if (IsArg (pa, "v", "verbose")==MagickTrue) {
      pob->verbose = MagickTrue;
    } else if (IsArg (pa, "version", "version")==MagickTrue) {
      fprintf (stdout, "%s\n", VERSION);
    } else {
      fprintf (stderr, "oogbox: ERROR: unknown option [%s]\n", pa);
      status = MagickFalse;
    }
  }

  if (pob->verbose) {
    fprintf (stderr, "oogbox options: ");
    fprintf (stderr, " method ");
    switch (pob->oogMethod) {
      case omLinear: fprintf (stderr, "Linear"); break;
      case omPower:  fprintf (stderr, "Power");  break;
    }
    fprintf (stderr, " loLimit %.*g", pob->precision, pob->loLimit);
    fprintf (stderr, " hiLimit %.*g", pob->precision, pob->hiLimit);
    if (pob->doForceMin) fprintf (stderr, " forceMin %.*g", pob->precision, pob->forceMin);
    if (pob->doForceMax) fprintf (stderr, " forceMax %.*g", pob->precision, pob->forceMax);
    fprintf (stderr, " channel %i", pob->chanNum);
    if (pob->verbose)    fprintf (stderr, " verbose");
    fprintf (stderr, "\n");
  }

  if (status == MagickFalse)
    usage ();

  return (status);
}


static double inline CalcLow (
    oogboxT * pob,
    double v)
{
  switch (pob->oogMethod) {
    case omLinear: return pob->a*v + pob->b;
    case omPower: {
      double dv = v - pob->min;
      if (dv < 0) return v;
      return pob->A0 * pow (v - pob->min, pob->B0);
    }
  }
  return 0; // Keep compiler happy.
}

static double inline CalcHigh (
    oogboxT * pob,
    double v)
{
  switch (pob->oogMethod) {
    case omLinear: return pob->c*v + pob->d;
    case omPower:  {
      double dv = pob->max - v;
      if (dv < 0) return v;
      return 1 - (pob->A1 * pow (dv, pob->B1));
    }
  }
  return 0; // Keep compiler happy.
}

// The next function corresponds in style to functions in transform.c
// It takes one image, and returns an image.
//
static Image *oogbox (
  Image *image,
  oogboxT * pob,
  ExceptionInfo *exception)
{
  // This ignores the alpha channel.

  if (image->debug != MagickFalse)
    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);

  if (pob->verbose) {
    fprintf (stderr, "oogbox: Input image [%s] %ix%i depth is %i\n",
             image->filename,
             (int)image->columns, (int)image->rows,
             (int)image->depth);
  }

  pob->doLo = (pob->loLimit > 0.0);
  pob->doHi = (pob->hiLimit < 1.0);


  // Ensure we have non-palette, 3 channels.

  Image * new_img = CloneImage (image, 0, 0, MagickTrue, exception);
  if (!new_img) return NULL;

  if (!SetNoPalette (new_img, exception)) return NULL;

  // Find min and max.
  CacheView * in_view = AcquireVirtualCacheView (new_img, exception);

  MagickBooleanType okay = MagickTrue;
  ssize_t y;

  pob->min = DBL_MAX;
  pob->max = DBL_MIN;

  MagickBooleanType DoR = (pob->chanNum == -1) || (pob->chanNum == 0);
  MagickBooleanType DoG = (pob->chanNum == -1) || (pob->chanNum == 1);
  MagickBooleanType DoB = (pob->chanNum == -1) || (pob->chanNum == 2);

  for (y = 0; y < new_img->rows; y++) {
    if (!okay) continue;

    const VIEW_PIX_PTR *pIn = GetCacheViewVirtualPixels(
      in_view,0,y,new_img->columns,1,exception);
    if (!pIn) {okay = MagickFalse; continue; }

    ssize_t x;
    for (x = 0; x < image->columns; x++) {

      if (DoR) {
        double v = GET_PIXEL_RED   (new_img, pIn);
        if (pob->min > v) pob->min = v;
        if (pob->max < v) pob->max = v;
      }
      if (DoG) {
        double v = GET_PIXEL_GREEN (new_img, pIn);
        if (pob->min > v) pob->min = v;
        if (pob->max < v) pob->max = v;
      }
      if (DoB) {
        double v = GET_PIXEL_BLUE  (new_img, pIn);
        if (pob->min > v) pob->min = v;
        if (pob->max < v) pob->max = v;
      }

      pIn += Inc_ViewPixPtr (image);
    }
  }

  in_view  = DestroyCacheView (in_view);

  pob->min /= QuantumRange;
  pob->max /= QuantumRange;

  if (pob->verbose) {
    fprintf (stderr, "oogbox: calculated range %.*g to %.*g\n",
      pob->precision, pob->min,
      pob->precision, pob->max);
  }

  if (pob->doForceMin) pob->min = pob->forceMin;
  if (pob->doForceMax) pob->max = pob->forceMax;

  // What do we need to do?

  pob->doLo &= (pob->min < 0.0);
  pob->doHi &= (pob->max > 1.0);

  if (pob->verbose) {
    fprintf (stderr, "oogbox: min=%.*g  max=%.*g  doLo=%i doH=%i\n",
      pob->precision, pob->min,
      pob->precision, pob->max,
      pob->doLo,
      pob->doHi);
  }

  if (!pob->doLo && !pob->doHi) return new_img;

  // Calculate the parameters.

  switch (pob->oogMethod) {
    case omLinear: {
      if (pob->doLo) {
        pob->a = -pob->loLimit / (pob->min - pob->loLimit);
        pob->b = pob->loLimit * (pob->min) / (pob->min - pob->loLimit);
      }
      if (pob->doHi) {
        pob->c = (1 - pob->hiLimit) / (pob->max - pob->hiLimit);
        pob->d = pob->hiLimit * (pob->max - 1) / (pob->max - pob->hiLimit);
      }

      if (pob->verbose) {
        fprintf (stderr, "oogbox: Linear a=%.*g  b=%.*g  c=%.*g  d=%.*g\n",
          pob->precision, pob->a,
          pob->precision, pob->b,
          pob->precision, pob->c,
          pob->precision, pob->d);
      }
      break;
    }
    case omPower: {
      if (pob->doLo) {
        pob->B0 = (pob->loLimit - pob->min) / (pob->loLimit);
        pob->A0 = pob->loLimit / pow(pob->loLimit - pob->min,pob->B0);
      }
      if (pob->doHi) {
        pob->B1 = (pob->max - pob->hiLimit) / (1 - pob->hiLimit);
        pob->A1 = (1 - pob->hiLimit) / pow(pob->max - pob->hiLimit,pob->B1);
      }

      if (pob->verbose) {
        fprintf (stderr, "oogbox: Power A0=%.*g  B0=%.*g  A1=%.*g  B1=%.*g\n",
          pob->precision, pob->A0,
          pob->precision, pob->B0,
          pob->precision, pob->A1,
          pob->precision, pob->B1);
      }
      break;
    }
  }


  // Calculate the new values.

  // Linear transformation:
  //   y=a*x+b at shadows
  //   y=c*x+d at highlights
  //
  // Power transformation:
  //   y=A0*x+B0 at shadows
  //   y=A1*x+B1 at highlights

  CacheView * upd_view = AcquireAuthenticCacheView (new_img, exception);

  for (y = 0; y < new_img->rows; y++) {
    if (!okay) continue;

    VIEW_PIX_PTR *pIn = GetCacheViewAuthenticPixels(
      upd_view,0,y,new_img->columns,1,exception);
    if (!pIn) {okay = MagickFalse; continue; }

    ssize_t x;
    for (x = 0; x < image->columns; x++) {

      if (pob->doLo) {
        if (DoR) {
          double v = GET_PIXEL_RED   (new_img, pIn) / QuantumRange;
          if (v < pob->loLimit) {
            SET_PIXEL_RED   (new_img, QuantumRange * CalcLow (pob, v), pIn);
          }
        }
        if (DoG) {
          double v = GET_PIXEL_GREEN (new_img, pIn) / QuantumRange;
          if (v < pob->loLimit) {
            SET_PIXEL_GREEN (new_img, QuantumRange * CalcLow (pob, v), pIn);
          }
        }
        if (DoB) {
          double v = GET_PIXEL_BLUE  (new_img, pIn) / QuantumRange;
          if (v < pob->loLimit) {
            SET_PIXEL_BLUE  (new_img, QuantumRange * CalcLow (pob, v), pIn);
          }
        }
      }

      if (pob->doHi) {
        if (DoR) {
          double v = GET_PIXEL_RED   (new_img, pIn) / QuantumRange;
          if (v > pob->hiLimit) {
            SET_PIXEL_RED   (new_img, QuantumRange * CalcHigh (pob, v), pIn);
          }
        }
        if (DoG) {
          double v = GET_PIXEL_GREEN (new_img, pIn) / QuantumRange;
          if (v > pob->hiLimit) {
            SET_PIXEL_GREEN (new_img, QuantumRange * CalcHigh (pob, v), pIn);
          }
        }
        if (DoB) {
          double v = GET_PIXEL_BLUE  (new_img, pIn) / QuantumRange;
          if (v > pob->hiLimit) {
            SET_PIXEL_BLUE  (new_img, QuantumRange * CalcHigh (pob, v), pIn);
          }
        }
      }

      pIn += Inc_ViewPixPtr (image);
    }
    if (SyncCacheViewAuthenticPixels(upd_view,exception) == MagickFalse)
      okay = MagickFalse;
  }

  upd_view  = DestroyCacheView (upd_view);

  return (new_img);
}


ModuleExport size_t oogboxImage (
  Image **images,
  const int argc,
  const char **argv,
  ExceptionInfo *exception)
{
  Image
    *image,
    *new_image;

  MagickBooleanType
    status;

  oogboxT ob;

  assert(images != (Image **) NULL);
  assert(*images != (Image *) NULL);
  assert((*images)->signature == MAGICK_CORE_SIG);

  ob.precision = GetMagickPrecision();

  status = menu (argc, argv, &ob);
  if (status == MagickFalse)
    return (-1);

  for (image=(*images); image != (Image *) NULL; image=GetNextImageInList(image))
  {
    new_image = oogbox (image, &ob, exception);
    if (!new_image) return (-1);

    ReplaceImageInList(&image,new_image);
    *images=GetFirstImageInList(image);
  }

  return(MagickImageFilterSignature);
}

paintpatches.c

/*
   Reference: http://im.snibgo.com/paintpatches.htm

   Updated:
     3-April-2018 for v7.0.7-28
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <math.h>
#include <ctype.h>
#include "vsn_defines.h"
#include "chklist.h"

#include "usercoord.inc"
#include "rmsealpha.inc"
#include "resetpage.inc"

#define VERSION "paintpatches v1.0  Copyright (c) 2017 Alan Gibson"

// FIXME: composites shouldn't clamp.


typedef enum {
  cfUndef,
  cfAvg,
  cfCent,
  cfGradient,
  cfPalette,
  cfSample
} colFromT;

typedef enum {
  psRect,
  psEllipse,
  psMin
} patchShapeT;


typedef struct {
  int
    precision,
    verbose;

  Image
    *master,
    *canvas,
    *saliency, // grayscale: white=important, black=unimportant
    *samples,
    *subsrch;

  int
    patchWi,
    patchHt,
    maxIter;

  UserCoordXyT
    patchDims;

// patch width and height, min and max

  double
    patchOpacity, // 0.0 to 1.0.
    multSal,
    wrngThresh,
    adjLcComp,
    adjLcPat,
    featherEdges;

  colFromT
    colFrom;

  patchShapeT
    patchShape;

  MagickBooleanType
    debug,
    setRectCanv,
    hotSpot,
    doMebc;

  char
    *frameName;

  ssize_t
    wrngX,
    wrngY;

  double
    wrongness;

  int
    frameNum;

  RmseAlphaT
    rmseAlpha;
} paintpatchesT;

static void usage (void)
{
  printf ("Usage: -process 'paintpatches [OPTION]...'\n");
  printf ("Paint an image with patches.\n");
  printf ("\n");
  printf ("  c,  colFrom string       'Avg', 'Cent', 'Gradient', 'Palette' or 'Sample'\n");
  printf ("  ps, patchShape string    'Rect', 'Ellipse' or 'Min'\n");
  printf ("  pd, patchDims W,H        maximum patch dimensions\n");
  printf ("  o,  opacity number       0.0 to 1.0\n");
  printf ("  ms, multSal number       multiplier for saliency 0.0 to 1.0\n");
  printf ("  wt, wrngThresh number    wrongness threshold 0.0 to 1.0\n");
  printf ("  ac, adjLcComp number     adjust lightness & contrast for comparisons\n");
  printf ("  ap, adjLcPat number      adjust lightness & contrast of patches\n");
  printf ("  fe, feather number       feather patch edges (blur sigma)\n");
  printf ("  x,  maxIter integer      maximum iterations\n");
  printf ("  m,  mebc                 do minimum error boundary cuts\n");
  printf ("  rc, rectCanv             set rectangle to canvas\n");
  printf ("  hs, hotSpot              hotspot only (for ps Min)\n");
  printf ("  w,  write filename       write iterations eg frame_%%06d.png\n");
  printf ("  d,  debug                write debugging information\n");
  printf ("  v,  verbose              write text information\n");
  printf ("  v2, verbose2             write more text information\n");
  printf ("      version              write version information to stdout\n");
  printf ("\n");
}
// FIXME: option for centre of interest: generates saliency map.

static MagickBooleanType IsArg (char * pa, char * ShtOpt, char * LongOpt)
{
  // LocaleCompare is not case-sensitive,
  // so we use strcmp for the short option.

  if ((strcmp(pa, ShtOpt)==0) || (LocaleCompare(pa, LongOpt)==0))
    return MagickTrue;

  return MagickFalse;
}

#define NEXTARG \
  if (i < argc-1) i++; \
  else { \
    fprintf (stderr, "paintpatches: ERROR: [%s] needs argument\n", pa); \
    status = MagickFalse; \
  }

static MagickBooleanType menu(
  const int argc,
  const char **argv,
  paintpatchesT * ppp
)
// Returns MagickTrue if okay.
{
  int
    i;

  MagickBooleanType
    status = MagickTrue;

  ppp->master = ppp->canvas = ppp->saliency
    = ppp->samples = ppp->subsrch = NULL;

  ppp->verbose = 0;
  ppp->debug = MagickFalse;
  ppp->patchOpacity = 1.0;
  ppp->multSal = 0.5;
  ppp->wrngThresh = 0.1;
  ppp->adjLcComp = 0.0;
  ppp->adjLcPat = 0.0;
  ppp->featherEdges = 0.0;
  ppp->colFrom = cfUndef;
  ppp->patchShape = psRect;
  ppp->doMebc = MagickFalse;
  ppp->setRectCanv = MagickFalse;
  ppp->hotSpot = MagickFalse;
  ppp->patchWi = 50;
  ppp->patchHt = 50;
  ppp->maxIter = 1000;
  ppp->frameName = NULL;
  ppp->frameNum = 0;
  if (!ParseXy ("40c,40c", &ppp->patchDims)) return MagickFalse;
    // FIXME: If patchDims not specified, default according to mebc etc?

  char ** pargv = (char **)argv;

  for (i=0; i < argc; i++) {
    char * pa = (char *)argv[i];

    if (IsArg (pa, "c", "colFrom")==MagickTrue) {
      NEXTARG;
      if (LocaleCompare(argv[i], "avg")==0) ppp->colFrom = cfAvg;
      else if (LocaleCompare(argv[i], "cent")==0) ppp->colFrom = cfCent;
      else if (LocaleCompare(argv[i], "gradient")==0) ppp->colFrom = cfGradient;
      else if (LocaleCompare(argv[i], "palette")==0) ppp->colFrom = cfPalette;
      else if (LocaleCompare(argv[i], "sample")==0) ppp->colFrom = cfSample;
      else {
        fprintf (stderr, "kcluster: ERROR: unknown colFrom [%s]\n", argv[i]);
        status = MagickFalse;
      }
    } else if (IsArg (pa, "ps", "patchShape")==MagickTrue) {
      NEXTARG;
      if (LocaleCompare(argv[i], "rect")==0) ppp->patchShape = psRect;
      else if (LocaleCompare(argv[i], "ellipse")==0) ppp->patchShape = psEllipse;
      else if (LocaleCompare(argv[i], "min")==0) ppp->patchShape = psMin;
      else {
        fprintf (stderr, "kcluster: ERROR: unknown patchShape [%s]\n", argv[i]);
        status = MagickFalse;
      }
    } else if (IsArg (pa, "o", "opacity")==MagickTrue) {
      NEXTARG;
      ppp->patchOpacity = atof (argv[i]);
    } else if (IsArg (pa, "ms", "multSal")==MagickTrue) {
      NEXTARG;
      ppp->multSal = atof (argv[i]);
    } else if (IsArg (pa, "wt", "wrngThresh")==MagickTrue) {
      NEXTARG;
      ppp->wrngThresh = atof (argv[i]);
    } else if (IsArg (pa, "ac", "adjLcComp")==MagickTrue) {
      NEXTARG;
      ppp->adjLcComp = atof (argv[i]);
    } else if (IsArg (pa, "ap", "adjLcPat")==MagickTrue) {
      NEXTARG;
      ppp->adjLcPat = atof (argv[i]);
    } else if (IsArg (pa, "fe", "feather")==MagickTrue) {
      NEXTARG;
      ppp->featherEdges = atof (argv[i]);
    } else if (IsArg (pa, "pd", "patchdims")==MagickTrue) {
      NEXTARG;
      if (!ParseXy (argv[i], &ppp->patchDims)) {
        fprintf (stderr, "kcluster: ERROR: bad XY [%s]\n", argv[i]);
        status = MagickFalse;
      }
    } else if (IsArg (pa, "x", "maxiter")==MagickTrue) {
      NEXTARG;
      ppp->maxIter = atoi (argv[i]);
    } else if (IsArg (pa, "m", "mebc")==MagickTrue) {
      ppp->doMebc = MagickTrue;
    } else if (IsArg (pa, "rc", "rectCanv")==MagickTrue) {
      ppp->setRectCanv = MagickTrue;
    } else if (IsArg (pa, "hs", "hotSpot")==MagickTrue) {
      ppp->hotSpot = MagickTrue;
    } else if (IsArg (pa, "w", "write")==MagickTrue) {
      NEXTARG;
      ppp->frameName = pargv[i];
    } else if (IsArg (pa, "d", "debug")==MagickTrue) {
      ppp->debug = MagickTrue;
    } else if (IsArg (pa, "v", "verbose")==MagickTrue) {
      ppp->verbose = 1;
    } else if (IsArg (pa, "v2", "verbose2")==MagickTrue) {
      ppp->verbose = 2;
    } else if (IsArg (pa, "version", "version")==MagickTrue) {
      fprintf (stdout, "%s\n", VERSION);
    } else {
      fprintf (stderr, "paintpatches: ERROR: unknown option [%s]\n", pa);
      status = MagickFalse;
    }
  }

  if (ppp->verbose) {
    fprintf (stderr, "paintpatches options: ");
    if (ppp->verbose==1)    fprintf (stderr, " verbose");
    if (ppp->verbose==2)    fprintf (stderr, " verbose2");

    fprintf (stderr, "  colFrom ");
    switch (ppp->colFrom) {
      case cfUndef:
        fprintf (stderr, "Undefined");
        break;
      case cfAvg:
        fprintf (stderr, "Avg");
        break;
      case cfCent:
        fprintf (stderr, "Cent");
        break;
      case cfGradient:
        fprintf (stderr, "Gradient");
        break;
      case cfPalette:
        fprintf (stderr, "Palette");
        break;
      case cfSample:
        fprintf (stderr, "Sample");
        break;
    }
    fprintf (stderr, "  patchShape ");
    switch (ppp->patchShape) {
      case psRect:
        fprintf (stderr, "Rect");
        break;
      case psEllipse:
        fprintf (stderr, "Ellipse");
        break;
      case psMin:
        fprintf (stderr, "Min");
        break;
    }

    fprintf (stderr, "  patchDims ");
    WrUserCoord (&ppp->patchDims);

    fprintf (stderr, "  opacity %g", ppp->patchOpacity);
    fprintf (stderr, "  multSal %g", ppp->multSal);
    fprintf (stderr, "  wrngThresh %g", ppp->wrngThresh);
    fprintf (stderr, "  adjLcComp %g", ppp->adjLcComp);
    fprintf (stderr, "  adjLcPat %g", ppp->adjLcPat);
    fprintf (stderr, "  maxIter %i", ppp->maxIter);
    fprintf (stderr, "  featherEdges %g", ppp->featherEdges);

    if (ppp->doMebc) fprintf (stderr, "  mebc ");
    if (ppp->setRectCanv) fprintf (stderr, "  rectCanv ");
    if (ppp->hotSpot) fprintf (stderr, "  hotSpot ");
    if (ppp->frameName) fprintf (stderr, "  write %s", ppp->frameName);

    if (ppp->debug) fprintf (stderr, "  debug ");

    fprintf (stderr, "\n");
  }

  if (status == MagickFalse)
    usage ();

  return (status);
}


static MagickBooleanType SameSize (Image * i0, Image * i1)
{
  if (!i1) return MagickFalse;
  return ((i0->columns == i1->columns) && (i0->rows == i1->rows));
}

static void WrImage (char * title, Image * img)
{
  fprintf (stderr, "%s: ", title);

  if (img) {
    if (img->filename)
      fprintf (stderr, "[%s] ", img->filename);

    fprintf (stderr, "%lix%li\n", img->columns, img->rows);
    chkentry ("WrImage", &img);
  } else {
    fprintf (stderr, "none\n");
  }
}


static MagickStatusType WriteFrame (
  paintpatchesT * ppp,
  Image * img,
  ExceptionInfo *exception)
{
  if (!ppp->frameName) return MagickTrue;

  ImageInfo *ii = AcquireImageInfo ();

  Image * copy_img = CloneImage (img, 0, 0, MagickTrue, exception);
  if (!copy_img) return MagickFalse;

  copy_img->scene = ppp->frameNum++;

  CopyMagickString (copy_img->filename, ppp->frameName, MaxTextExtent);

  MagickBooleanType okay = WRITEIMAGE(ii, copy_img, exception);

  DestroyImageList (copy_img);

  ii = DestroyImageInfo (ii);

  return okay;
}


static MagickBooleanType MostWrong (
  paintpatchesT * ppp,
  ExceptionInfo *exception)

// FIXME: Or least wrong first? To get the broad-brush stuff first?
{
  Image * wrng_img = CloneImage (ppp->master, 0, 0, MagickTrue, exception);
  if (!wrng_img) {
    fprintf (stderr, "MostWrong: clone failed\n");
    return MagickFalse;
  }

  // FIXME: If we have adjusted patches,
  // we should do likewise for the comparison.

  if (!COMPOSITE(wrng_img, DifferenceCompositeOp, ppp->canvas, 0,0, exception))
  {
    fprintf (stderr, "MostWrong: composite failed\n");
    return MagickFalse;
  }

// Blurring removes outliers, but it's expensive.
//
//  Image * wrng_img2 = BlurImage (wrng_img, 0, 3, exception);
//  if (!wrng_img2) return MagickFalse;
//  ReplaceImageInList (&wrng_img, wrng_img2);

  if (ppp->verbose > 1) WrImage ("wrong", wrng_img);

  MagickBooleanType
    status;

  CacheView * wrng_view = AcquireVirtualCacheView (wrng_img, exception);
  CacheView * slnc_view = AcquireVirtualCacheView (ppp->saliency, exception);

  status = MagickTrue;

  double val;
  ppp->wrongness = 99e9; // for most wrong last.
  ppp->wrongness = 0;    // for most wrong first.
  ppp->wrngX = ppp->wrngY = 0;

  ssize_t y;
  for (y=0; y < (ssize_t) wrng_img->rows; y++)
  {
    ssize_t x;

    if (status == MagickFalse) continue;

    VIEW_PIX_PTR const *wp = GetCacheViewVirtualPixels(
      wrng_view,0,y,wrng_img->columns,1,exception);
    if (!wp) status=MagickFalse;

    VIEW_PIX_PTR const *sp = GetCacheViewVirtualPixels(
      slnc_view,0,y,ppp->saliency->columns,1,exception);
    if (!sp) status=MagickFalse;

    for (x=0; x < (ssize_t) wrng_img->columns; x++) {
      val = GetPixelIntensity (wrng_img, wp)
            * GET_PIXEL_RED (ppp->saliency, sp) / (double)QuantumRange;

      // '<' for most wrong first
      if (ppp->wrongness < val) {
        ppp->wrongness = val;
        ppp->wrngX = x;
        ppp->wrngY = y;
        if (val < 0) {
          fprintf (stderr, "val<0 %li,%li %g %g\n",
            x, y,
            (double)GetPixelIntensity (wrng_img, wp),
            (double)GET_PIXEL_RED (ppp->saliency, sp));
        }
      }
      wp += Inc_ViewPixPtr (wrng_img);
      sp += Inc_ViewPixPtr (ppp->saliency);
    }
  }

  ppp->wrongness /= QuantumRange;

  // FIXME: for ordinary images, how can wrongness be negative?
  if (ppp->wrongness < 0) ppp->wrongness = 0;
  if (ppp->wrongness > 1) ppp->wrongness = 1;

  slnc_view = DestroyCacheView (slnc_view);
  wrng_view = DestroyCacheView (wrng_view);

  wrng_img = DestroyImage (wrng_img);

  return MagickTrue;
}


static double GetSalientG (
  paintpatchesT * ppp,
  ssize_t x,
  ssize_t y,
  ExceptionInfo *exception)
{
  CacheView * slnc_view = AcquireVirtualCacheView (ppp->saliency, exception);

  VIEW_PIX_PTR const *sp = GetCacheViewVirtualPixels(
    slnc_view,x,y,1,1,exception);
  if (!sp) return 0;

  //double val = GetPixelIntensity (ppp->saliency, sp) / (double)QuantumRange;
  double val = GET_PIXEL_GREEN (ppp->saliency, sp) / (double)QuantumRange;

  slnc_view = DestroyCacheView (slnc_view);

  // FIXME: For ordinary images, how can this be negative?
  if (val < 0) val = 0;
  if (val > 1) val = 1;

  if (ppp->verbose > 1) {
    fprintf (stderr, "GetSalientG %li,%li %g\n",
      x, y,
      val);
  }

  return val;
}

static MagickBooleanType GetRect (
  paintpatchesT *ppp,
  RectangleInfo *prect,
  ExceptionInfo *exception)
// Gets a rectangle that includes ppp->wrngX and ppp->wrngY.
// Rectangle may straddle canvas edges.
{
  double slntFact = 1.0 - GetSalientG (ppp, ppp->wrngX, ppp->wrngY, exception);

  //if (slntFact==0) return MagickFalse; // FIXME: so white saliency does nothing?!?!?

  // FIXME: we could vary the width and height randomly.

  prect->x = floor (ppp->wrngX - slntFact * ppp->patchWi/2.0 + 0.5);
  prect->y = floor (ppp->wrngY - slntFact * ppp->patchHt/2.0 + 0.5);
  prect->width  = floor (slntFact * ppp->patchWi + 0.5);
  prect->height = floor (slntFact * ppp->patchHt + 0.5);
  if (prect->width  < 1) prect->width  = 1;
  if (prect->height < 1) prect->height = 1;

  if (ppp->verbose > 1) {
    fprintf (stderr, "GetRect %lix%li+%li+%li  slntFact=%g\n",
      prect->width, prect->height, prect->x, prect->y, slntFact);
  }

  return MagickTrue;
}


static MagickBooleanType SetImgAlpha (
  paintpatchesT * ppp,
  Image * img,
  ExceptionInfo *exception)
{
  if (ppp->patchOpacity == 1.0) return MagickTrue;

  CacheView * img_view = AcquireAuthenticCacheView (img, exception);

  // FIXME: enable alpha for the image?

  double alph = ppp->patchOpacity * QuantumRange;

  MagickBooleanType okay = MagickTrue;

  ssize_t y;
#if defined(MAGICKCORE_OPENMP_SUPPORT)
  #pragma omp parallel for schedule(static,4) \
    shared(okay) \
    MAGICK_THREADS(img,img,img->rows,1)
#endif
  for (y = 0; y < img->rows; y++) {
    ssize_t x;

    if (!okay) continue;

    VIEW_PIX_PTR *sp = GetCacheViewAuthenticPixels(
      img_view,0,y,img->columns,1,exception);
    if (!sp) okay = MagickFalse;

    for (x = 0; x < img->columns; x++) {
      SET_PIXEL_ALPHA (img, alph, sp);
      sp += Inc_ViewPixPtr (img);
    }
    if (!SyncCacheViewAuthenticPixels(img_view,exception)) {
      okay = MagickFalse;
    }
  }

  if (!okay) return MagickFalse;

  img_view = DestroyCacheView (img_view);

  return MagickTrue;
}

static MagickBooleanType EllipseOnly (
  paintpatchesT * ppp,
  Image * pat_img,
  ExceptionInfo *exception)
// Makes pixels outside an ellipse transparent.
{
  CacheView * img_view = AcquireAuthenticCacheView (pat_img, exception);

  // FIXME: enable alpha for the image?

  // Quick and simple anti-aliasing.

  if (pat_img->columns <= 2 || pat_img->rows <= 2) return MagickTrue;

  // FIXME: following isn't symmetrical.

  double a = pat_img->columns / 2.0;
  double b = pat_img->rows / 2.0;

  double a2 = a*a;
  double b2 = b*b;
  double a2b2 = a2*b2;

  MagickBooleanType okay = MagickTrue;

  ssize_t y;
#if defined(MAGICKCORE_OPENMP_SUPPORT)
  #pragma omp parallel for schedule(static,4) \
    shared(okay) \
    MAGICK_THREADS(pat_img,pat_img,pat_img->rows,1)
#endif
  for (y = 0; y < pat_img->rows; y++) {
    ssize_t x;

    VIEW_PIX_PTR *sp = GetCacheViewAuthenticPixels(
      img_view,0,y,pat_img->columns,1,exception);
    if (!sp) okay = MagickFalse;

    if (!okay) continue;

    double a2y2   = a2 * (y-b) * (y-b);
    double a2y2m1 = a2 * (y-b-0.5) * (y-b-0.5);
    double a2y2p1 = a2 * (y-b+0.5) * (y-b+0.5);

    for (x = 0; x < pat_img->columns; x++) {
      double b2x2   = b2 * (x-a) * (x-a);
      double b2x2m1 = b2 * (x-a-0.5) * (x-a-0.5);
      double b2x2p1 = b2 * (x-a+0.5) * (x-a+0.5);
      int nIn = 0;

      if (b2x2m1 + a2y2m1 < a2b2) nIn++;
      if (b2x2   + a2y2m1 < a2b2) nIn++;
      if (b2x2p1 + a2y2m1 < a2b2) nIn++;

      if (b2x2m1 + a2y2   < a2b2) nIn++;
      if (b2x2   + a2y2   < a2b2) nIn++;
      if (b2x2p1 + a2y2   < a2b2) nIn++;

      if (b2x2m1 + a2y2p1 < a2b2) nIn++;
      if (b2x2   + a2y2p1 < a2b2) nIn++;
      if (b2x2p1 + a2y2p1 < a2b2) nIn++;

      if (nIn < 9) {
        SET_PIXEL_ALPHA (pat_img,
                         nIn / 9.0 * GET_PIXEL_ALPHA(pat_img,sp),
                         sp);
      }
      sp += Inc_ViewPixPtr (pat_img);
    }
    if (SyncCacheViewAuthenticPixels(img_view,exception) == MagickFalse)
      okay = MagickFalse;
  }

  if (!okay) return MagickFalse;

  img_view = DestroyCacheView (img_view);

  return MagickTrue;
}


static Image * SearchSamples (
  paintpatchesT * ppp,
  Image *pat_image,
  ExceptionInfo *exception)
// Search samples image for best match to pat_image.
// Creates and returns an image, which caller should destroy.
{
  // Note: InitRmseAlpha/DeInit are at a higher level.

  Image * srch = ppp->subsrch;
  if (!srch) srch = ppp->samples;

  ppp->rmseAlpha.adjustMeanSd = ppp->adjLcComp;
  ppp->rmseAlpha.saveInpScales = MagickTrue;
  ppp->rmseAlpha.do_verbose = (ppp->verbose > 1);

  if (!subRmseAlphaMS (&ppp->rmseAlpha, srch, pat_image, exception)) {
    fprintf (stderr, "SearchSamples: subRmseAlphaMS failed\n");
    return NULL;
  }
  if (ppp->verbose > 1) {
    fprintf (stderr, "SearchSamples: found %g at %li,%li\n",
      ppp->rmseAlpha.score, ppp->rmseAlpha.solnX, ppp->rmseAlpha.solnY);
  }

  RectangleInfo fndRect;
  fndRect.x = ppp->rmseAlpha.solnX;
  fndRect.y = ppp->rmseAlpha.solnY;
  fndRect.width  = pat_image->columns;
  fndRect.height = pat_image->rows;

  // FIXME: Option to get larger patch, same size as canvas.

  if (ppp->verbose > 1) {
    fprintf (stderr, "fndRect %lix%li+%li+%li  canvas %lix%li\n",
      fndRect.width, fndRect.height, fndRect.x, fndRect.y,
      ppp->canvas->columns, ppp->canvas->rows);
  }

  if (ppp->setRectCanv) {
    ssize_t dx = ppp->canvas->columns - fndRect.width;
    ssize_t dy = ppp->canvas->rows - fndRect.height;
    fndRect.x -= dx/2;
    fndRect.y -= dy/2;
    if (fndRect.x < 0) fndRect.x = 0;
    if (fndRect.y < 0) fndRect.y = 0;
    fndRect.width = ppp->canvas->columns;
    fndRect.height = ppp->canvas->rows;
    if (fndRect.x + fndRect.width > ppp->samples->columns) fndRect.x = ppp->samples->columns - fndRect.width;
    if (fndRect.y + fndRect.height > ppp->samples->rows) fndRect.y = ppp->samples->rows - fndRect.height;
  }

  if (ppp->verbose > 1) {
    fprintf (stderr, "fndRect %lix%li+%li+%li\n",
      fndRect.width, fndRect.height, fndRect.x, fndRect.y);
  }

  Image * fnd_img = CropImage (ppp->samples, &fndRect, exception);
  if (!fnd_img) {
    fprintf (stderr, "SearchSamples crop failed\n");
    return NULL;
  }

  return fnd_img;
}


static void ClipPatchRect (
  paintpatchesT * ppp,
  RectangleInfo * patchRect,
  RectangleInfo * clippedRect,
  int *dx,
  int *dy)
{
  *clippedRect = *patchRect;

  if (clippedRect->x < 0) {
    *dx = -clippedRect->x;
    clippedRect->width += clippedRect->x;
    clippedRect->x = 0;
  } else {
    *dx = 0;
  }

  if (clippedRect->y < 0) {
    *dy = -clippedRect->y;
    clippedRect->height += clippedRect->y;
    clippedRect->y = 0;
  } else {
    *dy = 0;
  }

  if (clippedRect->width + clippedRect->x > ppp->canvas->columns)
    clippedRect->width = ppp->canvas->columns - clippedRect->x;
  if (clippedRect->height + clippedRect->y > ppp->canvas->rows)
    clippedRect->height = ppp->canvas->rows - clippedRect->y;
}

static MagickBooleanType AdjLcFndToPat (
  paintpatchesT * ppp,
  Image * fnd_pat,
  Image * pat_img,
  ExceptionInfo *exception)
// Adjust lightness and contrast of found patch to match pat_img.
{
  MeanSdT msFnd, msPat;

  if (!CalcMeanSdImg (fnd_pat, &msFnd, exception)) return MagickFalse;
  if (!CalcMeanSdImg (pat_img, &msPat, exception)) return MagickFalse;

  GainBiasT gb;
  MeanSdToGainBias (ppp->adjLcPat, &msFnd, &msPat, &gb);

  // FIXME: apply the gain and bias to fnd_pat.

  CacheView * img_view = AcquireAuthenticCacheView (fnd_pat, exception);

  MagickBooleanType okay = MagickTrue;

  ssize_t y;
#if defined(MAGICKCORE_OPENMP_SUPPORT)
  #pragma omp parallel for schedule(static,4) \
    shared(okay) \
    MAGICK_THREADS(fnd_pat,pat_img,fnd_pat->rows,1)
#endif
  for (y = 0; y < fnd_pat->rows; y++) {
    ssize_t x;

    VIEW_PIX_PTR *sp = GetCacheViewAuthenticPixels(
      img_view,0,y,fnd_pat->columns,1,exception);
    if (!sp) okay = MagickFalse;

    if (!okay) continue;

    for (x = 0; x < fnd_pat->columns; x++) {
      SET_PIXEL_RED   (fnd_pat,
                       GET_PIXEL_RED(pat_img,sp)*gb.gainR+gb.biasR,
                       sp);
      SET_PIXEL_GREEN (fnd_pat,
                       GET_PIXEL_GREEN(pat_img,sp)*gb.gainG+gb.biasG,
                       sp);
      SET_PIXEL_BLUE  (fnd_pat,
                       GET_PIXEL_BLUE(pat_img,sp)*gb.gainB+gb.biasB,
                       sp);
      sp += Inc_ViewPixPtr (fnd_pat);
    }
    if (SyncCacheViewAuthenticPixels(img_view,exception) == MagickFalse)
      okay = MagickFalse;
  }

  if (!okay) return MagickFalse;

  img_view = DestroyCacheView (img_view);

  return MagickTrue;
}


static void SetRectCanv (
  paintpatchesT * ppp,
  RectangleInfo * rect
)
{
  rect->x = rect->y = 0;
  rect->width  = ppp->canvas->columns;
  rect->height = ppp->canvas->rows;
}

static Image *GetPatchImg (
  paintpatchesT * ppp,
  RectangleInfo * patchRect,
  ExceptionInfo *exception)
// Creates and returns an image, which caller should destroy.
{
  if (ppp->verbose > 1)
    fprintf (stderr, "GetPatchImg: %lix%li+%li+%li\n",
      patchRect->width, patchRect->height, patchRect->x, patchRect->y);

  Image * pat_img = CropImage (ppp->master, patchRect, exception);
  if (!pat_img) {
    fprintf (stderr, "GetPatchImg: crop failed\n");
    return NULL;
  }
  ResetPage (pat_img);
  // Caution: this may be smaller than requested, when at edge of ppp->master.

  if (ppp->colFrom == cfAvg) {
    Image * pat_img2 = ScaleImage (pat_img, 1, 1, exception);
    if (!pat_img2) return NULL;
    ReplaceImageInList (&pat_img, pat_img2);

    if (ppp->setRectCanv) SetRectCanv (ppp, patchRect);
    pat_img2 = SampleImage (pat_img, patchRect->width,
      patchRect->height, exception);
    if (!pat_img2) return NULL;
    ReplaceImageInList (&pat_img, pat_img2);
  } else if (ppp->colFrom == cfCent) {
    RectangleInfo rect;
    rect.x = patchRect->width / 2;
    rect.y = patchRect->height / 2;
    rect.width  = 1;
    rect.height = 1;
    // If patch overlaps edge, we may not have the centre point.
    if (rect.x < 0) rect.x = 0;
    if (rect.y < 0) rect.y = 0;
    if (rect.x >= pat_img->columns) rect.x = pat_img->columns-1;
    if (rect.y >= pat_img->rows)    rect.y = pat_img->rows-1;

    Image * pat_img2 = CropImage (pat_img, &rect, exception);
    if (!pat_img2) {
      fprintf (stderr, "GetPatchImg: pat_img2 fail: %lix%li+%li+%li\n",
        rect.width, rect.height, rect.x, rect.y);
      return NULL;
    }
    ResetPage (pat_img2);
    ReplaceImageInList (&pat_img, pat_img2);

    if (ppp->setRectCanv) SetRectCanv (ppp, patchRect);
    pat_img2 = SampleImage (pat_img, patchRect->width,
      patchRect->height, exception);
    if (!pat_img2) return NULL;
    ReplaceImageInList (&pat_img, pat_img2);
  } else if (ppp->colFrom == cfSample) {

    if (ppp->setRectCanv) SetRectCanv (ppp, patchRect);
    Image * fnd_pat = SearchSamples (ppp, pat_img, exception);
    if (!fnd_pat) {
      return NULL;
    }

    // FIXME: better place for next?
    if (ppp->adjLcPat > 0.0) {
      if (!AdjLcFndToPat (ppp, fnd_pat, pat_img, exception)) return NULL;
    }

    ReplaceImageInList (&pat_img, fnd_pat);
    // What if the image is smaller than requested?
    // Answer: extent.
    if (pat_img->columns < patchRect->width
     || pat_img->rows < patchRect->height)
    {
      RectangleInfo extRect;
      int dx, dy;
      ClipPatchRect (ppp, patchRect, &extRect, &dx, &dy);
      // Ignore calculated extRect; we want only dx and dy.
      extRect.width  = patchRect->width;
      extRect.height = patchRect->height;
      extRect.x = -dx;
      extRect.y = -dy;
      Image * pat_img2 = ExtentImage (pat_img, &extRect, exception);
      if (!pat_img2) return NULL;
      ReplaceImageInList (&pat_img, pat_img2);
    }
  } else {

    // FIXME: For now, get gradient from master.
    // Gradient: chop to get the 4 corners, and resize.

    // If too small to chop and resize, don't.

    if (patchRect->width > 2 && patchRect->height > 2) {
      RectangleInfo chpRect;
      chpRect.x = chpRect.y = 1;
      chpRect.width  = pat_img->columns  - 2;
      chpRect.height = pat_img->rows - 2;

      Image * pat_img2 = ChopImage (pat_img, &chpRect, exception);
      if (!pat_img2) return NULL;
      ReplaceImageInList (&pat_img, pat_img2);

      if (pat_img->columns != 2 || pat_img->rows != 2) {
        fprintf (stderr, "GetPatchImg: after chop %lix%li\n",
          pat_img->columns, pat_img->rows);
      }

      if (ppp->setRectCanv) SetRectCanv (ppp, patchRect);
#if IMV6OR7==6
      pat_img2 = ResizeImage (pat_img, patchRect->width,
        patchRect->height, UndefinedFilter, 1.0, exception);
#else
      pat_img2 = ResizeImage (pat_img, patchRect->width,
        patchRect->height, UndefinedFilter, exception);
#endif
      if (!pat_img2) return NULL;
      ReplaceImageInList (&pat_img, pat_img2);
    }
  }

  // FIXME: possibly adjust to match pat_img.
  if (ppp->adjLcPat > 0.0) {

    // Bugger, we've already replaced it.
    //Image * pat_img2 = AdjLcFndToPat (ppp, ??, ??, exception);
  }

#if IMV6OR7==6
  SetImageAlphaChannel (pat_img, SetAlphaChannel);
#else
  SetImageAlphaChannel (pat_img, SetAlphaChannel, exception);
#endif

  if (!SetImgAlpha (ppp, pat_img, exception)) return NULL;

  if (ppp->patchShape == psEllipse) {
    if (!EllipseOnly (ppp, pat_img, exception)) return NULL;
  }

  return pat_img;
}


static MagickBooleanType PaintPatchImg (
  paintpatchesT * ppp,
  Image * dest_img,
  RectangleInfo * patchRect,
  Image * pat_img,
  ExceptionInfo *exception)
{
  if (!COMPOSITE(dest_img,
         OverCompositeOp, pat_img, patchRect->x, patchRect->y, exception))
  {
    fprintf (stderr, "PaintPatch: composite failed\n");
    return MagickFalse;
  }
  return MagickTrue;
}

static MagickBooleanType PaintPatchCanvas (
  paintpatchesT * ppp,
  RectangleInfo * patchRect,
  Image * pat_img,
  ExceptionInfo *exception)
{
  return PaintPatchImg (ppp, ppp->canvas, patchRect, pat_img, exception);
}


static MagickBooleanType SetOneNonSalient (
  paintpatchesT * ppp,
  ssize_t x,
  ssize_t y,
  ExceptionInfo *exception)
{
  // FIXME: This could draw the patch shape, in black, on the saliency image.

  CacheView * slnc_view = AcquireAuthenticCacheView (ppp->saliency, exception);

  VIEW_PIX_PTR *sp = GetCacheViewAuthenticPixels(
    slnc_view,x,y,1,1,exception);
  if (!sp) return MagickFalse;

  SET_PIXEL_RED (ppp->saliency, 0, sp);
//  SET_PIXEL_GREEN (ppp->saliency, 0, sp);
//  SET_PIXEL_BLUE (ppp->saliency, 0, sp);

  if (SyncCacheViewAuthenticPixels(slnc_view,exception) == MagickFalse)
    return MagickFalse;

  slnc_view = DestroyCacheView (slnc_view);

  return MagickTrue;
}

static MagickBooleanType MultOneSalient (
  paintpatchesT * ppp,
  ssize_t x,
  ssize_t y,
  double fact,
  ExceptionInfo *exception)
{
  CacheView * slnc_view = AcquireAuthenticCacheView (ppp->saliency, exception);

  VIEW_PIX_PTR *sp = GetCacheViewAuthenticPixels(
    slnc_view,x,y,1,1,exception);
  if (!sp) return MagickFalse;

  SET_PIXEL_RED (ppp->saliency, fact * GET_PIXEL_RED(ppp->saliency,sp), sp);
//  SET_PIXEL_GREEN (ppp->saliency, fact * GET_PIXEL_GREEN(ppp->saliency,sp), sp);
//  SET_PIXEL_BLUE (ppp->saliency, fact * GET_PIXEL_BLUE(ppp->saliency,sp), sp);

  if (SyncCacheViewAuthenticPixels(slnc_view,exception) == MagickFalse)
    return MagickFalse;

  slnc_view = DestroyCacheView (slnc_view);

  return MagickTrue;
}

static MagickBooleanType MultSalientPatch (
  paintpatchesT * ppp,
  RectangleInfo * patchRect,
  Image *pat_img,
  double factor,
  ExceptionInfo *exception)
// Where patch is not totally transparent, multiplies saliency by factor.
{
  CacheView * slnc_view = AcquireAuthenticCacheView (ppp->saliency, exception);
  CacheView * pat_view = AcquireVirtualCacheView (pat_img, exception);

  if (ppp->verbose > 1)
    fprintf (stderr, "MultSalientPatch: rect %lix%li+%li+%li x%g\n",
      patchRect->width, patchRect->height, patchRect->x, patchRect->y,
      factor);

  RectangleInfo clippedRect;
  int dx, dy;
  ClipPatchRect (ppp, patchRect, &clippedRect, &dx, &dy);

  if (ppp->verbose > 1)
    fprintf (stderr, "MultSalientPatch: clpd %lix%li+%li+%li dx=%i dy=%i\n",
      clippedRect.width, clippedRect.height, clippedRect.x, clippedRect.y,
      dx, dy);

  MagickBooleanType okay = MagickTrue;

  ssize_t y;
#if defined(MAGICKCORE_OPENMP_SUPPORT)
  #pragma omp parallel for schedule(static,4) \
    shared(okay) \
    MAGICK_THREADS(pat_img,pat_img,clippedRect.height,1)
#endif
  for (y = clippedRect.y; y < clippedRect.y + clippedRect.height; y++) {
    VIEW_PIX_PTR *sp = GetCacheViewAuthenticPixels(
      slnc_view,clippedRect.x,y,clippedRect.width,1,exception);
    if (!sp) okay = MagickFalse;

    const VIEW_PIX_PTR *pp = GetCacheViewVirtualPixels(
      pat_view,dx,y-clippedRect.y+dy,clippedRect.width,1,exception);
    if (!pp) okay = MagickFalse;


    const double opacThresh = ppp->patchOpacity * 0.5;
    // Patch alpha may have been reduced.
    ssize_t x;
    for (x = 0; x < clippedRect.width; x++) {
      if (GET_PIXEL_ALPHA (pat_img, pp) > opacThresh) {
        SET_PIXEL_RED (ppp->saliency,
                       factor * GET_PIXEL_RED(ppp->saliency,sp),
                       sp);
      }
      sp += Inc_ViewPixPtr (ppp->saliency);
      pp += Inc_ViewPixPtr (pat_img);
    }
    if (SyncCacheViewAuthenticPixels(slnc_view,exception) == MagickFalse)
      okay = MagickFalse;
  }

  if (!okay) return MagickFalse;

  pat_view  = DestroyCacheView (pat_view);
  slnc_view = DestroyCacheView (slnc_view);

  return MagickTrue;
}


static double inline RelColDiff (
  Image * img0,
  Image * img1,
  const VIEW_PIX_PTR *vp0,
  const VIEW_PIX_PTR *vp1)
{
  double dr = GET_PIXEL_RED(img0,vp0)   - GET_PIXEL_RED(img1,vp1);
  double dg = GET_PIXEL_GREEN(img0,vp0) - GET_PIXEL_GREEN(img1,vp1);
  double db = GET_PIXEL_BLUE(img0,vp0)  - GET_PIXEL_BLUE(img1,vp1);

  return (dr*dr + dg+dg + db*db);
}

static double inline SumSq (
  Image * img,
  const VIEW_PIX_PTR *vp)
{
  double dr = GET_PIXEL_RED(img,vp);
  double dg = GET_PIXEL_GREEN(img,vp);
  double db = GET_PIXEL_BLUE(img,vp);

  return (dr*dr + dg+dg + db*db);
}


static MagickBooleanType NearestWhite (
  paintpatchesT * ppp,
  Image * img,
  ssize_t cx,
  ssize_t cy,
  ssize_t *fndX,
  ssize_t *fndY,
  MagickBooleanType * isFound,
  ExceptionInfo *exception)
// Returns the nearest white pixel to cx,cy.
{
  CacheView * img_view = AcquireVirtualCacheView (img, exception);

#define LIMIT (QuantumRange - 1e-6)
//#define EPS 1e-6

  MagickBooleanType okay = MagickTrue, found = MagickFalse;
  double nearDistSq = 0;

  ssize_t y, nearX=-1, nearY=-1;

#if defined(MAGICKCORE_OPENMP_SUPPORT)
  #pragma omp parallel for schedule(static,4) \
    shared(okay) \
    MAGICK_THREADS(img,img,img->rows,1)
#endif
  for (y = 0; y < img->rows; y++) {
    if (!okay) continue;

    double
      dx, dy;

    const VIEW_PIX_PTR *p = GetCacheViewVirtualPixels(
      img_view,0,y,img->columns,1,exception);
    if (!p) okay = MagickFalse;
    ssize_t x;
    for (x = 0; x < img->columns; x++) {

      if (  GET_PIXEL_RED(img,p)   >= LIMIT
         && GET_PIXEL_GREEN(img,p) >= LIMIT
         && GET_PIXEL_BLUE(img,p)  >= LIMIT)
      {
        if (found) {
          dx = cx - x;
          dy = cy - y;
          double DistSq = dx*dx + dy*dy;
          if (nearDistSq > DistSq) {
            nearDistSq = DistSq;
            nearX = x;
            nearY = y;
          }
        } else {
          found = MagickTrue;
          dx = cx - x;
          dy = cy - y;
          nearDistSq = dx*dx + dy*dy;
          nearX = x;
          nearY = y;
        }
      }

      p  += Inc_ViewPixPtr (img);
    }
  }

  img_view = DestroyCacheView (img_view);

  *fndX = nearX;
  *fndY = nearY;
  *isFound = found;

  if (ppp->debug) {
    fprintf (stderr, "NearestWhite: %li,%li => %li,%li\n",
      cx, cy, *fndX, *fndY);
  }

  return okay;
}

static MagickBooleanType OnlyHotspot (
  paintpatchesT * ppp,
  Image * opaq_img,
  ExceptionInfo *exception)
// opaq_img represents required opacity, white = opaque.
// This sets all pixels black, except those connected to wrngX,wrngY.
// If no pixels were white, returns a black image (and MagickTrue).
{
  // Poss flood-fill at hotspot. But where is the hotspot?
  // If we are doing rectCanv, the hotspot is wrngX,wrngY.

  ssize_t hsX, hsY;
  MagickBooleanType isFound;
  if (! NearestWhite (ppp,
          opaq_img, ppp->wrngX, ppp->wrngY,
          &hsX, &hsY, &isFound, exception)) return MagickFalse;

  if (!isFound) return MagickTrue;

//fprintf (stderr, "OnlyHotspot 1 %lix%li @ %li,%li => %li,%li\n",
//  opaq_img->columns, opaq_img->rows,
//  ppp->wrngX, ppp->wrngY, hsX, hsY);

  DrawInfo *draw_info = AcquireDrawInfo();

  PIX_INFO target; // white

#if IMV6OR7==6
  QueryColorDatabase ("gray50", &draw_info->fill, exception);

  GetMagickPixelPacket (opaq_img, &target);
  QueryMagickColor ("white", &target, exception);

  if (!FloodfillPaintImage (
    opaq_img, DefaultChannels, draw_info,
    &target, hsX, hsY, MagickFalse)) return MagickFalse;
#else
  QueryColorCompliance ("gray50", AllCompliance, &draw_info->fill, exception);

  GetPixelInfo (opaq_img, &target);
  QueryColorCompliance ("white", AllCompliance, &target, exception);

  if (!FloodfillPaintImage (
    opaq_img, draw_info,
    &target, hsX, hsY, MagickFalse, exception)) return MagickFalse;
#endif

  draw_info = DestroyDrawInfo(draw_info);

//  if (ppp->debug) {
//    WriteFrame (ppp, opaq_img, exception);
//  }

  // Now change white to black, and gray to white.

  CacheView * opaq_view = AcquireAuthenticCacheView (opaq_img, exception);

  VIEW_PIX_PTR vpBlack, vpWhite;
  SET_PIXEL_RED   (opaq_img, 0, &vpBlack);
  SET_PIXEL_GREEN (opaq_img, 0, &vpBlack);
  SET_PIXEL_BLUE  (opaq_img, 0, &vpBlack);
  SET_PIXEL_RED   (opaq_img, QuantumRange, &vpWhite);
  SET_PIXEL_GREEN (opaq_img, QuantumRange, &vpWhite);
  SET_PIXEL_BLUE  (opaq_img, QuantumRange, &vpWhite);

  const double NearWhite = QuantumRange * 0.9;
  const double NearBlack = QuantumRange * 0.1;

  MagickBooleanType okay = MagickTrue;
  ssize_t y;
#if defined(MAGICKCORE_OPENMP_SUPPORT)
  #pragma omp parallel for schedule(static,4) \
    shared(okay) \
    MAGICK_THREADS(opaq_img,opaq_img,opaq_img->rows,1)
#endif
  for (y = 0; y < opaq_img->rows; y++) {
    if (!okay) continue;

    VIEW_PIX_PTR *pp = GetCacheViewAuthenticPixels(
      opaq_view,0,y,opaq_img->columns,1,exception);
    if (!pp) okay = MagickFalse;

    double val;
    ssize_t x;
    for (x = 0; x < opaq_img->columns; x++) {
      val = GetPixelIntensity (opaq_img, pp);
      if (val > NearWhite) {
        *pp = vpBlack;
      } else if (val > NearBlack) {
        *pp = vpWhite;
      }

      pp  += Inc_ViewPixPtr (opaq_img);
    }

    if (SyncCacheViewAuthenticPixels(opaq_view,exception) == MagickFalse)
      okay = MagickFalse;
  }

  opaq_view = DestroyCacheView (opaq_view);

  return okay;
}

static MagickBooleanType TransNoImp (
  paintpatchesT * ppp,
  RectangleInfo * patchRect,
  Image *pat_img,
  MagickBooleanType * anyOpaque,
  ExceptionInfo *exception)
// Where patch would not improve the canvas, make patch pixels transparent.
// Optionally also: make transparent all pixels
//   except those that would be flood-filled from the hotspot.
// 
{
  if (ppp->verbose > 1) {
    fprintf (stderr, "TransNoImp\n");
    fprintf (stderr, "TransNoImp: rect %lix%li+%li+%li\n",
      patchRect->width, patchRect->height, patchRect->x, patchRect->y);
  }

  // Paste the patch over a trial copy of the canvas.

  Image * trial_canv = CloneImage(ppp->canvas, 0, 0, MagickTrue, exception);
  if (!trial_canv) return MagickFalse;

  if (!PaintPatchImg (ppp, trial_canv, patchRect, pat_img, exception))
    return MagickFalse;

  // Crop master, trial and canvas.
  // Find the differences:
  //   crp_trial := abs (master - trial)
  //   crp_canv  := abs (master - canvas)
  // Possibly blur.
  // The smallest tells us which we should keep.
  // So where crp_trial > crp_canv, make patch transparent.

  RectangleInfo clippedRect;
  int dx, dy;
  ClipPatchRect (ppp, patchRect, &clippedRect, &dx, &dy);

  Image * crp_mast = CropImage (ppp->master, &clippedRect, exception);
  if (!crp_mast) return MagickFalse;
  ResetPage (crp_mast);

  Image * crp_trial = CropImage (trial_canv, &clippedRect, exception);
  if (!crp_trial) return MagickFalse;
  ResetPage (crp_trial);

  Image * crp_canv = CropImage (ppp->canvas, &clippedRect, exception);
  if (!crp_canv) return MagickFalse;
  ResetPage (crp_canv);

  if (!COMPOSITE(crp_trial, DifferenceCompositeOp, crp_mast, 0,0, exception))
  {
    fprintf (stderr, "TransNoImp: composite1 failed\n");
    return MagickFalse;
  }

  if (!COMPOSITE(crp_canv, DifferenceCompositeOp, crp_mast, 0,0, exception))
  {
    fprintf (stderr, "TransNoImp: composite2 failed\n");
    return MagickFalse;
  }

  if (ppp->verbose > 1) {
    fprintf (stderr, "TransNoImp3: rect %lix%li+%li+%li\n",
      clippedRect.width, clippedRect.height, clippedRect.x, clippedRect.y);
  }

  Image * blr_img2 = BlurImage (crp_trial, 0, 3, exception);
  if (!blr_img2) return MagickFalse;
  ReplaceImageInList (&crp_trial, blr_img2);

  blr_img2 = BlurImage (crp_canv, 0, 3, exception);
  if (!blr_img2) return MagickFalse;
  ReplaceImageInList (&crp_canv, blr_img2);

  if (ppp->debug) {
    WrImage ("trial", crp_trial);
    WrImage (" canv", crp_canv);
    WrImage ("  pat", pat_img);
    WriteFrame (ppp, crp_trial, exception);
    WriteFrame (ppp, crp_canv, exception);
  }

  // Write opacity as black or white to a cloned image opaq_img,
  // possibly flood-fill etc,
  // then "-CopyOpacity" that to the patch.

  Image * opaq_img = CloneImage (
      pat_img, pat_img->columns, pat_img->rows, MagickTrue, exception);
  if (!opaq_img) return MagickFalse;
  SetAllOneCol (opaq_img, "White", exception);

  CacheView * crp_trial_view = AcquireVirtualCacheView (crp_trial, exception);
  CacheView * crp_canv_view = AcquireVirtualCacheView (crp_canv, exception);
  CacheView * opaq_view = AcquireAuthenticCacheView (opaq_img, exception);

  if (ppp->verbose > 1) fprintf (stderr, "TransNoImp4 dx=%i dy=%i\n", dx, dy);

  *anyOpaque = MagickFalse;
  MagickBooleanType okay = MagickTrue;
  ssize_t y;
#if defined(MAGICKCORE_OPENMP_SUPPORT)
  #pragma omp parallel for schedule(static,4) \
    shared(okay) \
    MAGICK_THREADS(crp_trial,crp_canv,clippedRect.height,1)
#endif
  for (y = 0; y < clippedRect.height; y++) {
    if (!okay) continue;

    const VIEW_PIX_PTR *ctp = GetCacheViewVirtualPixels(
      crp_trial_view,0,y,clippedRect.width,1,exception);
    if (!ctp) okay = MagickFalse;

    const VIEW_PIX_PTR *ccp = GetCacheViewVirtualPixels(
      crp_canv_view,0,y,clippedRect.width,1,exception);
    if (!ccp) okay = MagickFalse;

    VIEW_PIX_PTR *pp = GetCacheViewAuthenticPixels(
      opaq_view,dx,y+dy,clippedRect.width,1,exception);
    if (!pp) okay = MagickFalse;

    ssize_t x;
    for (x = 0; x < clippedRect.width; x++) {
      // FIXME: We could use a threshold here.
      if (SumSq (crp_trial, ctp) >= SumSq (crp_canv, ccp)) {
        SET_PIXEL_RED   (opaq_img, 0, pp);
        SET_PIXEL_GREEN (opaq_img, 0, pp);
        SET_PIXEL_BLUE  (opaq_img, 0, pp);
      } else {
        *anyOpaque = MagickTrue;
      }

      ctp += Inc_ViewPixPtr (crp_trial);
      ccp += Inc_ViewPixPtr (crp_canv);
      pp  += Inc_ViewPixPtr (opaq_img);
    }
    if (SyncCacheViewAuthenticPixels(opaq_view,exception) == MagickFalse)
      okay = MagickFalse;
  }

  if (!okay) return MagickFalse;

  if (ppp->verbose > 1) fprintf (stderr, "TransNoImp5\n");

  opaq_view      = DestroyCacheView (opaq_view);
  crp_canv_view  = DestroyCacheView (crp_canv_view);
  crp_trial_view = DestroyCacheView (crp_trial_view);

  if (ppp->debug) {
    WriteFrame (ppp, opaq_img, exception);
  }

  if (anyOpaque) {

    if (ppp->hotSpot) {
      if (!OnlyHotspot (ppp, opaq_img, exception)) {
        fprintf (stderr, "TransNoImp: OnlyHotspot failed\n");
        return MagickFalse;
      }
    }

    if (ppp->featherEdges > 0) {
      Image * blr_img2 = BlurImage (opaq_img,
        4*ppp->featherEdges, ppp->featherEdges, exception);
      if (!blr_img2) return MagickFalse;
      ReplaceImageInList (&opaq_img, blr_img2);
    }

    if (ppp->debug) {
      WriteFrame (ppp, opaq_img, exception);
    }

#if IMV6OR7==6
    SetImageAlphaChannel (opaq_img, DeactivateAlphaChannel);
#else
    SetImageAlphaChannel (opaq_img, DeactivateAlphaChannel, exception);
#endif

    if (!COMPOSITE(pat_img, COPY_OPACITY, opaq_img, 0,0, exception))
    {
      fprintf (stderr, "TransNoImp: composite CO failed\n");
      return MagickFalse;
    }

    if (ppp->debug) {
      WriteFrame (ppp, pat_img, exception);
    }
  }

  opaq_img   = DestroyImage (opaq_img);
  crp_canv   = DestroyImage (crp_canv);
  crp_trial  = DestroyImage (crp_trial);
  crp_mast   = DestroyImage (crp_mast);
  trial_canv = DestroyImage (trial_canv);

  return MagickTrue;
}


//#if IMV6OR7==6
//#  define CLAMP(img,exc) ClampImage(img)
//#else
//#  define CLAMP(img,exc) ClampImage(img,exc)
//#endif


// The next function is similar in style to functions in transform.c
// It takes one to five images, and returns one image.
//
static Image *paintpatches (
  Image *image,
  paintpatchesT * ppp,
  ExceptionInfo *exception)
{
  if (image->debug != MagickFalse)
    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);

  if (ppp->verbose) {
    fprintf (stderr, "paintpatches: Input image [%s] %ix%i depth is %i\n",
             image->filename,
             (int)image->columns, (int)image->rows,
             (int)image->depth);
  }

  // Check the images.

  ppp->master = ppp->canvas = ppp->saliency
    = ppp->samples = ppp->subsrch = NULL;

  ppp->master = image;
  if (!ppp->master) return NULL;
  CLAMP (ppp->master, exception);

  Image *i2=NULL, *i3=NULL, *i4=NULL;

  i2 = GetNextImageInList(ppp->master);
  if (i2) i3 = GetNextImageInList(i2);
  if (i3) i4 = GetNextImageInList(i3);

  if (SameSize (ppp->master, i2)) {
    ppp->canvas = i2;
    if (SameSize (ppp->canvas, i3)) {
      ppp->saliency = i3;
      ppp->samples = i4;
    } else {
      ppp->samples = i3;
    }
  } else {
    ppp->samples = i2;
  }
  if (ppp->saliency) {
    CLAMP (ppp->saliency, exception);
  }
  if (ppp->samples) {
    CLAMP (ppp->samples, exception);
    ppp->subsrch = GetNextImageInList(ppp->samples);
  }
  if (ppp->subsrch) {
    CLAMP (ppp->subsrch, exception);
  }

  if (ppp->verbose) {
    WrImage ("master", ppp->master);
    WrImage ("canvas", ppp->canvas);
    WrImage ("saliency", ppp->saliency);
    WrImage ("samples", ppp->samples);
    WrImage ("subsrch", ppp->subsrch);
  }

  if (ppp->master->rows < 2) {
    fprintf (stderr, "Master < 2 rows.\n");
    return NULL;
  }

  if (ppp->samples) {
    if ((ppp->samples->columns <= ppp->master->columns) ||
        (ppp->samples->rows <= ppp->master->rows))
    {
      fprintf (stderr, "Samples smaller than master.\n");
      return NULL;
    }
  }

  if (ppp->samples && ppp->subsrch) {
    if ((ppp->samples->columns != ppp->subsrch->columns) ||
        (ppp->samples->rows != ppp->subsrch->rows))
    {
      fprintf (stderr, "Samples and subsrch are different sizes.\n");
      return NULL;
    }
  }

  if (!SetNoPalette (ppp->master, exception))
    return (Image *)NULL;

  if (ppp->canvas) {
    if (!SetNoPalette (ppp->canvas, exception)) return (Image *)NULL;
  } else {
    if (ppp->verbose) fprintf (stderr, "Creating canvas\n");

    ppp->canvas = CloneImage (
      ppp->master, ppp->master->columns, ppp->master->rows, MagickTrue, exception);
    if (!ppp->canvas) return NULL;
    SetAllBlack (ppp->canvas, exception);
  }

  if (ppp->verbose) WrImage ("canvas", ppp->canvas);

  if (ppp->saliency) {
    if (!SetNoPalette (ppp->saliency, exception)) return (Image *)NULL;
  } else {
    if (ppp->verbose) fprintf (stderr, "Creating saliency\n");

    ppp->saliency = CloneImage (
      ppp->master, ppp->master->columns, ppp->master->rows, MagickTrue, exception);
    if (!ppp->saliency) return NULL;
    SetAllOneCol (ppp->saliency, "gray(50%)", exception);
  }

  if (ppp->verbose) WrImage ("saliency", ppp->saliency);
  // FIXME: negate red channel?

  if (ppp->colFrom == cfUndef && ppp->samples != NULL)
    ppp->colFrom = cfSample;

  if (ppp->colFrom == cfSample && !ppp->samples) {
    fprintf (stderr, "colFrom Samples but no samples image.\n");
    return (Image *)NULL;
  }

  if (!ResolveUserDims (&ppp->patchDims, ppp->master->columns, ppp->master->rows, 0.0))
    return NULL;
  ppp->patchWi = ppp->patchDims.x.Pix;
  ppp->patchHt = ppp->patchDims.y.Pix;

  if (ppp->verbose)
    fprintf (stderr, "patches dims: %ix%i\n", ppp->patchWi, ppp->patchHt);

  InitRmseAlpha (&ppp->rmseAlpha);

  ssize_t prev_wrngX = -1, prev_wrngY = -1;
  int cnt_same = 0;
  int iter;
  for (iter=0; iter < ppp->maxIter; iter++) {

    if (!MostWrong (ppp, exception)) {
      fprintf (stderr, "MostWrong failed\n");
      return NULL;
    }

    if (ppp->verbose) {
      fprintf (stderr, "iter=%i wrng: %g @ %li,%li\n",
        iter, ppp->wrongness, ppp->wrngX, ppp->wrngY);
    }

    if (ppp->wrongness < ppp->wrngThresh) break;

    RectangleInfo patchRect;

    if (GetRect (ppp, &patchRect, exception)) {

      Image * pat_img = GetPatchImg (ppp, &patchRect, exception);
      if (!pat_img) return NULL;

      if (ppp->verbose > 1) WrImage ("Patch", pat_img);

      MagickBooleanType anyOpaque = MagickTrue;

      if (ppp->patchShape == psMin) {
        if (!TransNoImp (ppp, &patchRect, pat_img, &anyOpaque, exception)) {
          fprintf (stderr, "TransNoImp failed\n");
          return NULL;
        }
      }

      if (anyOpaque) {
        if (!PaintPatchCanvas (ppp, &patchRect, pat_img, exception))
          return NULL;

        if (!MultSalientPatch (ppp, &patchRect, pat_img, ppp->multSal, exception))
          return NULL;
      }

      pat_img = DestroyImage (pat_img);

      if (ppp->frameName && anyOpaque) {
        // For performance: don't need to create and destroy every time.
        Image * frame_img = CloneImage(ppp->canvas, 0, 0, MagickTrue, exception);
        if (frame_img == (Image *) NULL)
          return MagickFalse;

        WriteFrame (ppp, frame_img, exception);
        DestroyImage (frame_img);
      }

      cnt_same = 0;

      if (!MultOneSalient (ppp, ppp->wrngX, ppp->wrngY, 0.25, exception)) {
        fprintf (stderr, "MultOneSalient failed\n");
        return NULL;
      }
    }

    if (ppp->wrngX == prev_wrngX && ppp->wrngY == prev_wrngY) {
      cnt_same++;
      if (cnt_same > 3) {
        cnt_same = 0;

        if (!SetOneNonSalient (ppp, ppp->wrngX, ppp->wrngY, exception)) {
          fprintf (stderr, "SetOneSalient failed\n");
          return NULL;
        }
      }
    } else {
      prev_wrngX = ppp->wrngX;
      prev_wrngY = ppp->wrngY;
      cnt_same = 0;
    }
    if (ppp->verbose > 2) fprintf (stderr, "loop\n");
  }

  if (ppp->verbose) {
    fprintf (stderr, "Finished paintpatches iter=%i\n", iter);
    if (ppp->frameNum)
      fprintf (stderr, " Created %i frames\n", ppp->frameNum);
  }

  chkentry ("canvas1", &ppp->canvas);

  DeInitRmseAlpha (&ppp->rmseAlpha);

  Image * new_img = CloneImage (ppp->canvas, 0, 0, MagickTrue, exception);
  if (!new_img) {
    fprintf (stderr, "new_img: clone failed\n");
    return NULL;
  }

  // Destroy all the images except for master.

  Image * nxt_img = GetNextImageInList(ppp->master);
  while (nxt_img) {
    fprintf (stderr, "DeleteImageFromList: ");
    WrImage ("  difl", nxt_img);
    DeleteImageFromList (&nxt_img);
    nxt_img = GetNextImageInList(ppp->master);
  }

  chkentry ("new_img1", &new_img);
  return (new_img);
}


ModuleExport size_t paintpatchesImage (
  Image **images,
  const int argc,
  const char **argv,
  ExceptionInfo *exception)
{
  Image
    *new_image;

  MagickBooleanType
    status;

  paintpatchesT ppp;

  assert(images != (Image **) NULL);
  assert(*images != (Image *) NULL);
  assert((*images)->signature == MAGICK_CORE_SIG);

  status = menu (argc, argv, &ppp);
  if (status == MagickFalse)
    return (-1);

  ppp.precision = GetMagickPrecision();

  chklist ("main0", images);
  new_image = paintpatches (*images, &ppp, exception);
  if (!new_image) {
    fprintf (stderr, "No new_image\n");
    return -1;
  }
  chkentry ("main1 newimage", &new_image);
  chkentry ("main1 image", images);

  chkentry ("main1 bef repl", images);

  ReplaceImageInList (images, new_image);

  chkentry ("main1 aft repl", images);
  chkentry ("main1 after repl", &new_image);

  return(MagickImageFilterSignature);
}

plotrg.c

/* Updated:
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <float.h>
#include "vsn_defines.h"

#define VERSION "plotrg v1.0  Copyright (c) 2018 Alan Gibson"

#define countT double

typedef struct {

  int countsDim;  // width and height

  int precision;

  MagickBooleanType
    verbose,
    regardAlpha,
    do_norm,
    do_norm_sum,
    calcOnly;

  FILE *
    fh_data;

  countT
    *counts,
    minCount,
    maxCount,
    numOutside;

  MagickRealType
    minVal,
    maxVal;

} plotrgT;


static void usage (void)
{
  printf ("Usage: -process 'plotrg [OPTION]...'\n");
  printf ("plot histogram of Red and Green channels.\n");
  printf ("\n");
  printf ("  d,  dim integer      width and height of square output\n");
  printf ("  ra, regardAlpha      use alpha for increment\n");
  printf ("  n,  norm             make max count 100%% of quantum\n");
  printf ("  ns, normSum          make sum of counts 100%% of quantum\n");
  printf ("  co, calcOnly         calculate only; don't replace image\n");
  printf ("  f,  file string      write to file stream stdout or stderr\n");
  printf ("  v,  verbose          write text information to stdout\n");
  printf ("      version          write version information to stdout\n");
  printf ("\n");
}


static MagickBooleanType IsArg (char * pa, char * ShtOpt, char * LongOpt)
{
  if ((LocaleCompare(pa, ShtOpt)==0) || (LocaleCompare(pa, LongOpt)==0))
    return MagickTrue;

  return MagickFalse;
}


#define NEXTARG \
  if (i < argc-1) i++; \
  else { \
    fprintf (stderr, "plotrg: ERROR: [%s] needs argument\n", pa); \
    status = MagickFalse; \
  }

static MagickBooleanType menu(
  const int argc,
  const char **argv,
  plotrgT * pprg
)
// Returns MagickTrue if okay.
{
  int
    i;

  MagickBooleanType
    status = MagickTrue;

  pprg->countsDim = 256;
  pprg->verbose = MagickFalse;
  pprg->regardAlpha = MagickFalse;
  pprg->do_norm = MagickFalse;
  pprg->do_norm_sum = MagickFalse;
  pprg->calcOnly = MagickFalse;
  pprg->fh_data = stderr;

  for (i=0; i < argc; i++) {
    char * pa = (char *)argv[i];

    if (IsArg (pa, "d", "dim")==MagickTrue) {
      NEXTARG;
      pprg->countsDim = atoi(argv[i]);
    } else if (IsArg (pa, "ra", "regardAlpha")==MagickTrue) {
      pprg->regardAlpha = MagickTrue;
    } else if (IsArg (pa, "n", "norm")==MagickTrue) {
      pprg->do_norm = MagickTrue;
      pprg->do_norm_sum = MagickFalse;
    } else if (IsArg (pa, "ns", "normSum")==MagickTrue) {
      pprg->do_norm = MagickFalse;
      pprg->do_norm_sum = MagickTrue;
    } else if (IsArg (pa, "co", "calcConly")==MagickTrue) {
      pprg->calcOnly = MagickTrue;
    } else if (IsArg (pa, "f", "file")==MagickTrue) {
      NEXTARG;
      if (strcasecmp (argv[i], "stdout")==0) pprg->fh_data = stdout;
      else if (strcasecmp (argv[i], "stderr")==0) pprg->fh_data = stderr;
      else status = MagickFalse;
    } else if (IsArg (pa, "v", "verbose")==MagickTrue) {
      pprg->verbose = MagickTrue;
    } else if (IsArg (pa, "version", "version")==MagickTrue) {
      fprintf (stdout, "%s\n", VERSION);
    } else {
      fprintf (stderr, "plotrg: ERROR: unknown option\n");
      status = MagickFalse;
    }
  }

  if (pprg->countsDim < 1) {
    fprintf (stderr, "dim must be at least one\n");
    status = MagickFalse;
  }

  if (pprg->verbose) {
    fprintf (stderr, "plotrg options:");
    fprintf (stderr, "  dim %i", pprg->countsDim);
    if (pprg->regardAlpha) fprintf (stderr, "  regardAlpha");
    if (pprg->verbose) fprintf (stderr, "  verbose");
    if (pprg->do_norm) fprintf (stderr, "  normalise");
    if (pprg->do_norm_sum) fprintf (stderr, "  normaliseSum");
    if (pprg->calcOnly) fprintf (stderr, "  calcOnly");
    fprintf (stderr, "\n");
  }

  if (status == MagickFalse)
    usage ();

  return (status);
}


// The next function takes an image,
// and returns it or a new image.
//
static Image * plotrg (
  Image *image,
  plotrgT * pprg,
  ExceptionInfo *exception)
{
  MagickBooleanType
    status = MagickTrue;

  int nArray = pprg->countsDim*pprg->countsDim;

  if (nArray < 1) return NULL;

  pprg->counts = (countT *)malloc(nArray*sizeof(countT));
  if (!pprg->counts) {
    fprintf (stderr, "Array allocation failed.\n");
    return NULL;
  }

  int i;
  countT * pcnt = pprg->counts;
  for (i=0; i < nArray; i++) *(pcnt++) = 0;

  CacheView * image_view = AcquireVirtualCacheView (image,exception);

  MagickRealType
    minR = DBL_MAX, minG = DBL_MAX,
    maxR = -DBL_MAX, maxG = -DBL_MAX;

  MagickRealType
    fact = pprg->countsDim / (MagickRealType)(QuantumRange+1);

  double numOOG = 0;
  double totalCnt = 0;

  ssize_t y;
  for (y = 0; y < image->rows; y++) {
    VIEW_PIX_PTR const
      *p;

    if (status == MagickFalse)
      continue;

    p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
    if (!p) {
      status=MagickFalse;
      continue;
    }

    MagickRealType r, g;

    MagickRealType alph = 1.0;

    ssize_t x;
    for (x = 0; x < image->columns; x++) {
      r = GET_PIXEL_RED   (image, p);
      g = GET_PIXEL_GREEN (image, p);

      if (pprg->regardAlpha)
        alph = GET_PIXEL_ALPHA (image, p) / (MagickRealType)QuantumRange;

      if (minR > r) minR = r;
      if (minG > g) minG = g;
      if (maxR < r) maxR = r;
      if (maxG < g) maxG = g;

      int buckR = floor (fact * r);
      int buckG = floor (fact * g);

//      printf ("%i %i  ", buckR, buckG);

      if (buckR < 0 || buckG < 0 ||
          buckR >= pprg->countsDim || buckG >= pprg->countsDim)
      {
        numOOG += alph;
      } else {
        pprg->counts[buckG * pprg->countsDim + buckR] += alph;
        totalCnt += alph;
      }

      p += Inc_ViewPixPtr (image);
    }
  }

  if (!status) return NULL;

  image_view = DestroyCacheView (image_view);

  if (pprg->verbose) {
    fprintf (pprg->fh_data,
      "totalCnt=%.*g\n",
      pprg->precision, totalCnt);

    fprintf (pprg->fh_data,
      "minR=%.*g\nmaxR=%.*g\n",
      pprg->precision, minR / (double)QuantumRange,
      pprg->precision, maxR / (double)QuantumRange);

    fprintf (pprg->fh_data,
      "minG=%.*g\nmaxG=%.*g\n",
      pprg->precision, minG / (double)QuantumRange,
      pprg->precision, maxG / (double)QuantumRange);

    fprintf (pprg->fh_data,
      "minVal=%.*g\nmaxVal=%.*g\n",
      pprg->precision,
        ((minR<minG) ? minR : minG) / (double)QuantumRange,
      pprg->precision,
        ((maxR>maxG) ? maxR : maxG) / (double)QuantumRange);

    fprintf (pprg->fh_data,
      "numOOG=%.*g\n", pprg->precision, numOOG);
  }

  countT minCnt, maxCnt;
  minCnt = maxCnt = pprg->counts[0];
  pcnt = pprg->counts;
  for (i=0; i < nArray; i++) {
    if (minCnt > *pcnt) minCnt = *pcnt;
    if (maxCnt < *pcnt) maxCnt = *pcnt;
    pcnt++;
  }

  if (pprg->verbose) {
    fprintf (pprg->fh_data,
      "minCnt=%.*g\nmaxCnt=%.*g\n",
      pprg->precision, minCnt,
      pprg->precision, maxCnt);
  }

  if (pprg->do_norm) {
    pcnt = pprg->counts;
    MagickRealType fact = QuantumRange / (MagickRealType)maxCnt;
    for (i=0; i < nArray; i++) {
      *pcnt = *pcnt * fact;
      pcnt++;
    }
  } else if (pprg->do_norm_sum) {
    pcnt = pprg->counts;
    MagickRealType fact = QuantumRange / (MagickRealType)totalCnt;
    for (i=0; i < nArray; i++) {
      *pcnt = *pcnt * fact;
      pcnt++;
    }
  }


  Image * newImg = NULL;

  if (!pprg->calcOnly) {
    newImg = CloneImage (image, pprg->countsDim, pprg->countsDim, MagickTrue, exception);
    if (!newImg) return NULL;

    CacheView * out_view = AcquireAuthenticCacheView (newImg, exception);

    for (y = 0; y < newImg->rows; y++) {
      if (status == MagickFalse)
        continue;

      VIEW_PIX_PTR *q = GetCacheViewAuthenticPixels(
        out_view, 0, y, newImg->columns, 1, exception);
      if (!q) {
        status=MagickFalse;
        continue;
      }

      pcnt = &pprg->counts[y * pprg->countsDim];
      ssize_t x;
      for (x = 0; x < newImg->columns; x++) {
        SET_PIXEL_RED (newImg, *pcnt, q);
        SET_PIXEL_GREEN (newImg, *pcnt, q);
        SET_PIXEL_BLUE (newImg, *pcnt, q);
        SET_PIXEL_ALPHA (newImg, QuantumRange, q);
        q += Inc_ViewPixPtr (newImg);
        pcnt++;
      }

      if (!SyncCacheViewAuthenticPixels(out_view,exception)) {
        fprintf (stderr, "bad sync\n");
        status=MagickFalse;
      }
    }
    out_view = DestroyCacheView (out_view);
  }
  if (!status) return NULL;

  free (pprg->counts);

  return (newImg) ? newImg : image;
}



ModuleExport size_t plotrgImage(Image **images,const int argc,
  const char **argv,ExceptionInfo *exception)
{
  MagickBooleanType
    status;

  plotrgT
    prg;

  assert(images != (Image **) NULL);
  assert(*images != (Image *) NULL);
  assert((*images)->signature == MAGICK_CORE_SIG);

  prg.precision = GetMagickPrecision();

  status = menu (argc, argv, &prg);
  if (status == MagickFalse)
    return (-1);

  Image * image;
  for (image=(*images); image != (Image *) NULL; image=GetNextImageInList(image))
  {
    Image * newImg = plotrg (image, &prg, exception);
    if (!newImg) return (-1);

    if (newImg != image) {
      ReplaceImageInList (&image, newImg);
      *images=GetFirstImageInList (newImg);
    }
  }

  return(MagickImageFilterSignature);
}

centroid.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include "vsn_defines.h"

/*
    Find coordinate of the lightest pixel.

    If there is more than one with equal lightness,
    finds the one that is nearest the centroid of those pixels.
*/


// The next function corresponds in style to functions in enhance.c
// It takes one image, and returns a status.
//
static MagickBooleanType centroid(const Image *image,
  ExceptionInfo *exception)
{
  MagickBooleanType
    status;

  CacheView
    *image_view;

  ssize_t
    y;


  assert(image != (Image *) NULL);
  assert(image->signature == MAGICK_CORE_SIG);
  if (image->debug != MagickFalse)
    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);

  image_view = AcquireVirtualCacheView(image,exception);

  status = MagickTrue;

//  valLightest = 0;
//  xLightest = yLightest = 0;
//  int nFound = 0;

//  int sigX=0, sigY=0;

  long double sigW = 0.0, sigWX = 0.0, sigWY = 0.0;

  for (y=0; y < (ssize_t) image->rows; y++)
  {
    VIEW_PIX_PTR const *p;

    register ssize_t x;

    long double val;

    if (status == MagickFalse) continue;

    p=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
    if (p == (const VIEW_PIX_PTR *) NULL) status=MagickFalse;

    for (x=0; x < (ssize_t) image->columns; x++) {
      val = GetPixelIntensity(image, p);

      sigW += val;
      sigWX += x * val;
      sigWY += y * val;

      p += Inc_ViewPixPtr (image);
    }
  }

  if (!status) return MagickFalse;

  int precision = GetMagickPrecision();

  if (sigW > 0) {
    fprintf (stderr, "centroid: %.*g,%.*g\n",
             precision, (double)(sigWX/sigW),
             precision, (double)(sigWY/sigW));
  } else {
    fprintf (stderr, "centroid: %.*g,%.*g\n",
             precision, (double)((image->columns-1.0)/2.0),
             precision, (double)((image->rows-1.0)/2.0));
  }

  return MagickTrue;
}


ModuleExport size_t centroidImage(Image **images,const int argc,
  const char **argv,ExceptionInfo *exception)
{
  Image
    *image;

  MagickBooleanType
    status;

  (void) argc;
  (void) argv;

  assert(images != (Image **) NULL);
  assert(*images != (Image *) NULL);
  assert((*images)->signature == MAGICK_CORE_SIG);

  for (image=(*images); image != (Image *) NULL; image=GetNextImageInList(image))
  {
    status = centroid(image, exception);

    if (status == MagickFalse)
      continue;
  }

  assert (status == MagickTrue);

  return(MagickImageFilterSignature);
}

barymap.c

/* Updated:
     14-December-2019 Moved 3x3 matrix functions to mat3x3.inc
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <ctype.h>
#include "vsn_defines.h"

#include "mat3x3.inc"

#define VERSION "barymap v1.0  Copyright (c) 2019 Alan Gibson"

typedef struct {
  double x;
  double y;
} coordsT;

typedef struct {
  double x;
  double y;
  double z;
} xyzCoordsT;

typedef struct {
  double b1;
  double b2;
  double b3;
} baryCoordsT;

typedef struct {
  coordsT primA;  // usually for red
  coordsT primB;  // usually for green
  coordsT primC;  // usually for blue

  // Calculated:
  double
    y23, x13, x32, y13, y31, x3, y3;
  double det;
} triangleT;

typedef enum {
  chRGB,
  chXYY,
  chXYZ
} channelsT;

typedef struct {
  char * name;
  double
    power,
    slope,
    limit,
    offset;

  double
    invPower,
    limit2,
    offsetP1;
} transT;

typedef struct {
  channelsT chans;
  triangleT triMain;
  coordsT   WP;     // white point
  double    gamma;  // -1 etc means sRGB etc.
  coordsT   XYZwp;  // white point for XYZ

  MagickBooleanType
    menuSetChan, menuSetPrim, menuSetWP, menuSetGam, menuSetXYZwp;

  // Calculated:
  //
  triangleT
    subTris[4];

  mat3x3T
    RGB2XYZ,
    XYZ2RGB,
    chromAdapTrans,
    RGB2XYZchr,
    XYZ2RGBchr;

  transT
    trans;
} colSpT;

typedef struct {
  double
    direction,
    width,
    factor;

  // Calculated:
  double
    halfWidth,
    minDir,
    maxDir;

} AngMultT;

#define MaxAngMults 10

typedef struct {
  colSpT colSpIn;
  colSpT colSpOut;

  int ndxCat;  // index into cats array

  int
    verbose,
    precision;

  double
    gain2,
    gain,
    bias,
    power;

  int nAngMults;
  AngMultT AngMults[MaxAngMults];

  MagickBooleanType
    clampXy,
    clampBary,
    ignoreWP,
    doTriangles,
    round16;

  FILE *
    fh_data;

  mat3x3T
    chromAdapTrans;

} barymT;


typedef struct {
  char * name;
  coordsT
    coordsR,
    coordsG,
    coordsB,
    coordsWP;
  double gamma;
} colSpNumsT;

// These are CIE 2 degrees
const colSpNumsT colSpNums[] = {
  {"sRGB",  // From https://en.wikipedia.org/wiki/SRGB,
            // also IM "-colorspace sRGB"
     {0.64, 0.33},
     {0.3, 0.6},
     {0.15, 0.06},
     {0.3127, 0.3290}, // D65
     -1
  },
  {"sRgbD50",  // From http://www.brucelindbloom.com/index.html?WorkingSpaceInfo.html
     {0.648431, 0.330856},
     {0.321152, 0.597871},
     {0.155886, 0.066044},
     {0.34567, 0.35850}, // D50
     -1
  },
  {"Rec709",  // From https://en.wikipedia.org/wiki/Rec._709
     {0.64, 0.33},
     {0.3, 0.6},
     {0.15, 0.06},
     {0.3127, 0.3290}, // D65
     2.4 // But not really, see https://en.wikipedia.org/wiki/Rec._709
  },
  {"P3D65", // From https://en.wikipedia.org/wiki/DCI-P3
     {0.680, 0.320},
     {0.265, 0.690},
     {0.150, 0.060},
     {0.3127, 0.3290}, // D65
     2.6
  },
  {"DisplayP3", // From https://en.wikipedia.org/wiki/DCI-P3
     {0.680, 0.320},
     {0.265, 0.690},
     {0.150, 0.060},
     {0.3127, 0.3290}, // D65
     -1
  },
  {"AdobeRGB", // From https://en.wikipedia.org/wiki/Adobe_RGB_color_space
     {0.6400, 0.3300},
     {0.2100, 0.7100},
     {0.1500, 0.0600},
     {0.3127, 0.3290}, // D65
     563/256  // = 2.19921875 exactly
  },
  {"Rec2020", // From https://en.wikipedia.org/wiki/Rec._2020
     {0.708, 0.292},
     {0.170, 0.797},
     {0.131, 0.046},
     {0.3127, 0.3290}, // D65
     2.4 // But not really, see https://en.wikipedia.org/wiki/Rec._2020#Transfer_characteristics
  },
  {"ProPhotoRGB", // From https://en.wikipedia.org/wiki/ProPhoto_RGB_color_space
     {0.7347, 0.2653},
     {0.1596, 0.8404},
     {0.0366, 0.0001},
     {0.3457, 0.3585},
     1.8 // Not really, see https://en.wikipedia.org/wiki/ProPhoto_RGB_color_space#Encoding_function
  },
  {"ACEScg", // From https://www.oscars.org/science-technology/aces/aces-documentation
     {0.713, 0.293},
     {0.165, 0.830},
     {0.128, 0.044},
     {0.32168, 0.33767}, // Close to D60, apparently.
     1
  },
  {"ACES2065-1", // From https://en.wikipedia.org/wiki/Academy_Color_Encoding_System
     {0.7347, 0.2653},
     {0.0000, 1.0000},
     {0.0001, -0.0770},
     {0.32168, 0.33767}, // Close to D60, apparently.
     1
  },
  {"xyY",
     {1.0, 0.0},
     {0.0, 1.0},
     {0.0, 0.0},
     {0.34567, 0.35850}, // Assumed to be D50
     1
  },
  {"XYZ",
     {1.0, 0.0},
     {0.0, 1.0},
     {0.0, 0.0},
     {0.34567, 0.35850}, // Assumed to be D50
     1
  }
};


typedef struct {
  char * name;
  coordsT
    coordsWP;
} wpCoordsT;

// White points,
// mostly from https://en.wikipedia.org/wiki/Standard_illuminant#White_point
//
// CIE 1931 2 degrees.
const wpCoordsT wpCoords [] = {
  {"A",    {0.44757, 0.40745}},
  {"D50",  {0.34567, 0.35850}},
  {"D60",  {0.32168, 0.33767}},  // from https://en.wikipedia.org/wiki/Academy_Color_Encoding_System
  {"D65",  {0.31271, 0.32902}},
  {"D65s", {0.3127,  0.3290}},
  {"D75",  {0.29902, 0.31485}},
  {"D100", {0.2824,  0.2898}},  // from Vendian
  {"D200", {0.2580,  0.2574}},  // from Vendian
  {"D300", {0.2516,  0.2481}},  // from Vendian
  {"D400", {0.2487,  0.2438}},  // from Vendian
  {"E",    {1/3.0,   1/3.0}}
};

// Vendian = http://www.vendian.org/mncharity/dir3/blackbody/UnstableURLs/bbr_color.html
// Vendian actually gives xy for 10000K etc, which is close to D100.

// "a" suffix from http://www.brucelindbloom.com/index.html?Eqn_ChromAdapt.html,
// citing "ASTM E308-01 except B which comes from Wyszecki & Stiles, p. 769".
// "i" suffix from ICC1v43_2010-12.
// These are values of X and Z (Y=1), not x and y.
const wpCoordsT wpCoordsXZ [] = {
  {"Aa",    {1.09850, 0.35585}},
  {"D50a",  {0.96422, 0.82521}},
  {"D55a",  {0.95682, 0.92149}},
  {"D65a",  {0.95047, 1.08883}},
  {"D75a",  {0.94972, 1.22638}},
  {"Ea",    {1,       1}},
  {"D50i",  {0.9642,  0.8249}}
};


//--------------------------------------------
//
// Colour conversion.
//

typedef struct {
  char * name;
  mat3x3T mat;
} catT;

// Ref: http://www.ivl.disco.unimib.it/download/bianco2010two-new.pdf
//
// http://www.brucelindbloom.com/index.html?Eqn_ChromAdapt.html

catT cats[] = {
  { "None",
     {1,0,0, 0,1,0, 0,0,1}
  },
  { "XYZ",
     {1,0,0, 0,1,0, 0,0,1}
  },
  { "VonKries",
     { 0.3897, 0.6890, -0.0787,
      -0.2298, 1.1834,  0.0464,
      0, 0, 1}
  },
  { "Bradford",
     { 0.8951, 0.2664, -0.1614,
      -0.7502, 1.7135,  0.0367,
       0.0389, -0.0685, 1.0296}
// Bianco gives -0.0686; Lindbloom gives -0.0685
  },
  { "Sharp",
     { 1.2694, 0.0988, -0.1706,
      -0.8364, 1.8006,  0.0357,
       0.0297, -0.0315, 1.0018}
  },
  { "CMCCAT2000",
     { 0.7982, 0.3389, -0.1371,
      -0.5918, 1.5512,  0.0406,
       0.0008, 0.239,   0.9753 }
  },
  { "CAT02",
     // See also http://www.rit-mcsl.org/fairchild/PDFs/PRO19.pdf eqn (7).
     { 0.7328, 0.4296, -0.1624,
      -0.7036, 1.6975,  0.0061,
       0.0030, 0.0136,  0.9834 }
  },
  { "BS",
     { 0.8752, 0.2787, -0.1539,
      -0.8904, 1.8709,  0.0195,
      -0.0061, 0.0162,  0.9899}
  },
  { "BS-PC",
     { 0.6489, 0.3915, -0.0404,
      -0.3775, 1.3055,  0.0720,
      -0.0271, 0.0888,  0.9383}
  },
  { "Fairchild",
     // From Spectral Sharpening of Color Sensors:  Diagonal Color Constancy and Beyond
     // https://www.ncbi.nlm.nih.gov/pmc/articles/PMC4003926/
     { 0.8562, 0.3372, -0.1934,
      -0.8360, 1.8327,  0.0033,
       0.0357, -0.0469, 1.0112}
  }
};

#define dfltCat 3  // Bradford

typedef struct {
  char * name;
  double
    power,
    slope,
    limit,
    offset;
} trcT;

trcT trcs[] = {
  { "None",
     1, 1, 0, 0
  },
  { "sRGB",
     2.4, 12.92, 0.04045, 0.055
  },
  { "Rec709",
     1.0/0.45, 4.5, 0.018, 0.099
  },
  { "Rec2020",
     1.0/0.45, 4.5, 0.018053968510807, 0.09929682680944
  },
  { "ProPhoto", // also see http://www.color.org/ROMMRGB.pdf
     1.8, 16, 1.0/512.0, 0
  }
};


//--------------------------------------------

static MagickBooleanType ParseNamedPrims (
  barymT *pbary,
  const char * name,
  colSpT * pcs)
// return TRUE if okay.
{
  MagickBooleanType ok = MagickFalse;
  int i;
  for (i = 0; i < sizeof(colSpNums) / sizeof(colSpNumsT); i++) {
    const colSpNumsT * csn = &colSpNums[i];
    if (LocaleCompare(name, csn->name)==0) {
      if (pbary->verbose)
        fprintf (pbary->fh_data, "Setting colSp from %s\n", csn->name);
      pcs->triMain.primA.x = csn->coordsR.x;
      pcs->triMain.primA.y = csn->coordsR.y;
      pcs->triMain.primB.x = csn->coordsG.x;
      pcs->triMain.primB.y = csn->coordsG.y;
      pcs->triMain.primC.x = csn->coordsB.x;
      pcs->triMain.primC.y = csn->coordsB.y;
      pcs->WP.x = csn->coordsWP.x;
      pcs->WP.y = csn->coordsWP.y;
      pcs->XYZwp = pcs->WP;
      pcs->gamma = csn->gamma;
      ok = MagickTrue;
      break;
    }
  }

  return ok;
}

static MagickBooleanType ParseNamedWP (
  barymT *pbary,
  const char * name,
  coordsT * pwp)
// return TRUE if okay.
{
  MagickBooleanType ok = MagickFalse;
  int i;
  for (i = 0; i < sizeof(wpCoords) / sizeof(wpCoordsT); i++) {

    if (LocaleCompare(name, wpCoords[i].name)==0) {
      if (pbary->verbose)
        fprintf (pbary->fh_data, "Setting WP from %s\n", wpCoords[i].name);
      pwp->x = wpCoords[i].coordsWP.x;
      pwp->y = wpCoords[i].coordsWP.y;
      ok = MagickTrue;
      break;
    }
  }

  if (!ok) {
    for (i = 0; i < sizeof(wpCoordsXZ) / sizeof(wpCoordsT); i++) {

      if (LocaleCompare(name, wpCoordsXZ[i].name)==0) {
        if (pbary->verbose)
          fprintf (pbary->fh_data, "Setting WP from %s\n", wpCoords[i].name);
        pwp->x = wpCoordsXZ[i].coordsWP.x / (wpCoordsXZ[i].coordsWP.x+wpCoordsXZ[i].coordsWP.y+1);
        pwp->y = 1.0 / (wpCoordsXZ[i].coordsWP.x+wpCoordsXZ[i].coordsWP.y+1);
        ok = MagickTrue;
        break;
      }
    }
  }

  return ok;
}

static MagickBooleanType ParseNamedCat (
  barymT *pbary,
  const char * name)
// return TRUE if okay.
{
  MagickBooleanType ok = MagickFalse;
  int i;
  for (i = 0; i < sizeof(cats) / sizeof(catT); i++) {

    if (LocaleCompare(name, cats[i].name)==0) {
      pbary->ndxCat = i;
      ok = MagickTrue;
      break;
    }
  }

  return ok;
}

static MagickBooleanType ParseNamedTrc (
  barymT *pbary,
  const char * name,
  colSpT * pcs)
// return TRUE if okay.
{
//  fprintf (stderr, "ParseNamedTrc: [%s]\n", name);

  MagickBooleanType ok = MagickFalse;
  int i;
  for (i = 0; i < sizeof(trcs) / sizeof(trcT); i++) {
//    fprintf (stderr, "  [%s]\n", trcs[i].name);

    if (LocaleCompare(name, trcs[i].name)==0) {
      pcs->gamma = -i;
      ok = MagickTrue;
      break;
    }
  }

//  if (ok) fprintf (stderr, "ParseNamedTrc: [%s] found %g\n", name, pcs->gamma);
//  else fprintf (stderr, "ParseNamedTrc: [%s] not found\n", name);
  return ok;
}

static void ListColSp (FILE * fh)
{
  int i;
  for (i = 0; i < sizeof(colSpNums) / sizeof(colSpNumsT); i++) {
    fprintf (fh, "%s\n", colSpNums[i].name);
  }
}

static void ListWP (FILE * fh)
{
  int i;
  for (i = 0; i < sizeof(wpCoords) / sizeof(wpCoordsT); i++) {
    fprintf (fh, "%s\n", wpCoords[i].name);
  }
  for (i = 0; i < sizeof(wpCoordsXZ) / sizeof(wpCoordsT); i++) {
    fprintf (fh, "%s\n", wpCoordsXZ[i].name);
  }
}

static void ListTrans (FILE * fh)
{
  int i;
  for (i = 0; i < sizeof(trcs) / sizeof(trcT); i++) {
    fprintf (fh, "%s\n", trcs[i].name);
  }
}

static void ListCats (FILE * fh)
{
  int i;
  for (i = 0; i < sizeof(cats) / sizeof(catT); i++) {
    fprintf (fh, "%s\n", cats[i].name);
  }
}

static void WrCoords (barymT * pbary, coordsT * pc, FILE * fh)
{
  fprintf (fh, "%.*g,%.*g",
    pbary->precision, pc->x,
    pbary->precision, pc->y);
}

static void WrTri (barymT * pbary, triangleT * ptri, FILE * fh)
{
  WrCoords (pbary, &ptri->primA, fh);
  fprintf (fh, ",");
  WrCoords (pbary, &ptri->primB, fh);
  fprintf (fh, ",");
  WrCoords (pbary, &ptri->primC, fh);
}

static void WrColSp (barymT * pbary, FILE * fh, colSpT * cs, char * pref)
{
  fprintf (stderr, "  %sChannels ", pref);
  if (cs->menuSetChan) fprintf (fh, "* ");
  switch (cs->chans) {
    case chRGB: fprintf (stderr, "RGB"); break;
    case chXYY: fprintf (stderr, "xyY "); break;
    case chXYZ: fprintf (stderr, "XYZ "); break;
  }

  fprintf (fh, "\n  %sPrim ", pref);
  if (cs->menuSetPrim) fprintf (fh, "* ");
  WrTri (pbary, &cs->triMain, fh);

  fprintf (fh, "\n  %sWP ", pref);
  if (cs->menuSetWP) fprintf (fh, "* ");
  WrCoords (pbary, &cs->WP, fh);

  fprintf (fh, "\n  %sTransfer ", pref);
  if (cs->menuSetGam) fprintf (fh, "* ");
  if (cs->gamma >= 0) {
    fprintf (fh, "%.*g",
      pbary->precision, cs->gamma);
  } else {
    fprintf (fh, "%s",
      trcs[(int)floor(-cs->gamma)].name);

  }

  fprintf (fh, "\n  XYZwp ");
  if (cs->menuSetXYZwp) fprintf (fh, "* ");
  WrCoords (pbary, &cs->XYZwp, fh);

  fprintf (fh, "\n");
}

static void WrBarymap (barymT * pbary, FILE * fh)
{
  fprintf (fh, "In:\n");
  WrColSp (pbary, fh, &pbary->colSpIn, "in");

  fprintf (fh, "Out:\n");
  WrColSp (pbary, fh, &pbary->colSpOut, "out");
}

//--------------------------------------------
//
// Colour conversion functions.
//

typedef struct {
  double X;
  double Y;
  double Z;
} XYZT;

typedef struct {
  double R;
  double G;
  double B;
} RGBT;

static void xyYtoXYZ (
  double x,
  double y,
  double UY,
  XYZT * out
)
{
  // Convert xyY to xYZ.

  out->Y = UY;

  if (y==0) {
    out->X = out->Z = 0;
  } else {
    out->X = UY * x / y;
    out->Z = UY * (1 - x - y) / y;
  }
}

static void xyOneToXYZ (
  double x,
  double y,
  XYZT * out
)
{
  // Convert xyY to XYZ where Y==1.

  out->Y = 1;

  if (y==0) {
    out->X = out->Z = 0;
  } else {
    out->X = x / y;
    out->Z = (1 - x - y) / y;
  }
}

static double inline Ro16If (double v, MagickBooleanType DoRound)
{
  return (DoRound) ? round (v * 65536) / 65536 : v;
}

static void inline Round16 (double * v)
{
  *v = Ro16If (*v, MagickTrue);
}

static void inline Round16xy (barymT * pbary, coordsT * xy)
{
  if (pbary->round16) {
    Round16 (&xy->x);
    Round16 (&xy->y);
  }
}

static void inline Round16XYZ (barymT * pbary, XYZT * XYZ)
{
  if (pbary->round16) {
    Round16 (&XYZ->X);
    Round16 (&XYZ->Y);
    Round16 (&XYZ->Z);
  }
}

static void inline Round16mat3x3 (barymT * pbary, mat3x3T m)
{
  if (pbary->round16) {
    int i;
    for (i=0; i<9; i++) Round16 (&m[i]);
  }
}

static void WrWpNums (FILE * fh, barymT * pbary)
{
  int i;
  XYZT XYZ;
  fprintf (fh, "WpNums:\n");
  fprintf (fh, "  name,  x,y,z  X,Y,Z\n");

  for (i = 0; i < sizeof(wpCoords) / sizeof(wpCoordsT); i++) {
    const wpCoordsT * pwp = &wpCoords[i];
    fprintf (fh, "  %s", pwp->name);
    xyOneToXYZ (pwp->coordsWP.x, pwp->coordsWP.y, &XYZ);
    fprintf (fh, "  %.*g, %.*g, %.*g,  %.*g, %.*g, %.*g\n",
      pbary->precision, Ro16If (pwp->coordsWP.x, pbary->round16),
      pbary->precision, Ro16If (pwp->coordsWP.y, pbary->round16),
      pbary->precision, Ro16If (1.0 - pwp->coordsWP.x - pwp->coordsWP.y, pbary->round16),
      pbary->precision, Ro16If (XYZ.X, pbary->round16),
      pbary->precision, Ro16If (XYZ.Y, pbary->round16),
      pbary->precision, Ro16If (XYZ.Z, pbary->round16)
    );
  }

  for (i = 0; i < sizeof(wpCoordsXZ) / sizeof(wpCoordsT); i++) {
    const wpCoordsT * pwp = &wpCoordsXZ[i];
    xyzCoordsT xyzLo;
    double div = pwp->coordsWP.x + 1 + pwp->coordsWP.y;
    xyzLo.x = pwp->coordsWP.x / div;
    xyzLo.y = 1.0 / div;
    xyzLo.z = 1.0 - xyzLo.x - xyzLo.y;
    fprintf (fh, "  %s", pwp->name);
    xyOneToXYZ (xyzLo.x, xyzLo.y, &XYZ);
    fprintf (fh, "  %.*g, %.*g, %.*g,  %.*g, %.*g, %.*g\n",
      pbary->precision, Ro16If (xyzLo.x, pbary->round16),
      pbary->precision, Ro16If (xyzLo.y, pbary->round16),
      pbary->precision, Ro16If (xyzLo.z, pbary->round16),
      pbary->precision, Ro16If (XYZ.X, pbary->round16),
      pbary->precision, Ro16If (XYZ.Y, pbary->round16),
      pbary->precision, Ro16If (XYZ.Z, pbary->round16)
    );
  }
}

static void WrXYZ (barymT * pbary, const XYZT * in, char * title)
{
  fprintf (pbary->fh_data, "%s XYZ = %.*g, %.*g, %.*g\n",
    title,
    pbary->precision, in->X,
    pbary->precision, in->Y,
    pbary->precision, in->Z);
}

static void inline MultMatXYZ (const mat3x3T m, const XYZT *XYZ, XYZT * out)
// [out] = [m][XYZ]
{
  out->X = XYZ->X*m[0] + XYZ->Y*m[1] + XYZ->Z*m[2];
  out->Y = XYZ->X*m[3] + XYZ->Y*m[4] + XYZ->Z*m[5];
  out->Z = XYZ->X*m[6] + XYZ->Y*m[7] + XYZ->Z*m[8];
}

static void WrMat3x3Bary (barymT * pbary, const mat3x3T in, char * title)
{
  WrMat3x3 (pbary->fh_data, pbary->precision, in, title);
}

static void TstInvMat3x3 (barymT * pbary)
{
  mat3x3T out;

  // http://www.brucelindbloom.com/index.html?Eqn_chromAdap.html
  // Bradford MA:
  //
  double in[] = {
    0.8951000,  0.2664000, -0.1614000,
   -0.7502000,  1.7135000,  0.0367000,
    0.0389000, -0.0685000,  1.0296000};

  WrMat3x3Bary (pbary, in, "in:");

  IdentMat3x3 (out);

  if (!InvMat3x3 (in, out)) {
    fprintf (pbary->fh_data, "Not invertible.\n");
  }

  WrMat3x3Bary (pbary, out, "out");

  mat3x3T mult;
  MultMat3x3 (in, out, mult);
  WrMat3x3Bary (pbary, mult, "mult");
}

static MagickBooleanType ChromAdapInOut (
  barymT * pbary,
  coordsT * WPin,
  coordsT * WPout,
  mat3x3T matCa)
// From the in and out white points,
//   returns a 3x3 Chromatic Adaptation matrix.
// Also calculates unadapted and adapted primaries,
//   but does nothing with them.
// Ref http://www.brucelindbloom.com/index.html?Eqn_chromAdap.html
// Also https://ninedegreesbelow.com/photography/srgb-color-space-to-profile.html
// Also ICC spec, Annex E.
//
// Note: in ICC profiles, numbers are recorded as fixed-point
//   (not floating-point), with 16 bits for the fractional part,
//   about 4 decimal places.
{
  if (pbary->verbose >= 2)
    fprintf (pbary->fh_data, "ChromAdap: WP from %.*g, %.*g  to %.*g, %.*g\n",
      pbary->precision, WPin->x,
      pbary->precision, WPin->y,
      pbary->precision, WPout->x,
      pbary->precision, WPout->y);

  if ( (WPin->x != WPout->x) || (WPin->y != WPout->y)) {

    XYZT XYZsrc, XYZdst, coneSrc, coneDst;

    mat3x3T Ma, MaInv;
    CopyMat3x3 (cats[pbary->ndxCat].mat, Ma);

    if (!InvMat3x3 (Ma, MaInv)) {
      fprintf (pbary->fh_data, "Ma not invertible.\n");
      return MagickFalse;
    }

    if (pbary->verbose >= 2) {
      WrMat3x3Bary (pbary, Ma, "chromAdap: Ma");
      WrMat3x3Bary (pbary, MaInv, "chromAdap: MaInv:");
    }

    // For the white points, get XYZ from xy.
    xyOneToXYZ (WPin->x, WPin->y, &XYZsrc);
    xyOneToXYZ (WPout->x, WPout->y, &XYZdst);

    if (pbary->verbose >= 2) {
      WrXYZ (pbary, &XYZsrc, "src WP");
      WrXYZ (pbary, &XYZdst, "dst WP");
    }

    // Transform WP XYZs to cone response domains (rho, gamma, beta).
    MultMatXYZ (Ma, &XYZsrc, &coneSrc);
    MultMatXYZ (Ma, &XYZdst, &coneDst);

    if (pbary->verbose >= 2) {
      WrXYZ (pbary, &coneSrc, "cone src");
      WrXYZ (pbary, &coneDst, "cone dst");
    }

    mat3x3T crdMat;
    ZeroMat3x3 (crdMat);
    crdMat[0] = coneDst.X / coneSrc.X;
    crdMat[4] = coneDst.Y / coneSrc.Y;
    crdMat[8] = coneDst.Z / coneSrc.Z;

    mat3x3T tmp;
    MultMat3x3 (MaInv, crdMat, tmp);
    if (pbary->verbose >= 2) {
      WrMat3x3Bary (pbary, crdMat, "chromAdap: crdMat:");
      WrMat3x3Bary (pbary, tmp, "chromAdap: tmp:");
    }
    MultMat3x3 (tmp, Ma, matCa);

    Round16mat3x3 (pbary, matCa);
  } else {
    IdentMat3x3 (matCa);
  }

  if (pbary->verbose >= 2) {
    WrMat3x3Bary (pbary, matCa, "chromAdap: matCa:");
  }

  // Following Elle,
  // we calculate unadjusted and adjusted XYZ of primaries,
  // but we do nothing with these.

  XYZT rXYZ, gXYZ, bXYZ;

  xyOneToXYZ (pbary->colSpIn.triMain.primA.x, pbary->colSpIn.triMain.primA.y, &rXYZ);
  xyOneToXYZ (pbary->colSpIn.triMain.primB.x, pbary->colSpIn.triMain.primB.y, &gXYZ);
  xyOneToXYZ (pbary->colSpIn.triMain.primC.x, pbary->colSpIn.triMain.primC.y, &bXYZ);

  if (pbary->verbose >= 2) {
    WrXYZ (pbary, &rXYZ, "interim unadap r");
    WrXYZ (pbary, &gXYZ, "interim unadap g");
    WrXYZ (pbary, &bXYZ, "interim unadap b");
  }

  mat3x3T tmp;
  tmp[0] = rXYZ.X;
  tmp[1] = gXYZ.X;
  tmp[2] = bXYZ.X;

  tmp[3] = rXYZ.Y;
  tmp[4] = gXYZ.Y;
  tmp[5] = bXYZ.Y;

  tmp[6] = rXYZ.Z;
  tmp[7] = gXYZ.Z;
  tmp[8] = bXYZ.Z;

  if (pbary->verbose >= 2) {
    WrMat3x3Bary (pbary, tmp, "chromAdap: tmp:");
  }

  mat3x3T tmpInv;  // Elle's table 5c.

  if (!InvMat3x3 (tmp, tmpInv)) {
    fprintf (pbary->fh_data, "tmp not invertible.\n");
    return MagickFalse;
  }

  if (pbary->verbose >= 2) {
    WrMat3x3Bary (pbary, tmpInv, "chromAdap: tmpInv:");
  }

  // For the source white point, get XYZ from xy.
  XYZT XYZsrc;
  xyOneToXYZ (pbary->colSpIn.WP.x, pbary->colSpIn.WP.y, &XYZsrc);

  XYZT adjY; // Calculated unadapted Y for red, green and blue primaries
  MultMatXYZ (tmpInv, &XYZsrc, &adjY);

  xyYtoXYZ (pbary->colSpIn.triMain.primA.x, pbary->colSpIn.triMain.primA.y, adjY.X, &rXYZ);
  xyYtoXYZ (pbary->colSpIn.triMain.primB.x, pbary->colSpIn.triMain.primB.y, adjY.Y, &gXYZ);
  xyYtoXYZ (pbary->colSpIn.triMain.primC.x, pbary->colSpIn.triMain.primC.y, adjY.Z, &bXYZ);

  if (pbary->verbose >= 2) {
    WrXYZ (pbary, &rXYZ, "unadapted r");
    WrXYZ (pbary, &gXYZ, "unadapted g");
    WrXYZ (pbary, &bXYZ, "unadapted b");
  }

  XYZT rAdXYZ, gAdXYZ, bAdXYZ;
  MultMatXYZ (matCa, &rXYZ, &rAdXYZ);
  MultMatXYZ (matCa, &gXYZ, &gAdXYZ);
  MultMatXYZ (matCa, &bXYZ, &bAdXYZ);

  if (pbary->verbose >= 2) {
    WrXYZ (pbary, &rAdXYZ, "adapted r");
    WrXYZ (pbary, &gAdXYZ, "adapted g");
    WrXYZ (pbary, &bAdXYZ, "adapted b");
  }

  return MagickTrue;
}

static MagickBooleanType ChromAdap (barymT * pbary)
{
  if (pbary->verbose >= 2) fprintf (pbary->fh_data, "ChromAdap WP->WP\n");
  if (!ChromAdapInOut (
    pbary,
    &pbary->colSpIn.WP, &pbary->colSpOut.WP,
    pbary->chromAdapTrans)) return MagickFalse;

  if (pbary->verbose >= 2) fprintf (pbary->fh_data, "ChromAdap in WP->X\n");
  if (!ChromAdapInOut (
    pbary,
    &pbary->colSpIn.WP, &pbary->colSpIn.XYZwp,
    pbary->colSpIn.chromAdapTrans)) return MagickFalse;
  if (pbary->verbose >= 2) WrMat3x3Bary (pbary, pbary->colSpIn.chromAdapTrans, "ChromAdapTrans in ");

  if (pbary->verbose >= 2) fprintf (pbary->fh_data, "ChromAdap out X->WP\n");
  if (!ChromAdapInOut (
    pbary,
    &pbary->colSpOut.XYZwp, &pbary->colSpOut.WP,
    pbary->colSpOut.chromAdapTrans)) return MagickFalse;
  if (pbary->verbose >= 2) WrMat3x3Bary (pbary, pbary->colSpOut.chromAdapTrans, "ChromAdapTrans out ");

  // Multiply matrices to incorporate chromatic adaptation.

  if (pbary->verbose >= 2) {
    WrMat3x3Bary (pbary, pbary->colSpIn.chromAdapTrans, "RGB2XYZchr A");
    WrMat3x3Bary (pbary, pbary->colSpIn.RGB2XYZ, "RGB2XYZchr B");
  }
  MultMat3x3 (pbary->colSpIn.chromAdapTrans, pbary->colSpIn.RGB2XYZ, pbary->colSpIn.RGB2XYZchr);
  Round16mat3x3 (pbary, pbary->colSpIn.RGB2XYZchr);
  if (pbary->verbose >= 2) WrMat3x3Bary (pbary, pbary->colSpIn.RGB2XYZchr, "RGB2XYZchr");

  if (pbary->verbose >= 2) {
    WrMat3x3Bary (pbary, pbary->colSpOut.XYZ2RGB, "RGB2XYZchr C");
    WrMat3x3Bary (pbary, pbary->colSpOut.chromAdapTrans, "RGB2XYZchr D");
  }
  MultMat3x3 (pbary->colSpOut.XYZ2RGB, pbary->colSpOut.chromAdapTrans, pbary->colSpOut.XYZ2RGBchr);
  Round16mat3x3 (pbary, pbary->colSpIn.XYZ2RGBchr);
  if (pbary->verbose >= 2) WrMat3x3Bary (pbary, pbary->colSpOut.XYZ2RGBchr, "XYZ2RGBchr");

  return MagickTrue;
}

static MagickBooleanType RgbXyzMat (
  barymT * pbary,
  colSpT * colSp)
// Given a colorspace primaries and WP,
// Calculates the 3x3 matrices to convert between linear RGB and XYZ.
// Ref http://www.brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html
{
  double Yr = 1.0;
  double Yg = 1.0;
  double Yb = 1.0;

  double Xr, Zr, Xg, Zg, Xb, Zb;

  if (colSp->triMain.primA.y==0) {
    Xr = Zr = 0;
  } else {
    Xr = colSp->triMain.primA.x / colSp->triMain.primA.y;
    Zr = (1 - colSp->triMain.primA.x - colSp->triMain.primA.y) /
              colSp->triMain.primA.y;
  }

  if (colSp->triMain.primB.y==0) {
    Xg = Zg = 0;
  } else {
    Xg = colSp->triMain.primB.x / colSp->triMain.primB.y;
    Zg = (1 - colSp->triMain.primB.x - colSp->triMain.primB.y) /
              colSp->triMain.primB.y;
  }

  if (colSp->triMain.primC.y==0) {
    Xb = Zb = 0;
  } else {
    Xb = colSp->triMain.primC.x / colSp->triMain.primC.y;
    Zb = (1 - colSp->triMain.primC.x - colSp->triMain.primC.y) /
              colSp->triMain.primC.y;
  }

  mat3x3T tmp, tmpInv;
  tmp[0] = Xr;
  tmp[1] = Xg;
  tmp[2] = Xb;
  tmp[3] = Yr;
  tmp[4] = Yg;
  tmp[5] = Yb;
  tmp[6] = Zr;
  tmp[7] = Zg;
  tmp[8] = Zb;

  if (pbary->verbose >= 2) {
    WrMat3x3Bary (pbary, tmp, "RgbXyzMat: tmp:");
  }

  if (!InvMat3x3 (tmp, tmpInv)) {
    fprintf (pbary->fh_data, "RgbXyzMat: tmp not invertible.\n");
    return MagickFalse;
  }

  if (pbary->verbose >= 2) {
    WrMat3x3Bary (pbary, tmpInv, "RgbXyzMat: tmpInv:");
  }

  // For the white point, get XYZ from xy.
  XYZT XYZsrc;
  xyOneToXYZ (colSp->WP.x, colSp->WP.y, &XYZsrc);

  if (pbary->verbose >= 2) {
    WrXYZ (pbary, &XYZsrc, "src WP");
  }

  XYZT S;
  MultMatXYZ (tmpInv, &XYZsrc, &S);

  if (pbary->verbose >= 2) {
    WrXYZ (pbary, &S, "S");
  }

  colSp->RGB2XYZ[0] = S.X * Xr;
  colSp->RGB2XYZ[1] = S.Y * Xg;
  colSp->RGB2XYZ[2] = S.Z * Xb;

  colSp->RGB2XYZ[3] = S.X * Yr;
  colSp->RGB2XYZ[4] = S.Y * Yg;
  colSp->RGB2XYZ[5] = S.Z * Yb;

  colSp->RGB2XYZ[6] = S.X * Zr;
  colSp->RGB2XYZ[7] = S.Y * Zg;
  colSp->RGB2XYZ[8] = S.Z * Zb;

  if (!InvMat3x3 (colSp->RGB2XYZ, colSp->XYZ2RGB)) {
    fprintf (pbary->fh_data, "RgbXyzMat: colSp->RGB2XYZ not invertible.\n");
    return MagickFalse;
  }

  Round16mat3x3 (pbary, colSp->RGB2XYZ);
  Round16mat3x3 (pbary, colSp->XYZ2RGB);
  // FIXME: or round before invert?

  if (pbary->verbose >= 2) {
    WrMat3x3Bary (pbary, colSp->RGB2XYZ, "RgbXyzMat: RGB2XYZ:");
    WrMat3x3Bary (pbary, colSp->XYZ2RGB, "RgbXyzMat: XYZ2RGB:");
  }

  return MagickTrue;
}


/* RGB to XYZ:
   Ref http://www.brucelindbloom.com/index.html?Eqn_RGB_to_XYZ.html

   inverse companding to linear RGB
   linear RGB to XYZ via 3x3 matrix
   if needed, apply chromatic adaptation transform to XYZ
*/

static double inline powchk (double v, double p)
{
  return (v <= 0) ? v : pow (v, p);
  // FIXME: Crude. When v<0, should use slope at zero.
}


static void ZeroTrans (transT * pt)
{
  pt->name = NULL;
  pt->power = 1;
  pt->slope = 1;
  pt->limit = 0;
  pt->offset = 0;

  pt->invPower = 1;
  pt->limit2 = 0;
  pt->offsetP1 = 1;
}

static void CalcTrans (barymT * pbary, transT * pt, int ndxTrc)
{
  trcT * ptf = &trcs[ndxTrc];

  pt->name = ptf->name;
  pt->power    = Ro16If (ptf->power, pbary->round16);
  pt->slope    = Ro16If (ptf->slope, pbary->round16);
  pt->limit    = Ro16If (ptf->limit, pbary->round16);
  pt->offset   = Ro16If (ptf->offset, pbary->round16);

  pt->invPower = Ro16If (1.0 / pt->power, pbary->round16);
  pt->limit2   = Ro16If (pt->limit / pt->slope, pbary->round16);
  pt->offsetP1 = Ro16If (pt->offset + 1.0, pbary->round16);

  if (pbary->verbose >= 2) {
    fprintf (pbary->fh_data, "%s %.*g %.*g %.*g %.*g  %.*g %.*g %.*g\n",
      pt->name,
      pbary->precision, pt->power,
      pbary->precision, pt->slope,
      pbary->precision, pt->limit,
      pbary->precision, pt->offset,
      pbary->precision, pt->invPower,
      pbary->precision, pt->limit2,
      pbary->precision, pt->offsetP1);
  }
}

static double inline TrcToLin (const transT * pt, double v)
{
  if (v < pt->limit) return v / pt->slope;

  return pow ((v + pt->offset) / pt->offsetP1, pt->power);
}

static double inline TrcFromLin (const transT * pt, double v)
{
  if (v < pt->limit2) return pt->slope * v;

  return pow (v, pt->invPower) * pt->offsetP1 - pt->offset;

}

static void inline Rgb2Xyz (const colSpT * colSp, RGBT * rgb, XYZT * XYZ)
{
  // From non-lin to lin.
  if (colSp->gamma != 1) {
    if (colSp->gamma > 0) {
      rgb->R = powchk(rgb->R, colSp->gamma);
      rgb->G = powchk(rgb->G, colSp->gamma);
      rgb->B = powchk(rgb->B, colSp->gamma);
    } else {
      rgb->R = TrcToLin(&colSp->trans, rgb->R);
      rgb->G = TrcToLin(&colSp->trans, rgb->G);
      rgb->B = TrcToLin(&colSp->trans, rgb->B);
    }
  }
  MultMatXYZ (colSp->RGB2XYZ, (XYZT *)rgb, XYZ);
}

/* XYZ to RGB:
   Ref http://www.brucelindbloom.com/index.html?Eqn_XYZ_to_RGB.html

   if needed, apply chromatic adaptation transform to XYZ
   XYZ to linear RGB via 3x3 matrix
   companding from linear RGB to non-linear RGB
*/

static void inline Xyz2Rgb (const colSpT * colSp, XYZT * XYZ, RGBT * rgb)
{
  MultMatXYZ (colSp->XYZ2RGB, XYZ, (XYZT *)rgb);

  // From lin to non-lin
  if (colSp->gamma != 1) {
    if (colSp->gamma > 0) {
      double g = 1.0 / colSp->gamma;
      rgb->R = powchk(rgb->R, g);
      rgb->G = powchk(rgb->G, g);
      rgb->B = powchk(rgb->B, g);
    } else {
      rgb->R = TrcFromLin(&colSp->trans, rgb->R);
      rgb->G = TrcFromLin(&colSp->trans, rgb->G);
      rgb->B = TrcFromLin(&colSp->trans, rgb->B);
    }
  }
}

static void inline ChromAdapXYZ (
  barymT * pbary,
  XYZT * xyz)
{
  XYZT tmp;
  MultMatXYZ (pbary->chromAdapTrans, xyz, &tmp);
  xyz->X = tmp.X;
  xyz->Y = tmp.Y;
  xyz->Z = tmp.Z;
}

static void inline xy2XYZ (
  coordsT * xy,
  XYZT * XYZ)
{
  if (xy->y == 0) {
    XYZ->X = 0;
    XYZ->Z = 0;
  } else {
    XYZ->X = XYZ->Y * xy->x / xy->y;
    XYZ->Z = XYZ->Y * (1 - xy->x - xy->y) / xy->y;
  }
}

static MagickBooleanType ImageHasChromaticity (Image * im)
{
  return (
    im->chromaticity.red_primary.x != 0 &&
    im->chromaticity.red_primary.y &&
    im->chromaticity.white_point.x &&
    im->chromaticity.white_point.y);
}

static void ColSpFromIm (
  Image * im,
  barymT * pbary)
/* Copy metadata from image _only_ where these were not set in menu.
*/
{
  if (!pbary->colSpIn.menuSetChan) {
    if (im->colorspace == XYZColorspace)
      pbary->colSpIn.chans = chXYZ;
    else if (im->colorspace == xyYColorspace)
      pbary->colSpIn.chans = chXYY;
    else pbary->colSpIn.chans = chRGB;
  }

  if (!pbary->colSpIn.menuSetPrim && ImageHasChromaticity (im)) {
    // These might not be set in im->chromaticity.
    pbary->colSpIn.triMain.primA.x = im->chromaticity.red_primary.x;
    pbary->colSpIn.triMain.primA.y = im->chromaticity.red_primary.y;
    pbary->colSpIn.triMain.primB.x = im->chromaticity.green_primary.x;
    pbary->colSpIn.triMain.primB.y = im->chromaticity.green_primary.y;
    pbary->colSpIn.triMain.primC.x = im->chromaticity.blue_primary.x;
    pbary->colSpIn.triMain.primC.y = im->chromaticity.blue_primary.y;

    if (!pbary->colSpIn.menuSetWP) {
      pbary->colSpIn.WP.x = im->chromaticity.white_point.x;
      pbary->colSpIn.WP.y = im->chromaticity.white_point.y;
    }

    if (!pbary->colSpIn.menuSetGam) {
      // FIXME: and gamma
    }
    // FIXME: and XYZwp
  }
}

static void ColSpInToOut (barymT * pbary)
/* Copy input metadata to output metadata _only_ where these were
   not set in menu.
*/
{
  if (!pbary->colSpOut.menuSetChan) {
    pbary->colSpOut.chans = pbary->colSpIn.chans;
  }
  if (!pbary->colSpOut.menuSetPrim) {
    pbary->colSpOut.triMain.primA.x = pbary->colSpIn.triMain.primA.x;
    pbary->colSpOut.triMain.primA.y = pbary->colSpIn.triMain.primA.y;
    pbary->colSpOut.triMain.primB.x = pbary->colSpIn.triMain.primB.x;
    pbary->colSpOut.triMain.primB.y = pbary->colSpIn.triMain.primB.y;
    pbary->colSpOut.triMain.primC.x = pbary->colSpIn.triMain.primC.x;
    pbary->colSpOut.triMain.primC.y = pbary->colSpIn.triMain.primC.y;

    if (!pbary->colSpOut.menuSetWP) {
      pbary->colSpOut.WP.x = pbary->colSpIn.WP.x;
      pbary->colSpOut.WP.y = pbary->colSpIn.WP.y;
    }
    if (!pbary->colSpOut.menuSetGam) {
      pbary->colSpOut.gamma = pbary->colSpIn.gamma;
    }
  }
}

static void ColSpToIm (
  barymT * pbary,
  Image * im)
{
  switch (pbary->colSpOut.chans) {
    case chRGB: im->colorspace = RGBColorspace; break;
    case chXYY: im->colorspace = xyYColorspace; break;
    case chXYZ: im->colorspace = XYZColorspace; break;
  }

  im->chromaticity.red_primary.x = pbary->colSpOut.triMain.primA.x;
  im->chromaticity.red_primary.y = pbary->colSpOut.triMain.primA.y;
  im->chromaticity.green_primary.x = pbary->colSpOut.triMain.primB.x;
  im->chromaticity.green_primary.y = pbary->colSpOut.triMain.primB.y;
  im->chromaticity.blue_primary.x = pbary->colSpOut.triMain.primC.x;
  im->chromaticity.blue_primary.y = pbary->colSpOut.triMain.primC.y;

  im->chromaticity.white_point.x = pbary->colSpOut.WP.x;
  im->chromaticity.white_point.y = pbary->colSpOut.WP.y;

  // FIXME: and gamma. Save negative values in attribute?
}


//--------------------------------------------


static void usage (void)
{
  printf ("Usage: -process 'barymap [OPTION]...'\n");
  printf ("process barycentric coordinates.\n");
  printf ("\n");
  printf ("  ic,  inChannels string   RGB or xyY or XYZ [xyY]\n");
  printf ("  ip,  inPrim 6_floats     xy coords of input primaries [sRGB]\n");
  printf ("  iw,  inWP 2_floats       xy coords of input white point\n");
  printf ("  it,  inTransfer number   name or gamma of input\n");
  printf ("  xw,  XYZwp 2_floats      xy coords of XYZ white point\n");
  printf ("  gn2, gain2 number        gain factor for squared distance from WP [0]\n");
  printf ("  gn,  gain number         gain factor for distance from WP [1]\n");
  printf ("  bs,  bias number         bias for distance from WP [0]\n");
  printf ("  pow, power number        power factor for distance from WP [1]\n");
  printf ("  am,  angleMult string    list of: direction,width,factor\n");
  printf ("  st,  skipTri             skip triangle processing\n");
  printf ("  ign, ignoreWP            ignore white point for triangulation\n");
  printf ("  clC, clampCartesian      clamp cartesian coordinates\n");
  printf ("  clB, clampBarycentric    clamp barycentric coordinates\n");
  printf ("  oc,  outChannels string  RGB or xyY or XYZ [xyY]\n");
  printf ("  op,  outPrim 6_floats    xy coords of output primaries [sRGB]\n");
  printf ("  ow,  outWP 2_floats      xy coords of output white point\n");
  printf ("  ot,  outTransfer number  name or gamma of output\n");
  printf ("  ca,  chromAdap string    name of Chromatic Adaptation Transform [Bradford]\n");
  printf ("  r16, round16             round some fractions to 16 binary places\n");
  printf ("  li,  list string         write list to stdout or stderr\n");
  printf ("  f,   file string         write to file stream stdout or stderr\n");
  printf ("  v,   verbose             write text information to stdout\n");
  printf ("  v2,  verbose2            write more text information\n");
  printf ("  v9,  verbose9            write voluminous text information\n");
  printf ("       version             write version information to stdout\n");

/*
  printf ("\nNamed primaries:\n");
  ListColSp (stdout);
  printf ("\nNamed illuminants:\n");
  ListWP (stdout);
  printf ("\nNamed transfers:\n");
  ListTrans (stdout);
  printf ("\nChromatic Adaptation Transforms:\n");
  ListCats (stdout);
  printf ("\n");
*/
}


static MagickBooleanType ParseCoord (const char * s, coordsT * pc)
{
  double d;
  int n;
  char * p = (char *)s;

  sscanf (p, "%lg%n", &d, &n);
  if (!n) return MagickFalse;
  p += n;
  if (*p != ',') return MagickFalse;
  p++;
  pc->x = d;

  sscanf (p, "%lg%n", &d, &n);
  if (!n) return MagickFalse;
  p += n;
  if (*p != '\0') return MagickFalse;
  pc->y = d;

  return MagickTrue;
}


static MagickBooleanType Parse3Coord (
  const char * s,
  coordsT * pc0,
  coordsT * pc1,
  coordsT * pc2)
{
  double d;
  int n;
  char * p = (char *)s;

  sscanf (p, "%lg%n", &d, &n);
  if (!n) return MagickFalse;
  p += n;
  if (*p != ',') return MagickFalse;
  p++;
  pc0->x = d;

  sscanf (p, "%lg%n", &d, &n);
  if (!n) return MagickFalse;
  p += n;
  if (*p != ',') return MagickFalse;
  p++;
  pc0->y = d;

  sscanf (p, "%lg%n", &d, &n);
  if (!n) return MagickFalse;
  p += n;
  if (*p != ',') return MagickFalse;
  p++;
  pc1->x = d;

  sscanf (p, "%lg%n", &d, &n);
  if (!n) return MagickFalse;
  p += n;
  if (*p != ',') return MagickFalse;
  p++;
  pc1->y = d;

  sscanf (p, "%lg%n", &d, &n);
  if (!n) return MagickFalse;
  p += n;
  if (*p != ',') return MagickFalse;
  p++;
  pc2->x = d;

  sscanf (p, "%lg%n", &d, &n);
  if (!n) return MagickFalse;
  p += n;
  if (*p != '\0') return MagickFalse;
  pc2->y = d;

  return MagickTrue;
}

static MagickBooleanType ParseGamma (
  barymT * pbary,
  const char * s,
  colSpT * pcs)
{
  ZeroTrans (&pcs->trans);

  int n;
  char * p = (char *)s;

  pcs->gamma = 0;

  sscanf (p, "%lg%n", &pcs->gamma, &n);
  if (!n) return MagickFalse;
  p += n;
  if (*p != '\0') return MagickFalse;

  if (pcs->gamma != 0) return MagickTrue;

  if (ParseNamedTrc (pbary, s, pcs)) {
    CalcTrans (pbary, &pcs->trans, -(int)floor(pcs->gamma));
    return MagickTrue;
  }

  return MagickFalse;
}

static MagickBooleanType Parse3Prims (
  barymT * pbary,
  const char * s,
  colSpT * pcs)
{
  if (Parse3Coord (s, &pcs->triMain.primA, &pcs->triMain.primB, &pcs->triMain.primC))
    return MagickTrue;

  if (ParseNamedPrims (pbary, s, pcs)) {
    ParseGamma (pbary, s, pcs);
    // (If not successful, no problem.)
    return MagickTrue;
  }

  return MagickFalse;
}

static MagickBooleanType ParseWPkelvin (
  barymT * pbary,
  const char * s,
  coordsT * pwp)
{
  // Calculate x,y from correlated colour temperature.

  int slen = strlen(s);
  if (slen < 2) return MagickFalse;
  if (toupper(s[slen-1]) != 'K') return MagickFalse;
  if (!isdigit((int)s[0])) return MagickFalse;

  char * p = (char *)s;

  double T = 0;

  int n = 0;
  sscanf (p, "%lg%n", &T, &n);
  if (!n) return MagickFalse;
  p += n+1;  // +1 to skip over terminal K.
  if (*p != '\0') return MagickFalse;

  // Adjust from nominal.
  // See https://vdocuments.mx/colorimetry-cie-colorimetry.html

  if (s[slen-1]=='k') T *= 1.4388/1.4380;

  // Formulae from https://en.wikipedia.org/wiki/Standard_illuminant#Illuminant_series_D
  // Also in http://www.brucelindbloom.com/index.html?Eqn_T_to_xy.html

  if (T < 4000 || T > 25000) return MagickFalse;

  if (T <= 7000) {
    pwp->x = 0.244063 + 0.09911e3/T + 2.9678e6/(T*T) - 4.6070e9 / (T*T*T);
  } else {
    pwp->x = 0.237040 + 0.24748e3/T + 1.9018e6/(T*T) - 2.0064e9 / (T*T*T);
  }
  pwp->y = -3.000*pwp->x*pwp->x + 2.870*pwp->x - 0.275;

  if (pbary->verbose >= 2) fprintf (pbary->fh_data, "ParseWPkelvin3 T=%g  xy=%g,%g\n", T, pwp->x, pwp->y);

  return MagickTrue;
}

static MagickBooleanType ParseWP (
  barymT * pbary,
  const char * s,
  coordsT * pwp)
{
  MagickBooleanType okay = ParseWPkelvin (pbary, s, pwp);

  if (!okay) okay = ParseCoord (s, pwp);

  if (!okay) okay = ParseNamedWP (pbary, s, pwp);

  if (okay) Round16xy (pbary, pwp);

  return okay;
}

static MagickBooleanType ParseAngleMult (barymT * pbary, const char * s)
{
  double d;
  int n;
  char * p = (char *)s;

  int i = 0;
  AngMultT *pam = &pbary->AngMults[0];
  MagickBooleanType Finished = MagickFalse;
  while (i < MaxAngMults && !Finished) {

    sscanf (p, "%lg%n", &d, &n);
    if (!n) return MagickFalse;
    p += n;
    if (*p != ',') {
      printf ("AngMult: expected ',' at %s\n", p);
      return MagickFalse;
    }
    p++;
    while (d >= 180.0) d -= 360.0;
    while (d < -180.0) d += 360.0;
    pam->direction = d * M_PI/180.0;

    sscanf (p, "%lg%n", &d, &n);
    if (!n) return MagickFalse;
    p += n;
    if (*p != ',') {
      printf ("AngMult: expected ',' at %s\n", p);
      return MagickFalse;
    }
    p++;
    pam->width = d * M_PI/180.0;
    pam->halfWidth = pam->width / 2.0;
    pam->minDir = pam->direction - pam->halfWidth;
    pam->maxDir = pam->direction + pam->halfWidth;

    sscanf (p, "%lg%n", &d, &n);
    if (!n) return MagickFalse;
    p += n;
    pam->factor = d;
    if (*p == '\0') Finished = MagickTrue;
    else {
      if (*p != ';') {
        printf ("AngMult: expected ';' at %s\n", p);
        return MagickFalse;
      }
      p++;
    }
    pam++;
    i++;
  }

  if (!Finished) {
    printf ("AngMult: too many\n");
  }
  pbary->nAngMults = i;
  return Finished;
}

static MagickBooleanType IsArg (char * pa, char * ShtOpt, char * LongOpt)
{
  if ((LocaleCompare(pa, ShtOpt)==0) || (LocaleCompare(pa, LongOpt)==0))
    return MagickTrue;

  return MagickFalse;
}


#define NEXTARG \
  if (i < argc-1) i++; \
  else { \
    fprintf (stderr, "barymap: ERROR: [%s] needs argument\n", pa); \
    status = MagickFalse; \
  }

static MagickBooleanType menu(
  const int argc,
  const char **argv,
  barymT * pbary
)
// Returns MagickTrue if okay.
{
  int
    i;

  MagickBooleanType
    status = MagickTrue;

  pbary->verbose = 0;
  pbary->fh_data = stderr;

  pbary->colSpIn.chans = chXYY;
  pbary->colSpOut.chans = chXYY;
  pbary->ndxCat = dfltCat;
  pbary->ignoreWP = MagickFalse;
  pbary->clampXy = MagickFalse;
  pbary->clampBary = MagickFalse;
  pbary->doTriangles = MagickTrue;
  pbary->round16 = MagickFalse;
  pbary->gain2 = 0.0;
  pbary->gain = 1.0;
  pbary->bias = 0.0;
  pbary->power = 1.0;
  pbary->nAngMults = 0;
  pbary->colSpIn.menuSetChan =
    pbary->colSpIn.menuSetPrim =
    pbary->colSpIn.menuSetWP =
    pbary->colSpIn.menuSetGam =
    pbary->colSpIn.menuSetXYZwp = MagickFalse;
  pbary->colSpOut.menuSetChan =
    pbary->colSpOut.menuSetPrim =
    pbary->colSpOut.menuSetWP =
    pbary->colSpOut.menuSetGam =
    pbary->colSpOut.menuSetXYZwp = MagickFalse;
  ParseNamedPrims (pbary, "sRGB", &pbary->colSpIn);
  ParseNamedPrims (pbary, "sRGB", &pbary->colSpOut);

  for (i=0; i < argc; i++) {
    char * pa = (char *)argv[i];

    if (IsArg (pa, "ic", "inChannels")==MagickTrue) {
      NEXTARG;
      pbary->colSpIn.menuSetChan = MagickTrue;
      if (strcasecmp (argv[i], "rgb")==0) pbary->colSpIn.chans = chRGB;
      else if (strcasecmp (argv[i], "xyy")==0) pbary->colSpIn.chans = chXYY;
      else if (strcasecmp (argv[i], "xyz")==0) pbary->colSpIn.chans = chXYZ;
      else status = MagickFalse;
    } else if (IsArg (pa, "oc", "outChannels")==MagickTrue) {
      NEXTARG;
      pbary->colSpOut.menuSetChan = MagickTrue;
      if (strcasecmp (argv[i], "rgb")==0) pbary->colSpOut.chans = chRGB;
      else if (strcasecmp (argv[i], "xyy")==0) pbary->colSpOut.chans = chXYY;
      else if (strcasecmp (argv[i], "xyz")==0) pbary->colSpOut.chans = chXYZ;
      else status = MagickFalse;
    } else if (IsArg (pa, "st", "skipTri")==MagickTrue) {
      pbary->doTriangles = MagickFalse;
    } else if (IsArg (pa, "ign", "ignoreWP")==MagickTrue) {
      pbary->ignoreWP = MagickTrue;
    } else if (IsArg (pa, "clC", "clampCartesian")==MagickTrue) {
      pbary->clampXy = MagickTrue;
    } else if (IsArg (pa, "clB", "clampBarycentric")==MagickTrue) {
      pbary->clampBary = MagickTrue;
    } else if (IsArg (pa, "gn", "gain")==MagickTrue) {
      NEXTARG;
      pbary->gain = atof(argv[i]);
    } else if (IsArg (pa, "gn2", "gain2")==MagickTrue) {
      NEXTARG;
      pbary->gain2 = atof(argv[i]);
    } else if (IsArg (pa, "bs", "bias")==MagickTrue) {
      NEXTARG;
      pbary->bias = atof(argv[i]);
    } else if (IsArg (pa, "pow", "power")==MagickTrue) {
      NEXTARG;
      pbary->power = atof(argv[i]);
    } else if (IsArg (pa, "ip", "inPrim")==MagickTrue) {
      NEXTARG;
      pbary->colSpIn.menuSetPrim = MagickTrue;
      if (!Parse3Prims (pbary, argv[i], &pbary->colSpIn))
      {
        fprintf (stderr, "barymap: problem parsing inPrim\n");
        status = MagickFalse;
      }
    } else if (IsArg (pa, "iw", "inWP")==MagickTrue) {
      NEXTARG;
      pbary->colSpIn.menuSetWP = MagickTrue;
      if (!ParseWP (pbary, argv[i], &pbary->colSpIn.WP)) {
        fprintf (stderr, "barymap: problem parsing inWP\n");
        status = MagickFalse;
      }
    } else if (IsArg (pa, "xw", "XYZwp")==MagickTrue) {
      NEXTARG;
      pbary->colSpIn.menuSetXYZwp = MagickTrue;
      pbary->colSpOut.menuSetXYZwp = MagickTrue;
      if (!ParseWP (pbary, argv[i], &pbary->colSpIn.XYZwp)) {
        fprintf (stderr, "barymap: problem parsing XYZwp\n");
        status = MagickFalse;
      }
      else pbary->colSpOut.XYZwp = pbary->colSpIn.XYZwp;
    } else if (IsArg (pa, "it", "inTransfer")==MagickTrue) {
      NEXTARG;
      pbary->colSpIn.menuSetGam = MagickTrue;
      if (!ParseGamma (pbary, argv[i], &pbary->colSpIn)) {
        fprintf (stderr, "barymap: problem parsing inTransfer\n");
        status = MagickFalse;
      }
    } else if (IsArg (pa, "op", "outPrim")==MagickTrue) {
      NEXTARG;
      pbary->colSpOut.menuSetPrim = MagickTrue;
      if (!Parse3Prims (pbary, argv[i], &pbary->colSpOut))
      {
        fprintf (stderr, "barymap: problem parsing outPrim\n");
        status = MagickFalse;
      }
    } else if (IsArg (pa, "ow", "outWP")==MagickTrue) {
      NEXTARG;
      pbary->colSpOut.menuSetWP = MagickTrue;
      if (!ParseWP (pbary, argv[i], &pbary->colSpOut.WP)) {
        fprintf (stderr, "barymap: problem parsing outWP\n");
        status = MagickFalse;
      }
    } else if (IsArg (pa, "ot", "outTransfer")==MagickTrue) {
      NEXTARG;
      pbary->colSpOut.menuSetGam = MagickTrue;
      if (!ParseGamma (pbary, argv[i], &pbary->colSpOut)) {
        fprintf (stderr, "barymap: problem parsing outTransfer\n");
        status = MagickFalse;
      }
    } else if (IsArg (pa, "ca", "chromAdap")==MagickTrue) {
      NEXTARG;
      if (!ParseNamedCat (pbary, argv[i])) {
        fprintf (stderr, "barymap: problem parsing chromAdap\n");
        status = MagickFalse;
      }
    } else if (IsArg (pa, "am", "angleMult")==MagickTrue) {
      NEXTARG;
      if (!ParseAngleMult (pbary, argv[i])) {
        fprintf (stderr, "barymap: problem parsing angleMult\n");
        status = MagickFalse;
      }
    } else if (IsArg (pa, "r16", "round16")==MagickTrue) {
      pbary->round16 = MagickTrue;
    } else if (IsArg (pa, "li", "list")==MagickTrue) {
      NEXTARG;
      if (strcasecmp (argv[i], "list")==0) {
        fprintf (pbary->fh_data, "list\nprimaries\nilluminants\ntransfers\ncats\nwpnums\n");
      } else if (strcasecmp (argv[i], "primaries")==0) {
        ListColSp (pbary->fh_data);
      } else if (strcasecmp (argv[i], "illuminants")==0) {
        ListWP (pbary->fh_data);
      } else if (strcasecmp (argv[i], "transfers")==0) {
        ListTrans (pbary->fh_data);
      } else if (strcasecmp (argv[i], "cats")==0) {
        ListCats (pbary->fh_data);
      } else if (strcasecmp (argv[i], "wpnums")==0) {
        WrWpNums (pbary->fh_data, pbary);
      } else {
        fprintf (stderr, "barymap: ERROR: unknown option '%s' for list\n", argv[i]);
        status = MagickFalse;
      }
    } else if (IsArg (pa, "f", "file")==MagickTrue) {
      NEXTARG;
      if (strcasecmp (argv[i], "stdout")==0) pbary->fh_data = stdout;
      else if (strcasecmp (argv[i], "stderr")==0) pbary->fh_data = stderr;
      else {
        fprintf (stderr, "barymap: ERROR: unknown option '%s' for file\n", argv[i]);
        status = MagickFalse;
      }
    } else if (IsArg (pa, "v", "verbose")==MagickTrue) {
      pbary->verbose = 1;
    } else if (IsArg (pa, "v2", "verbose2")==MagickTrue) {
      pbary->verbose = 2;
    } else if (IsArg (pa, "v9", "verbose9")==MagickTrue) {
      pbary->verbose = 9;
    } else if (IsArg (pa, "version", "version")==MagickTrue) {
      fprintf (stdout, "%s\n", VERSION);
    } else {
      fprintf (stderr, "barymap: ERROR: unknown option '%s'\n", pa);
      status = MagickFalse;
    }
  }

  if (!pbary->colSpIn.menuSetXYZwp)
    pbary->colSpIn.XYZwp = pbary->colSpIn.WP;
  if (!pbary->colSpOut.menuSetXYZwp)
    pbary->colSpOut.XYZwp = pbary->colSpIn.XYZwp;

  Round16xy (pbary, &pbary->colSpIn.WP);
  Round16xy (pbary, &pbary->colSpOut.WP);
  Round16xy (pbary, &pbary->colSpIn.XYZwp);
  Round16xy (pbary, &pbary->colSpOut.XYZwp);

  if (pbary->verbose) {
    fprintf (stderr, "barymap options:");

    if (pbary->colSpIn.menuSetChan) {
      fprintf (stderr, "  inChannels ");
      switch (pbary->colSpIn.chans) {
        case chRGB: fprintf (stderr, "RGB"); break;
        case chXYY: fprintf (stderr, "xyY "); break;
        case chXYZ: fprintf (stderr, "XYZ "); break;
      }
    }

    if (pbary->colSpIn.menuSetChan) {
      fprintf (stderr, "  outChannels ");
      switch (pbary->colSpOut.chans) {
        case chRGB: fprintf (stderr, "RGB"); break;
        case chXYY: fprintf (stderr, "xyY "); break;
        case chXYZ: fprintf (stderr, "XYZ "); break;
      }
    }

    if (!pbary->doTriangles) fprintf (stderr, "  skipTri");
    if (pbary->ignoreWP) fprintf (stderr, "  ignoreWP");
    if (pbary->clampXy) fprintf (stderr, "  clampCartesian");
    if (pbary->clampBary) fprintf (stderr, "  clampBarycentric");
    if (pbary->gain2 != 0) fprintf (stderr, "  gain2 %g", pbary->gain2);
    if (pbary->gain != 1) fprintf (stderr, "  gain %g", pbary->gain);
    if (pbary->bias != 0) fprintf (stderr, "  bias %g", pbary->bias);
    if (pbary->power != 1) fprintf (stderr, "  power %g", pbary->power);

    fprintf (stderr, "  chromAdap %s", cats[pbary->ndxCat].name);

    if (pbary->round16) fprintf (stderr, "  round16");
    if (pbary->verbose >= 2) fprintf (stderr, "  verbose%i", pbary->verbose);
    else if (pbary->verbose) fprintf (stderr, "  verbose");
    fprintf (stderr, "\n");

    WrBarymap (pbary, stderr);  // FIXME: Prefer different words?
  }

  if (pbary->verbose) {
    fprintf (stderr, "nAngMults %i\n", pbary->nAngMults);
    int i;
    for (i=0; i < pbary->nAngMults; i++) {
      AngMultT *pam = &pbary->AngMults[i];
      fprintf (stderr, "%g %g %g %g %g\n",
        pam->direction, pam->minDir, pam->maxDir, pam->halfWidth, pam->factor);
    }
  }

  if (status && pbary->verbose) {
    // FIXME: temporary, for testing.
    if (pbary->verbose >= 2) {
      WrWpNums (pbary->fh_data, pbary);
      //TstInvMat3x3 (pbary);
    }
  }

  if (status == MagickFalse)
    usage ();

  return (status);
}

static MagickBooleanType CalcTri (triangleT *ptri)
{
  ptri->y23 = ptri->primB.y - ptri->primC.y;
  ptri->x13 = ptri->primA.x - ptri->primC.x;
  ptri->x32 = ptri->primC.x - ptri->primB.x;
  ptri->y13 = ptri->primA.y - ptri->primC.y;
  ptri->y31 = -ptri->y13;
  ptri->x3 = ptri->primC.x;
  ptri->y3 = ptri->primC.y;

  ptri->det = ptri->y23 * ptri->x13 + ptri->x32 * ptri->y13;

  // FIXME: error if det is zero.
  if (fabs(ptri->det) < 1e-6) {
    printf ("ERROR: ptri->det is %g\n", ptri->det);
    return MagickFalse;
  }
  return MagickTrue;
}

static MagickBooleanType PopSubTrisInOut (colSpT * cs)
{
  MagickBooleanType okay = MagickTrue;

  if (!CalcTri (&cs->triMain)) okay = MagickFalse;
  cs->subTris[0] = cs->triMain;

  cs->subTris[1].primA = cs->WP;
  cs->subTris[1].primB = cs->triMain.primA;
  cs->subTris[1].primC = cs->triMain.primB;
  if (!CalcTri (&cs->subTris[1])) okay = MagickFalse;

  cs->subTris[2].primA = cs->WP;
  cs->subTris[2].primB = cs->triMain.primB;
  cs->subTris[2].primC = cs->triMain.primC;
  if (!CalcTri (&cs->subTris[2])) okay = MagickFalse;

  cs->subTris[3].primA = cs->WP;
  cs->subTris[3].primB = cs->triMain.primC;
  cs->subTris[3].primC = cs->triMain.primA;
  if (!CalcTri (&cs->subTris[3])) okay = MagickFalse;

  return okay;
}

static MagickBooleanType PopSubTris (barymT * pbary)
{
  MagickBooleanType okay = MagickTrue;

  if (!PopSubTrisInOut (&pbary->colSpIn)) okay = MagickFalse;
  if (!PopSubTrisInOut (&pbary->colSpOut)) okay = MagickFalse;

  return okay;
}

static void inline CartToBary (
  const coordsT * xy,
  baryCoordsT * cb,
  const triangleT * ptri)
{
  // Assumes det is not zero.
  double dx = xy->x - ptri->x3;
  double dy = xy->y - ptri->y3;
  cb->b1 = (ptri->y23 * dx + ptri->x32 * dy) / ptri->det;
  cb->b2 = (ptri->y31 * dx + ptri->x13 * dy) / ptri->det;
  cb->b3 = 1 - cb->b1 - cb->b2;
}

static void inline ClampCart (coordsT * xy)
{
  if      (xy->x < 0) xy->x = 0;
  else if (xy->x > 1) xy->x = 1;

  if      (xy->y < 0) xy->y = 0;
  else if (xy->y > 1) xy->y = 1;
}

static void inline ClampBary (baryCoordsT * cb)
{
  double div;
  if (cb->b1 < 0) {
    cb->b1 = 0;
    div = cb->b2 + cb->b3;
    cb->b2 /= div;
    cb->b3 /= div;
  }
  if (cb->b2 < 0) {
    cb->b2 = 0;
    div = cb->b1 + cb->b3;
    cb->b1 /= div;
    cb->b3 /= div;
  }
  if (cb->b3 < 0) {
    cb->b3 = 0;
    div = cb->b1 + cb->b2;
    cb->b1 /= div;
    cb->b2 /= div;
  }

  // FIXME: Next can never happen?
  if (cb->b1 > 1) cb->b1 = 1;
  if (cb->b2 > 1) cb->b2 = 1;
  if (cb->b3 > 1) cb->b3 = 1;
}

static void inline BaryToCart (
  const baryCoordsT * cb,
  coordsT * xy,
  const triangleT * ptri)
{
  xy->x = cb->b1 * ptri->primA.x + cb->b2 * ptri->primB.x + cb->b3 * ptri->primC.x;
  xy->y = cb->b1 * ptri->primA.y + cb->b2 * ptri->primB.y + cb->b3 * ptri->primC.y;
}

static MagickBooleanType inline IsInside (baryCoordsT * cb)
{
  return (cb->b1 >= 0) && (cb->b2 >= 0) && (cb->b3 >= 0);
}

static void WhichTri (
  barymT * pbary,
  colSpT * cs,
  coordsT * xy,
  baryCoordsT * cb,
  int * triNum)
{
  // Finds which triangle the cartesian coordinate is in.
  // Returns those barycentric coords in cb,
  // and triangle number in triNum.

  baryCoordsT cbs[4];

  if (pbary->ignoreWP) {
    CartToBary (xy, &cbs[0], &cs->subTris[0]);
    *triNum = 0;
  } else {
    *triNum = 0;  // Means we don't know yet.
    CartToBary (xy, &cbs[1], &cs->subTris[1]);
    if (IsInside (&cbs[1])) {
      *triNum = 1;
    } else {

      CartToBary (xy, &cbs[2], &cs->subTris[2]);
      if (IsInside (&cbs[2])) {
        *triNum = 2;
      } else {

        CartToBary (xy, &cbs[3], &cs->subTris[3]);
        if (IsInside (&cbs[3])) {
          *triNum = 3;
        } else {
          // Outside all triangles.
          // Fall back to main triangle.
          CartToBary (xy, &cbs[0], &cs->subTris[0]);
          *triNum = 0;
        }
      }
    }
  }
  *cb = cbs[*triNum];
}


// The next function takes an image, and returns it.
//
static Image * barymap (
  Image *image,
  barymT * pbary,
  ExceptionInfo *exception)
{
  // This function ignores alpha.

  // When in and out are xyY,
  // this function reads and writes the R and G channels.
  // leaving the B channel unchanged.

  MagickBooleanType
    status = MagickTrue;

  ColSpFromIm (image, pbary);
  ColSpInToOut (pbary);

  if (pbary->verbose >= 2) {
    fprintf (pbary->fh_data, "For this image:\n");
    WrBarymap (pbary, stderr);
  }

  ZeroTrans (&pbary->colSpIn.trans);
  ZeroTrans (&pbary->colSpOut.trans);
  if (pbary->colSpIn.gamma < 0) {
    CalcTrans (pbary, &pbary->colSpIn.trans, -(int)floor(pbary->colSpIn.gamma));
  }
  if (pbary->colSpOut.gamma < 0) {
    CalcTrans (pbary, &pbary->colSpOut.trans, -(int)floor(pbary->colSpOut.gamma));
  }

  if (!RgbXyzMat (pbary, &pbary->colSpIn)) {
    printf ("Problem in RgbXyzMat in\n");
    status = MagickFalse;
  }

  if (!RgbXyzMat (pbary, &pbary->colSpOut)) {
    printf ("Problem in RgbXyzMat out\n");
    status = MagickFalse;
  }

  if (!ChromAdap (pbary)) {
    printf ("Problem in ChromAdap\n");
    status = MagickFalse;
  }

  MagickBooleanType
    doGBP = (pbary->gain2 != 0.0) ||
            (pbary->gain  != 1.0) ||
            (pbary->bias  != 0.0) ||
            (pbary->power != 1.0);

  if (!PopSubTris (pbary)) {
    printf ("Problem in PopSubTris\n");
    status = MagickFalse;
  }

  CacheView * image_view = AcquireAuthenticCacheView (image,exception);

  if (pbary->verbose == 9)
    fprintf (pbary->fh_data, "Pixels:");

  ssize_t y;
#if defined(MAGICKCORE_OPENMP_SUPPORT)
  #pragma omp parallel for schedule(static,4) \
    shared(okay) \
    MAGICK_THREADS(image,image,image->rows,1)
#endif
  for (y = 0; y < image->rows; y++) {
    VIEW_PIX_PTR *p;

    if (status == MagickFalse)
      continue;

    p=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
    if (!p) {
      status=MagickFalse;
      continue;
    }

    ssize_t x;
    RGBT rgb;
    double div;
    for (x = 0; x < image->columns; x++) {
      if (pbary->verbose == 9)
        fprintf (pbary->fh_data, "%li,%li:", x, y);

      coordsT xy = {0,0};
      XYZT XYZ = {0,0,0};

      switch (pbary->colSpIn.chans) {
        case chRGB:
          rgb.R = GET_PIXEL_RED   (image, p) / (MagickRealType)QuantumRange;
          rgb.G = GET_PIXEL_GREEN (image, p) / (MagickRealType)QuantumRange;
          rgb.B = GET_PIXEL_BLUE  (image, p) / (MagickRealType)QuantumRange;

          if (pbary->verbose == 9)
            fprintf (pbary->fh_data, "RGB: %.*g, %.*g, %.*g\n",
              pbary->precision, rgb.R,
              pbary->precision, rgb.G,
              pbary->precision, rgb.B);

          Rgb2Xyz (&pbary->colSpIn, &rgb, &XYZ);

          if (pbary->verbose == 9)
            fprintf (pbary->fh_data, "XYZ: %.*g, %.*g, %.*g\n",
              pbary->precision, XYZ.X,
              pbary->precision, XYZ.Y,
              pbary->precision, XYZ.Z);

          div = XYZ.X + XYZ.Y + XYZ.Z;
          if (div == 0) {
            xy.x = 0;
            xy.y = 0;
          } else {
            xy.x = XYZ.X / div;
            xy.y = XYZ.Y / div;
          }
          break;
        case chXYY:
          xy.x = GET_PIXEL_RED   (image, p) / (MagickRealType)QuantumRange;
          xy.y = GET_PIXEL_GREEN (image, p) / (MagickRealType)QuantumRange;
          XYZ.Y = GET_PIXEL_BLUE (image, p) / (MagickRealType)QuantumRange;
          break;
        case chXYZ:
          XYZ.X = GET_PIXEL_RED   (image, p) / (MagickRealType)QuantumRange;
          XYZ.Y = GET_PIXEL_GREEN (image, p) / (MagickRealType)QuantumRange;
          XYZ.Z = GET_PIXEL_BLUE (image, p) / (MagickRealType)QuantumRange;

          if (pbary->verbose == 9)
            fprintf (pbary->fh_data, "XYZ: %.*g, %.*g, %.*g\n",
              pbary->precision, XYZ.X,
              pbary->precision, XYZ.Y,
              pbary->precision, XYZ.Z);

          div = XYZ.X + XYZ.Y + XYZ.Z;
          if (div == 0) {
            xy.x = 0;
            xy.y = 0;
          } else {
            xy.x = XYZ.X / div;
            xy.y = XYZ.Y / div;
          }
          break;
      }

      if (pbary->clampXy) ClampCart (&xy);

      if (pbary->verbose == 9)
        fprintf (pbary->fh_data, "xy: %.*g, %.*g\n",
          pbary->precision, xy.x,
          pbary->precision, xy.y);

      if (doGBP) {
        double dx = xy.x - pbary->colSpIn.WP.x;
        double dy = xy.y - pbary->colSpIn.WP.y;
        double v = hypot (dx, dy);

        double vrat = pow (v * v * pbary->gain2
                             + v * pbary->gain
                             + pbary->bias,
                           pbary->power)
                      / v;

        xy.x = dx * vrat + pbary->colSpIn.WP.x;
        xy.y = dy * vrat + pbary->colSpIn.WP.y;

        if (pbary->clampXy) ClampCart (&xy);

        if (pbary->verbose == 9)
          fprintf (pbary->fh_data, "  xy: %.*g, %.*g\n",
            pbary->precision, xy.x,
            pbary->precision, xy.y);
      }

      if (pbary->nAngMults > 0) {
        double dx = xy.x - pbary->colSpIn.WP.x;
        double dy = xy.y - pbary->colSpIn.WP.y;
        double t = atan2 (dx, dy);
        int i;
        for (i = 0; i < pbary->nAngMults; i++) {
          AngMultT * pam = &pbary->AngMults[i];
          double f = fabs (t - pam->direction);

          if (f > pam->halfWidth) {
            f = fabs (t - M_TWOPI - pam->direction);
          }
          if (f < pam->halfWidth) {
            f = (f / pam->halfWidth);
            f = sin ((f-0.5) * M_PI);
            f = f * (1 - pam->factor) + pam->factor;
            xy.x = dx * f + pbary->colSpIn.WP.x;
            xy.y = dy * f + pbary->colSpIn.WP.y;

            if (pbary->clampXy) ClampCart (&xy);
          }
        }
        if (pbary->verbose == 9)
          fprintf (pbary->fh_data, "  xy: %.*g, %.*g\n",
            pbary->precision, xy.x,
            pbary->precision, xy.y);
      }

      if (pbary->doTriangles) {
        baryCoordsT cb;

        int triNum;
        WhichTri (pbary, &pbary->colSpIn, &xy, &cb, &triNum);
        if (pbary->clampBary) ClampBary (&cb);

        if (pbary->verbose == 9)
          fprintf (pbary->fh_data, "  t=%i  b: %.*g, %.*g, %.*g\n",
            triNum,
            pbary->precision, cb.b1,
            pbary->precision, cb.b2,
            pbary->precision, cb.b3);

        BaryToCart (&cb, &xy, &pbary->colSpOut.subTris[triNum]);
        if (pbary->clampXy) ClampCart (&xy);

        if (pbary->verbose == 9)
          fprintf (pbary->fh_data, "  xy: %.*g, %.*g\n",
            pbary->precision, xy.x,
            pbary->precision, xy.y);
      }

      switch (pbary->colSpOut.chans) {
        case chRGB:
          xy2XYZ (&xy, &XYZ);

          if (pbary->verbose == 9)
            fprintf (pbary->fh_data, "  XYZ: %.*g, %.*g, %.*g\n",
              pbary->precision, XYZ.X,
              pbary->precision, XYZ.Y,
              pbary->precision, XYZ.Z);

//          ChromAdapXYZ (pbary, &XYZ);

          if (pbary->verbose == 9)
            fprintf (pbary->fh_data, "  aca XYZ: %.*g, %.*g, %.*g\n",
              pbary->precision, XYZ.X,
              pbary->precision, XYZ.Y,
              pbary->precision, XYZ.Z);

          Xyz2Rgb (&pbary->colSpOut, &XYZ, &rgb);

          if (pbary->verbose == 9)
            fprintf (pbary->fh_data, "  RGB: %.*g, %.*g, %.*g\n",
              pbary->precision, rgb.R,
              pbary->precision, rgb.G,
              pbary->precision, rgb.B);

          SET_PIXEL_RED   (image, rgb.R * QuantumRange, p);
          SET_PIXEL_GREEN (image, rgb.G * QuantumRange, p);
          SET_PIXEL_BLUE  (image, rgb.B * QuantumRange, p);
          break;
        case chXYY:
          SET_PIXEL_RED   (image, xy.x * QuantumRange, p);
          SET_PIXEL_GREEN (image, xy.y * QuantumRange, p);
          SET_PIXEL_BLUE  (image, XYZ.Y * QuantumRange, p);
          break;
        case chXYZ:
          xy2XYZ (&xy, &XYZ);
          // We need CA, even if we stay in XYZ.
          // FIXME: this is the simple version, directly from in to out.
          ChromAdapXYZ (pbary, &XYZ);
          SET_PIXEL_RED   (image, XYZ.X * QuantumRange, p);
          SET_PIXEL_GREEN (image, XYZ.Y * QuantumRange, p);
          SET_PIXEL_BLUE  (image, XYZ.Z * QuantumRange, p);
          break;
      }

      p += Inc_ViewPixPtr (image);
    }
    if (!SyncCacheViewAuthenticPixels(image_view,exception)) {
      fprintf (stderr, "bad sync\n");
      status=MagickFalse;
    }
  }

  if (!status) return NULL;

  image_view = DestroyCacheView (image_view);

  ColSpToIm (pbary, image);

  return image;
}



ModuleExport size_t barymapImage(Image **images,const int argc,
  const char **argv,ExceptionInfo *exception)
{
  MagickBooleanType
    status;

  barymT
    pbary;

  assert(images != (Image **) NULL);
  assert(*images != (Image *) NULL);
  assert((*images)->signature == MAGICK_CORE_SIG);

  pbary.precision = GetMagickPrecision();

  status = menu (argc, argv, &pbary);

  if (status == MagickFalse)
    return (-1);

  Image * image;
  for (image=(*images); image != (Image *) NULL; image=GetNextImageInList(image))
  {
    Image * newImg = barymap (image, &pbary, exception);
    if (!newImg) return (-1);

    if (newImg != image) {
      ReplaceImageInList (&image, newImg);
      *images=GetFirstImageInList (newImg);
    }
  }

  return(MagickImageFilterSignature);
}

mat3x3.inc

typedef double mat3x3T[9];

// abcdefghi
// 012345678

static void ZeroMat3x3 (mat3x3T out)
{
  int i;
  for (i=0; i<9; i++) out[i] = 0.0;
}

static void IdentMat3x3 (mat3x3T out)
{
  ZeroMat3x3 (out);
  out[0] = out[4] = out[8] = 1.0;
}

static void CopyMat3x3 (const mat3x3T in, mat3x3T out)
{
  int i;
  for (i=0; i<9; i++) out[i] = in[i];
}

static void TransposeMat3x3self (mat3x3T inout)
{
  double t;

  t = inout[1];
  inout[1] = inout[3];
  inout[3] = t;

  t = inout[2];
  inout[2] = inout[6];
  inout[6] = t;

  t = inout[5];
  inout[5] = inout[7];
  inout[7] = t;
}

static void MultMat3x3 (const mat3x3T inA, const mat3x3T inB, mat3x3T out)
{
  out[0] = inA[0]*inB[0] + inA[1]*inB[3] + inA[2]*inB[6];
  out[1] = inA[0]*inB[1] + inA[1]*inB[4] + inA[2]*inB[7];
  out[2] = inA[0]*inB[2] + inA[1]*inB[5] + inA[2]*inB[8];

  out[3] = inA[3]*inB[0] + inA[4]*inB[3] + inA[5]*inB[6];
  out[4] = inA[3]*inB[1] + inA[4]*inB[4] + inA[5]*inB[7];
  out[5] = inA[3]*inB[2] + inA[4]*inB[5] + inA[5]*inB[8];

  out[6] = inA[6]*inB[0] + inA[7]*inB[3] + inA[8]*inB[6];
  out[7] = inA[6]*inB[1] + inA[7]*inB[4] + inA[8]*inB[7];
  out[8] = inA[6]*inB[2] + inA[7]*inB[5] + inA[8]*inB[8];
}

static MagickBooleanType InvMat3x3 (const mat3x3T in, mat3x3T out)
{
  // Ref: https://en.wikipedia.org/wiki/Invertible_matrix#Inversion_of_3_%C3%97_3_matrices
  out[0] =  (in[4]*in[8] - in[5]*in[7]);
  out[1] = -(in[3]*in[8] - in[5]*in[6]);
  out[2] =  (in[3]*in[7] - in[4]*in[6]);

  out[3] = -(in[1]*in[8] - in[2]*in[7]);
  out[4] =  (in[0]*in[8] - in[2]*in[6]);
  out[5] = -(in[0]*in[7] - in[1]*in[6]);

  out[6] =  (in[1]*in[5] - in[2]*in[4]);
  out[7] = -(in[0]*in[5] - in[2]*in[3]);
  out[8] =  (in[0]*in[4] - in[1]*in[3]);

  double det = in[0] * out[0] + in[1] * out[1] + in[2] * out[2];

  if (fabs(det) < MagickEpsilon) return MagickFalse;

  TransposeMat3x3self (out);

  int i;
  for (i=0; i < 9; i++) {
    out[i] /= det;
  }
  return MagickTrue;
}

static void WrMat3x3 (FILE * fh, int precision, const mat3x3T in, char * title)
{
  fprintf (fh, "%s\n", title);

  fprintf (fh, "  %.*g, %.*g, %.*g\n",
    precision, in[0],
    precision, in[1],
    precision, in[2]);

  fprintf (fh, "  %.*g, %.*g, %.*g\n",
    precision, in[3],
    precision, in[4],
    precision, in[5]);

  fprintf (fh, "  %.*g, %.*g, %.*g\n",
    precision, in[6],
    precision, in[7],
    precision, in[8]);
}

chklist.h

I use these to check list integrity during development. See Hints and tips above.

#define chklist(STRING,IMAGES) \
{ \
  Image **chkimg; \
  chkimg=IMAGES; \
  fprintf (stderr, "chklist (%s) %s ", __PRETTY_FUNCTION__, STRING); \
  assert(chkimg != (Image **) NULL); \
  assert(*chkimg != (Image *) NULL); \
  assert((*chkimg)->signature == MAGICK_CORE_SIG); \
  fprintf (stderr, "  List length %i  ", (int)GetImageListLength(*chkimg)); \
  assert((*chkimg)->previous == (Image *) NULL); \
  fprintf (stderr, "chklist OK\n"); \
}

#define chkentry(STRING,IMAGE) \
{ \
  Image **chkimg; \
  chkimg=IMAGE; \
  fprintf (stderr, "chkentry (%s) %s ", __PRETTY_FUNCTION__, STRING); \
  assert(chkimg != (Image **) NULL); \
  assert(*chkimg != (Image *) NULL); \
  assert((*chkimg)->signature == MAGICK_CORE_SIG); \
  fprintf (stderr, "  List length %i  ", (int)GetImageListLength(*chkimg)); \
  fprintf (stderr, "chkentry OK\n"); \
}

Makefile.am

This file is in imdev/filters. IM installs it for the analyze module. I have added entries for my own modules.

#  Copyright 1999-2014 ImageMagick Studio LLC, a non-profit organization
#  dedicated to making software imaging solutions freely available.
#
#  You may not use this file except in compliance with the License.  You may
#  obtain a copy of the License at
#
#    http://www.imagemagick.org/script/license.php
#
#  Unless required by applicable law or agreed to in writing, software
#  distributed under the License is distributed on an "AS IS" BASIS,
#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#  See the License for the specific language governing permissions and
#  limitations under the License.
#
#  Makefile for building ImageMagick filter modules.

#  Updated by Alan Gibson ("snibgo"):
#    29-September-2016 removed sortlines, eqfish2rect, segscanmerge
#

# Where filter modules get installed
filtersdir = $(FILTER_PATH)

MAGICK_FILTER_CPPFLAGS = $(AM_CPPFLAGS)

MAGICK_FILTER_SRCS = \
	filters/analyze.c \
	filters/hellow.c \
	filters/echostuff.c \
	filters/dumpimage.c \
	filters/onewhite.c \
	filters/nearestwhite.c \
	filters/allwhite.c \
	filters/onelightest.c \
	filters/midlightest.c \
	filters/rmsealpha.c \
	filters/addend.c \
	filters/replacelast.c \
	filters/replacefirst.c \
	filters/replaceall.c \
	filters/replaceeach.c \
	filters/replacespec.c \
	filters/grad2.c \
	filters/drawcirc.c \
	filters/sortpixels.c \
	filters/sortpixelsblue.c \
	filters/shadowsortpixels.c \
	filters/img2knl.c \
	filters/interppix.c \
	filters/mkgauss.c \
	filters/applines.c \
	filters/mkhisto.c \
	filters/cumulhisto.c \
	filters/invdispmap.c \
	filters/geodist.c \
	filters/invclut.c \
	filters/rect2eqfish.c \
	filters/growcut.c \
	filters/fillholes.c \
	filters/fillholespri.c \
	filters/pixmatch.c \
	filters/darkestpath.c \
	filters/darkestmeander.c \
	filters/darkestpntpnt.c \
	filters/srt3d.c \
	filters/arctan2.c \
	filters/pause.c \
	filters/shell.c \
	filters/sphaldcl.c \
	filters/setmnsd.c \
	filters/integim.c \
	filters/deintegim.c \
	filters/deintegim2.c \
	filters/kcluster.c \
	filters/srchimg.c \
	filters/paintpatches.c \
	filters/avgconcrings.c \
	filters/multisrch.c \
	filters/whatrot.c \
	filters/whatscale.c \
	filters/whatrotscale.c \
	filters/findsinks.c \
	filters/srchmask.c \
	filters/aggrsrch.c \
	filters/centsmcrop.c \
	filters/cols2mat.c \
	filters/oogbox.c \
	filters/plotrg.c \
	filters/centroid.c \
	filters/barymap.c \
	filters/rhotheta.c \
	filters/colsp012.c

if WITH_MODULES
filters_LTLIBRARIES = filters/analyze.la \
	filters/hellow.la \
	filters/echostuff.la \
	filters/dumpimage.la \
	filters/onewhite.la \
	filters/nearestwhite.la \
	filters/allwhite.la \
	filters/onelightest.la \
	filters/midlightest.la \
	filters/rmsealpha.la \
	filters/addend.la \
	filters/replacelast.la \
	filters/replacefirst.la \
	filters/replaceall.la \
	filters/replaceeach.la \
	filters/replacespec.la \
	filters/grad2.la \
	filters/drawcirc.la \
	filters/sortpixels.la \
	filters/sortpixelsblue.la \
	filters/shadowsortpixels.la \
	filters/img2knl.la \
	filters/interppix.la \
	filters/mkgauss.la \
	filters/applines.la \
	filters/mkhisto.la \
	filters/cumulhisto.la \
	filters/invdispmap.la \
	filters/geodist.la \
	filters/invclut.la \
	filters/rect2eqfish.la \
	filters/growcut.la \
	filters/fillholes.la \
	filters/fillholespri.la \
	filters/pixmatch.la \
	filters/darkestpath.la \
	filters/darkestmeander.la \
	filters/darkestpntpnt.la \
	filters/srt3d.la \
	filters/arctan2.la \
	filters/pause.la \
	filters/shell.la \
	filters/sphaldcl.la \
	filters/setmnsd.la \
	filters/integim.la \
	filters/deintegim.la \
	filters/deintegim2.la \
	filters/kcluster.la \
	filters/srchimg.la \
	filters/paintpatches.la \
	filters/avgconcrings.la \
	filters/multisrch.la \
	filters/whatrot.la \
	filters/whatscale.la \
	filters/whatrotscale.la \
	filters/findsinks.la \
	filters/srchmask.la \
	filters/aggrsrch.la \
	filters/centsmcrop.la \
	filters/cols2mat.la \
	filters/oogbox.la \
	filters/plotrg.la \
	filters/centroid.la \
	filters/barymap.la \
	filters/rhotheta.la \
	filters/colsp012.la
else
filters_LTLIBRARIES =
endif # WITH_MODULES
filters_CPPFLAGS = $(MAGICK_FILTER_CPPFLAGS)

# analyze filter module
filters_analyze_la_SOURCES      = filters/analyze.c
filters_analyze_la_CPPFLAGS     = $(MAGICK_FILTER_CPPFLAGS)
filters_analyze_la_LDFLAGS      = $(MODULECOMMONFLAGS)
filters_analyze_la_LIBADD       = $(MAGICKCORE_LIBS) $(MATH_LIBS)

# hellow filter module
filters_hellow_la_SOURCES      = filters/hellow.c
filters_hellow_la_CPPFLAGS     = $(MAGICK_FILTER_CPPFLAGS)
filters_hellow_la_LDFLAGS      = $(MODULECOMMONFLAGS)
filters_hellow_la_LIBADD       = $(MAGICKCORE_LIBS) $(MATH_LIBS)

# echostuff filter module
filters_echostuff_la_SOURCES      = filters/echostuff.c
filters_echostuff_la_CPPFLAGS     = $(MAGICK_FILTER_CPPFLAGS)
filters_echostuff_la_LDFLAGS      = $(MODULECOMMONFLAGS)
filters_echostuff_la_LIBADD       = $(MAGICKCORE_LIBS) $(MATH_LIBS)

# dumpimage filter module
filters_dumpimage_la_SOURCES      = filters/dumpimage.c
filters_dumpimage_la_CPPFLAGS     = $(MAGICK_FILTER_CPPFLAGS)
filters_dumpimage_la_LDFLAGS      = $(MODULECOMMONFLAGS)
filters_dumpimage_la_LIBADD       = $(MAGICKCORE_LIBS) $(MATH_LIBS)

# onewhite filter module
filters_onewhite_la_SOURCES      = filters/onewhite.c
filters_onewhite_la_CPPFLAGS     = $(MAGICK_FILTER_CPPFLAGS)
filters_onewhite_la_LDFLAGS      = $(MODULECOMMONFLAGS)
filters_onewhite_la_LIBADD       = $(MAGICKCORE_LIBS) $(MATH_LIBS)

# onewhite filter module
filters_nearestwhite_la_SOURCES      = filters/nearestwhite.c
filters_nearestwhite_la_CPPFLAGS     = $(MAGICK_FILTER_CPPFLAGS)
filters_nearestwhite_la_LDFLAGS      = $(MODULECOMMONFLAGS)
filters_nearestwhite_la_LIBADD       = $(MAGICKCORE_LIBS) $(MATH_LIBS)

# allwhite filter module
filters_allwhite_la_SOURCES      = filters/allwhite.c
filters_allwhite_la_CPPFLAGS     = $(MAGICK_FILTER_CPPFLAGS)
filters_allwhite_la_LDFLAGS      = $(MODULECOMMONFLAGS)
filters_allwhite_la_LIBADD       = $(MAGICKCORE_LIBS) $(MATH_LIBS)

# onelightest filter module
filters_onelightest_la_SOURCES      = filters/onelightest.c
filters_onelightest_la_CPPFLAGS     = $(MAGICK_FILTER_CPPFLAGS)
filters_onelightest_la_LDFLAGS      = $(MODULECOMMONFLAGS)
filters_onelightest_la_LIBADD       = $(MAGICKCORE_LIBS) $(MATH_LIBS)

# midlightest filter module
filters_midlightest_la_SOURCES      = filters/midlightest.c
filters_midlightest_la_CPPFLAGS     = $(MAGICK_FILTER_CPPFLAGS)
filters_midlightest_la_LDFLAGS      = $(MODULECOMMONFLAGS)
filters_midlightest_la_LIBADD       = $(MAGICKCORE_LIBS) $(MATH_LIBS)

# rmsealpha filter module
filters_rmsealpha_la_SOURCES      = filters/rmsealpha.c
filters_rmsealpha_la_CPPFLAGS     = $(MAGICK_FILTER_CPPFLAGS)
filters_rmsealpha_la_LDFLAGS      = $(MODULECOMMONFLAGS)
filters_rmsealpha_la_LIBADD       = $(MAGICKCORE_LIBS) $(MATH_LIBS)

# addend filter module
filters_addend_la_SOURCES      = filters/addend.c
filters_addend_la_CPPFLAGS     = $(MAGICK_FILTER_CPPFLAGS)
filters_addend_la_LDFLAGS      = $(MODULECOMMONFLAGS)
filters_addend_la_LIBADD       = $(MAGICKCORE_LIBS) $(MATH_LIBS)

# replacelast filter module
filters_replacelast_la_SOURCES      = filters/replacelast.c
filters_replacelast_la_CPPFLAGS     = $(MAGICK_FILTER_CPPFLAGS)
filters_replacelast_la_LDFLAGS      = $(MODULECOMMONFLAGS)
filters_replacelast_la_LIBADD       = $(MAGICKCORE_LIBS) $(MATH_LIBS)

# replacefirst filter module
filters_replacefirst_la_SOURCES      = filters/replacefirst.c
filters_replacefirst_la_CPPFLAGS     = $(MAGICK_FILTER_CPPFLAGS)
filters_replacefirst_la_LDFLAGS      = $(MODULECOMMONFLAGS)
filters_replacefirst_la_LIBADD       = $(MAGICKCORE_LIBS) $(MATH_LIBS)

# replaceall filter module
filters_replaceall_la_SOURCES      = filters/replaceall.c
filters_replaceall_la_CPPFLAGS     = $(MAGICK_FILTER_CPPFLAGS)
filters_replaceall_la_LDFLAGS      = $(MODULECOMMONFLAGS)
filters_replaceall_la_LIBADD       = $(MAGICKCORE_LIBS) $(MATH_LIBS)

# replaceeach filter module
filters_replaceeach_la_SOURCES      = filters/replaceeach.c
filters_replaceeach_la_CPPFLAGS     = $(MAGICK_FILTER_CPPFLAGS)
filters_replaceeach_la_LDFLAGS      = $(MODULECOMMONFLAGS)
filters_replaceeach_la_LIBADD       = $(MAGICKCORE_LIBS) $(MATH_LIBS)

# replacespec filter module
filters_replacespec_la_SOURCES      = filters/replacespec.c
filters_replacespec_la_CPPFLAGS     = $(MAGICK_FILTER_CPPFLAGS)
filters_replacespec_la_LDFLAGS      = $(MODULECOMMONFLAGS)
filters_replacespec_la_LIBADD       = $(MAGICKCORE_LIBS) $(MATH_LIBS)

# grad2 filter module
filters_grad2_la_SOURCES      = filters/grad2.c
filters_grad2_la_CPPFLAGS     = $(MAGICK_FILTER_CPPFLAGS)
filters_grad2_la_LDFLAGS      = $(MODULECOMMONFLAGS)
filters_grad2_la_LIBADD       = $(MAGICKCORE_LIBS) $(MATH_LIBS)

# drawcirc filter module
filters_drawcirc_la_SOURCES      = filters/drawcirc.c
filters_drawcirc_la_CPPFLAGS     = $(MAGICK_FILTER_CPPFLAGS)
filters_drawcirc_la_LDFLAGS      = $(MODULECOMMONFLAGS)
filters_drawcirc_la_LIBADD       = $(MAGICKCORE_LIBS) $(MATH_LIBS)

# sortpixels filter module
filters_sortpixels_la_SOURCES      = filters/sortpixels.c
filters_sortpixels_la_CPPFLAGS     = $(MAGICK_FILTER_CPPFLAGS)
filters_sortpixels_la_LDFLAGS      = $(MODULECOMMONFLAGS)
filters_sortpixels_la_LIBADD       = $(MAGICKCORE_LIBS) $(MATH_LIBS)

# sortpixelsblue filter module
filters_sortpixelsblue_la_SOURCES      = filters/sortpixelsblue.c
filters_sortpixelsblue_la_CPPFLAGS     = $(MAGICK_FILTER_CPPFLAGS)
filters_sortpixelsblue_la_LDFLAGS      = $(MODULECOMMONFLAGS)
filters_sortpixelsblue_la_LIBADD       = $(MAGICKCORE_LIBS) $(MATH_LIBS)

# shadowsortpixels filter module
filters_shadowsortpixels_la_SOURCES      = filters/shadowsortpixels.c
filters_shadowsortpixels_la_CPPFLAGS     = $(MAGICK_FILTER_CPPFLAGS)
filters_shadowsortpixels_la_LDFLAGS      = $(MODULECOMMONFLAGS)
filters_shadowsortpixels_la_LIBADD       = $(MAGICKCORE_LIBS) $(MATH_LIBS)

# img2knl filter module
filters_img2knl_la_SOURCES      = filters/img2knl.c
filters_img2knl_la_CPPFLAGS     = $(MAGICK_FILTER_CPPFLAGS)
filters_img2knl_la_LDFLAGS      = $(MODULECOMMONFLAGS)
filters_img2knl_la_LIBADD       = $(MAGICKCORE_LIBS) $(MATH_LIBS)

# interppix filter module
filters_interppix_la_SOURCES      = filters/interppix.c
filters_interppix_la_CPPFLAGS     = $(MAGICK_FILTER_CPPFLAGS)
filters_interppix_la_LDFLAGS      = $(MODULECOMMONFLAGS)
filters_interppix_la_LIBADD       = $(MAGICKCORE_LIBS) $(MATH_LIBS)

# mkgauss filter module
filters_mkgauss_la_SOURCES      = filters/mkgauss.c
filters_mkgauss_la_CPPFLAGS     = $(MAGICK_FILTER_CPPFLAGS)
filters_mkgauss_la_LDFLAGS      = $(MODULECOMMONFLAGS)
filters_mkgauss_la_LIBADD       = $(MAGICKCORE_LIBS) $(MATH_LIBS)

# applines filter module
filters_applines_la_SOURCES      = filters/applines.c
filters_applines_la_CPPFLAGS     = $(MAGICK_FILTER_CPPFLAGS)
filters_applines_la_LDFLAGS      = $(MODULECOMMONFLAGS)
filters_applines_la_LIBADD       = $(MAGICKCORE_LIBS) $(MATH_LIBS)

# cumulhisto filter module
filters_cumulhisto_la_SOURCES      = filters/cumulhisto.c
filters_cumulhisto_la_CPPFLAGS     = $(MAGICK_FILTER_CPPFLAGS)
filters_cumulhisto_la_LDFLAGS      = $(MODULECOMMONFLAGS)
filters_cumulhisto_la_LIBADD       = $(MAGICKCORE_LIBS) $(MATH_LIBS)

# invdispmap filter module
filters_invdispmap_la_SOURCES      = filters/invdispmap.c
filters_invdispmap_la_CPPFLAGS     = $(MAGICK_FILTER_CPPFLAGS)
filters_invdispmap_la_LDFLAGS      = $(MODULECOMMONFLAGS)
filters_invdispmap_la_LIBADD       = $(MAGICKCORE_LIBS) $(MATH_LIBS)

# mkhisto filter module
filters_mkhisto_la_SOURCES      = filters/mkhisto.c
filters_mkhisto_la_CPPFLAGS     = $(MAGICK_FILTER_CPPFLAGS)
filters_mkhisto_la_LDFLAGS      = $(MODULECOMMONFLAGS)
filters_mkhisto_la_LIBADD       = $(MAGICKCORE_LIBS) $(MATH_LIBS)

# geodist filter module
filters_geodist_la_SOURCES      = filters/geodist.c
filters_geodist_la_CPPFLAGS     = $(MAGICK_FILTER_CPPFLAGS)
filters_geodist_la_LDFLAGS      = $(MODULECOMMONFLAGS)
filters_geodist_la_LIBADD       = $(MAGICKCORE_LIBS) $(MATH_LIBS)

# invclut filter module
filters_invclut_la_SOURCES      = filters/invclut.c
filters_invclut_la_CPPFLAGS     = $(MAGICK_FILTER_CPPFLAGS)
filters_invclut_la_LDFLAGS      = $(MODULECOMMONFLAGS)
filters_invclut_la_LIBADD       = $(MAGICKCORE_LIBS) $(MATH_LIBS)

# rect2eqfish filter module
filters_rect2eqfish_la_SOURCES      = filters/rect2eqfish.c
filters_rect2eqfish_la_CPPFLAGS     = $(MAGICK_FILTER_CPPFLAGS)
filters_rect2eqfish_la_LDFLAGS      = $(MODULECOMMONFLAGS)
filters_rect2eqfish_la_LIBADD       = $(MAGICKCORE_LIBS) $(MATH_LIBS)

# growcut filter module
filters_growcut_la_SOURCES      = filters/growcut.c
filters_growcut_la_CPPFLAGS     = $(MAGICK_FILTER_CPPFLAGS)
filters_growcut_la_LDFLAGS      = $(MODULECOMMONFLAGS)
filters_growcut_la_LIBADD       = $(MAGICKCORE_LIBS) $(MATH_LIBS)

# fillholes filter module
filters_fillholes_la_SOURCES      = filters/fillholes.c
filters_fillholes_la_CPPFLAGS     = $(MAGICK_FILTER_CPPFLAGS)
filters_fillholes_la_LDFLAGS      = $(MODULECOMMONFLAGS)
filters_fillholes_la_LIBADD       = $(MAGICKCORE_LIBS) $(MATH_LIBS)

# fillholespri filter module
filters_fillholespri_la_SOURCES      = filters/fillholespri.c
filters_fillholespri_la_CPPFLAGS     = $(MAGICK_FILTER_CPPFLAGS)
filters_fillholespri_la_LDFLAGS      = $(MODULECOMMONFLAGS)
filters_fillholespri_la_LIBADD       = $(MAGICKCORE_LIBS) $(MATH_LIBS)

# pixmatch filter module
filters_pixmatch_la_SOURCES      = filters/pixmatch.c
filters_pixmatch_la_CPPFLAGS     = $(MAGICK_FILTER_CPPFLAGS)
filters_pixmatch_la_LDFLAGS      = $(MODULECOMMONFLAGS)
filters_pixmatch_la_LIBADD       = $(MAGICKCORE_LIBS) $(MATH_LIBS)

# darkestpath filter module
filters_darkestpath_la_SOURCES      = filters/darkestpath.c
filters_darkestpath_la_CPPFLAGS     = $(MAGICK_FILTER_CPPFLAGS)
filters_darkestpath_la_LDFLAGS      = $(MODULECOMMONFLAGS)
filters_darkestpath_la_LIBADD       = $(MAGICKCORE_LIBS) $(MATH_LIBS)

# darkestmeander filter module
filters_darkestmeander_la_SOURCES      = filters/darkestmeander.c
filters_darkestmeander_la_CPPFLAGS     = $(MAGICK_FILTER_CPPFLAGS)
filters_darkestmeander_la_LDFLAGS      = $(MODULECOMMONFLAGS)
filters_darkestmeander_la_LIBADD       = $(MAGICKCORE_LIBS) $(MATH_LIBS)

# darkestpntpnt filter module
filters_darkestpntpnt_la_SOURCES      = filters/darkestpntpnt.c
filters_darkestpntpnt_la_CPPFLAGS     = $(MAGICK_FILTER_CPPFLAGS)
filters_darkestpntpnt_la_LDFLAGS      = $(MODULECOMMONFLAGS)
filters_darkestpntpnt_la_LIBADD       = $(MAGICKCORE_LIBS) $(MATH_LIBS)

# srt3d filter module
filters_srt3d_la_SOURCES      = filters/srt3d.c
filters_srt3d_la_CPPFLAGS     = $(MAGICK_FILTER_CPPFLAGS)
filters_srt3d_la_LDFLAGS      = $(MODULECOMMONFLAGS)
filters_srt3d_la_LIBADD       = $(MAGICKCORE_LIBS) $(MATH_LIBS)

# arctan2 filter module
filters_arctan2_la_SOURCES      = filters/arctan2.c
filters_arctan2_la_CPPFLAGS     = $(MAGICK_FILTER_CPPFLAGS)
filters_arctan2_la_LDFLAGS      = $(MODULECOMMONFLAGS)
filters_arctan2_la_LIBADD       = $(MAGICKCORE_LIBS) $(MATH_LIBS)

# pause filter module
filters_pause_la_SOURCES      = filters/pause.c
filters_pause_la_CPPFLAGS     = $(MAGICK_FILTER_CPPFLAGS)
filters_pause_la_LDFLAGS      = $(MODULECOMMONFLAGS)
filters_pause_la_LIBADD       = $(MAGICKCORE_LIBS) $(MATH_LIBS)

# shell filter module
filters_shell_la_SOURCES      = filters/shell.c
filters_shell_la_CPPFLAGS     = $(MAGICK_FILTER_CPPFLAGS)
filters_shell_la_LDFLAGS      = $(MODULECOMMONFLAGS)
filters_shell_la_LIBADD       = $(MAGICKCORE_LIBS) $(MATH_LIBS)

# sphaldcl filter module
filters_sphaldcl_la_SOURCES      = filters/sphaldcl.c
filters_sphaldcl_la_CPPFLAGS     = $(MAGICK_FILTER_CPPFLAGS)
filters_sphaldcl_la_LDFLAGS      = $(MODULECOMMONFLAGS)
filters_sphaldcl_la_LIBADD       = $(MAGICKCORE_LIBS) $(MATH_LIBS)

# setmnsd filter module
filters_setmnsd_la_SOURCES      = filters/setmnsd.c
filters_setmnsd_la_CPPFLAGS     = $(MAGICK_FILTER_CPPFLAGS)
filters_setmnsd_la_LDFLAGS      = $(MODULECOMMONFLAGS)
filters_setmnsd_la_LIBADD       = $(MAGICKCORE_LIBS) $(MATH_LIBS)

# integim filter module
filters_integim_la_SOURCES      = filters/integim.c
filters_integim_la_CPPFLAGS     = $(MAGICK_FILTER_CPPFLAGS)
filters_integim_la_LDFLAGS      = $(MODULECOMMONFLAGS)
filters_integim_la_LIBADD       = $(MAGICKCORE_LIBS) $(MATH_LIBS)

# deintegim filter module
filters_deintegim_la_SOURCES      = filters/deintegim.c
filters_deintegim_la_CPPFLAGS     = $(MAGICK_FILTER_CPPFLAGS)
filters_deintegim_la_LDFLAGS      = $(MODULECOMMONFLAGS)
filters_deintegim_la_LIBADD       = $(MAGICKCORE_LIBS) $(MATH_LIBS)

# deintegim2 filter module
filters_deintegim2_la_SOURCES      = filters/deintegim2.c
filters_deintegim2_la_CPPFLAGS     = $(MAGICK_FILTER_CPPFLAGS)
filters_deintegim2_la_LDFLAGS      = $(MODULECOMMONFLAGS)
filters_deintegim2_la_LIBADD       = $(MAGICKCORE_LIBS) $(MATH_LIBS)

# kcluster filter module
filters_kcluster_la_SOURCES      = filters/kcluster.c
filters_kcluster_la_CPPFLAGS     = $(MAGICK_FILTER_CPPFLAGS)
filters_kcluster_la_LDFLAGS      = $(MODULECOMMONFLAGS)
filters_kcluster_la_LIBADD       = $(MAGICKCORE_LIBS) $(MATH_LIBS)

# colredmrge filter module
# filters_colredmrge_la_SOURCES      = filters/colredmrge.c
# filters_colredmrge_la_CPPFLAGS     = $(MAGICK_FILTER_CPPFLAGS)
# filters_colredmrge_la_LDFLAGS      = $(MODULECOMMONFLAGS)
# filters_colredmrge_la_LIBADD       = $(MAGICKCORE_LIBS) $(MATH_LIBS)

# srchimg filter module
filters_srchimg_la_SOURCES      = filters/srchimg.c
filters_srchimg_la_CPPFLAGS     = $(MAGICK_FILTER_CPPFLAGS)
filters_srchimg_la_LDFLAGS      = $(MODULECOMMONFLAGS)
filters_srchimg_la_LIBADD       = $(MAGICKCORE_LIBS) $(MATH_LIBS)

# paintpatches filter module
filters_paintpatches_la_SOURCES      = filters/paintpatches.c
filters_paintpatches_la_CPPFLAGS     = $(MAGICK_FILTER_CPPFLAGS)
filters_paintpatches_la_LDFLAGS      = $(MODULECOMMONFLAGS)
filters_paintpatches_la_LIBADD       = $(MAGICKCORE_LIBS) $(MATH_LIBS)

# avgconcrings filter module
filters_avgconcrings_la_SOURCES      = filters/avgconcrings.c
filters_avgconcrings_la_CPPFLAGS     = $(MAGICK_FILTER_CPPFLAGS)
filters_avgconcrings_la_LDFLAGS      = $(MODULECOMMONFLAGS)
filters_avgconcrings_la_LIBADD       = $(MAGICKCORE_LIBS) $(MATH_LIBS)

# findsinks filter module
filters_findsinks_la_SOURCES      = filters/findsinks.c
filters_findsinks_la_CPPFLAGS     = $(MAGICK_FILTER_CPPFLAGS)
filters_findsinks_la_LDFLAGS      = $(MODULECOMMONFLAGS)
filters_findsinks_la_LIBADD       = $(MAGICKCORE_LIBS) $(MATH_LIBS)

# multisrch filter module
filters_multisrch_la_SOURCES      = filters/multisrch.c
filters_multisrch_la_CPPFLAGS     = $(MAGICK_FILTER_CPPFLAGS)
filters_multisrch_la_LDFLAGS      = $(MODULECOMMONFLAGS)
filters_multisrch_la_LIBADD       = $(MAGICKCORE_LIBS) $(MATH_LIBS)

# whatrot filter module
filters_whatrot_la_SOURCES      = filters/whatrot.c
filters_whatrot_la_CPPFLAGS     = $(MAGICK_FILTER_CPPFLAGS)
filters_whatrot_la_LDFLAGS      = $(MODULECOMMONFLAGS)
filters_whatrot_la_LIBADD       = $(MAGICKCORE_LIBS) $(MATH_LIBS)

# whatscale filter module
filters_whatscale_la_SOURCES      = filters/whatscale.c
filters_whatscale_la_CPPFLAGS     = $(MAGICK_FILTER_CPPFLAGS)
filters_whatscale_la_LDFLAGS      = $(MODULECOMMONFLAGS)
filters_whatscale_la_LIBADD       = $(MAGICKCORE_LIBS) $(MATH_LIBS)

# whatrotscale filter module
filters_whatrotscale_la_SOURCES      = filters/whatrotscale.c
filters_whatrotscale_la_CPPFLAGS     = $(MAGICK_FILTER_CPPFLAGS)
filters_whatrotscale_la_LDFLAGS      = $(MODULECOMMONFLAGS)
filters_whatrotscale_la_LIBADD       = $(MAGICKCORE_LIBS) $(MATH_LIBS)

# srchmask filter module
filters_srchmask_la_SOURCES      = filters/srchmask.c
filters_srchmask_la_CPPFLAGS     = $(MAGICK_FILTER_CPPFLAGS)
filters_srchmask_la_LDFLAGS      = $(MODULECOMMONFLAGS)
filters_srchmask_la_LIBADD       = $(MAGICKCORE_LIBS) $(MATH_LIBS)

# aggrsrch filter module
filters_aggrsrch_la_SOURCES      = filters/aggrsrch.c
filters_aggrsrch_la_CPPFLAGS     = $(MAGICK_FILTER_CPPFLAGS)
filters_aggrsrch_la_LDFLAGS      = $(MODULECOMMONFLAGS)
filters_aggrsrch_la_LIBADD       = $(MAGICKCORE_LIBS) $(MATH_LIBS)

# cols2mat filter module
filters_cols2mat_la_SOURCES      = filters/cols2mat.c
filters_cols2mat_la_CPPFLAGS     = $(MAGICK_FILTER_CPPFLAGS)
filters_cols2mat_la_LDFLAGS      = $(MODULECOMMONFLAGS)
filters_cols2mat_la_LIBADD       = $(MAGICKCORE_LIBS) $(MATH_LIBS)

# oogbox filter module
filters_oogbox_la_SOURCES      = filters/oogbox.c
filters_oogbox_la_CPPFLAGS     = $(MAGICK_FILTER_CPPFLAGS)
filters_oogbox_la_LDFLAGS      = $(MODULECOMMONFLAGS)
filters_oogbox_la_LIBADD       = $(MAGICKCORE_LIBS) $(MATH_LIBS)

# centsmcrop filter module
filters_centsmcrop_la_SOURCES      = filters/centsmcrop.c
filters_centsmcrop_la_CPPFLAGS     = $(MAGICK_FILTER_CPPFLAGS)
filters_centsmcrop_la_LDFLAGS      = $(MODULECOMMONFLAGS)
filters_centsmcrop_la_LIBADD       = $(MAGICKCORE_LIBS) $(MATH_LIBS)

# plotrg filter module
filters_plotrg_la_SOURCES      = filters/plotrg.c
filters_plotrg_la_CPPFLAGS     = $(MAGICK_FILTER_CPPFLAGS)
filters_plotrg_la_LDFLAGS      = $(MODULECOMMONFLAGS)
filters_plotrg_la_LIBADD       = $(MAGICKCORE_LIBS) $(MATH_LIBS)

# centroid filter module
filters_centroid_la_SOURCES      = filters/centroid.c
filters_centroid_la_CPPFLAGS     = $(MAGICK_FILTER_CPPFLAGS)
filters_centroid_la_LDFLAGS      = $(MODULECOMMONFLAGS)
filters_centroid_la_LIBADD       = $(MAGICKCORE_LIBS) $(MATH_LIBS)

# barymap filter module
filters_barymap_la_SOURCES      = filters/barymap.c
filters_barymap_la_CPPFLAGS     = $(MAGICK_FILTER_CPPFLAGS)
filters_barymap_la_LDFLAGS      = $(MODULECOMMONFLAGS)
filters_barymap_la_LIBADD       = $(MAGICKCORE_LIBS) $(MATH_LIBS)

# rhotheta filter module
filters_rhotheta_la_SOURCES      = filters/rhotheta.c
filters_rhotheta_la_CPPFLAGS     = $(MAGICK_FILTER_CPPFLAGS)
filters_rhotheta_la_LDFLAGS      = $(MODULECOMMONFLAGS)
filters_rhotheta_la_LIBADD       = $(MAGICKCORE_LIBS) $(MATH_LIBS)

# colsp012 filter module
filters_colsp012_la_SOURCES      = filters/colsp012.c
filters_colsp012_la_CPPFLAGS     = $(MAGICK_FILTER_CPPFLAGS)
filters_colsp012_la_LDFLAGS      = $(MODULECOMMONFLAGS)
filters_colsp012_la_LIBADD       = $(MAGICKCORE_LIBS) $(MATH_LIBS)

eqLimit.bat

rem From image %1,
rem make contrast-limited histogram-equalised (with iterative redistribution) version.
@rem
@rem Optional parameters:
@rem   %2 is limiting factor SD_FACT, so limit = mean + SD_FACT * standard_deviation.
@rem        Default 1.
@rem   %3 is percentage lift for shadows. Maximum < 100. Default 0, no lift.
@rem   %4 is percentage drop for highlights. Maximum < 100. Default 0, no drop.
@rem   %5 is output file, or null: for no output.
@rem
@rem Can also use:
@rem   eqlDEBUG if 1, also creates curve histograms.
@rem   eqlDIFF_LIMIT iteration stops when the count redistributed is less than this percentage.
@rem     Set to a value >= 100 to prevent iteration.
@rem     Default 1.0.
@rem   eqlSUPPRESS_OUT if 1, suppresses output.
@rem
@rem Updated:
@rem   26-July-2022 for IM v7.
@rem


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

@setlocal enabledelayedexpansion

@call echoOffSave

call %PICTBAT%setInOut %1 eql

set SD_FACT=%2
if "%SD_FACT%"=="." set SD_FACT=
if "%SD_FACT%"=="" set SD_FACT=1

set LIFT_SHADOW_PC=%3
if "%LIFT_SHADOW_PC%"=="." set LIFT_SHADOW_PC=
if "%LIFT_SHADOW_PC%"=="" set LIFT_SHADOW_PC=0

set DROP_HIGHLIGHT_PC=%4
if "%DROP_HIGHLIGHT_PC%"=="." set DROP_HIGHLIGHT_PC=
if "%DROP_HIGHLIGHT_PC%"=="" set DROP_HIGHLIGHT_PC=0

if not "%5"=="" set OUTFILE=%5

if "%5"=="" (
  set EQL_BASE=%~n1_%sioCODE%
) else (
  set EQL_BASE=%~n5_%sioCODE%
)

if "%eqlDIFF_LIMIT%"=="" set eqlDIFF_LIMIT=1

set TMPEXT=miff

for /F "usebackq" %%L in (`cygpath %TEMP%`) do set CYGTEMP=%%L

%IM7DEV%magick ^
  %INFILE% ^
  -colorspace Lab -channel R -separate -set colorspace sRGB ^
  -process 'mkhisto norm' ^
  %CYGTEMP%\%EQL_BASE%_gch.%TMPEXT%

if ERRORLEVEL 1 exit /B 1	

for /F "usebackq" %%L in (`%IMG7%magick ^
  %TEMP%\%EQL_BASE%_gch.%TMPEXT% ^
  -precision 15 ^
  -format "histcap=%%[fx:(mean+%SD_FACT%*standard_deviation)*100]" ^
  info:`) do set %%L

echo %0: histcap=%histcap% 


set nIter=0
:loop

%IM7DEV%magick ^
  %CYGTEMP%\%EQL_BASE%_gch.%TMPEXT% ^
  -channel RGB ^
  -evaluate Min %histcap%%% ^
  +channel ^
  %CYGTEMP%\%EQL_BASE%_gchc_cap.%TMPEXT%

for /F "usebackq" %%L in (`%IMG7%magick ^
  %TEMP%\%EQL_BASE%_gch.%TMPEXT% ^
  %TEMP%\%EQL_BASE%_gchc_cap.%TMPEXT% ^
  -compose MinusSrc -composite ^
  -precision 15 ^
  -format "MeanDiffPC=%%[fx:mean*100]" ^
  info:`) do set %%L

echo %0: MeanDiffPC=%MeanDiffPC%

%IM7DEV%magick ^
  %CYGTEMP%\%EQL_BASE%_gch.%TMPEXT% ^
  -channel RGB ^
  -evaluate Min %histcap%%% ^
  +channel ^
  -evaluate Add %MeanDiffPC%%% ^
  %CYGTEMP%\%EQL_BASE%_gch.%TMPEXT%

for /F "usebackq" %%L in (`%IMG7%magick identify ^
  -format "DO_LOOP=%%[fx:%MeanDiffPC%>%eqlDIFF_LIMIT%?1:0]" ^
  xc:`) do set %%L

rem If max(eql_gch) > histcap + epsilon, repeat.
rem OR
rem if %MeanDiffPC% > epsilon, repeat

set /A nIter+=1

if %DO_LOOP%==1 goto loop

echo %0: nIter=%nIter%


if %LIFT_SHADOW_PC%==0 if %DROP_HIGHLIGHT_PC%==0 %IM7DEV%magick ^
  %CYGTEMP%\%EQL_BASE%_gch.%TMPEXT% ^
  -auto-level ^
  %CYGTEMP%\%EQL_BASE%_gch.%TMPEXT%

if %LIFT_SHADOW_PC%==0 goto skipShad
set WX=0

rem In following, "-blur" should really be "decomb".
set DECOMB=-blur 0x1

for /F "usebackq tokens=2 delims=:, " %%A in (`%IM7DEV%magick ^
  %CYGTEMP%\%EQL_BASE%_gch.%TMPEXT% ^
  -auto-level ^
  +write %CYGTEMP%\%EQL_BASE%_gch.%TMPEXT% ^
  %DECOMB% ^
  -threshold %LIFT_SHADOW_PC%%% ^
  -process onewhite ^
  NULL: 2^>^&1`) do set WX=%%A

rem %0: echo WX=%WX%

if "%WX%"=="none" (
  %IMG7%magick ^
  %TEMP%\%EQL_BASE%_gch.%TMPEXT% ^
  -fill #fff -colorize 100 ^
  %TEMP%\%EQL_BASE%_gch.%TMPEXT%
) else if not %WX%==0 %IM7DEV%magick ^
  %CYGTEMP%\%EQL_BASE%_gch.%TMPEXT% ^
  -size %WX%x1 xc:gray(%LIFT_SHADOW_PC%%%) ^
  -gravity West -composite ^
  %CYGTEMP%\%EQL_BASE%_gch.%TMPEXT%

:skipShad


if %DROP_HIGHLIGHT_PC%==0 goto skipHigh
set WX=0

rem In following, "-blur" should really be "decomb".
set DECOMB=-blur 0x1

for /F "usebackq tokens=2 delims=:, " %%A in (`%IM7DEV%magick ^
  %CYGTEMP%\%EQL_BASE%_gch.%TMPEXT% ^
  -auto-level ^
  +write %CYGTEMP%\%EQL_BASE%_gch.%TMPEXT% ^
  %DECOMB% ^
  -threshold %DROP_HIGHLIGHT_PC%%% ^
  -flop ^
  -process onewhite ^
  NULL: 2^>^&1`) do set WX=%%A

rem %0: echo WX=%WX%

if "%WX%"=="none" (
  %IMG7%magick ^
  %TEMP%\%EQL_BASE%_gch.%TMPEXT% ^
  -fill #fff -colorize 100 ^
  %TEMP%\%EQL_BASE%_gch.%TMPEXT%
) else if not %WX%==0 %IM7DEV%magick ^
  %CYGTEMP%\%EQL_BASE%_gch.%TMPEXT% ^
  -size %WX%x1 xc:gray(%DROP_HIGHLIGHT_PC%%%) ^
  -gravity East -composite ^
  %CYGTEMP%\%EQL_BASE%_gch.%TMPEXT%

:skipHigh


%IM7DEV%magick ^
  %CYGTEMP%\%EQL_BASE%_gch.%TMPEXT% ^
  -process 'cumulhisto norm' ^
  %CYGTEMP%\%EQL_BASE%_gchc_clhe_red.%TMPEXT%



if /I "%OUTFILE%" NEQ "null:" if not "%eqlSUPPRESS_OUT%"=="1" %IMG7%magick ^
  %INFILE% ^
  %TEMP%\%EQL_BASE%_gchc_clhe_red.%TMPEXT% ^
  -clut ^
  %OUTFILE%


if "%eqlDEBUG%"=="1" (
  call %PICTBAT%graphLineCol %TEMP%\%EQL_BASE%_gch.%TMPEXT% . . 0 %EQL_BASE%_gch.png
  call %PICTBAT%graphLineCol %TEMP%\%EQL_BASE%_gchc_cap.%TMPEXT% . . 0 %EQL_BASE%_gchc_cap.png
  call %PICTBAT%graphLineCol %TEMP%\%EQL_BASE%_gchc_clhe_red.%TMPEXT% . . 0 %EQL_BASE%_gchc_clhe_red.png
)


call echoRestore

@endlocal & set eqlOUTFILE=%OUTFILE% &set eqlCLUT=%TEMP%\%EQL_BASE%_gchc_clhe_red.%TMPEXT%

matchHisto.bat

rem Makes version of image %1 with histogram that matches the histogram of image %2.
rem Optional %3 is output file.
@rem
@rem Can also use:
@rem   mhCOL_SP_IN eg -colorspace Lab -channel R
@rem   mhCOL_SP_OUT eg +channel -colorspace sRGB
@rem   mhDEBUG if 1, makes a file of the clut used.
@rem
@rem Updated:
@rem   27-July-2022 for IM v7.
@rem


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

@setlocal enabledelayedexpansion

@call echoOffSave

call %PICTBAT%setInOut %1 mh

if not "%3"=="" set OUTFILE=%3

set DBG_NAME=
set WR_CLUT=
set DBG_H1=
set DBG_H2=
set WR_DBG_H1=
set WR_DBG_H2=

if "%mhDEBUG%"=="1" (
  set DBG_NAME=%INNAME%_mhcl.miff
  set WR_CLUT= ^( +clone +write %INNAME%_mhcl.miff +delete ^)
) else if "%mhDEBUG%"=="2" (
  set DBG_NAME=%INNAME%_mhcl.miff
  set WR_CLUT= ^( +clone +write %INNAME%_mhcl.miff +delete ^)
  set DBG_H1=%INNAME%_h1.miff
  set WR_DBG_H1=+write !DBG_H1!
  set DBG_H2=%INNAME%_h2.miff
  set WR_DBG_H2=+write !DBG_H2!
)

%IM7DEV%magick ^
  %INFILE% ^
  %mhCOL_SP_IN% ^
  ( -clone 0 ^
    -process 'mkhisto cumul norm v' ^
    %WR_DBG_H1% ^
  ) ^
  ( %2 ^
    %mhCOL_SP_IN% ^
    -process 'mkhisto cumul norm v' ^
    %WR_DBG_H2% ^
    -process 'mkhisto cumul norm v' ^
  ) ^
  ( -clone 1-2 -clut ) ^
  -delete 1-2 ^
  %WR_CLUT% ^
  -clut ^
  %mhCOL_SP_OUT% ^
  %OUTFILE%

if ERRORLEVEL 1 exit /B1

if not "%DBG_NAME%"=="" (
  echo Also created %DBG_NAME%
  call %PICTBAT%graphLineCol %DBG_NAME%
)

if not "%DBG_H1%"=="" (
  echo Also created %DBG_H1%
  call %PICTBAT%graphLineCol %DBG_H1%
)

if not "%DBG_H2%"=="" (
  echo Also created %DBG_H2%
  call %PICTBAT%graphLineCol %DBG_H2%
)

call echoRestore

@endlocal & set mhOUTFILE=%OUTFILE%

knl2img.bat

rem Given %1 a quoted kernel string 
rem  or name of text file, prefixed with "@",
rem makes image %2 of that kernel.
rem %3 is optional scale parameter, format:
rem   {kernel_scale}[!^] [,{origin_addition}] [%]
rem %4 is post-processing, eg "-auto-level".
@rem
@rem Updated:
@rem   1-August-2022 for IM v7. Assumes magick is HDRI.
@rem

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

@setlocal enabledelayedexpansion

@call echoOffSave

call %PICTBAT%setInOut %1 k2i

set qKNL=%1
set sKNL=%~1
set OUTFILE=%2

set SCALE=%~3
if "%SCALE%"=="." set SCALE=

set sPOST=%~4
if "%sPOST%"=="." set sPOST=


if "%SCALE%"=="" (
  set sSCALE=
) else (
  set sSCALE=-define "convolve:scale=%SCALE%"
)

if "%sPOST%"=="." set sPOST=

echo sSCALE=%sSCALE%  sPOST=%sPOST%

:: We use an impulse image, same size as kernel,
:: black but with white pixel at kernel origin.

:: Sample showkernel output:
::   Kernel "Blur" of size 41x1+20+0 with values from 0 to 0.0796737
:: But "User defined" (two words).

set WW=
set IS_FIRST=1
for /F "usebackq tokens=* eol=: delims= " %%A in (`%IMG7%magick ^
  xc: ^
  -define morphology:showkernel^=1 ^
  -morphology convolve:0 %qKNL% ^
  NULL: 2^>^&1`) do if !IS_FIRST!==1 (
  set SIZE=%%A
  set IS_FIRST=0
)

for /F "tokens=1-4 eol=: delims=x+ " %%A in ("%SIZE:*size =%") do (
  set WW=%%A
  set HH=%%B
  set X=%%C
  set Y=%%D
)

if "%WW%"=="" exit /B 1
if %WW% LSS 0 exit /B 1
if %WW% GTR a exit /B 1
if %WW% GTR A exit /B 1

echo %0: %WW%x%HH%+%X%+%Y%

%IMG7%magick ^
  -size %WW%x%HH% xc:Black ^
  -fill White -draw "point %X%,%Y%" ^
  -alpha off ^
  -define morphology:showkernel^=1 ^
  %sSCALE% ^
  -morphology convolve %qKNL% ^
  %sPOST% ^
  %OUTFILE%

call echoRestore

@endlocal & set k2iOUTFILE=%OUTFILE%

img2knl4.bat

rem Given %1, an image with RGBA channels,
rem creates four kernel strings,
rem writing to environment variable prefix %2, suffixed _R, _G, _B and _A.
rem
rem CAUTION: Do not use this with large kernels, eg > 100 pixels.
@rem
@rem Updated:
@rem   1-August-2022 for IM v7.
@rem

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

@setlocal enabledelayedexpansion

@call echoOffSave

call %PICTBAT%setInOut %1 i2k

set N=0
set sR=
for /F "usebackq" %%L in (`%IM7DEV%magick ^
  %INFILE% ^
  -channel RGBA ^
  -separate ^
  +channel ^
  -process img2knl ^
  NULL:`) do (
  if !N!==0 set sR=%%L
  if !N!==1 set sG=%%L
  if !N!==2 set sB=%%L
  if !N!==3 set sA=%%L
  set /A N+=1
)

if "%sR%"=="" exit /B 1

call echoRestore

endlocal & set i2kOUTFILE=%OUTFILE%& set %2_R=%sR%& set %2_G=%sG%& set %2_B=%sB%& set %2_A=%sA%

img2knl4f.bat

rem Given %1, an image with RGBA channels,
rem creates four kernel strings,
rem writing to text files prefix %2, suffixed _R, _G, _B and _A.
rem %3 is list of channels to extract, any of RGBA any case.
rem %4 is string to insert before the colon.
rem
rem FIXME: When "-process img2knl" can insert text before the colon, use that instead of chStrs.
@rem
@rem Updated:
@rem   1-August-2022 for IM v7. Assumes magick is HDRI.
@rem

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

@setlocal enabledelayedexpansion

@call echoOffSave

call %PICTBAT%setInOut %1 i2kf

set OUTPREF=%~dpn2
set OUTEXT=%~x2

set CHAN=%3
if "%CHAN%"=="." set CHAN=
if "%CHAN%"=="" set CHAN=RGBA

set BEF_COL=%~4
if "%BEF_COL%"=="." set BEF_COL=

call %PICTBAT%getrgba %CHAN%

if %rgbaR%==1 (
  %IM7DEV%magick ^
  %INFILE% ^
  -channel R ^
  -separate ^
  +channel ^
  -process img2knl ^
  NULL: >%OUTPREF%_R%OUTEXT%

  if not "%BEF_COL%"=="" chStrs /p0 /m1 /i%OUTPREF%_R%OUTEXT% /f":" /t%BEF_COL%:
)

if %rgbaG%==1 (
  %IM7DEV%magick ^
  %INFILE% ^
  -channel G ^
  -separate ^
  +channel ^
  -process img2knl ^
  NULL: >%OUTPREF%_G%OUTEXT%

  if not "%BEF_COL%"=="" chStrs /p0 /m1 /i%OUTPREF%_G%OUTEXT% /f":" /t%BEF_COL%:
)

if %rgbaB%==1 (
  %IM7DEV%magick ^
  %INFILE% ^
  -channel B ^
  -separate ^
  +channel ^
  -process img2knl ^
  NULL: >%OUTPREF%_B%OUTEXT%

  if not "%BEF_COL%"=="" chStrs /p0 /m1 /i%OUTPREF%_B%OUTEXT% /f":" /t%BEF_COL%:
)

if %rgbaA%==1 (
  %IM7DEV%magick ^
  %INFILE% ^
  -channel A ^
  -separate ^
  +channel ^
  -process img2knl ^
  NULL: >%OUTPREF%_A%OUTEXT%

  if not "%BEF_COL%"=="" chStrs /p0 /m1 /i%OUTPREF%_A%OUTEXT% /f":" /t%BEF_COL%:
)

dir %OUTPREF%*

call echoRestore

@endlocal

All images on this page were created by the commands shown.

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

My usual version of IM is:

%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)

This customised development version is:

%IM7DEV%magick -version
Version: ImageMagick 7.1.1-27 (Beta) Q32-HDRI x86_64 83eefaf2a:20240107 https://imagemagick.org
Copyright: (C) 1999 ImageMagick Studio LLC
License: https://imagemagick.org/script/license.php
Features: Cipher DPC HDRI Modules OpenCL 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 zstd
Compiler: gcc (11.3)

Source file for this web page is customim.h1. To re-create this web page, run "procH1 customim". Also run "procH1 zipbats" to refresh the C files in the zip.


This page, including the images, is my copyright. Anyone is permitted to use or adapt any of my code, scripts or images for any purpose, including commercial use.

My C code borrows heavily from ImageMagick code, and of course copyright for those portions remain with ImageMagick Studio LLC. Any use of my code must also abide by their conditions:

Copyright 1999-2014 ImageMagick Studio LLC, a non-profit organization dedicated to making software imaging solutions freely available.

You may not use this file except in compliance with the License. Obtain a copy of the License at

http://www.imagemagick.org/script/license.php

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.

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 21-Aug-2014.

Page created 02-Mar-2024 17:13:27.

Copyright © 2024 Alan Gibson.