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\imdevins6937\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.0.1-0\ImageMagick 
echo %IM7DEV% 
C:\cygwin64\home\Alan\imdevins7010\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 if 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 convert.exe.

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

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

%IMDEV%convert xc: -process analyze NULL:
echo %ERRORLEVEL% 
0 
%IMDEV%convert 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
allwhite srchImgAg.bat fillPix.bat histoPeaks.bat traceLines.bat
arctan2 slopeXYdirn.bat
cumulhisto sh2shPolar.bat equSlopeH.bat equSlope.bat eqLimit.bat r2shPol.bat integral.bat invDiffGrad.bat sh2shLinear.bat
darkestpath rectDp.bat tileDp.bat mebcOne.bat
darkestpntpnt traceLn.bat minDp.bat lns2ptsX.bat lns2pts.bat shapeDp.bat traceLines.bat
deintegim iiMeanSd.bat
fillholespri deEdgeFft.bat extLineEnds.bat
img2knl img2knl4.bat gaussSlpMag.bat img2knl4f.bat
integim iiMeanSd.bat
invclut r2shPol.bat invHRDM.bat sh2shPolar.bat invLogPolar.bat
midlightest membrane.bat loHiCols.bat traceLines.bat find4cornEdgeBlr.bat
mkhisto angramGr.bat analGrids.bat matchHistoLong.bat mkOvClut.bat mkLapPyr.bat mkHistoImg.bat mkGausPyr.bat matchLapHisto.bat matchHistoPyr.bat matchHisto.bat matchGauss.bat invLogPolar.bat hueChart.bat histoPeaks.bat equSlope.bat eqLimit.bat demoNoise.bat mkOvClutEnds.bat angramRGB.bat
nearestwhite nearCoast2.bat traceLines.bat lns2ptsX.bat lns2pts.bat TjuncLineEnds.bat traceContour.bat
onelightest histoPeaks.bat findVertLine.bat lgstConnComp.bat tileDp.bat
onewhite clc2pts.bat nLightest.bat nearCoast.bat histToeInt.bat minDp.bat midWhite.bat line2Grad.bat eqLimit.bat followLine.bat find4cornPolDist.bat dp2Grad.bat clc2lpbrk.bat shapeDp.bat
sortpixelsblue lns2ptsX.bat
srt3d rotCubeAnim.bat

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.

%IMDEV%convert 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 1.0000000000000000777e-15
sizeof:
  MagickRealType 8
  int 4
  long int 8
  long long int 8
  float 4
  double 8
  long double 16
  Image 13344
  ImageInfo 16912
  Quantum 8
Quantum format: [%g]
MaxMap 65535
MaxTextExtent 4096
Includes HDRI

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.

%IMDEV%convert 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
Artifact=[filename]  value=[xc:red]

 

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

%IMDEV%convert ^
  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
Artifact=[filename]  value=[gradient:blue-green]
mc=black
echostuff: Input image [1] [blue-green]  depth 32  size 1x10
  Virtual pixel method 0 [(null)]
  mattecolor: (null)
  fuzz 0
  alpha is on: no
Artifact=[filename]  value=[gradient:blue-green]

 

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

%IMDEV%convert ^
  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
Artifact=[filename]  value=[rose:]
mc=black
echostuff: Input image [1] [blue-green]  depth 32  size 1x10
  Virtual pixel method 0 [(null)]
  mattecolor: (null)
  fuzz 0
  alpha is on: no
Artifact=[filename]  value=[rose:]
mc=black
echostuff: Input image [2] [ROSE]  depth 8  size 70x46
  Virtual pixel method 0 [(null)]
  mattecolor: (null)
  fuzz 0
  alpha is on: no
Artifact=[filename]  value=[rose:]

 

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

%IMDEV%convert ^
  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
Artifact=[filename]  value=[rose:]

 

What happens if we don't clone?

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

cmd /c exit /B 0
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
Artifact=[filename]  value=[wizard:]
mc=black
echostuff: Input image [1] [blue-green]  depth 32  size 1x10
  Virtual pixel method 0 [(null)]
  mattecolor: (null)
  fuzz 0
  alpha is on: no
Artifact=[filename]  value=[wizard:]
mc=black
echostuff: Input image [2] [ROSE]  depth 8  size 70x46
  Virtual pixel method 0 [(null)]
  mattecolor: (null)
  fuzz 0
  alpha is on: no
Artifact=[filename]  value=[wizard:]
mc=black
echostuff: Input image [3] [WIZARD]  depth 8  size 480x640
  Virtual pixel method 0 [(null)]
  mattecolor: (null)
  fuzz 0
  alpha is on: no
Artifact=[filename]  value=[wizard:]

This is 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.

%IMDEV%convert ^
  -size 1x10 gradient:red-blue ^
  -precision 15 ^
  -process dumpimage ^
  NULL: 
Input image [0] [red-blue]  depth 32  size 1x10
channels=0
0,0: 4294967295,0,0,4294967295
0,1: 3817748706.66667,0,477218588.333334,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 ).

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

%IMDEV%convert ^
  toes.png ^
  ( +clone -fill khaki -colorize 50 -resize 75%% ) ^
  -process addend ^
  -process echostuff ^
  -write info: ^
  +append ^
  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
Artifact=[filename]  value=[toes.png]
Artifact=[fill]  value=[khaki]
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
Artifact=[filename]  value=[toes.png]
Artifact=[fill]  value=[khaki]
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
Artifact=[filename]  value=[toes.png]
Artifact=[fill]  value=[khaki]
toes.png[0] PNG 267x233 267x233+0+0 16-bit sRGB 320KB 0.453u 0:00.524
toes.png[1] PNG 200x175 200x175+0+0 16-bit sRGB 320KB 0.437u 0:00.504
toes.png[2] PNG 200x175 200x175+0+0 16-bit sRGB 320KB 0.281u 0:00.376
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.

%IMDEV%convert ^
  toes.png ^
  ( +clone -fill khaki -colorize 50 -resize 75%% ) ^
  -process replacelast ^
  -process echostuff ^
  -write info: ^
  +append ^
  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
Artifact=[filename]  value=[toes.png]
Artifact=[fill]  value=[khaki]
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
Artifact=[filename]  value=[toes.png]
Artifact=[fill]  value=[khaki]
toes.png[0] PNG 267x233 267x233+0+0 16-bit sRGB 320KB 0.390u 0:00.461
toes.png[1] PNG 200x175 200x175+0+0 8-bit sRGB 320KB 0.266u 0:00.271
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.

%IMDEV%convert ^
  toes.png ^
  ( +clone -fill khaki -colorize 50 -resize 75%% ) ^
  -process replacefirst ^
  -process echostuff ^
  -write info: ^
  +append ^
  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
Artifact=[filename]  value=[toes.png]
Artifact=[fill]  value=[khaki]
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
Artifact=[filename]  value=[toes.png]
Artifact=[fill]  value=[khaki]
toes.png[0] PNG 200x175 200x175+0+0 8-bit sRGB 0.297u 0:00.308
toes.png[1] PNG 200x175 200x175+0+0 16-bit sRGB 0.453u 0:00.636
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.

%IMDEV%convert ^
  toes.png ^
  ( +clone -fill khaki -colorize 50 -resize 75%% ) ^
  -process replaceall ^
  -process echostuff ^
  -write info: ^
  +append ^
  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
Artifact=[filename]  value=[toes.png]
Artifact=[fill]  value=[khaki]
toes.png PNG 200x175 200x175+0+0 8-bit sRGB 0.266u 0:00.264
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.

%IMDEV%convert ^
  toes.png ^
  ( +clone -fill khaki -colorize 50 -resize 75%% ) ^
  -process replaceeach ^
  -process echostuff ^
  -write info: ^
  +append ^
  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
Artifact=[filename]  value=[toes.png]
Artifact=[fill]  value=[khaki]
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
Artifact=[filename]  value=[toes.png]
Artifact=[fill]  value=[khaki]
toes.png[0] PNG 267x233 267x233+0+0 8-bit sRGB 0.343u 0:00.336
toes.png[1] PNG 200x175 200x175+0+0 8-bit sRGB 0.344u 0:00.353
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:

%IMDEV%convert ^
  toes.png ^
  ( +clone -fill khaki -colorize 50 -resize 75%% ) ^
  -process replacespec ^
  +append ^
  cu_repspec1.png
cu_repspec1.png
%IMDEV%convert ^
  toes.png ^
  ( +clone -fill khaki -colorize 50 -resize 75%% ) ^
  -process 'replacespec  color red size 20x20' ^
  +append ^
  cu_repspec2.png
cu_repspec2.png
%IMDEV%convert ^
  toes.png ^
  ( +clone -fill khaki -colorize 50 -resize 75%% ) ^
  -process 'replacespec  color green size x20' ^
  +append ^
  cu_repspec3.png
cu_repspec3.png
%IMDEV%convert ^
  toes.png ^
  ( +clone -fill khaki -colorize 50 -resize 75%% ) ^
  -process 'replacespec  color blue size 20x' ^
  +append ^
  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.

%IMDEV%convert ^
  toes.png ^
  ( +clone -fill khaki -colorize 50 -resize 75%% ) ^
  -process 'replacespec verbose' ^
  +append ^
  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.

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

cmd /c exit /B 0
replacespec: ERROR: unknown option [help]
convert: image filter signature mismatch `replacespec': ffffffffffffffff !=      220 @ error/module.c/InvokeDynamicImageFilter/1048.
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 ??.

%IMDEV%convert ^
  toes.png ^
  ( +clone -fill khaki -colorize 50 -resize 75%% ) ^
  -process grad2 ^
  +append ^
  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).

%IMDEV%convert ^
  toes.png ^
  ( +clone -fill khaki -colorize 50 -resize 75%% ) ^
  -process drawcirc ^
  +append ^
  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.

%IMDEV%convert 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.

%IMDEV%convert 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.

%IMDEV%convert 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:

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

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

%IM%convert ^
  toes.png ^
  -virtual-pixel Black ^
  %FX% ^
  cu_fishfx.jpg

call StopWatch 
cu_fishfx.jpg

The -fx version took this long:

0 00:00:09

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:

%IMDEV%convert ^
  xc:black ^
  xc:white ^
  -process onewhite ^
  NULL: 
onewhite: none
onewhite: 0,0

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

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

%IMDEV%convert ^
  xc:black ^
  xc:white ^
  -process nearestwhite ^
  NULL: 
nearestwhite: none
nearestwhite: 0,0

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

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

%IMDEV%convert ^
  xc:black ^
  ( +clone ) ^
  -process allwhite ^
  NULL: 
allwhite:
allwhite:
%IMDEV%convert ^
  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.

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

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

%IMDEV%convert ^
  toes.png ^
  -process sortpixels ^
  cu_sl.png
cu_sl.pngjpg

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

%IMDEV%convert ^
  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 (`%IM%identify ^
  -format "WW=%%w" ^
  toes.png`) do set %%L

%IMDEV%convert ^
  toes.png ^
  -crop x1 +repage ^
  +append ^
  -intensity Rec709Luminance ^
  -process sortpixels ^
  -flop ^
  -crop %WW%x1 ^
  -append ^
  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:

%IMDEV%convert ^
  toes.png ^
  -crop x1 +append ^
  -process sortpixels ^
  -gravity Center -crop 1x1+0+0 +repage ^
  txt: 
# ImageMagick pixel enumeration: 1,1,4294967295,srgb
0,0: (2.23927e+09,1.99966e+09,2.1352e+09)  #857877307F44  srgb(52.137%,46.5583%,49.7139%)
%IMDEV%convert ^
  toes.png ^
  -colorspace Gray ^
  -crop x1 +append ^
  -process sortpixels ^
  -gravity Center -crop 1x1+0+0 +repage ^
  txt: 
# ImageMagick pixel enumeration: 1,1,4294967295,gray
0,0: (2.0604e+09,2.0604e+09,2.0604e+09)  #7ACF7ACF7ACF  gray(47.9725%)

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.

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

%IMDEV%convert ^
  %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 (`%IMDEV%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

%IMDEV%convert ^
  %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 ^
  -crop 1x1 ^
  -format "[%%p]=%%[fx:int(%Wm1%*r+0.5)],%%[fx:int(%Hm1%*g+0.5)],%%[fx:b]\n" ^
  info: 
[0]=255,217,0.833774
[1]=255,218,0.82594
[2]=225,45,0.818281
[3]=225,46,0.815475
[4]=256,219,0.801323
[5]=255,219,0.800682
[6]=254,217,0.799995
[7]=266,37,0.793027
[8]=266,38,0.784939
[9]=256,218,0.781946

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.

%IMDEV%convert ^
  %SRC% ^
  ( cu_spb_map.miff ^
    -crop %WW%x +repage -append ^
  ) ^
  -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:

%IMDEV%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: 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.

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

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

%IMDEV%convert -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 (`%IMDEV%convert ^
  -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:

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

%IMDEV%convert ^
  -size 2x1 ^
  xc:Black xc:White +append ^
  -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.43597e+09,2.68435e+09,1.27323e-313) (0%,0%,80%,62.5%,2.96439e-321%)
interppix: 0: @0,0 (0,0,0,1.07374e+09,1.27323e-313) (0%,0%,0%,25%,2.96439e-321%)
interppix: 0: @0.5,0 (0,0,0,1.07374e+09,1.27323e-313) (0%,0%,0%,25%,2.96439e-321%)
interppix: 0: @1,0 (0,0,0,1.07374e+09,1.27323e-313) (0%,0%,0%,25%,2.96439e-321%)
interppix: 0: @1.25,0 (1.07374e+09,1.07374e+09,1.07374e+09,1.07374e+09,1.27323e-313) (25%,25%,25%,25%,2.96439e-321%)
interppix: 0: @1.5,0 (2.14748e+09,2.14748e+09,2.14748e+09,1.07374e+09,1.27323e-313) (50%,50%,50%,25%,2.96439e-321%)
interppix: 0: @2,0 (4.29497e+09,4.29497e+09,4.29497e+09,1.07374e+09,1.27323e-313) (100%,100%,100%,25%,2.96439e-321%)
interppix: 0: @2.5,0 (4.29497e+09,4.29497e+09,4.29497e+09,1.07374e+09,1.27323e-313) (100%,100%,100%,25%,2.96439e-321%)
interppix: 0: @3,0 (4.29497e+09,4.29497e+09,4.29497e+09,1.07374e+09,1.27323e-313) (100%,100%,100%,25%,2.96439e-321%)
interppix: 0: @3.5,0 (8.58993e+08,8.58993e+08,4.29497e+09,2.68435e+09,1.27323e-313) (20%,20%,100%,62.5%,2.96439e-321%)
interppix: 0: @4,0 (0,0,4.29497e+09,4.29497e+09,1.27323e-313) (0%,0%,100%,100%,2.96439e-321%)
interppix: 0: @4.5,0 (0,0,4.29497e+09,4.29497e+09,1.27323e-313) (0%,0%,100%,100%,2.96439e-321%)
# ImageMagick pixel enumeration: 4,1,4294967295,srgba
0,0: (0,0,0,1.07374e+09)  #00000000000000000000000040000000  srgba(0%,0%,0%,0.25)
1,0: (0,0,0,1.07374e+09)  #00000000000000000000000040000000  srgba(0%,0%,0%,0.25)
2,0: (4.29497e+09,4.29497e+09,4.29497e+09,1.07374e+09)  #FFFFFFFFFFFFFFFFFFFFFFFF40000000  srgba(100%,100%,100%,0.25)
3,0: (4.29497e+09,4.29497e+09,4.29497e+09,1.07374e+09)  #FFFFFFFFFFFFFFFFFFFFFFFF40000000  srgba(100%,100%,100%,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:

%IMDEV%convert ^
  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.86331e+09,3.22123e+09,1.27323e-313) (0%,0%,66.6667%,75%,2.96439e-321%)
interppix: 0: @0,0 (0,0,1.90887e+09,2.5054e+09,1.27323e-313) (0%,0%,44.4444%,58.3333%,2.96439e-321%)
interppix: 0: @0.5,0 (1.49131e+07,1.49131e+07,1.50622e+09,2.19222e+09,1.27323e-313) (0.347222%,0.347222%,35.0694%,51.0417%,2.96439e-321%)
interppix: 0: @1,0 (1.19305e+08,1.19305e+08,1.55096e+09,2.14748e+09,1.27323e-313) (2.77778%,2.77778%,36.1111%,50%,2.96439e-321%)
interppix: 0: @1.25,0 (2.27424e+08,2.27424e+08,1.65908e+09,2.14748e+09,1.27323e-313) (5.29514%,5.29514%,38.6285%,50%,2.96439e-321%)
interppix: 0: @1.5,0 (3.57914e+08,3.57914e+08,1.78957e+09,2.14748e+09,1.27323e-313) (8.33333%,8.33333%,41.6667%,50%,2.96439e-321%)
interppix: 0: @2,0 (5.96523e+08,5.96523e+08,2.02818e+09,2.14748e+09,1.27323e-313) (13.8889%,13.8889%,47.2222%,50%,2.96439e-321%)
interppix: 0: @2.5,0 (6.86002e+08,6.86002e+08,2.17731e+09,2.19222e+09,1.27323e-313) (15.9722%,15.9722%,50.6944%,51.0417%,2.96439e-321%)
interppix: 0: @3,0 (5.96523e+08,5.96523e+08,2.5054e+09,2.5054e+09,1.27323e-313) (13.8889%,13.8889%,58.3333%,58.3333%,2.96439e-321%)
interppix: 0: @3.5,0 (3.57914e+08,3.57914e+08,3.22123e+09,3.22123e+09,1.27323e-313) (8.33333%,8.33333%,75%,75%,2.96439e-321%)
interppix: 0: @4,0 (1.19305e+08,1.19305e+08,3.93705e+09,3.93705e+09,1.27323e-313) (2.77778%,2.77778%,91.6667%,91.6667%,2.96439e-321%)
interppix: 0: @4.5,0 (1.49131e+07,1.49131e+07,4.25023e+09,4.25023e+09,1.27323e-313) (0.347222%,0.347222%,98.9583%,98.9583%,2.96439e-321%)

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?)

%IMDEV%convert ^
  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,4294967295,srgba
0,0: (8.29902e+08,8.29902e+08,4.29497e+09,2.71888e+09)  #31774E7531774E75FFFFFFFFA20EE108  srgba(19.3227%,19.3227%,100%,0.63304)

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:

%IMDEV%convert ^
  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.996094  sd=5.20644  max=39.1346

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

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

Other examples, displaying the Nx1 image as a graph:

%IMDEV%convert ^
  xc: ^
  -process 'mkgauss sd 1%%' ^
  -delete 0 ^
  cu_mg0.tiff

call %PICTBAT%graphLineCol ^
  cu_mg0.tiff . . 0 cu_mg0_glc.png
cu_mg0_glc.png
%IMDEV%convert ^
  xc: ^
  -process 'mkgauss sd 30%%' ^
  -delete 0 ^
  cu_mg1.tiff

call %PICTBAT%graphLineCol ^
  cu_mg1.tiff . . 0 cu_mg1_glc.png
cu_mg1_glc.png
%IMDEV%convert ^
  xc: ^
  -process 'mkgauss sd 30%% norm' ^
  -delete 0 ^
  cu_mg2.tiff

call %PICTBAT%graphLineCol ^
  cu_mg2.tiff . . 0 cu_mg2_glc.png
cu_mg2_glc.png
%IMDEV%convert ^
  xc: ^
  -process 'mkgauss sd 30%% cumul norm' ^
  -delete 0 ^
  cu_mg3.tiff

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

Shift the mean to 20% of the width.

%IMDEV%convert ^
  xc: ^
  -process 'mkgauss sd 30%% mean 20%% norm' ^
  -delete 0 ^
  cu_mg4.tiff

call %PICTBAT%graphLineCol ^
  cu_mg4.tiff . . 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.

%IMDEV%convert ^
  xc: ^
  -process 'mkgauss sd 30%% skew -20%% norm' ^
  -delete 0 ^
  cu_mg5.tiff

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

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

%IMDEV%convert ^
  xc: ^
  -process 'mkgauss sd 30%% skew 20%% norm' ^
  -delete 0 ^
  cu_mg6.tiff

call %PICTBAT%graphLineCol ^
  cu_mg6.tiff . . 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.

%IMDEV%convert ^
  xc: ^
  -process 'mkgauss sd 30%% skew -20%% cumul norm' ^
  -delete 0 ^
  cu_mg7.tiff

call %PICTBAT%graphLineCol ^
  cu_mg7.tiff . . 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.

%IMDEV%convert ^
  xc: ^
  -process 'mkgauss sd 30%% skew 20%% zeroize norm' ^
  -delete 0 ^
  cu_mg6a.tiff

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

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

cu_mg6a_glc.png
%IMDEV%convert ^
  xc: ^
  -process 'mkgauss sd 30%% skew -20%% zeroize cumul norm' ^
  -delete 0 ^
  cu_mg7a.tiff

call %PICTBAT%graphLineCol ^
  cu_mg7a.tiff . . 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.

%IMDEV%convert ^
  xc: ^
  -process 'mkgauss sd 30%% norm' ^
  -delete 0 ^
  cu_noskew.tiff

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

Skew left by 20% with method 1.

%IMDEV%convert ^
  xc: ^
  -process 'mkgauss sd 30%% skew -20%% skewmethod 1 norm' ^
  -delete 0 ^
  cu_sm1.tiff

call %PICTBAT%graphLineCol ^
  cu_sm1.tiff . . 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.

%IMDEV%convert ^
  xc: ^
  -process 'mkgauss sd 30%% skew -20%% skewmethod 2 norm' ^
  -delete 0 ^
  cu_sm2.tiff

call %PICTBAT%graphLineCol ^
  cu_sm2.tiff . . 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 (`%IMDEV%convert ^
  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.217
94.1298
62.1693
26.6485
0

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.
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 = 4 billion.

%IMDEV%convert ^
  toes.png ^
  -process 'mkhisto capnumbuckets 500' ^
  -depth 32 ^
  cu_h1.tiff

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

The counts are too low to register on the graph.

cu_h1_glc.png
%IMDEV%convert ^
  toes.png ^
  -process 'mkhisto capnumbuckets 500 norm' ^
  -depth 32 ^
  cu_hn.tiff

call %PICTBAT%graphLineCol ^
  cu_hn.tiff . . 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:

%IM%convert ^
  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
%IMDEV%convert ^
  toes.png ^
  -process 'mkhisto capnumbuckets 500 cumul' ^
  -depth 32 ^
  cu_hc.tiff

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

The counts are too low to register on the graph.

cu_hc_glc.png
%IMDEV%convert ^
  toes.png ^
  -process 'mkhisto capnumbuckets 500 cumul norm' ^
  -depth 32 ^
  cu_hcn.tiff

call %PICTBAT%graphLineCol ^
  cu_hcn.tiff . . 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):

%IMDEV%convert ^
  toes.png ^
  -process 'mkhisto capnumbuckets 500 norm' ^
  -process sortpixels ^
  -depth 32 ^
  cu_hcns.tiff

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

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

%IMDEV%convert ^
  toes.png ^
  -modulate 100,0,100 ^
  -process 'mkhisto capnumbuckets 500 norm' ^
  -process sortpixels ^
  -depth 32 ^
  cu_hcngs.tiff

call %PICTBAT%graphLineCol ^
  cu_hcngs.tiff . . 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:

%IMDEV%convert cu_hn.tiff -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:

%IMDEV%convert cu_h1.tiff -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:.

%IMDEV%convert ^
  toes.png ^
  -process 'mkhisto verbose capnumbuckets 1000000' ^
  -depth 32 ^
  -write info: ^
  cu_h1wide.tiff 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 0.140u 0:00.133

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:

%IMDEV%convert cu_h1wide.tiff -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.tiff, the cumulative normalised histogram created above.

We will invert this:

call %PICTBAT%graphLineCol ^
  cu_hcn.tiff . . 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.tiff. The second uses the invclut method.

%IMDEV%convert ^
  cu_hcn.tiff ^
  -process 'mkhisto capnumbuckets 500 cumul norm' ^
  -depth 32 ^
  cu_hcn_i1.tiff

call %PICTBAT%graphLineCol ^
  cu_hcn_i1.tiff . . 0 cu_hcn_i1_glc.png
cu_hcn_i1_glc.png
%IMDEV%convert ^
  cu_hcn.tiff ^
  -process invclut ^
  -depth 32 ^
  cu_hcn_i2.tiff

call %PICTBAT%graphLineCol ^
  cu_hcn_i2.tiff . . 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.

%IMDEV%convert ^
  cu_hcn_i1.tiff ^
  -process 'mkhisto capnumbuckets 500 cumul norm' ^
  -depth 32 ^
  cu_hcn_i3.tiff

call %PICTBAT%graphLineCol ^
  cu_hcn_i3.tiff . . 0 cu_hcn_i3_glc.png
cu_hcn_i3_glc.png
%IMDEV%convert ^
  cu_hcn_i2.tiff ^
  -process invclut ^
  -depth 32 ^
  cu_hcn_i4.tiff

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

Compare each with the original:

%IMDEV%compare -metric RMSE cu_hcn.tiff cu_hcn_i3.tiff NULL: 
cmd /c exit /B 0
8.01454e+06 (0.00186603)
%IMDEV%compare -metric RMSE cu_hcn.tiff cu_hcn_i4.tiff NULL: 
cmd /c exit /B 0
8.402e+06 (0.00195624)

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.

%IMDEV%convert ^
  toes.png ^
  -process 'mkhisto cumul norm' ^
  -write cu_hcn_wide.tiff ^
  -process 'mkhisto cumul norm' ^
  -process 'mkhisto cumul norm' ^
  -depth 32 ^
  cu_hcn_wide1.tiff

%IMDEV%convert ^
  cu_hcn_wide.tiff ^
  -process invclut ^
  -process invclut ^
  -depth 32 ^
  cu_hcn_wide2.tiff

%IMDEV%compare -metric RMSE cu_hcn_wide.tiff cu_hcn_wide1.tiff NULL: 
cmd /c exit /B 0
258629 (6.02168e-05)
%IMDEV%compare -metric RMSE cu_hcn_wide.tiff cu_hcn_wide2.tiff NULL: 
cmd /c exit /B 0
258431 (6.01708e-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:

%IMDEV%convert ^
  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,4294967295,gray
0,0: (2.06039e+09,2.06039e+09,2.06039e+09)  #7ACF7ACF7ACF  gray(47.9721%)

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 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.tiff created above.

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

From that input, we make a cumulative histogram.

%IMDEV%convert ^
  cu_hn.tiff ^
  -process 'cumulhisto norm' ^
  -depth 32 ^
  cu_hn_c.tiff

call %PICTBAT%graphLineCol ^
  cu_hn_c.tiff . . 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.

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

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

call %PICTBAT%graphLineCol ^
  cu_gauss_ch_icl.tiff . . 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.

%IM%convert ^
  cu_hn_c.tiff ^
  cu_gauss_ch_icl.tiff ^
  -clut ^
  cu_to_gauss.tiff

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

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

%IM%convert ^
  toes.png ^
  cu_to_gauss.tiff ^
  -clut ^
  cu_toes_gauss.png

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

%IMDEV%convert ^
  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.tiff created above, we make a de-cumulated histogram.

%IMDEV%convert ^
  cu_hn_c.tiff ^
  -process 'cumulhisto decumul norm' ^
  -depth 32 ^
  cu_hn_dc.tiff

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

We can verify the round-trip:

%IM%compare -metric RMSE cu_hn.tiff cu_hn_dc.tiff NULL: 
cmd /c exit /B 0
0.287518 (4.38725e-006)

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

cu_hn_dc_glc.png

De-cumulate a sigmoidal curve.

%IMDEV%convert ^
  -size 1x500 gradient: -rotate 90 ^
  -sigmoidal-contrast 10x50%% ^
  -process 'cumulhisto decumul norm' ^
  -depth 32 ^
  cu_sig_hn.tiff

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

De-cumulate a different sigmoidal curve.

%IMDEV%convert ^
  -size 1x500 gradient: -rotate 90 ^
  -sigmoidal-contrast 10x20%% ^
  -process 'cumulhisto decumul norm' ^
  -depth 32 ^
  cu_sig2_hn.tiff

call %PICTBAT%graphLineCol ^
  cu_sig2_hn.tiff . . 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.

Practical module: invert displacement map

invdispmap.c inverts displacment maps.

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

Option Description
Short
form
Long form
t string type string Declare the map type.
string should be Absolute or Relative.
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.

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

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.

%IMDEV%convert ^
  toes.png ^
  -colorspace Gray ^
  -process darkestpath ^
  cu_dp1.png
cu_dp1.png

Check the opposite direction.

%IMDEV%convert ^
  toes.png ^
  -colorspace Gray ^
  -flip ^
  -process darkestpath ^
  -flip ^
  cu_dp2.png
cu_dp2.png

Darkest path between left and right.

%IMDEV%convert ^
  toes.png ^
  -colorspace Gray ^
  ( +clone ^
    -rotate 90 ^
    -process darkestpath ^
    -rotate -90 ^
    -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 itertions 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. It 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.

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.
j just_score Write the score only, with no trailing \\n.
so stdout Write data to stdout.
se stderr Write data to stderr. (Default.)
v verbose Write some text output to stderr.

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.

By default, the module writes one line of text per image-pair to stderr. The line is of the format:

rmsealpha: n @ 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.

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.

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.

%IMDEV%convert ^
  rose: ^
  ( -size 11x11 xc:None -fill Red -draw "translate 5,5 circle 0,0 0,5" ) ^
  -process rmsealpha ^
  NULL: 
rmsealpha: 0.184429 @ 32,9

The returned coordinates are the best position for the top-left of the second image within the first.

Possible enhancement: set the canvas offsets of the second image to the returned coordinates.

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: 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:

%IMDEV%convert ^
  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%. It is the equivalent of "-fx atan2(u,v)/(2*pi)+0.5", but much faster.

The module currrently takes no arguments.

The module processes red, green and blue channels independently, and currrently 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

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

Find the arctangent.

%IMDEV%convert ^
  cu_2grad-0.png cu_2grad-1.png ^
  -process arctan2 ^
  cu_atan2_1.png
cu_atan2_1.pngjpg

Subtract 50%, then find the arctangent.

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

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

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

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

For example:

%IMDEV%convert xc: -process 'shell pwd' NULL: >cu_shell1.lis 2>&1
/cygdrive/f/prose/PICTURES
%IMDEV%convert xc: -verbose -process 'shell pwd' +verbose NULL: >cu_shell2.lis 2>&1
shell: sCmd = [pwd]
/cygdrive/f/prose/PICTURES
shell: status 0.

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

%IMDEV%convert ^
  toes.png ^
  -process 'setmnsd sd 0 t 0.01 i2 1000 mid mean' ^
  cu_sss0.png
cu_sss0.pngjpg
%IMDEV%convert ^
  toes.png ^
  -process 'setmnsd sd 0.1 mid mean' ^
  cu_sss1.png
cu_sss1.pngjpg
%IMDEV%convert ^
  toes.png ^
  -process 'setmnsd sd 0.2 mid mean' ^
  cu_sss2.png
cu_sss2.pngjpg
%IMDEV%convert ^
  toes.png ^
  -process 'setmnsd sd 0.3 mid mean' ^
  cu_sss3.png
cu_sss3.pngjpg
%IMDEV%convert ^
  toes.png ^
  -process 'setmnsd sd 0.4 mid mean' ^
  cu_sss4.png
cu_sss4.pngjpg
%IMDEV%convert ^
  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":

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

For images with transparency, when regardalpha is used, each colour value is pre-multiplied by the alpha at that pixel.

%IMDEV%convert ^
  toes_holed.png ^
  -process 'integim' ^
  -depth 32 ^
  -define quantum:format=floating-point ^
  +write cu_th_ii1.miff ^
  -auto-level ^
  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.
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.

%IMDEV%convert ^
  cu_ii1.miff ^
  -process 'deintegim' ^
  -define quantum:format=floating-point ^
  cu_ii1_di.png
cu_ii1_di.pngjpg
%IMDEV%convert ^
  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:

%IMDEV%convert ^
  cu_ii1.miff ^
  -process 'deintegim window 25x5' ^
  cu_ii1_bl.png
cu_ii1_bl.pngjpg
%IMDEV%convert ^
  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:

%IMDEV%convert ^
  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: 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 kCols N Number of clusters to find.
Default = 10.
j N,M jump N,M 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:

%IMDEV%convert ^
  toes.png ^
  -process 'kcluster' ^
  cu_kc.png
cu_kc.pngjpg

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

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.
%IMDEV%convert ^
  ( 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 8.58993e+06
  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.7
mkhisto options: capnumbuckets 500  multiply 1  cumul norm verbose 
mkhisto: Input image [toes.png] depth is 32
  NumBuckets 500  BucketSize 8.58993e+06
  counts: min_value 1, max_value 85
  sum_red 500, sum_green 500, sum_blue 500
  Cumulating and normalising...
    max_sum=500 mult_fact=8.58993e+06
mkhisto options: capnumbuckets 500  multiply 1  cumul norm verbose 
mkhisto: Input image [toes_x.jpg] depth is 8
  NumBuckets 256  BucketSize 1.67772e+07
  counts: min_value 1, max_value 1432
  sum_red 62211, sum_green 62211, sum_blue 62211
  Cumulating and normalising...
    max_sum=62211 mult_fact=69038.7
mkhisto options: capnumbuckets 500  multiply 1  cumul norm verbose 
mkhisto: Input image [toes_x.jpg] depth is 32
  NumBuckets 500  BucketSize 8.58993e+06
  counts: min_value 1, max_value 9
  sum_red 256, sum_green 256, sum_blue 256
  Cumulating and normalising...
    max_sum=256 mult_fact=1.67772e+07

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

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

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

 %IM%convert ^
  toes.png ^
  cu_t_ixt.png ^
  -clut ^
  cu_toes_txit.png

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

%IMDEV%compare -metric RMSE toes.png cu_toes_x_txit.png NULL: 
echo. 
%IMDEV%compare -metric RMSE toes_x.jpg cu_toes_txit.png NULL: 

cmd /c exit /B 0
3.14358e+07 (0.00731921) 
2.07676e+07 (0.00483532)

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

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

%IMDEV%convert ^
  ( 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.7
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 1.67772e+07
  counts: min_value 1, max_value 1432
  sum_red 62211, sum_green 62211, sum_blue 62211
  Cumulating and normalising...
    max_sum=62211 mult_fact=69038.7
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=1.67772e+07

Calculate the two transformation cluts:

%IM%convert ^
  cu_w_toes_x_chist.png ^
  cu_w_toes_chist_icl.png ^
  -clut ^
  cu_w_tx_it.png

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

 %IM%convert ^
  toes.png ^
  cu_w_t_ixt.png ^
  -clut ^
  cu_w_toes_txit.png

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

%IMDEV%compare -metric RMSE toes.png cu_w_toes_x_txit.png NULL: 
echo. 
%IMDEV%compare -metric RMSE toes_x.jpg cu_w_toes_txit.png NULL: 

cmd /c exit /B 0
2.9301e+07 (0.00682218) 
1.78138e+07 (0.0041476)

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:

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

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

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

convert 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:

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

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

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

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

Applying the 3-channel CH as a clut will equalise the histogram of the image. As three different cluts are applied, this shifts the hue.

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

%IM%convert ^
  toes.png ^
  -channel RGB ^
  -equalize ^
  cu_eq.png
cu_eq.pngjpg

Equalise by using the cumulative histogram.

%IM%convert ^
  toes.png ^
  cu_toes_chist.png ^
  -clut ^
  cu_cheq.png
cu_cheq.pngjpg

De-equalise by using the inverse cumulative histogram.

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

%IM%convert ^
  toes.png ^
  -equalize ^
  cu_eq_sync.png
cu_eq_sync.pngjpg

Equalise by using the cumulative histogram.

%IMDEV%convert ^
  toes.png ^
  -modulate 100,0,100 ^
  -process 'mkhisto cumul norm' ^
  cu_chsync.png

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

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

%IMDEV%convert ^
  cu_gch.png ^
  -process 'cumulhisto norm' ^
  cu_gchc.png

call %PICTBAT%graphLineCol ^
  cu_gchc.png . . 0 cu_gchc_glc.png

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

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

%IM%convert ^
  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 (`%IM%convert ^
  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.254441 
statSD=0.263963 
histcap=78.2367 

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 (`%IM%convert ^
  cu_gch.png ^
  cu_gchc_cap.png ^
  -compose MinusSrc -composite ^
  -format "MeanDiffPC=%%[fx:mean*100]" ^
  info:`) do set %%L

echo MeanDiffPC=%MeanDiffPC% 
MeanDiffPC=1.0708 

Equalise by using the capped cumulative histogram, redistributing the excess.

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

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

%IMDEV%convert ^
  toes.png ^
  ( +clone ^
    -process 'mkhisto capnumbuckets 500 norm' ^
    +write cu_dhh.tiff ^
    -delete 0 ^
  ) ^
  -process 'mkhisto cumul norm' ^
  -process 'mkhisto cumul norm' ^
  cu_dehist.tiff

call %PICTBAT%graphLineCol ^
  cu_dhh.tiff . . 0 cu_dhh_glc.png
cu_dhh_glc.png

Show the histogram of cu_dehist.tiff.

%IMDEV%convert ^
  cu_dehist.tiff ^
  -process 'mkhisto capnumbuckets 500 norm' ^
  cu_dehist_h.tiff

call %PICTBAT%graphLineCol ^
  cu_dehist_h.tiff . . 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.

The created small image is size 65536x1. If we resize it, we can see it:

%IMDEV%convert ^
  cu_dehist.tiff ^
  -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:

%IMDEV%convert ^
  cu_dehist.tiff ^
  -flop ^
  -crop 256x1 +repage ^
  -append ^
  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.

%IMDEV%convert ^
  toes.png ^
  -crop x1 +repage ^
  +append ^
  -process sortpixels ^
  -flop ^
  -resize "65536x1^!" ^
  -crop 256x1 +repage ^
  -append ^
  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

cmd /c exit /B 0
--- /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 convert 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

// 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)

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 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"
#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)


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 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;

  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 ("sizeof:\n");
  printf ("  MagickRealType %i\n", (int)sizeof(MagickRealType));
  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 ("  Image %i\n", (int)sizeof(Image));
  printf ("  ImageInfo %i\n", (int)sizeof(ImageInfo));
  printf ("  Quantum %i\n", (int)sizeof(Quantum));

  printf ("Quantum format: [%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);
      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);
    } 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

#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
  (void) QueryColorDatabase("blue", &draw_info->fill, exception);
#else
  // In v7, fill is type PixelInfo.
  // Use:
  // MagickBooleanType QueryColorCompliance(const char *name,
  ///  const ComplianceType compliance,PixelInfo *color,ExceptionInfo *exception)
  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()).

#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

#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);
}

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.

    Update 2-Feb-2016
      For v7.
*/


#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.
*/


#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;

#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++) {
    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"



/* 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

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


// 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=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;
        xLightest = x;
        yLightest = y;
      }
      p += Inc_ViewPixPtr (image);
    }
  }

  fprintf (stderr, "%lu,%lu\n", xLightest, yLightest);

  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"

/* 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%.

// 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 (image->matte && 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.
//

typedef struct {
  int
    cap_numbuckets;
  int
    mult;
  MagickBooleanType
    do_alpha,
    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 ("  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 = 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, "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_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;

  status = MagickTrue;

  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)
//        q->red   *= mult_fact;
//        q->green *= mult_fact;
//        q->blue  *= mult_fact;
        SET_PIXEL_RED (new_image,
          GET_PIXEL_RED (new_image, q) * mult_fact,
          q);
        SET_PIXEL_GREEN (new_image,
          GET_PIXEL_GREEN (new_image, q) * mult_fact,
          q);
        SET_PIXEL_BLUE (new_image,
          GET_PIXEL_BLUE (new_image, q) * mult_fact,
          q);
#else
//        q->red   = q->red   * mult_fact + 0.5;
//        q->green = q->green * mult_fact + 0.5;
//        q->blue  = q->blue  * mult_fact + 0.5;
        SET_PIXEL_RED (new_image,
          GET_PIXEL_RED (new_image, q) * mult_fact + 0.5,
          q);
        SET_PIXEL_GREEN (new_image,
          GET_PIXEL_GREEN (new_image, q) * mult_fact + 0.5,
          q);
        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;

  long double
    cumul_red, cumul_green, cumul_blue;

  status = MagickTrue;

  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); // q->red;
      cumul_green += GET_PIXEL_GREEN (image, q); // q->green;
      cumul_blue  += GET_PIXEL_BLUE  (image, q); // q->blue;

//      q->red   = mult_fact * cumul_red;
//      q->green = mult_fact * cumul_green;
//      q->blue  = mult_fact * cumul_blue;

      SET_PIXEL_RED   (image, mult_fact * cumul_red,   q);
      SET_PIXEL_GREEN (image, mult_fact * cumul_green, q);
      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;

  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 ;
      }

      // For v7, the index into new_image is wrong.

      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;
//      SetPixelRed(q_out+out_x, value);
      SET_PIXEL_RED (new_image, value, q_out+out_x);

      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;
//      SetPixelGreen(q_out+out_x, value);
      SET_PIXEL_GREEN (new_image, value, q_out+out_x);

      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;
//      SetPixelBlue(q_out+out_x, value);
      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_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.
*/

#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.
*/

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

#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.
*/

#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


//---------------------------------------------------------------------------
// 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 != ',') 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 ResolveUserCords (UserCoordXyT * uc, ssize_t width, ssize_t height)
// 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) {
    printf ("Coordinates must be positive.");
    status = 0;
  }

  if (uc->x.Pix >= width || uc->y.Pix >= height) {
    printf ("Coordinates (%li,%li) must be less than width and height (%li,%li).",
      uc->x.Pix, uc->y.Pix, width, height);
    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);
  }
}

//---------------------------------------------------------------------------
// 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 = ResolveUserCords (&dpp.start, image->columns, image->rows);
    if (!r) return -1;

    r = ResolveUserCords (&dpp.end, image->columns, image->rows);
    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.
*/

#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 ("Make a histogram image.\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;
}


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) {
      i++;
      ps3d->transStr = pargv[i];
    } else if (IsArg (pa, "a", "array")==MagickTrue) {
      i++;
      ps3d->fInArray = pargv[i];
    } else if (IsArg (pa, "A", "out-array")==MagickTrue) {
      i++;
      ps3d->fOutArray = pargv[i];
    } else if (IsArg (pa, "c", "coords")==MagickTrue) {
      i++;
      ps3d->fInCoords = pargv[i];
    } else if (IsArg (pa, "C", "out-coords")==MagickTrue) {
      i++;
      ps3d->fOutCoords = pargv[i];
    } else if (IsArg (pa, "n", "nOutCoords")==MagickTrue) {
      i++;
      ps3d->numCoordsOut = atoi(pargv[i]);
    } else if (IsArg (pa, "f", "focal-length")==MagickTrue) {
      i++;
      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;
    }
    SetImageOpacity (out_image, TransparentOpacity);
  }

  if (s3d->verbose) {
    fprintf (stderr, "Finished srt3d\n");
  }

  //DestroyImage (new_image);

  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);

    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

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <ctype.h>
#include "vsn_defines.h"


typedef enum {
  fullframe,
  circular
} FormatT;

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;
}


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) {
      i++;
      pat->scale = atof(argv[i]);
    } else if (IsArg (pa, "b", "bias")==MagickTrue) {
      i++;
      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(in_image1,p1),
                                 GET_PIXEL_RED(in_image2,p2)
                                ) * pat->scale + pat->bias),
                       q);

      SET_PIXEL_GREEN (new_image,
                       QuantumRange *
                         (atan2 (GET_PIXEL_GREEN(in_image1,p1),
                                 GET_PIXEL_GREEN(in_image2,p2)
                                ) * pat->scale + pat->bias),
                       q);

      SET_PIXEL_BLUE  (new_image,
                       QuantumRange *
                         (atan2 (GET_PIXEL_BLUE(in_image1,p1),
                                 GET_PIXEL_BLUE(in_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;

  MagickBooleanType
    status;

  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);
  }

  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);
}

rmsealpha.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include "vsn_defines.h"


/* 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.
*/

typedef struct {
  MagickBooleanType
    do_verbose,
    avoidIdentical,
    dontDecreaseOpacity,
    outJustScore;
  FILE * outhandle;
} RmseAlphaT;
// 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 ("  j,   just_score             write the score only, with no \\n\n");
  printf ("  v,   verbose                write text information to stdout\n");
  printf ("  so,  stdout                 write data to stdout\n");
  printf ("  se,  stderr                 write data to stderr (default)\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,
  RmseAlphaT * pra
)
// Returns MagickTrue if okay.
{
  int
    i;

  MagickBooleanType
    status;

  status = MagickTrue;

  pra->do_verbose = pra->avoidIdentical = pra->dontDecreaseOpacity = MagickFalse;
  pra->outJustScore = MagickFalse;
  pra->outhandle = stderr;

  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, "so", "stdout")==MagickTrue) {
      pra->outhandle = stdout;
    } else if (IsArg (pa, "se", "stderr")==MagickTrue) {
      pra->outhandle = stderr;
    } 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->do_verbose) fprintf (stderr, "  verbose");

    fprintf (stderr, "\n");
  }

  if (status == MagickFalse)
    usage ();

  return (status);
}



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 inx, ssize_t iny,
  ExceptionInfo *exception)
//
// Compares entire subimage with same-size window in inp,
// starting at top-left location (inx,iny) of inp.
// Returns number between 0.0 and 1.0 inclusive.
//
{
  ssize_t
    xy;

  const VIEW_PIX_PTR
    *inpxy,
    *subxy;

  double
    sigScore = 0,
    sigmAlpha = 0;

  MagickBooleanType
    isIdentical = MagickTrue;

  inpxy = GetCacheViewVirtualPixels(inp_view,inx,iny,width,height,exception);
  subxy = GetCacheViewVirtualPixels(subimage_view,0,0,width,height,exception);

  if (inpxy == (const VIEW_PIX_PTR *) NULL || subxy == (const VIEW_PIX_PTR *) NULL) {
    printf ("CompareWindow: badgcvvp");
    return -1;
  }

  for (xy=0; xy < width * height; xy++) {
    double inpa = GET_PIXEL_ALPHA(inp_image, inpxy)/QuantumRange;
    double suba = GET_PIXEL_ALPHA(sub_image, subxy)/QuantumRange;

    double mAlpha = inpa * suba;

    double dRed   =
       (GET_PIXEL_RED(inp_image,inpxy)
      - GET_PIXEL_RED(sub_image,subxy))/QuantumRange;
    double dGreen =
       (GET_PIXEL_GREEN(inp_image,inpxy)
      - GET_PIXEL_GREEN(sub_image,subxy))/QuantumRange;
    double dBlue  =
       (GET_PIXEL_BLUE(inp_image,inpxy)
      - GET_PIXEL_BLUE(sub_image,subxy))/QuantumRange;

    if (pra->dontDecreaseOpacity && inpa < suba) {
      return 1.0;
    }

    if (dRed!=0 || dGreen!=0 || dBlue!=0 || inpa!=suba) {
      isIdentical = MagickFalse;
    }

    if (mAlpha > 0) {
      sigScore += mAlpha * (dRed*dRed + dGreen*dGreen + dBlue*dBlue);

      sigmAlpha += mAlpha;
    }

    inpxy += Inc_ViewPixPtr (inp_image);
    subxy += Inc_ViewPixPtr (sub_image);
  }

  if (pra->avoidIdentical && isIdentical) return 1.0;

  if (sigmAlpha==0) return 0.0;

  return sqrt(sigScore / sigmAlpha / 3.0);
}


// The next function corresponds in style to functions in enhance.c
// It takes one image, and returns a status.
//
static MagickBooleanType rmsealpha(RmseAlphaT *pra,
  const Image *inp_image, const Image *sub_image,
  ExceptionInfo *exception)
{
  MagickBooleanType
    status;

  CacheView
    *inp_view,
    *sub_view;

  ssize_t
    nPosY, nPosX,
    y;

  ssize_t
    wi_sub,
    ht_sub,
    solnX = 0,
    solnY = 0;

  double
    bestScore = 9999e10;

  int
    precision;

  assert(inp_image != (Image *) NULL);
  assert(inp_image->signature == MAGICK_CORE_SIG);
  if (inp_image->debug != MagickFalse)
    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",inp_image->filename);

  precision = GetMagickPrecision();

  inp_view = AcquireVirtualCacheView(inp_image,exception);
  sub_view = AcquireVirtualCacheView(sub_image,exception);

  status = MagickTrue;

  wi_sub = sub_image->columns;
  ht_sub = sub_image->rows;

  if (wi_sub > inp_image->columns || ht_sub > inp_image->rows) {
    fprintf (stderr, "Subimage larger than input.");

    return MagickFalse;
  }

  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 (bestScore > score) {
        bestScore = score;
        solnX = x;
        solnY = y;
      }
      if (bestScore == 0.0) break;
    }
    if (bestScore == 0.0) break;
  }

  if (pra->outJustScore) {
    fprintf (pra->outhandle, "%.*g",
             precision, bestScore);
  } else {
    fprintf (pra->outhandle, "rmsealpha: %.*g @ %lu,%lu\n",
             precision, bestScore, solnX, 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);

  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 (status == MagickFalse)
      continue;
  }

  assert (status == MagickTrue);

  return(MagickImageFilterSignature);
}

pixmatch.c

/*
    Created 30-Nov-2015

*/


/* 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

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <ctype.h>
#include "vsn_defines.h"

#define DEBUG 0

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 ("\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 {
      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,
  // Ouput 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,
  // Ouput 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;

#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, QuantumRange * inRed   / lev2m1, q);
          SET_PIXEL_GREEN (new_image, QuantumRange * inGreen / lev2m1, q);
          SET_PIXEL_BLUE  (new_image, QuantumRange * inBlue  / lev2m1, 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 * QuantumRange / lev2m1,
            inGreen * QuantumRange / lev2m1,
            inBlue * QuantumRange / lev2m1,
            &fr, &fg, &fb);

          SET_PIXEL_RED   (new_image, fr + QuantumRange * inRed   / lev2m1, q);
          SET_PIXEL_GREEN (new_image, fg + QuantumRange * inGreen / lev2m1, q);
          SET_PIXEL_BLUE  (new_image, fb + QuantumRange * inBlue  / lev2m1, q);
          break;
        }
        case mVoronoi: {
          calcVoronoi (pat, image, p0, p1,
            inRed * QuantumRange / lev2m1,
            inGreen * QuantumRange / lev2m1,
            inBlue * QuantumRange / lev2m1,
            &fr, &fg, &fb);

          SET_PIXEL_RED   (new_image, fr + QuantumRange * inRed   / lev2m1, q);
          SET_PIXEL_GREEN (new_image, fg + QuantumRange * inGreen / lev2m1, q);
          SET_PIXEL_BLUE  (new_image, fb + QuantumRange * inBlue  / lev2m1, 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);

  //DeleteImageFromList (&image2);

  ReplaceImageInList(&image1,new_image);
  // Replace messes up the images pointer. Make it good:
  *images=GetFirstImageInList(image1);

  return(MagickImageFilterSignature);
}

setmnsd.c

#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;
}

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) {
      i++;
      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) {
      i++;
      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) {
      i++;
      psss->tolerance = atof(argv[i]);
      if (EndsPc (argv[i])) psss->tolerance /= 100.0;
    } else if (IsArg (pa, "m", "mid")==MagickTrue) {
      i++;
      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) {
      i++;
      psss->initCon0 = atof(argv[i]);
    } else if (IsArg (pa, "i2", "initCon2")==MagickTrue) {
      i++;
      psss->initCon2 = atof(argv[i]);
    } else if (IsArg (pa, "d", "direction")==MagickTrue) {
      i++;
      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) {
      i++;
      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 (
    temp_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;
  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;
      // make image white
      PIX_INFO mpp;
#if IMV6OR7==6
      GetMagickPixelPacket (temp_image, &mpp);
      QueryMagickColor ("white", &mpp, exception);
      SetImageColor (temp_image, &mpp);
#else
      GetPixelInfo (temp_image, &mpp);
      QueryColorCompliance ("white", AllCompliance, &mpp, exception);
      SetImageColor (temp_image, &mpp, exception);
#endif

    } 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
)
{
  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);
    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);
    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);
    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);
    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);
      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);
  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 ("  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.)
*/

#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
    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 ("  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;

  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, "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->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;
    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;
      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
    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 = deintegim (image, &cumul_histo, exception);
    if (!new_image) return(-1);

    ReplaceImageInList(&image,new_image);
    *images=GetFirstImageInList(image);
  }

  return(MagickImageFilterSignature);
}

kcluster.c

/*
   Reference: http://im.snibgo.com/kcluster.htm

   Last update: 9-August-2017.
*/

#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 integer        number of clusters to find\n");
  printf ("  j, jump integer,integer find k\n");
  printf ("  s, sdNorm               normalise channel SD\n");
  printf ("  ?, 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");  // FIXME: NYI
  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: WxH
// where W and H are numbers optionally followed by '%' or 'c' or 'p'.
// 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;
}





#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]);
    } 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 init [%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, "  jump %i,%i", pkc->jump0, pkc->jump1);
      if (pkc->hasSetY) fprintf (stderr, "  jumpY %.*g", pkc->precision, pkc->jumpY);
    }

    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,
// FIXME: we need two images: the original input image and the one to be manipulated.
  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?
    SetImageExtent (uniq_img, pkc->kCols, 1);
    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 = MagickFalse;

  if (!QuantizeImage (qi, quant_img)) return NULL;

  qi = DestroyQuantizeInfo (qi);

  Image * uniq_img = MakeUnique (pkc, image, quant_img, exception);

  Image * uniq_img2 = MakeUnique (pkc, image, uniq_img, exception);

  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);

  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: init 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;
}

// FIXME: Also fromlist: uses last image in list.


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.

  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;

  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;
  }

  pkc->clusters = (clusterT *) AcquireQuantumMemory (pkc->kCols, sizeof(clusterT));
  if (!pkc->clusters) {
    fprintf (stderr, "kcluster: oom2\n");
    return (Image *)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:
      uniq_img = InitialiseRandomPartition (pkc, image, exception);
      break;
    case iSdLine:
      uniq_img = InitialiseSdLine (pkc, image, exception);
      break;
    case iFromList:
      uniq_img = InitialiseFromList (pkc, image, exception);
      break;
  }

  if (!uniq_img) {
    fprintf (stderr, "kcluster: Initialise failed\n");
    return 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) {
    // Both images entirely 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 (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 = IsGrayImage (image, exception) ? 1 : 3; // Number of dimensions.
      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 > 0,
    // start at jump0-1 but don't count first result for maxJ.

    MagickBooleanType ignoreThis = MagickFalse;
    int startK = pkc->jump0;
    if (startK > 1) {
      ignoreThis = MagickTrue;
      startK--;
    }

    int k;
    for (k = startK; 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: init 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);
}

writeframe.inc

static MagickStatusType WriteFrame (
  char *FileName,
  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, 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;
}

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/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/kcluster.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/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/kcluster.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)

# 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)

# 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)

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


@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

%IMDEV%convert ^
  %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 (`%IM%convert ^
  %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
rem %IM%convert %TEMP%\%EQL_BASE%_gch.%TMPEXT% -format "%EQL_BASE%_gch %%[fx:minima] %%[fx:maxima]\n" info:

%IMDEV%convert ^
  %CYGTEMP%\%EQL_BASE%_gch.%TMPEXT% ^
  -channel RGB ^
  -evaluate Min %histcap%%% ^
  +channel ^
  %CYGTEMP%\%EQL_BASE%_gchc_cap.%TMPEXT%

for /F "usebackq" %%L in (`%IM%convert ^
  %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%

%IMDEV%convert ^
  %CYGTEMP%\%EQL_BASE%_gch.%TMPEXT% ^
  -channel RGB ^
  -evaluate Min %histcap%%% ^
  +channel ^
  -evaluate Add %MeanDiffPC%%% ^
  %CYGTEMP%\%EQL_BASE%_gch.%TMPEXT%

for /F "usebackq" %%L in (`%IM%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%

rem %IM%convert %TEMP%\%EQL_BASE%_gch.%TMPEXT% -format "%EQL_BASE%_gch %%[fx:minima] %%[fx:maxima]\n" info:


if %LIFT_SHADOW_PC%==0 if %DROP_HIGHLIGHT_PC%==0 %IMDEV%convert ^
  %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 (`%IMDEV%convert ^
  %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" (
  %IM%convert ^
  %TEMP%\%EQL_BASE%_gch.%TMPEXT% ^
  -fill #fff -colorize 100 ^
  %TEMP%\%EQL_BASE%_gch.%TMPEXT%
) else if not %WX%==0 %IMDEV%convert ^
  %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 (`%IMDEV%convert ^
  %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" (
  %IM%convert ^
  %TEMP%\%EQL_BASE%_gch.%TMPEXT% ^
  -fill #fff -colorize 100 ^
  %TEMP%\%EQL_BASE%_gch.%TMPEXT%
) else if not %WX%==0 %IMDEV%convert ^
  %CYGTEMP%\%EQL_BASE%_gch.%TMPEXT% ^
  -size %WX%x1 xc:gray(%DROP_HIGHLIGHT_PC%%%) ^
  -gravity East -composite ^
  %CYGTEMP%\%EQL_BASE%_gch.%TMPEXT%

:skipHigh


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


@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

if "%mhDEBUG%"=="1" (
  set WR_CLUT= ^( +clone +write %INNAME%_mhcl.miff +delete ^)
) else (
  set WR_CLUT=
)

%IMDEV%convert ^
  %INFILE% ^
  %mhCOL_SP_IN% ^
  ( -clone 0 ^
    -process 'mkhisto cumul norm' ^
  ) ^
  ( %2 ^
    %mhCOL_SP_IN% ^
    -process 'mkhisto cumul norm' ^
    -process 'mkhisto cumul norm' ^
  ) ^
  ( -clone 1-2 -clut ) ^
  -delete 1-2 ^
  %WR_CLUT% ^
  -clut ^
  %mhCOL_SP_OUT% ^
  %OUTFILE%

rem call %PICTBAT%graphLineCol x1.png
rem call %PICTBAT%graphLineCol x2.png
rem call %PICTBAT%graphLineCol x3.png

if ERRORLEVEL 1 exit /B1

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

@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%

if "%IM32f%"=="" call %PICTBAT%setIm8

:: 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 (`%IM32f%convert ^
  xc: ^
  -define convolve: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%

%IM32f%convert ^
  -size %WW%x%HH% xc:Black ^
  -fill White -draw "point %X%,%Y%" ^
  -define convolve: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.

@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 (`%IMDEV%convert ^
  %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.

@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 (
  %IMDEV%convert ^
  %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 (
  %IMDEV%convert ^
  %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 (
  %IMDEV%convert ^
  %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 (
  %IMDEV%convert ^
  %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 to JPG.

My usual version of IM is:

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

This customised development version is:

%IMDEV%convert -version
Version: ImageMagick 6.9.3-7 Q32 x86_64 2017-08-05 http://www.imagemagick.org
Copyright: Copyright (C) 1999-2016 ImageMagick Studio LLC
License: http://www.imagemagick.org/script/license.php
Features: Cipher DPC HDRI Modules OpenMP 
Delegates (built-in): bzlib cairo fftw fontconfig freetype fpx jbig jng jpeg lcms ltdl lzma pangocairo png rsvg tiff webp wmf x xml zlib

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 16-Aug-2017 11:32:27.

Copyright © 2017 Alan Gibson.