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.
I develop code in a separate directory to the "as downloaded" official distribution. You might prefer to directly modify the official distribution. Adjust the following to suit.
Create new directories for development source code, and installation of the built development version:
bash $ cd ~ $ mkdir imdevsrc $ mkdir imdevins
Copy the unpacked IM distribution to the development source directory:
$ cp -R ImageMagick-6.8.9-6/* imdevsrc
Build this development version, to verify the build works before we start hacking:
$ cd imdevsrc $ ./configure --prefix=/home/Alan/imdevins --with-quantum-depth=32 --enable-hdri --with-rsvg=yes --with-windows-font-dir=/cygdrive/c/Windows/Fonts --disable-docs --with-modules=yes $ make $ make install
For convenience, I set the Windows environment variable IMDEV to this installation directory:
echo %IMDEV%
C:\cygwin64\home\Alan\imdevins7114\bin\
I have a parallel environment for v7 development. Source code lives in %IM7SRC%. Binary files live in %IM7DEV%.
echo %IM7SRC%
c:\cygwin64\home\Alan\imagemagick-7.1.1-4B\ImageMagick-Windows-7\ImageMagick
echo %IM7DEV%
C:\cygwin64\home\Alan\imdevins7114\bin\
Source code for my modules resides in %IMSRC%\filters and %IM7SRC%\filters. The files in those directories are identical, and could be symbolic links. There are also two header files in the parents of filters, ie in %IMSRC% and %IM7SRC%.
Verify the installation:
$ ~/imdevins/bin/convert -version
Version: ImageMagick 6.X.X-X Q32 x86_64 2014-08-06 http://www.imagemagick.org Copyright: Copyright (C) 1999-2014 ImageMagick Studio LLC Features: DPC HDRI OpenMP Delegates: bzlib fontconfig freetype fpx jbig jng jpeg lcms lzma png tiff x zlib
IM has a -process command. This takes a module name as an argument, optionally followed by arguments to the module. If the module has arguments, the module name and its arguments must be bracketed in quotes. (For this, Windows is happy with either single or double quotes. I use single quotes.)
The process module is a C function. IM gives it an image list, and any arguments the user has supplied in the command line. The function can do anything it wants, such as modifying the images or replacing the image list with another list.
Source code for the modules, a makefile and other files are in a filters directory from the development directory, eg ~/imdevsrc/filters.
ASIDE: This filters directory should not be confused with IM's -filter setting for resizing and other operations. They are not related.
Custom filters and coders are collectively known as "modules". If IM has been built with --with-modules=yes, modules are compiled into separate libraries and can be listed with IM's -list module command.
Verify the installed version contains the "analyze" module:
$ ~/imdevins/bin/convert rose: -process analyze r.png $ ~/imdevins/bin/identify -verbose r.png|grep filter
filter:brightness:kurtosis: -1.38096
filter:brightness:mean: 2.56068e+09
filter:brightness:skewness: -0.0077617
filter:brightness:standard-deviation: 1.15205e+09
filter:saturation:kurtosis: -1.42743
filter:saturation:mean: 1.8842e+09
filter:saturation:skewness: 0.00504635
filter:saturation:standard-deviation: 1.12284e+09
The analyze module successfully runs.
Edit ~/imdevsrc/filters/analyze.c so the first line with FormatLocaleString is:
(void) FormatLocaleString(text,MaxTextExtent,"%g (Hello world)",brightness_mean);
Like me, the compiler etc doesn't care what characters mark line ends, so Windows editors that write CR-LF can be used.
Remake, install, and test:
$ make $ make install $ ~/imdevins/bin/convert rose: -process analyze r.png $ ~/imdevins/bin/identify -verbose r.png |grep filter
filter:brightness:kurtosis: -1.38096
filter:brightness:mean: 2.56068e+09 (Hello world)
filter:brightness:skewness: -0.0077617
filter:brightness:standard-deviation: 1.15205e+09
filter:saturation:kurtosis: -1.42743
filter:saturation:mean: 1.8842e+09
filter:saturation:skewness: 0.00504635
filter:saturation:standard-deviation: 1.12284e+09
Yup. Our edit to analyze.c has taken effect.
Copy filters/analyze.c to filters/sortlines.c. Edit sortlines.c so the function is named sortlinesImage, and edit the Format line.
Edit imdevsrc/filters/Makefile.am, replicating references to analyze. My current version of Makefile.am is shown below.
Edit magick/module.c and magick/static.c. In these source files, search for analyze, and add code for any other modules. This is only needed if we link the modules into a single library or binary. If we configure it with --with-modules=yes, we don't need to change these source files (I think).
However, magick/module.c contains functions GetModuleList() and ListModuleInfo(), and these don't need editing. I think analyze is used as an example to find the path to modules, and they are all in the same place so the other modules don't need to be listed.
--with-modules=yes: modules are dynamically linked. That is, they are not known to source code within convert.exe etc. The alternative requires that we patch static.c and module.c. Look for occurrences of "analyze". Static build means that whenever a module is edited, all the utilities will be re-linked. Static build doesn't list modules (either coders or filters) with "-list module".
The normal build process is: configure, make, make install. When we add new filters, we need to precede these with automake (which uses filters/automake.am) and autoconf. So the sequence is:
$ automake $ autoconf $ ./configure --prefix=/home/Alan/imdevins --with-quantum-depth=32 --enable-hdri --with-rsvg=yes --with-windows-font-dir=/cygdrive/c/Windows/Fonts --disable-docs --with-modules=yes $ make $ make install
automake and autoconf are bash scripts, part of (I think) the GNU development toolset. ./configure is a bash script in the development directory. make is a binary program.
(The configure option --disable-docs prevents documentation from being installed. This is a time-consuming part of make install, and we don't need it.)
After we edit the source of any module, such as sortlines.c, we need to run make and make install again. The first three steps (automake, autoconf and ./configure) are needed only if we add new modules.
ASIDE: Back in the old days, I programmed in binary machine code. That was fun but error-prone and tedious. Assembly code was easier, and an assembler converted this to binary. Even easier was C or C++. A compiler converts that to assembly code, and an assembler converts that to binary, and binary files are linked with a linker. To ensure binaries are updated whenever source code changes, we use make. But the switches for compilation and assembly depend on our Q number, our operating system, and so on. So we need to specify our configuration, for which we use ./configure. But the configuration is complex, so we need to build that, with autoconf. And we need to build our makefile, with automake. I'm going crazy.
And this is before the complexities of licensing, back-end code management and code repositories and versioning and subversioning, regression testing, build environment validation, destination system validation, and front-end package distribution and dependencies.
Maintenance is another layer of complexity.
Of course, this is all useful. It means that I can write code that you can integrate into ImageMagick, whatever your Q-number or operating system or anything else. But I am nostalgic for the old days of binary machine code. They were simpler, somehow.
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.
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.
For convenience, I set an environment variable to the directory of the new magick.exe.
echo %IM7DEV%
C:\cygwin64\home\Alan\imdevins7114\bin\
If magick can't find a module, it returns with a non-zero exit code (and writes a message to stderr).
%IM7DEV%magick xc: -process analyze NULL: echo %ERRORLEVEL%
0
%IM7DEV%magick xc: -process nonesuch NULL: echo %ERRORLEVEL% cmd /c exit /B 0
1
Here are some sample process modules. The first few are skeletons or toys, to show how the MagickCore mechanisms work. Later modules have practical uses. The source files are shown at the bottom of this page. They are also available in a single zip file; see Zipped BAT files.
This is a partial list of which process modules are used in which of my BAT scripts. For a list of scripts and the pages in which they are described, see Zipped BAT files.
| Process module | BAT files |
|---|---|
| ( | iiGauss.bat |
| @rem | whatLevelP.bat maxLocCont.bat oogPower.bat whatLevel.bat |
| allwhite | srchImgAg.bat traceLines.bat histoPeaks.bat fillPix.bat |
| arctan2 | lab2lch.bat slopeXYdirn.bat setLabLch.bat |
| barymap | sqshxyY.bat |
| cols2mat | grayPoly.bat 24cardSelfGray.bat what3x3.bat |
| cumulhisto | eqLimit.bat equSlope.bat sh2shLinear.bat sh2shPolar.bat integral.bat invDiffGrad.bat mkThetaMap.bat histoStats.bat r2shPol.bat equSlopeH.bat histo2Img.bat |
| darkestmeander | combMeand.bat |
| darkestpath | rectDp.bat tileDp.bat mebcOne.bat |
| darkestpntpnt | minDp.bat lns2ptsX.bat traceLn.bat lns2pts.bat traceLines.bat shapeDp.bat |
| deintegim | minMnkSd.bat srndMinMnkSd.bat iiGauss2d.bat iiGaussOne.bat iiWinSd.bat genMean.bat iiMeanSd.bat deOutlier.bat lightColCh.bat |
| fillholespri | extLineEnds.bat deEdgeFft.bat |
| for | iiGaussK.bat iiGaussMult.bat |
| if | 24cardRot.bat |
| img2knl | gaussSlpMag.bat img2knl4f.bat img2knl4.bat |
| integim | minMnkSd.bat iiWinSd.bat iiMeanSd.bat lightColCh.bat iiGaussOne.bat iiGauss2d.bat iiGaussMult.bat iiGauss.bat deOutlier.bat genMean.bat srndMinMnkSd.bat |
| invclut | invHRDM.bat histo2Img.bat sh2shPolar.bat r2shPol.bat invLogPolar.bat |
| midlightest | loHiCols.bat find4cornEdgeBlr.bat membrane.bat traceLines.bat |
| mkhisto | mkOvClutEnds.bat mkOvClut.bat mkThetaMap.bat angramGr.bat rawDev.bat analGrids.bat mkHistoImg.bat mkGausPyr.bat matchLapHisto.bat matchHistoLong.bat matchHisto.bat matchGauss.bat invLogPolar.bat hueChart.bat histoStats.bat histoPeaks.bat matchHistoPyr.bat histoGraph.bat histo2Img.bat forceClip.bat mkLapPyr.bat equSlope.bat eqLimit.bat entropy.bat demoNoise.bat angramRGB.bat |
| nearestwhite | lns2pts.bat TjuncLineEnds.bat lns2ptsX.bat traceLines.bat traceContour.bat nearCoast2.bat |
| onelightest | closestMap.bat histoPeaks.bat findVertLine.bat tileDp.bat lgstConnCompB.bat lgstConnComp.bat |
| onewhite | eqLimit.bat shapeDp.bat dp2Grad.bat nLightest.bat minDp.bat clc2pts.bat line2Grad.bat histToeInt.bat followLine.bat find4cornPolDist.bat nearCoast.bat clc2lpbrk.bat midWhite.bat |
| oogbox | maxLocCol.bat maxLocCont.bat dehaze.bat rawDev.bat compressL.bat |
| plotrg | rawGamut.bat nefGamuts.bat xyyHorse.bat |
| polypix | detail2circles.bat sal2circ.bat |
| rhotheta | sqshxyY.bat rgybb2chb.bat chb2srgb.bat chb2rgybb.bat srgb2chb.bat |
| rmsealpha | mkQuilt.bat whatScale.bat mkQuiltLike.bat |
| sortpixelsblue | lns2ptsX.bat |
| sphaldcl | smSpHald.bat |
| srchimg | salMapCrop.bat subSrchPnts.bat srchImgCring.bat chainSrch.bat 24cardXX.bat 24cardX2.bat |
| srt3d | rotCubeAnim.bat |
| timecq( | timePolyreg.bat |
These process modules have used IM code in thread-private.h. This breaks in IM version 7.0.7-28 due to a macro changing name from magick_threads to magick_number_threads. My cure is to create my own macro named MAGICK_THREADS in vsn_defines.h, and use that instead.
Many other changes to the modules have been made in response to changes in IM between v7.0.1-0 and v7.0.7-28, and to fix module bugs.
Commands and scripts on this page are v6, but have also been mechanically converted from v6 to v7 using this table, and then tested:
| v6 | v7 |
|---|---|
| %IM%convert | %IMG7%magick |
| %IM%identify | %IMG7%magick identify |
| %IM%compare | %IMG7%magick compare |
| %IML%convert | %IMG7%magick convert |
| %IMDEV%convert | %IM7DEV%magick |
| %IM32f%convert | %IM7DEV%magick |
| %PICTBAT% | %PICTBATv7% |
| -mask | -read-mask |
| +mask | +read-mask |
| -radial-blur | -rotational-blur |
hellow.c is a minimal "hello world" example. It takes no arguments. It has no effect on the image list. It just writes text to stdout.
%IM7DEV%magick xc: -precision 20 -process hellow NULL:
Greetings to wizards of the ImageMagick world! MAGICKCORE_QUANTUM_DEPTH 32 QuantumRange 4294967295 V/V-1 0 QuantumScale 2.3283064370807973753e-10 MagickEpsilon 9.9999999999999997989e-13 Quantum Quantum sizeof: MagickRealType 8 MagickSizeType 8 int 4 long int 8 long long int 8 float 4 double 8 long double 16 _Float128 16 Image 13512 ImageInfo 13024 Quantum 8 MagickSizeType [MagickSizeType] MagickSizeFormat [llu] QuantumFormat: [%g] MaxMap 65535 MaxTextExtent 4096 MagickPathExtent 4096 Includes HDRI MAGICKCORE_WINDOWS_SUPPORT not defined WIN32 not defined WIN64 not defined MAGICKCORE_OPENMP_SUPPORT defined MAGICKCORE_HAVE_CL_CL_H defined MAGICKCORE_HAVE_OPENCL_CL_H not defined MAGICKCORE_OPENCL_SUPPORT defined Written ImageMagickOpenCL.log GetOpenCLEnabled() = true Written ImageMagickOpenCL.log mcle->number_devices = 0
The overhead per image is surprisingly large, but not usually a problem. Just for fun I created a long list with a command like ...
convert xc: xc: xc: ... xc: +append x.png
... with 100,000 xc images. This worked fine, though I suppose it took 1.3 GB of memory.
echostuff.c echos its arguments, and basic data on each image in the list, to stdout. It has no effect on the image list.
We can call it with no arguments. It lists all the images in the current list. In the following example, there is only one image.
%IM7DEV%magick xc:red -process echostuff NULL:
echostuff: no arguments mc=black echostuff: Input image [0] [red] depth 32 size 1x1 Virtual pixel method 0 [(null)] mattecolor: (null) fuzz 0 alpha is on: no channels 3 meta_channels 0 ( MaxPixelChannels = 64 ) GetPixelChannels() = 3 Number of channels with update trait = 3 Image channels: i, channel, name, traits 0, 0, R, traits: 2=(update ) 1, 1, G, traits: 2=(update ) 2, 2, B, traits: 2=(update ) channel_map: i, channel, offset, traits 0, 0, 0 traits: 2=(update ) 1, 1, 1 traits: 2=(update ) 2, 2, 2 traits: 2=(update ) 3, 0, 0 traits: 0=(undefined ) 4, 0, 0 traits: 0=(undefined ) 5, 0, 0 traits: 0=(undefined ) 6, 0, 0 traits: 0=(undefined ) 7, 0, 0 traits: 0=(undefined ) 8, 0, 0 traits: 0=(undefined ) 9, 0, 0 traits: 0=(undefined ) 10, 0, 0 traits: 0=(undefined ) 11, 0, 0 traits: 0=(undefined ) 12, 0, 0 traits: 0=(undefined ) 13, 0, 0 traits: 0=(undefined ) 14, 0, 0 traits: 0=(undefined ) 15, 0, 0 traits: 0=(undefined ) 16, 0, 0 traits: 0=(undefined ) 17, 0, 0 traits: 0=(undefined ) 18, 0, 0 traits: 0=(undefined ) 19, 0, 0 traits: 0=(undefined ) 20, 0, 0 traits: 0=(undefined ) 21, 0, 0 traits: 0=(undefined ) 22, 0, 0 traits: 0=(undefined ) 23, 0, 0 traits: 0=(undefined ) 24, 0, 0 traits: 0=(undefined ) 25, 0, 0 traits: 0=(undefined ) 26, 0, 0 traits: 0=(undefined ) 27, 0, 0 traits: 0=(undefined ) 28, 0, 0 traits: 0=(undefined ) 29, 0, 0 traits: 0=(undefined ) 30, 0, 0 traits: 0=(undefined ) 31, 0, 0 traits: 0=(undefined ) 32, 0, 0 traits: 0=(undefined ) 33, 0, 0 traits: 0=(undefined ) 34, 0, 0 traits: 0=(undefined ) 35, 0, 0 traits: 0=(undefined ) 36, 0, 0 traits: 0=(undefined ) 37, 0, 0 traits: 0=(undefined ) 38, 0, 0 traits: 0=(undefined ) 39, 0, 0 traits: 0=(undefined ) 40, 0, 0 traits: 0=(undefined ) 41, 0, 0 traits: 0=(undefined ) 42, 0, 0 traits: 0=(undefined ) 43, 0, 0 traits: 0=(undefined ) 44, 0, 0 traits: 0=(undefined ) 45, 0, 0 traits: 0=(undefined ) 46, 0, 0 traits: 0=(undefined ) 47, 0, 0 traits: 0=(undefined ) 48, 0, 0 traits: 0=(undefined ) 49, 0, 0 traits: 0=(undefined ) 50, 0, 0 traits: 0=(undefined ) 51, 0, 0 traits: 0=(undefined ) 52, 0, 0 traits: 0=(undefined ) 53, 0, 0 traits: 0=(undefined ) 54, 0, 0 traits: 0=(undefined ) 55, 0, 0 traits: 0=(undefined ) 56, 0, 0 traits: 0=(undefined ) 57, 0, 0 traits: 0=(undefined ) 58, 0, 0 traits: 0=(undefined ) 59, 0, 0 traits: 0=(undefined ) 60, 0, 0 traits: 0=(undefined ) 61, 0, 0 traits: 0=(undefined ) 62, 0, 0 traits: 0=(undefined ) 63, 0, 0 traits: 0=(undefined ) 64, 64, 0 traits: 0=(undefined )
When we have arguments, we need to bracket the module name and arguments with quotes.
%IM7DEV%magick ^ xc:red ^ -size 1x10 gradient:blue-green ^ -process "echostuff hello world" ^ NULL:
echostuff: 2 arguments: [hello] [world] mc=black echostuff: Input image [0] [red] depth 32 size 1x1 Virtual pixel method 0 [(null)] mattecolor: (null) fuzz 0 alpha is on: no channels 3 meta_channels 0 ( MaxPixelChannels = 64 ) GetPixelChannels() = 3 Number of channels with update trait = 3 Image channels: i, channel, name, traits 0, 0, R, traits: 2=(update ) 1, 1, G, traits: 2=(update ) 2, 2, B, traits: 2=(update ) channel_map: i, channel, offset, traits 0, 0, 0 traits: 2=(update ) 1, 1, 1 traits: 2=(update ) 2, 2, 2 traits: 2=(update ) 3, 0, 0 traits: 0=(undefined ) 4, 0, 0 traits: 0=(undefined ) 5, 0, 0 traits: 0=(undefined ) 6, 0, 0 traits: 0=(undefined ) 7, 0, 0 traits: 0=(undefined ) 8, 0, 0 traits: 0=(undefined ) 9, 0, 0 traits: 0=(undefined ) 10, 0, 0 traits: 0=(undefined ) 11, 0, 0 traits: 0=(undefined ) 12, 0, 0 traits: 0=(undefined ) 13, 0, 0 traits: 0=(undefined ) 14, 0, 0 traits: 0=(undefined ) 15, 0, 0 traits: 0=(undefined ) 16, 0, 0 traits: 0=(undefined ) 17, 0, 0 traits: 0=(undefined ) 18, 0, 0 traits: 0=(undefined ) 19, 0, 0 traits: 0=(undefined ) 20, 0, 0 traits: 0=(undefined ) 21, 0, 0 traits: 0=(undefined ) 22, 0, 0 traits: 0=(undefined ) 23, 0, 0 traits: 0=(undefined ) 24, 0, 0 traits: 0=(undefined ) 25, 0, 0 traits: 0=(undefined ) 26, 0, 0 traits: 0=(undefined ) 27, 0, 0 traits: 0=(undefined ) 28, 0, 0 traits: 0=(undefined ) 29, 0, 0 traits: 0=(undefined ) 30, 0, 0 traits: 0=(undefined ) 31, 0, 0 traits: 0=(undefined ) 32, 0, 0 traits: 0=(undefined ) 33, 0, 0 traits: 0=(undefined ) 34, 0, 0 traits: 0=(undefined ) 35, 0, 0 traits: 0=(undefined ) 36, 0, 0 traits: 0=(undefined ) 37, 0, 0 traits: 0=(undefined ) 38, 0, 0 traits: 0=(undefined ) 39, 0, 0 traits: 0=(undefined ) 40, 0, 0 traits: 0=(undefined ) 41, 0, 0 traits: 0=(undefined ) 42, 0, 0 traits: 0=(undefined ) 43, 0, 0 traits: 0=(undefined ) 44, 0, 0 traits: 0=(undefined ) 45, 0, 0 traits: 0=(undefined ) 46, 0, 0 traits: 0=(undefined ) 47, 0, 0 traits: 0=(undefined ) 48, 0, 0 traits: 0=(undefined ) 49, 0, 0 traits: 0=(undefined ) 50, 0, 0 traits: 0=(undefined ) 51, 0, 0 traits: 0=(undefined ) 52, 0, 0 traits: 0=(undefined ) 53, 0, 0 traits: 0=(undefined ) 54, 0, 0 traits: 0=(undefined ) 55, 0, 0 traits: 0=(undefined ) 56, 0, 0 traits: 0=(undefined ) 57, 0, 0 traits: 0=(undefined ) 58, 0, 0 traits: 0=(undefined ) 59, 0, 0 traits: 0=(undefined ) 60, 0, 0 traits: 0=(undefined ) 61, 0, 0 traits: 0=(undefined ) 62, 0, 0 traits: 0=(undefined ) 63, 0, 0 traits: 0=(undefined ) 64, 64, 0 traits: 0=(undefined ) mc=black echostuff: Input image [1] [blue] depth 32 size 1x10 Virtual pixel method 0 [(null)] mattecolor: (null) fuzz 0 alpha is on: no channels 3 meta_channels 0 ( MaxPixelChannels = 64 ) GetPixelChannels() = 3 Number of channels with update trait = 3 Image channels: i, channel, name, traits 0, 0, R, traits: 2=(update ) 1, 1, G, traits: 2=(update ) 2, 2, B, traits: 2=(update ) channel_map: i, channel, offset, traits 0, 0, 0 traits: 2=(update ) 1, 1, 1 traits: 2=(update ) 2, 2, 2 traits: 2=(update ) 3, 0, 0 traits: 0=(undefined ) 4, 0, 0 traits: 0=(undefined ) 5, 0, 0 traits: 0=(undefined ) 6, 0, 0 traits: 0=(undefined ) 7, 0, 0 traits: 0=(undefined ) 8, 0, 0 traits: 0=(undefined ) 9, 0, 0 traits: 0=(undefined ) 10, 0, 0 traits: 0=(undefined ) 11, 0, 0 traits: 0=(undefined ) 12, 0, 0 traits: 0=(undefined ) 13, 0, 0 traits: 0=(undefined ) 14, 0, 0 traits: 0=(undefined ) 15, 0, 0 traits: 0=(undefined ) 16, 0, 0 traits: 0=(undefined ) 17, 0, 0 traits: 0=(undefined ) 18, 0, 0 traits: 0=(undefined ) 19, 0, 0 traits: 0=(undefined ) 20, 0, 0 traits: 0=(undefined ) 21, 0, 0 traits: 0=(undefined ) 22, 0, 0 traits: 0=(undefined ) 23, 0, 0 traits: 0=(undefined ) 24, 0, 0 traits: 0=(undefined ) 25, 0, 0 traits: 0=(undefined ) 26, 0, 0 traits: 0=(undefined ) 27, 0, 0 traits: 0=(undefined ) 28, 0, 0 traits: 0=(undefined ) 29, 0, 0 traits: 0=(undefined ) 30, 0, 0 traits: 0=(undefined ) 31, 0, 0 traits: 0=(undefined ) 32, 0, 0 traits: 0=(undefined ) 33, 0, 0 traits: 0=(undefined ) 34, 0, 0 traits: 0=(undefined ) 35, 0, 0 traits: 0=(undefined ) 36, 0, 0 traits: 0=(undefined ) 37, 0, 0 traits: 0=(undefined ) 38, 0, 0 traits: 0=(undefined ) 39, 0, 0 traits: 0=(undefined ) 40, 0, 0 traits: 0=(undefined ) 41, 0, 0 traits: 0=(undefined ) 42, 0, 0 traits: 0=(undefined ) 43, 0, 0 traits: 0=(undefined ) 44, 0, 0 traits: 0=(undefined ) 45, 0, 0 traits: 0=(undefined ) 46, 0, 0 traits: 0=(undefined ) 47, 0, 0 traits: 0=(undefined ) 48, 0, 0 traits: 0=(undefined ) 49, 0, 0 traits: 0=(undefined ) 50, 0, 0 traits: 0=(undefined ) 51, 0, 0 traits: 0=(undefined ) 52, 0, 0 traits: 0=(undefined ) 53, 0, 0 traits: 0=(undefined ) 54, 0, 0 traits: 0=(undefined ) 55, 0, 0 traits: 0=(undefined ) 56, 0, 0 traits: 0=(undefined ) 57, 0, 0 traits: 0=(undefined ) 58, 0, 0 traits: 0=(undefined ) 59, 0, 0 traits: 0=(undefined ) 60, 0, 0 traits: 0=(undefined ) 61, 0, 0 traits: 0=(undefined ) 62, 0, 0 traits: 0=(undefined ) 63, 0, 0 traits: 0=(undefined ) 64, 64, 0 traits: 0=(undefined )
Single quotes also work. The "wizard" image occurs after -process, so echostuff can't see it.
%IM7DEV%magick ^ xc:red ^ -size 1x10 gradient:blue-green ^ rose: ^ -process 'echostuff hello world' ^ wizard: ^ NULL:
echostuff: 2 arguments: [hello] [world] mc=black echostuff: Input image [0] [red] depth 32 size 1x1 Virtual pixel method 0 [(null)] mattecolor: (null) fuzz 0 alpha is on: no channels 3 meta_channels 0 ( MaxPixelChannels = 64 ) GetPixelChannels() = 3 Number of channels with update trait = 3 Image channels: i, channel, name, traits 0, 0, R, traits: 2=(update ) 1, 1, G, traits: 2=(update ) 2, 2, B, traits: 2=(update ) channel_map: i, channel, offset, traits 0, 0, 0 traits: 2=(update ) 1, 1, 1 traits: 2=(update ) 2, 2, 2 traits: 2=(update ) 3, 0, 0 traits: 0=(undefined ) 4, 0, 0 traits: 0=(undefined ) 5, 0, 0 traits: 0=(undefined ) 6, 0, 0 traits: 0=(undefined ) 7, 0, 0 traits: 0=(undefined ) 8, 0, 0 traits: 0=(undefined ) 9, 0, 0 traits: 0=(undefined ) 10, 0, 0 traits: 0=(undefined ) 11, 0, 0 traits: 0=(undefined ) 12, 0, 0 traits: 0=(undefined ) 13, 0, 0 traits: 0=(undefined ) 14, 0, 0 traits: 0=(undefined ) 15, 0, 0 traits: 0=(undefined ) 16, 0, 0 traits: 0=(undefined ) 17, 0, 0 traits: 0=(undefined ) 18, 0, 0 traits: 0=(undefined ) 19, 0, 0 traits: 0=(undefined ) 20, 0, 0 traits: 0=(undefined ) 21, 0, 0 traits: 0=(undefined ) 22, 0, 0 traits: 0=(undefined ) 23, 0, 0 traits: 0=(undefined ) 24, 0, 0 traits: 0=(undefined ) 25, 0, 0 traits: 0=(undefined ) 26, 0, 0 traits: 0=(undefined ) 27, 0, 0 traits: 0=(undefined ) 28, 0, 0 traits: 0=(undefined ) 29, 0, 0 traits: 0=(undefined ) 30, 0, 0 traits: 0=(undefined ) 31, 0, 0 traits: 0=(undefined ) 32, 0, 0 traits: 0=(undefined ) 33, 0, 0 traits: 0=(undefined ) 34, 0, 0 traits: 0=(undefined ) 35, 0, 0 traits: 0=(undefined ) 36, 0, 0 traits: 0=(undefined ) 37, 0, 0 traits: 0=(undefined ) 38, 0, 0 traits: 0=(undefined ) 39, 0, 0 traits: 0=(undefined ) 40, 0, 0 traits: 0=(undefined ) 41, 0, 0 traits: 0=(undefined ) 42, 0, 0 traits: 0=(undefined ) 43, 0, 0 traits: 0=(undefined ) 44, 0, 0 traits: 0=(undefined ) 45, 0, 0 traits: 0=(undefined ) 46, 0, 0 traits: 0=(undefined ) 47, 0, 0 traits: 0=(undefined ) 48, 0, 0 traits: 0=(undefined ) 49, 0, 0 traits: 0=(undefined ) 50, 0, 0 traits: 0=(undefined ) 51, 0, 0 traits: 0=(undefined ) 52, 0, 0 traits: 0=(undefined ) 53, 0, 0 traits: 0=(undefined ) 54, 0, 0 traits: 0=(undefined ) 55, 0, 0 traits: 0=(undefined ) 56, 0, 0 traits: 0=(undefined ) 57, 0, 0 traits: 0=(undefined ) 58, 0, 0 traits: 0=(undefined ) 59, 0, 0 traits: 0=(undefined ) 60, 0, 0 traits: 0=(undefined ) 61, 0, 0 traits: 0=(undefined ) 62, 0, 0 traits: 0=(undefined ) 63, 0, 0 traits: 0=(undefined ) 64, 64, 0 traits: 0=(undefined ) mc=black echostuff: Input image [1] [blue] depth 32 size 1x10 Virtual pixel method 0 [(null)] mattecolor: (null) fuzz 0 alpha is on: no channels 3 meta_channels 0 ( MaxPixelChannels = 64 ) GetPixelChannels() = 3 Number of channels with update trait = 3 Image channels: i, channel, name, traits 0, 0, R, traits: 2=(update ) 1, 1, G, traits: 2=(update ) 2, 2, B, traits: 2=(update ) channel_map: i, channel, offset, traits 0, 0, 0 traits: 2=(update ) 1, 1, 1 traits: 2=(update ) 2, 2, 2 traits: 2=(update ) 3, 0, 0 traits: 0=(undefined ) 4, 0, 0 traits: 0=(undefined ) 5, 0, 0 traits: 0=(undefined ) 6, 0, 0 traits: 0=(undefined ) 7, 0, 0 traits: 0=(undefined ) 8, 0, 0 traits: 0=(undefined ) 9, 0, 0 traits: 0=(undefined ) 10, 0, 0 traits: 0=(undefined ) 11, 0, 0 traits: 0=(undefined ) 12, 0, 0 traits: 0=(undefined ) 13, 0, 0 traits: 0=(undefined ) 14, 0, 0 traits: 0=(undefined ) 15, 0, 0 traits: 0=(undefined ) 16, 0, 0 traits: 0=(undefined ) 17, 0, 0 traits: 0=(undefined ) 18, 0, 0 traits: 0=(undefined ) 19, 0, 0 traits: 0=(undefined ) 20, 0, 0 traits: 0=(undefined ) 21, 0, 0 traits: 0=(undefined ) 22, 0, 0 traits: 0=(undefined ) 23, 0, 0 traits: 0=(undefined ) 24, 0, 0 traits: 0=(undefined ) 25, 0, 0 traits: 0=(undefined ) 26, 0, 0 traits: 0=(undefined ) 27, 0, 0 traits: 0=(undefined ) 28, 0, 0 traits: 0=(undefined ) 29, 0, 0 traits: 0=(undefined ) 30, 0, 0 traits: 0=(undefined ) 31, 0, 0 traits: 0=(undefined ) 32, 0, 0 traits: 0=(undefined ) 33, 0, 0 traits: 0=(undefined ) 34, 0, 0 traits: 0=(undefined ) 35, 0, 0 traits: 0=(undefined ) 36, 0, 0 traits: 0=(undefined ) 37, 0, 0 traits: 0=(undefined ) 38, 0, 0 traits: 0=(undefined ) 39, 0, 0 traits: 0=(undefined ) 40, 0, 0 traits: 0=(undefined ) 41, 0, 0 traits: 0=(undefined ) 42, 0, 0 traits: 0=(undefined ) 43, 0, 0 traits: 0=(undefined ) 44, 0, 0 traits: 0=(undefined ) 45, 0, 0 traits: 0=(undefined ) 46, 0, 0 traits: 0=(undefined ) 47, 0, 0 traits: 0=(undefined ) 48, 0, 0 traits: 0=(undefined ) 49, 0, 0 traits: 0=(undefined ) 50, 0, 0 traits: 0=(undefined ) 51, 0, 0 traits: 0=(undefined ) 52, 0, 0 traits: 0=(undefined ) 53, 0, 0 traits: 0=(undefined ) 54, 0, 0 traits: 0=(undefined ) 55, 0, 0 traits: 0=(undefined ) 56, 0, 0 traits: 0=(undefined ) 57, 0, 0 traits: 0=(undefined ) 58, 0, 0 traits: 0=(undefined ) 59, 0, 0 traits: 0=(undefined ) 60, 0, 0 traits: 0=(undefined ) 61, 0, 0 traits: 0=(undefined ) 62, 0, 0 traits: 0=(undefined ) 63, 0, 0 traits: 0=(undefined ) 64, 64, 0 traits: 0=(undefined ) mc=black echostuff: Input image [2] [ROSE] depth 8 size 70x46 Virtual pixel method 0 [(null)] mattecolor: (null) fuzz 0 alpha is on: no channels 3 meta_channels 0 ( MaxPixelChannels = 64 ) GetPixelChannels() = 3 Number of channels with update trait = 3 Image channels: i, channel, name, traits 0, 0, R, traits: 2=(update ) 1, 1, G, traits: 2=(update ) 2, 2, B, traits: 2=(update ) channel_map: i, channel, offset, traits 0, 0, 0 traits: 2=(update ) 1, 1, 1 traits: 2=(update ) 2, 2, 2 traits: 2=(update ) 3, 0, 0 traits: 0=(undefined ) 4, 0, 0 traits: 0=(undefined ) 5, 0, 0 traits: 0=(undefined ) 6, 0, 0 traits: 0=(undefined ) 7, 0, 0 traits: 0=(undefined ) 8, 0, 0 traits: 0=(undefined ) 9, 0, 0 traits: 0=(undefined ) 10, 0, 0 traits: 0=(undefined ) 11, 0, 0 traits: 0=(undefined ) 12, 0, 0 traits: 0=(undefined ) 13, 0, 0 traits: 0=(undefined ) 14, 0, 0 traits: 0=(undefined ) 15, 0, 0 traits: 0=(undefined ) 16, 0, 0 traits: 0=(undefined ) 17, 0, 0 traits: 0=(undefined ) 18, 0, 0 traits: 0=(undefined ) 19, 0, 0 traits: 0=(undefined ) 20, 0, 0 traits: 0=(undefined ) 21, 0, 0 traits: 0=(undefined ) 22, 0, 0 traits: 0=(undefined ) 23, 0, 0 traits: 0=(undefined ) 24, 0, 0 traits: 0=(undefined ) 25, 0, 0 traits: 0=(undefined ) 26, 0, 0 traits: 0=(undefined ) 27, 0, 0 traits: 0=(undefined ) 28, 0, 0 traits: 0=(undefined ) 29, 0, 0 traits: 0=(undefined ) 30, 0, 0 traits: 0=(undefined ) 31, 0, 0 traits: 0=(undefined ) 32, 0, 0 traits: 0=(undefined ) 33, 0, 0 traits: 0=(undefined ) 34, 0, 0 traits: 0=(undefined ) 35, 0, 0 traits: 0=(undefined ) 36, 0, 0 traits: 0=(undefined ) 37, 0, 0 traits: 0=(undefined ) 38, 0, 0 traits: 0=(undefined ) 39, 0, 0 traits: 0=(undefined ) 40, 0, 0 traits: 0=(undefined ) 41, 0, 0 traits: 0=(undefined ) 42, 0, 0 traits: 0=(undefined ) 43, 0, 0 traits: 0=(undefined ) 44, 0, 0 traits: 0=(undefined ) 45, 0, 0 traits: 0=(undefined ) 46, 0, 0 traits: 0=(undefined ) 47, 0, 0 traits: 0=(undefined ) 48, 0, 0 traits: 0=(undefined ) 49, 0, 0 traits: 0=(undefined ) 50, 0, 0 traits: 0=(undefined ) 51, 0, 0 traits: 0=(undefined ) 52, 0, 0 traits: 0=(undefined ) 53, 0, 0 traits: 0=(undefined ) 54, 0, 0 traits: 0=(undefined ) 55, 0, 0 traits: 0=(undefined ) 56, 0, 0 traits: 0=(undefined ) 57, 0, 0 traits: 0=(undefined ) 58, 0, 0 traits: 0=(undefined ) 59, 0, 0 traits: 0=(undefined ) 60, 0, 0 traits: 0=(undefined ) 61, 0, 0 traits: 0=(undefined ) 62, 0, 0 traits: 0=(undefined ) 63, 0, 0 traits: 0=(undefined ) 64, 64, 0 traits: 0=(undefined )
The process is within parentheses, so the only image is the clone.
%IM7DEV%magick ^
xc:red ^
-size 1x10 gradient:blue-green ^
rose: ^
( -clone 0 ^
-process 'echostuff hello world' ^
) ^
wizard: ^
NULL:
echostuff: 2 arguments: [hello] [world] mc=black echostuff: Input image [0] [red] depth 32 size 1x1 Virtual pixel method 0 [(null)] mattecolor: (null) fuzz 0 alpha is on: no channels 3 meta_channels 0 ( MaxPixelChannels = 64 ) GetPixelChannels() = 3 Number of channels with update trait = 3 Image channels: i, channel, name, traits 0, 0, R, traits: 2=(update ) 1, 1, G, traits: 2=(update ) 2, 2, B, traits: 2=(update ) channel_map: i, channel, offset, traits 0, 0, 0 traits: 2=(update ) 1, 1, 1 traits: 2=(update ) 2, 2, 2 traits: 2=(update ) 3, 0, 0 traits: 0=(undefined ) 4, 0, 0 traits: 0=(undefined ) 5, 0, 0 traits: 0=(undefined ) 6, 0, 0 traits: 0=(undefined ) 7, 0, 0 traits: 0=(undefined ) 8, 0, 0 traits: 0=(undefined ) 9, 0, 0 traits: 0=(undefined ) 10, 0, 0 traits: 0=(undefined ) 11, 0, 0 traits: 0=(undefined ) 12, 0, 0 traits: 0=(undefined ) 13, 0, 0 traits: 0=(undefined ) 14, 0, 0 traits: 0=(undefined ) 15, 0, 0 traits: 0=(undefined ) 16, 0, 0 traits: 0=(undefined ) 17, 0, 0 traits: 0=(undefined ) 18, 0, 0 traits: 0=(undefined ) 19, 0, 0 traits: 0=(undefined ) 20, 0, 0 traits: 0=(undefined ) 21, 0, 0 traits: 0=(undefined ) 22, 0, 0 traits: 0=(undefined ) 23, 0, 0 traits: 0=(undefined ) 24, 0, 0 traits: 0=(undefined ) 25, 0, 0 traits: 0=(undefined ) 26, 0, 0 traits: 0=(undefined ) 27, 0, 0 traits: 0=(undefined ) 28, 0, 0 traits: 0=(undefined ) 29, 0, 0 traits: 0=(undefined ) 30, 0, 0 traits: 0=(undefined ) 31, 0, 0 traits: 0=(undefined ) 32, 0, 0 traits: 0=(undefined ) 33, 0, 0 traits: 0=(undefined ) 34, 0, 0 traits: 0=(undefined ) 35, 0, 0 traits: 0=(undefined ) 36, 0, 0 traits: 0=(undefined ) 37, 0, 0 traits: 0=(undefined ) 38, 0, 0 traits: 0=(undefined ) 39, 0, 0 traits: 0=(undefined ) 40, 0, 0 traits: 0=(undefined ) 41, 0, 0 traits: 0=(undefined ) 42, 0, 0 traits: 0=(undefined ) 43, 0, 0 traits: 0=(undefined ) 44, 0, 0 traits: 0=(undefined ) 45, 0, 0 traits: 0=(undefined ) 46, 0, 0 traits: 0=(undefined ) 47, 0, 0 traits: 0=(undefined ) 48, 0, 0 traits: 0=(undefined ) 49, 0, 0 traits: 0=(undefined ) 50, 0, 0 traits: 0=(undefined ) 51, 0, 0 traits: 0=(undefined ) 52, 0, 0 traits: 0=(undefined ) 53, 0, 0 traits: 0=(undefined ) 54, 0, 0 traits: 0=(undefined ) 55, 0, 0 traits: 0=(undefined ) 56, 0, 0 traits: 0=(undefined ) 57, 0, 0 traits: 0=(undefined ) 58, 0, 0 traits: 0=(undefined ) 59, 0, 0 traits: 0=(undefined ) 60, 0, 0 traits: 0=(undefined ) 61, 0, 0 traits: 0=(undefined ) 62, 0, 0 traits: 0=(undefined ) 63, 0, 0 traits: 0=(undefined ) 64, 64, 0 traits: 0=(undefined )
What happens if we don't clone?
%IM7DEV%magick ^
xc:red ^
-size 1x10 gradient:blue-green ^
rose: ^
( ^
-process 'echostuff hello world' ^
) ^
wizard: ^
NULL:
cmd /c exit /B 0
magick: no images found for operation `-process' at CLI arg 7 @ error/operation.c/CLIOption/5481.
IM v7 correctly reports an error: we start a new list with "(", and then try to "-process" an image, but that are no images in the list.
From v6, we get a weird result. The process module is given a list of four images: the three that come before it, and also the one that comes after it. This may be a legacy from old syntax when arguments were given before the images. Open parenthesis should normally be immediately followed by an image (not an operation) so I would regard the visibility of wizard to the module as an "undocumented feature".
dumpimage.c dumps some data about each image in the list to stdout. It is sensitive to the "-precision" setting. It has no effect on the image list.
%IM7DEV%magick ^ -size 1x10 gradient:red-blue ^ -precision 15 ^ -process dumpimage ^ NULL:
Input image [0] [red] depth 32 size 1x10 channels=3 0,0: 4294967295,0,0,4294967295 0,1: 3817748706.66667,0,477218588.333333,4294967295 0,2: 3340530118.33333,0,954437176.666667,4294967295 0,3: 2863311530,0,1431655765,4294967295 0,4: 2386092941.66667,0,1908874353.33333,4294967295 0,5: 1908874353.33333,0,2386092941.66667,4294967295 0,6: 1431655765,0,2863311530,4294967295 0,7: 954437176.666667,0,3340530118.33333,4294967295 0,8: 477218588.333334,0,3817748706.66667,4294967295 0,9: 0,0,4294967295,4294967295
addend.c adds an image to the end of the list. The new image is a clone of the last image, the same size, but entirely black.
-process addend is equivalent to ( -repect-parentheses +clone -fill black -colorize 100 ).
%IM7DEV%magick ^ toes.png ^ -process addend ^ -delete 0 ^ cu_addend.png |
|
We can use echostuff to see what is happening. This is similar to using -write info:. We create two images, so addend adds a third.
%IM7DEV%magick ^ toes.png ^ ( +clone -fill khaki -colorize 50 -resize 75%% ) ^ -process addend ^ -process echostuff ^ -write info: ^ +append +repage ^ cu_addend2.png echostuff: no arguments mc=black echostuff: Input image [0] [toes.png] depth 16 size 267x233 Virtual pixel method 0 [(null)] mattecolor: (null) fuzz 0 alpha is on: no channels 3 meta_channels 0 ( MaxPixelChannels = 64 ) GetPixelChannels() = 3 Number of channels with update trait = 3 Image channels: i, channel, name, traits 0, 0, R, traits: 2=(update ) 1, 1, G, traits: 2=(update ) 2, 2, B, traits: 2=(update ) channel_map: i, channel, offset, traits 0, 0, 0 traits: 2=(update ) 1, 1, 1 traits: 2=(update ) 2, 2, 2 traits: 2=(update ) 3, 0, 0 traits: 0=(undefined ) 4, 0, 0 traits: 0=(undefined ) 5, 0, 0 traits: 0=(undefined ) 6, 0, 0 traits: 0=(undefined ) 7, 0, 0 traits: 0=(undefined ) 8, 0, 0 traits: 0=(undefined ) 9, 0, 0 traits: 0=(undefined ) 10, 0, 0 traits: 0=(undefined ) 11, 0, 0 traits: 0=(undefined ) 12, 0, 0 traits: 0=(undefined ) 13, 0, 0 traits: 0=(undefined ) 14, 0, 0 traits: 0=(undefined ) 15, 0, 0 traits: 0=(undefined ) 16, 0, 0 traits: 0=(undefined ) 17, 0, 0 traits: 0=(undefined ) 18, 0, 0 traits: 0=(undefined ) 19, 0, 0 traits: 0=(undefined ) 20, 0, 0 traits: 0=(undefined ) 21, 0, 0 traits: 0=(undefined ) 22, 0, 0 traits: 0=(undefined ) 23, 0, 0 traits: 0=(undefined ) 24, 0, 0 traits: 0=(undefined ) 25, 0, 0 traits: 0=(undefined ) 26, 0, 0 traits: 0=(undefined ) 27, 0, 0 traits: 0=(undefined ) 28, 0, 0 traits: 0=(undefined ) 29, 0, 0 traits: 0=(undefined ) 30, 0, 0 traits: 0=(undefined ) 31, 0, 0 traits: 0=(undefined ) 32, 0, 0 traits: 0=(undefined ) 33, 0, 0 traits: 0=(undefined ) 34, 0, 0 traits: 0=(undefined ) 35, 0, 0 traits: 0=(undefined ) 36, 0, 0 traits: 0=(undefined ) 37, 0, 0 traits: 0=(undefined ) 38, 0, 0 traits: 0=(undefined ) 39, 0, 0 traits: 0=(undefined ) 40, 0, 0 traits: 0=(undefined ) 41, 0, 0 traits: 0=(undefined ) 42, 0, 0 traits: 0=(undefined ) 43, 0, 0 traits: 0=(undefined ) 44, 0, 0 traits: 0=(undefined ) 45, 0, 0 traits: 0=(undefined ) 46, 0, 0 traits: 0=(undefined ) 47, 0, 0 traits: 0=(undefined ) 48, 0, 0 traits: 0=(undefined ) 49, 0, 0 traits: 0=(undefined ) 50, 0, 0 traits: 0=(undefined ) 51, 0, 0 traits: 0=(undefined ) 52, 0, 0 traits: 0=(undefined ) 53, 0, 0 traits: 0=(undefined ) 54, 0, 0 traits: 0=(undefined ) 55, 0, 0 traits: 0=(undefined ) 56, 0, 0 traits: 0=(undefined ) 57, 0, 0 traits: 0=(undefined ) 58, 0, 0 traits: 0=(undefined ) 59, 0, 0 traits: 0=(undefined ) 60, 0, 0 traits: 0=(undefined ) 61, 0, 0 traits: 0=(undefined ) 62, 0, 0 traits: 0=(undefined ) 63, 0, 0 traits: 0=(undefined ) 64, 64, 0 traits: 0=(undefined ) mc=black echostuff: Input image [1] [toes.png] depth 16 size 200x175 Virtual pixel method 0 [(null)] mattecolor: (null) fuzz 0 alpha is on: no channels 3 meta_channels 0 ( MaxPixelChannels = 64 ) GetPixelChannels() = 3 Number of channels with update trait = 3 Image channels: i, channel, name, traits 0, 0, R, traits: 2=(update ) 1, 1, G, traits: 2=(update ) 2, 2, B, traits: 2=(update ) channel_map: i, channel, offset, traits 0, 0, 0 traits: 2=(update ) 1, 1, 1 traits: 2=(update ) 2, 2, 2 traits: 2=(update ) 3, 0, 0 traits: 0=(undefined ) 4, 0, 0 traits: 0=(undefined ) 5, 0, 0 traits: 0=(undefined ) 6, 0, 0 traits: 0=(undefined ) 7, 0, 0 traits: 0=(undefined ) 8, 0, 0 traits: 0=(undefined ) 9, 0, 0 traits: 0=(undefined ) 10, 0, 0 traits: 0=(undefined ) 11, 0, 0 traits: 0=(undefined ) 12, 0, 0 traits: 0=(undefined ) 13, 0, 0 traits: 0=(undefined ) 14, 0, 0 traits: 0=(undefined ) 15, 0, 0 traits: 0=(undefined ) 16, 0, 0 traits: 0=(undefined ) 17, 0, 0 traits: 0=(undefined ) 18, 0, 0 traits: 0=(undefined ) 19, 0, 0 traits: 0=(undefined ) 20, 0, 0 traits: 0=(undefined ) 21, 0, 0 traits: 0=(undefined ) 22, 0, 0 traits: 0=(undefined ) 23, 0, 0 traits: 0=(undefined ) 24, 0, 0 traits: 0=(undefined ) 25, 0, 0 traits: 0=(undefined ) 26, 0, 0 traits: 0=(undefined ) 27, 0, 0 traits: 0=(undefined ) 28, 0, 0 traits: 0=(undefined ) 29, 0, 0 traits: 0=(undefined ) 30, 0, 0 traits: 0=(undefined ) 31, 0, 0 traits: 0=(undefined ) 32, 0, 0 traits: 0=(undefined ) 33, 0, 0 traits: 0=(undefined ) 34, 0, 0 traits: 0=(undefined ) 35, 0, 0 traits: 0=(undefined ) 36, 0, 0 traits: 0=(undefined ) 37, 0, 0 traits: 0=(undefined ) 38, 0, 0 traits: 0=(undefined ) 39, 0, 0 traits: 0=(undefined ) 40, 0, 0 traits: 0=(undefined ) 41, 0, 0 traits: 0=(undefined ) 42, 0, 0 traits: 0=(undefined ) 43, 0, 0 traits: 0=(undefined ) 44, 0, 0 traits: 0=(undefined ) 45, 0, 0 traits: 0=(undefined ) 46, 0, 0 traits: 0=(undefined ) 47, 0, 0 traits: 0=(undefined ) 48, 0, 0 traits: 0=(undefined ) 49, 0, 0 traits: 0=(undefined ) 50, 0, 0 traits: 0=(undefined ) 51, 0, 0 traits: 0=(undefined ) 52, 0, 0 traits: 0=(undefined ) 53, 0, 0 traits: 0=(undefined ) 54, 0, 0 traits: 0=(undefined ) 55, 0, 0 traits: 0=(undefined ) 56, 0, 0 traits: 0=(undefined ) 57, 0, 0 traits: 0=(undefined ) 58, 0, 0 traits: 0=(undefined ) 59, 0, 0 traits: 0=(undefined ) 60, 0, 0 traits: 0=(undefined ) 61, 0, 0 traits: 0=(undefined ) 62, 0, 0 traits: 0=(undefined ) 63, 0, 0 traits: 0=(undefined ) 64, 64, 0 traits: 0=(undefined ) mc=black echostuff: Input image [2] [toes.png] depth 16 size 200x175 Virtual pixel method 0 [(null)] mattecolor: (null) fuzz 0 alpha is on: no channels 3 meta_channels 0 ( MaxPixelChannels = 64 ) GetPixelChannels() = 3 Number of channels with update trait = 3 Image channels: i, channel, name, traits 0, 0, R, traits: 2=(update ) 1, 1, G, traits: 2=(update ) 2, 2, B, traits: 2=(update ) channel_map: i, channel, offset, traits 0, 0, 0 traits: 2=(update ) 1, 1, 1 traits: 2=(update ) 2, 2, 2 traits: 2=(update ) 3, 0, 0 traits: 0=(undefined ) 4, 0, 0 traits: 0=(undefined ) 5, 0, 0 traits: 0=(undefined ) 6, 0, 0 traits: 0=(undefined ) 7, 0, 0 traits: 0=(undefined ) 8, 0, 0 traits: 0=(undefined ) 9, 0, 0 traits: 0=(undefined ) 10, 0, 0 traits: 0=(undefined ) 11, 0, 0 traits: 0=(undefined ) 12, 0, 0 traits: 0=(undefined ) 13, 0, 0 traits: 0=(undefined ) 14, 0, 0 traits: 0=(undefined ) 15, 0, 0 traits: 0=(undefined ) 16, 0, 0 traits: 0=(undefined ) 17, 0, 0 traits: 0=(undefined ) 18, 0, 0 traits: 0=(undefined ) 19, 0, 0 traits: 0=(undefined ) 20, 0, 0 traits: 0=(undefined ) 21, 0, 0 traits: 0=(undefined ) 22, 0, 0 traits: 0=(undefined ) 23, 0, 0 traits: 0=(undefined ) 24, 0, 0 traits: 0=(undefined ) 25, 0, 0 traits: 0=(undefined ) 26, 0, 0 traits: 0=(undefined ) 27, 0, 0 traits: 0=(undefined ) 28, 0, 0 traits: 0=(undefined ) 29, 0, 0 traits: 0=(undefined ) 30, 0, 0 traits: 0=(undefined ) 31, 0, 0 traits: 0=(undefined ) 32, 0, 0 traits: 0=(undefined ) 33, 0, 0 traits: 0=(undefined ) 34, 0, 0 traits: 0=(undefined ) 35, 0, 0 traits: 0=(undefined ) 36, 0, 0 traits: 0=(undefined ) 37, 0, 0 traits: 0=(undefined ) 38, 0, 0 traits: 0=(undefined ) 39, 0, 0 traits: 0=(undefined ) 40, 0, 0 traits: 0=(undefined ) 41, 0, 0 traits: 0=(undefined ) 42, 0, 0 traits: 0=(undefined ) 43, 0, 0 traits: 0=(undefined ) 44, 0, 0 traits: 0=(undefined ) 45, 0, 0 traits: 0=(undefined ) 46, 0, 0 traits: 0=(undefined ) 47, 0, 0 traits: 0=(undefined ) 48, 0, 0 traits: 0=(undefined ) 49, 0, 0 traits: 0=(undefined ) 50, 0, 0 traits: 0=(undefined ) 51, 0, 0 traits: 0=(undefined ) 52, 0, 0 traits: 0=(undefined ) 53, 0, 0 traits: 0=(undefined ) 54, 0, 0 traits: 0=(undefined ) 55, 0, 0 traits: 0=(undefined ) 56, 0, 0 traits: 0=(undefined ) 57, 0, 0 traits: 0=(undefined ) 58, 0, 0 traits: 0=(undefined ) 59, 0, 0 traits: 0=(undefined ) 60, 0, 0 traits: 0=(undefined ) 61, 0, 0 traits: 0=(undefined ) 62, 0, 0 traits: 0=(undefined ) 63, 0, 0 traits: 0=(undefined ) 64, 64, 0 traits: 0=(undefined ) toes.png[0] PNG 267x233 267x233+0+0 16-bit TrueColor sRGB 320268B 0.094u 0:00.130 toes.png[1] PNG 200x175 200x175+0+0 16-bit sRGB 320268B 0.094u 0:00.130 toes.png[2] PNG 200x175 200x175+0+0 16-bit sRGB 320268B 0.094u 0:00.130 |
|
replacelast.c replaces the last image in the list with a green image.
-process replacelast is equivalent to ( -repect-parentheses +clone -fill green -colorize 100 ) -delete -2.
%IM7DEV%magick ^ toes.png ^ ( +clone -fill khaki -colorize 50 -resize 75%% ) ^ -process replacelast ^ -process echostuff ^ -write info: ^ +append +repage ^ cu_repl.png echostuff: no arguments mc=black echostuff: Input image [0] [toes.png] depth 16 size 267x233 Virtual pixel method 0 [(null)] mattecolor: (null) fuzz 0 alpha is on: no channels 3 meta_channels 0 ( MaxPixelChannels = 64 ) GetPixelChannels() = 3 Number of channels with update trait = 3 Image channels: i, channel, name, traits 0, 0, R, traits: 2=(update ) 1, 1, G, traits: 2=(update ) 2, 2, B, traits: 2=(update ) channel_map: i, channel, offset, traits 0, 0, 0 traits: 2=(update ) 1, 1, 1 traits: 2=(update ) 2, 2, 2 traits: 2=(update ) 3, 0, 0 traits: 0=(undefined ) 4, 0, 0 traits: 0=(undefined ) 5, 0, 0 traits: 0=(undefined ) 6, 0, 0 traits: 0=(undefined ) 7, 0, 0 traits: 0=(undefined ) 8, 0, 0 traits: 0=(undefined ) 9, 0, 0 traits: 0=(undefined ) 10, 0, 0 traits: 0=(undefined ) 11, 0, 0 traits: 0=(undefined ) 12, 0, 0 traits: 0=(undefined ) 13, 0, 0 traits: 0=(undefined ) 14, 0, 0 traits: 0=(undefined ) 15, 0, 0 traits: 0=(undefined ) 16, 0, 0 traits: 0=(undefined ) 17, 0, 0 traits: 0=(undefined ) 18, 0, 0 traits: 0=(undefined ) 19, 0, 0 traits: 0=(undefined ) 20, 0, 0 traits: 0=(undefined ) 21, 0, 0 traits: 0=(undefined ) 22, 0, 0 traits: 0=(undefined ) 23, 0, 0 traits: 0=(undefined ) 24, 0, 0 traits: 0=(undefined ) 25, 0, 0 traits: 0=(undefined ) 26, 0, 0 traits: 0=(undefined ) 27, 0, 0 traits: 0=(undefined ) 28, 0, 0 traits: 0=(undefined ) 29, 0, 0 traits: 0=(undefined ) 30, 0, 0 traits: 0=(undefined ) 31, 0, 0 traits: 0=(undefined ) 32, 0, 0 traits: 0=(undefined ) 33, 0, 0 traits: 0=(undefined ) 34, 0, 0 traits: 0=(undefined ) 35, 0, 0 traits: 0=(undefined ) 36, 0, 0 traits: 0=(undefined ) 37, 0, 0 traits: 0=(undefined ) 38, 0, 0 traits: 0=(undefined ) 39, 0, 0 traits: 0=(undefined ) 40, 0, 0 traits: 0=(undefined ) 41, 0, 0 traits: 0=(undefined ) 42, 0, 0 traits: 0=(undefined ) 43, 0, 0 traits: 0=(undefined ) 44, 0, 0 traits: 0=(undefined ) 45, 0, 0 traits: 0=(undefined ) 46, 0, 0 traits: 0=(undefined ) 47, 0, 0 traits: 0=(undefined ) 48, 0, 0 traits: 0=(undefined ) 49, 0, 0 traits: 0=(undefined ) 50, 0, 0 traits: 0=(undefined ) 51, 0, 0 traits: 0=(undefined ) 52, 0, 0 traits: 0=(undefined ) 53, 0, 0 traits: 0=(undefined ) 54, 0, 0 traits: 0=(undefined ) 55, 0, 0 traits: 0=(undefined ) 56, 0, 0 traits: 0=(undefined ) 57, 0, 0 traits: 0=(undefined ) 58, 0, 0 traits: 0=(undefined ) 59, 0, 0 traits: 0=(undefined ) 60, 0, 0 traits: 0=(undefined ) 61, 0, 0 traits: 0=(undefined ) 62, 0, 0 traits: 0=(undefined ) 63, 0, 0 traits: 0=(undefined ) 64, 64, 0 traits: 0=(undefined ) mc=black echostuff: Input image [1] [toes.png] depth 8 size 200x175 Virtual pixel method 0 [(null)] mattecolor: (null) fuzz 0 alpha is on: no channels 3 meta_channels 0 ( MaxPixelChannels = 64 ) GetPixelChannels() = 3 Number of channels with update trait = 3 Image channels: i, channel, name, traits 0, 0, R, traits: 2=(update ) 1, 1, G, traits: 2=(update ) 2, 2, B, traits: 2=(update ) channel_map: i, channel, offset, traits 0, 0, 0 traits: 2=(update ) 1, 1, 1 traits: 2=(update ) 2, 2, 2 traits: 2=(update ) 3, 0, 0 traits: 0=(undefined ) 4, 0, 0 traits: 0=(undefined ) 5, 0, 0 traits: 0=(undefined ) 6, 0, 0 traits: 0=(undefined ) 7, 0, 0 traits: 0=(undefined ) 8, 0, 0 traits: 0=(undefined ) 9, 0, 0 traits: 0=(undefined ) 10, 0, 0 traits: 0=(undefined ) 11, 0, 0 traits: 0=(undefined ) 12, 0, 0 traits: 0=(undefined ) 13, 0, 0 traits: 0=(undefined ) 14, 0, 0 traits: 0=(undefined ) 15, 0, 0 traits: 0=(undefined ) 16, 0, 0 traits: 0=(undefined ) 17, 0, 0 traits: 0=(undefined ) 18, 0, 0 traits: 0=(undefined ) 19, 0, 0 traits: 0=(undefined ) 20, 0, 0 traits: 0=(undefined ) 21, 0, 0 traits: 0=(undefined ) 22, 0, 0 traits: 0=(undefined ) 23, 0, 0 traits: 0=(undefined ) 24, 0, 0 traits: 0=(undefined ) 25, 0, 0 traits: 0=(undefined ) 26, 0, 0 traits: 0=(undefined ) 27, 0, 0 traits: 0=(undefined ) 28, 0, 0 traits: 0=(undefined ) 29, 0, 0 traits: 0=(undefined ) 30, 0, 0 traits: 0=(undefined ) 31, 0, 0 traits: 0=(undefined ) 32, 0, 0 traits: 0=(undefined ) 33, 0, 0 traits: 0=(undefined ) 34, 0, 0 traits: 0=(undefined ) 35, 0, 0 traits: 0=(undefined ) 36, 0, 0 traits: 0=(undefined ) 37, 0, 0 traits: 0=(undefined ) 38, 0, 0 traits: 0=(undefined ) 39, 0, 0 traits: 0=(undefined ) 40, 0, 0 traits: 0=(undefined ) 41, 0, 0 traits: 0=(undefined ) 42, 0, 0 traits: 0=(undefined ) 43, 0, 0 traits: 0=(undefined ) 44, 0, 0 traits: 0=(undefined ) 45, 0, 0 traits: 0=(undefined ) 46, 0, 0 traits: 0=(undefined ) 47, 0, 0 traits: 0=(undefined ) 48, 0, 0 traits: 0=(undefined ) 49, 0, 0 traits: 0=(undefined ) 50, 0, 0 traits: 0=(undefined ) 51, 0, 0 traits: 0=(undefined ) 52, 0, 0 traits: 0=(undefined ) 53, 0, 0 traits: 0=(undefined ) 54, 0, 0 traits: 0=(undefined ) 55, 0, 0 traits: 0=(undefined ) 56, 0, 0 traits: 0=(undefined ) 57, 0, 0 traits: 0=(undefined ) 58, 0, 0 traits: 0=(undefined ) 59, 0, 0 traits: 0=(undefined ) 60, 0, 0 traits: 0=(undefined ) 61, 0, 0 traits: 0=(undefined ) 62, 0, 0 traits: 0=(undefined ) 63, 0, 0 traits: 0=(undefined ) 64, 64, 0 traits: 0=(undefined ) toes.png[0] PNG 267x233 267x233+0+0 16-bit TrueColor sRGB 320268B 0.109u 0:00.103 toes.png[1] PNG 200x175 200x175+0+0 8-bit sRGB 320268B 0.109u 0:00.103 |
|
replacefirst.c replaces the first image in the list with a red image the same size as the last image.
-process replacefirst is equivalent to ( -repect-parentheses +clone -fill red -colorize 100 ) -swap 0,-1 +delete.
%IM7DEV%magick ^ toes.png ^ ( +clone -fill khaki -colorize 50 -resize 75%% ) ^ -process replacefirst ^ -process echostuff ^ -write info: ^ +append +repage ^ cu_repf.png echostuff: no arguments mc=black echostuff: Input image [0] [toes.png] depth 8 size 200x175 Virtual pixel method 0 [(null)] mattecolor: (null) fuzz 0 alpha is on: no channels 3 meta_channels 0 ( MaxPixelChannels = 64 ) GetPixelChannels() = 3 Number of channels with update trait = 3 Image channels: i, channel, name, traits 0, 0, R, traits: 2=(update ) 1, 1, G, traits: 2=(update ) 2, 2, B, traits: 2=(update ) channel_map: i, channel, offset, traits 0, 0, 0 traits: 2=(update ) 1, 1, 1 traits: 2=(update ) 2, 2, 2 traits: 2=(update ) 3, 0, 0 traits: 0=(undefined ) 4, 0, 0 traits: 0=(undefined ) 5, 0, 0 traits: 0=(undefined ) 6, 0, 0 traits: 0=(undefined ) 7, 0, 0 traits: 0=(undefined ) 8, 0, 0 traits: 0=(undefined ) 9, 0, 0 traits: 0=(undefined ) 10, 0, 0 traits: 0=(undefined ) 11, 0, 0 traits: 0=(undefined ) 12, 0, 0 traits: 0=(undefined ) 13, 0, 0 traits: 0=(undefined ) 14, 0, 0 traits: 0=(undefined ) 15, 0, 0 traits: 0=(undefined ) 16, 0, 0 traits: 0=(undefined ) 17, 0, 0 traits: 0=(undefined ) 18, 0, 0 traits: 0=(undefined ) 19, 0, 0 traits: 0=(undefined ) 20, 0, 0 traits: 0=(undefined ) 21, 0, 0 traits: 0=(undefined ) 22, 0, 0 traits: 0=(undefined ) 23, 0, 0 traits: 0=(undefined ) 24, 0, 0 traits: 0=(undefined ) 25, 0, 0 traits: 0=(undefined ) 26, 0, 0 traits: 0=(undefined ) 27, 0, 0 traits: 0=(undefined ) 28, 0, 0 traits: 0=(undefined ) 29, 0, 0 traits: 0=(undefined ) 30, 0, 0 traits: 0=(undefined ) 31, 0, 0 traits: 0=(undefined ) 32, 0, 0 traits: 0=(undefined ) 33, 0, 0 traits: 0=(undefined ) 34, 0, 0 traits: 0=(undefined ) 35, 0, 0 traits: 0=(undefined ) 36, 0, 0 traits: 0=(undefined ) 37, 0, 0 traits: 0=(undefined ) 38, 0, 0 traits: 0=(undefined ) 39, 0, 0 traits: 0=(undefined ) 40, 0, 0 traits: 0=(undefined ) 41, 0, 0 traits: 0=(undefined ) 42, 0, 0 traits: 0=(undefined ) 43, 0, 0 traits: 0=(undefined ) 44, 0, 0 traits: 0=(undefined ) 45, 0, 0 traits: 0=(undefined ) 46, 0, 0 traits: 0=(undefined ) 47, 0, 0 traits: 0=(undefined ) 48, 0, 0 traits: 0=(undefined ) 49, 0, 0 traits: 0=(undefined ) 50, 0, 0 traits: 0=(undefined ) 51, 0, 0 traits: 0=(undefined ) 52, 0, 0 traits: 0=(undefined ) 53, 0, 0 traits: 0=(undefined ) 54, 0, 0 traits: 0=(undefined ) 55, 0, 0 traits: 0=(undefined ) 56, 0, 0 traits: 0=(undefined ) 57, 0, 0 traits: 0=(undefined ) 58, 0, 0 traits: 0=(undefined ) 59, 0, 0 traits: 0=(undefined ) 60, 0, 0 traits: 0=(undefined ) 61, 0, 0 traits: 0=(undefined ) 62, 0, 0 traits: 0=(undefined ) 63, 0, 0 traits: 0=(undefined ) 64, 64, 0 traits: 0=(undefined ) mc=black echostuff: Input image [1] [toes.png] depth 16 size 200x175 Virtual pixel method 0 [(null)] mattecolor: (null) fuzz 0 alpha is on: no channels 3 meta_channels 0 ( MaxPixelChannels = 64 ) GetPixelChannels() = 3 Number of channels with update trait = 3 Image channels: i, channel, name, traits 0, 0, R, traits: 2=(update ) 1, 1, G, traits: 2=(update ) 2, 2, B, traits: 2=(update ) channel_map: i, channel, offset, traits 0, 0, 0 traits: 2=(update ) 1, 1, 1 traits: 2=(update ) 2, 2, 2 traits: 2=(update ) 3, 0, 0 traits: 0=(undefined ) 4, 0, 0 traits: 0=(undefined ) 5, 0, 0 traits: 0=(undefined ) 6, 0, 0 traits: 0=(undefined ) 7, 0, 0 traits: 0=(undefined ) 8, 0, 0 traits: 0=(undefined ) 9, 0, 0 traits: 0=(undefined ) 10, 0, 0 traits: 0=(undefined ) 11, 0, 0 traits: 0=(undefined ) 12, 0, 0 traits: 0=(undefined ) 13, 0, 0 traits: 0=(undefined ) 14, 0, 0 traits: 0=(undefined ) 15, 0, 0 traits: 0=(undefined ) 16, 0, 0 traits: 0=(undefined ) 17, 0, 0 traits: 0=(undefined ) 18, 0, 0 traits: 0=(undefined ) 19, 0, 0 traits: 0=(undefined ) 20, 0, 0 traits: 0=(undefined ) 21, 0, 0 traits: 0=(undefined ) 22, 0, 0 traits: 0=(undefined ) 23, 0, 0 traits: 0=(undefined ) 24, 0, 0 traits: 0=(undefined ) 25, 0, 0 traits: 0=(undefined ) 26, 0, 0 traits: 0=(undefined ) 27, 0, 0 traits: 0=(undefined ) 28, 0, 0 traits: 0=(undefined ) 29, 0, 0 traits: 0=(undefined ) 30, 0, 0 traits: 0=(undefined ) 31, 0, 0 traits: 0=(undefined ) 32, 0, 0 traits: 0=(undefined ) 33, 0, 0 traits: 0=(undefined ) 34, 0, 0 traits: 0=(undefined ) 35, 0, 0 traits: 0=(undefined ) 36, 0, 0 traits: 0=(undefined ) 37, 0, 0 traits: 0=(undefined ) 38, 0, 0 traits: 0=(undefined ) 39, 0, 0 traits: 0=(undefined ) 40, 0, 0 traits: 0=(undefined ) 41, 0, 0 traits: 0=(undefined ) 42, 0, 0 traits: 0=(undefined ) 43, 0, 0 traits: 0=(undefined ) 44, 0, 0 traits: 0=(undefined ) 45, 0, 0 traits: 0=(undefined ) 46, 0, 0 traits: 0=(undefined ) 47, 0, 0 traits: 0=(undefined ) 48, 0, 0 traits: 0=(undefined ) 49, 0, 0 traits: 0=(undefined ) 50, 0, 0 traits: 0=(undefined ) 51, 0, 0 traits: 0=(undefined ) 52, 0, 0 traits: 0=(undefined ) 53, 0, 0 traits: 0=(undefined ) 54, 0, 0 traits: 0=(undefined ) 55, 0, 0 traits: 0=(undefined ) 56, 0, 0 traits: 0=(undefined ) 57, 0, 0 traits: 0=(undefined ) 58, 0, 0 traits: 0=(undefined ) 59, 0, 0 traits: 0=(undefined ) 60, 0, 0 traits: 0=(undefined ) 61, 0, 0 traits: 0=(undefined ) 62, 0, 0 traits: 0=(undefined ) 63, 0, 0 traits: 0=(undefined ) 64, 64, 0 traits: 0=(undefined ) toes.png[0] PNG 200x175 200x175+0+0 8-bit sRGB 320268B 0.110u 0:00.102 toes.png[1] PNG 200x175 200x175+0+0 16-bit sRGB 320268B 0.110u 0:00.102 |
|
replaceall.c replaces the all images in the list with a single blue image the same size as the last image.
-process replacefirst is equivalent to ( -repect-parentheses +clone -fill blue -colorize 100 ) -delete 0--2.
%IM7DEV%magick ^ toes.png ^ ( +clone -fill khaki -colorize 50 -resize 75%% ) ^ -process replaceall ^ -process echostuff ^ -write info: ^ +append +repage ^ cu_repa.png echostuff: no arguments mc=black echostuff: Input image [0] [toes.png] depth 8 size 200x175 Virtual pixel method 0 [(null)] mattecolor: (null) fuzz 0 alpha is on: no channels 3 meta_channels 0 ( MaxPixelChannels = 64 ) GetPixelChannels() = 3 Number of channels with update trait = 3 Image channels: i, channel, name, traits 0, 0, R, traits: 2=(update ) 1, 1, G, traits: 2=(update ) 2, 2, B, traits: 2=(update ) channel_map: i, channel, offset, traits 0, 0, 0 traits: 2=(update ) 1, 1, 1 traits: 2=(update ) 2, 2, 2 traits: 2=(update ) 3, 0, 0 traits: 0=(undefined ) 4, 0, 0 traits: 0=(undefined ) 5, 0, 0 traits: 0=(undefined ) 6, 0, 0 traits: 0=(undefined ) 7, 0, 0 traits: 0=(undefined ) 8, 0, 0 traits: 0=(undefined ) 9, 0, 0 traits: 0=(undefined ) 10, 0, 0 traits: 0=(undefined ) 11, 0, 0 traits: 0=(undefined ) 12, 0, 0 traits: 0=(undefined ) 13, 0, 0 traits: 0=(undefined ) 14, 0, 0 traits: 0=(undefined ) 15, 0, 0 traits: 0=(undefined ) 16, 0, 0 traits: 0=(undefined ) 17, 0, 0 traits: 0=(undefined ) 18, 0, 0 traits: 0=(undefined ) 19, 0, 0 traits: 0=(undefined ) 20, 0, 0 traits: 0=(undefined ) 21, 0, 0 traits: 0=(undefined ) 22, 0, 0 traits: 0=(undefined ) 23, 0, 0 traits: 0=(undefined ) 24, 0, 0 traits: 0=(undefined ) 25, 0, 0 traits: 0=(undefined ) 26, 0, 0 traits: 0=(undefined ) 27, 0, 0 traits: 0=(undefined ) 28, 0, 0 traits: 0=(undefined ) 29, 0, 0 traits: 0=(undefined ) 30, 0, 0 traits: 0=(undefined ) 31, 0, 0 traits: 0=(undefined ) 32, 0, 0 traits: 0=(undefined ) 33, 0, 0 traits: 0=(undefined ) 34, 0, 0 traits: 0=(undefined ) 35, 0, 0 traits: 0=(undefined ) 36, 0, 0 traits: 0=(undefined ) 37, 0, 0 traits: 0=(undefined ) 38, 0, 0 traits: 0=(undefined ) 39, 0, 0 traits: 0=(undefined ) 40, 0, 0 traits: 0=(undefined ) 41, 0, 0 traits: 0=(undefined ) 42, 0, 0 traits: 0=(undefined ) 43, 0, 0 traits: 0=(undefined ) 44, 0, 0 traits: 0=(undefined ) 45, 0, 0 traits: 0=(undefined ) 46, 0, 0 traits: 0=(undefined ) 47, 0, 0 traits: 0=(undefined ) 48, 0, 0 traits: 0=(undefined ) 49, 0, 0 traits: 0=(undefined ) 50, 0, 0 traits: 0=(undefined ) 51, 0, 0 traits: 0=(undefined ) 52, 0, 0 traits: 0=(undefined ) 53, 0, 0 traits: 0=(undefined ) 54, 0, 0 traits: 0=(undefined ) 55, 0, 0 traits: 0=(undefined ) 56, 0, 0 traits: 0=(undefined ) 57, 0, 0 traits: 0=(undefined ) 58, 0, 0 traits: 0=(undefined ) 59, 0, 0 traits: 0=(undefined ) 60, 0, 0 traits: 0=(undefined ) 61, 0, 0 traits: 0=(undefined ) 62, 0, 0 traits: 0=(undefined ) 63, 0, 0 traits: 0=(undefined ) 64, 64, 0 traits: 0=(undefined ) toes.png PNG 200x175 200x175+0+0 8-bit sRGB 320268B 0.109u 0:00.106 |
|
replaceeach.c replaces each of the images in the list with a magenta image the same size as the corresponding input image.
-process replaceeach is equivalent to -fill magenta -colorize 100.
%IM7DEV%magick ^ toes.png ^ ( +clone -fill khaki -colorize 50 -resize 75%% ) ^ -process replaceeach ^ -process echostuff ^ -write info: ^ +append +repage ^ cu_repe.png echostuff: no arguments mc=black echostuff: Input image [0] [toes.png] depth 8 size 267x233 Virtual pixel method 0 [(null)] mattecolor: (null) fuzz 0 alpha is on: no channels 3 meta_channels 0 ( MaxPixelChannels = 64 ) GetPixelChannels() = 3 Number of channels with update trait = 3 Image channels: i, channel, name, traits 0, 0, R, traits: 2=(update ) 1, 1, G, traits: 2=(update ) 2, 2, B, traits: 2=(update ) channel_map: i, channel, offset, traits 0, 0, 0 traits: 2=(update ) 1, 1, 1 traits: 2=(update ) 2, 2, 2 traits: 2=(update ) 3, 0, 0 traits: 0=(undefined ) 4, 0, 0 traits: 0=(undefined ) 5, 0, 0 traits: 0=(undefined ) 6, 0, 0 traits: 0=(undefined ) 7, 0, 0 traits: 0=(undefined ) 8, 0, 0 traits: 0=(undefined ) 9, 0, 0 traits: 0=(undefined ) 10, 0, 0 traits: 0=(undefined ) 11, 0, 0 traits: 0=(undefined ) 12, 0, 0 traits: 0=(undefined ) 13, 0, 0 traits: 0=(undefined ) 14, 0, 0 traits: 0=(undefined ) 15, 0, 0 traits: 0=(undefined ) 16, 0, 0 traits: 0=(undefined ) 17, 0, 0 traits: 0=(undefined ) 18, 0, 0 traits: 0=(undefined ) 19, 0, 0 traits: 0=(undefined ) 20, 0, 0 traits: 0=(undefined ) 21, 0, 0 traits: 0=(undefined ) 22, 0, 0 traits: 0=(undefined ) 23, 0, 0 traits: 0=(undefined ) 24, 0, 0 traits: 0=(undefined ) 25, 0, 0 traits: 0=(undefined ) 26, 0, 0 traits: 0=(undefined ) 27, 0, 0 traits: 0=(undefined ) 28, 0, 0 traits: 0=(undefined ) 29, 0, 0 traits: 0=(undefined ) 30, 0, 0 traits: 0=(undefined ) 31, 0, 0 traits: 0=(undefined ) 32, 0, 0 traits: 0=(undefined ) 33, 0, 0 traits: 0=(undefined ) 34, 0, 0 traits: 0=(undefined ) 35, 0, 0 traits: 0=(undefined ) 36, 0, 0 traits: 0=(undefined ) 37, 0, 0 traits: 0=(undefined ) 38, 0, 0 traits: 0=(undefined ) 39, 0, 0 traits: 0=(undefined ) 40, 0, 0 traits: 0=(undefined ) 41, 0, 0 traits: 0=(undefined ) 42, 0, 0 traits: 0=(undefined ) 43, 0, 0 traits: 0=(undefined ) 44, 0, 0 traits: 0=(undefined ) 45, 0, 0 traits: 0=(undefined ) 46, 0, 0 traits: 0=(undefined ) 47, 0, 0 traits: 0=(undefined ) 48, 0, 0 traits: 0=(undefined ) 49, 0, 0 traits: 0=(undefined ) 50, 0, 0 traits: 0=(undefined ) 51, 0, 0 traits: 0=(undefined ) 52, 0, 0 traits: 0=(undefined ) 53, 0, 0 traits: 0=(undefined ) 54, 0, 0 traits: 0=(undefined ) 55, 0, 0 traits: 0=(undefined ) 56, 0, 0 traits: 0=(undefined ) 57, 0, 0 traits: 0=(undefined ) 58, 0, 0 traits: 0=(undefined ) 59, 0, 0 traits: 0=(undefined ) 60, 0, 0 traits: 0=(undefined ) 61, 0, 0 traits: 0=(undefined ) 62, 0, 0 traits: 0=(undefined ) 63, 0, 0 traits: 0=(undefined ) 64, 64, 0 traits: 0=(undefined ) mc=black echostuff: Input image [1] [toes.png] depth 8 size 200x175 Virtual pixel method 0 [(null)] mattecolor: (null) fuzz 0 alpha is on: no channels 3 meta_channels 0 ( MaxPixelChannels = 64 ) GetPixelChannels() = 3 Number of channels with update trait = 3 Image channels: i, channel, name, traits 0, 0, R, traits: 2=(update ) 1, 1, G, traits: 2=(update ) 2, 2, B, traits: 2=(update ) channel_map: i, channel, offset, traits 0, 0, 0 traits: 2=(update ) 1, 1, 1 traits: 2=(update ) 2, 2, 2 traits: 2=(update ) 3, 0, 0 traits: 0=(undefined ) 4, 0, 0 traits: 0=(undefined ) 5, 0, 0 traits: 0=(undefined ) 6, 0, 0 traits: 0=(undefined ) 7, 0, 0 traits: 0=(undefined ) 8, 0, 0 traits: 0=(undefined ) 9, 0, 0 traits: 0=(undefined ) 10, 0, 0 traits: 0=(undefined ) 11, 0, 0 traits: 0=(undefined ) 12, 0, 0 traits: 0=(undefined ) 13, 0, 0 traits: 0=(undefined ) 14, 0, 0 traits: 0=(undefined ) 15, 0, 0 traits: 0=(undefined ) 16, 0, 0 traits: 0=(undefined ) 17, 0, 0 traits: 0=(undefined ) 18, 0, 0 traits: 0=(undefined ) 19, 0, 0 traits: 0=(undefined ) 20, 0, 0 traits: 0=(undefined ) 21, 0, 0 traits: 0=(undefined ) 22, 0, 0 traits: 0=(undefined ) 23, 0, 0 traits: 0=(undefined ) 24, 0, 0 traits: 0=(undefined ) 25, 0, 0 traits: 0=(undefined ) 26, 0, 0 traits: 0=(undefined ) 27, 0, 0 traits: 0=(undefined ) 28, 0, 0 traits: 0=(undefined ) 29, 0, 0 traits: 0=(undefined ) 30, 0, 0 traits: 0=(undefined ) 31, 0, 0 traits: 0=(undefined ) 32, 0, 0 traits: 0=(undefined ) 33, 0, 0 traits: 0=(undefined ) 34, 0, 0 traits: 0=(undefined ) 35, 0, 0 traits: 0=(undefined ) 36, 0, 0 traits: 0=(undefined ) 37, 0, 0 traits: 0=(undefined ) 38, 0, 0 traits: 0=(undefined ) 39, 0, 0 traits: 0=(undefined ) 40, 0, 0 traits: 0=(undefined ) 41, 0, 0 traits: 0=(undefined ) 42, 0, 0 traits: 0=(undefined ) 43, 0, 0 traits: 0=(undefined ) 44, 0, 0 traits: 0=(undefined ) 45, 0, 0 traits: 0=(undefined ) 46, 0, 0 traits: 0=(undefined ) 47, 0, 0 traits: 0=(undefined ) 48, 0, 0 traits: 0=(undefined ) 49, 0, 0 traits: 0=(undefined ) 50, 0, 0 traits: 0=(undefined ) 51, 0, 0 traits: 0=(undefined ) 52, 0, 0 traits: 0=(undefined ) 53, 0, 0 traits: 0=(undefined ) 54, 0, 0 traits: 0=(undefined ) 55, 0, 0 traits: 0=(undefined ) 56, 0, 0 traits: 0=(undefined ) 57, 0, 0 traits: 0=(undefined ) 58, 0, 0 traits: 0=(undefined ) 59, 0, 0 traits: 0=(undefined ) 60, 0, 0 traits: 0=(undefined ) 61, 0, 0 traits: 0=(undefined ) 62, 0, 0 traits: 0=(undefined ) 63, 0, 0 traits: 0=(undefined ) 64, 64, 0 traits: 0=(undefined ) toes.png[0] PNG 267x233 267x233+0+0 8-bit sRGB 320268B 0.109u 0:00.102 toes.png[1] PNG 200x175 200x175+0+0 8-bit sRGB 320268B 0.109u 0:00.102 |
|
replacespec.c replaces each of the images in the list with an image of the given size and colour.
The size defaults to the same as the corresponding input image. If size is given, all outputs will be the same size. The colour defaults to brown.
| Option | Description | |
|---|---|---|
| Short
form |
Long form | |
| s WxH | size WxH | Size width and height.
If W is omitted, the default is taken from the image's width. If H is omitted, the default is taken from the image's height. |
| c string | color string | Any IM colour, eg White or #123. Default: brown. |
| v | verbose | Write some text output to stderr. |
For example:
%IM7DEV%magick ^ toes.png ^ ( +clone -fill khaki -colorize 50 -resize 75%% ) ^ -process replacespec ^ +append +repage ^ cu_repspec1.png |
|
%IM7DEV%magick ^ toes.png ^ ( +clone -fill khaki -colorize 50 -resize 75%% ) ^ -process 'replacespec color red size 20x20' ^ +append +repage ^ cu_repspec2.png |
|
%IM7DEV%magick ^ toes.png ^ ( +clone -fill khaki -colorize 50 -resize 75%% ) ^ -process 'replacespec color green size x20' ^ +append +repage ^ cu_repspec3.png |
|
%IM7DEV%magick ^ toes.png ^ ( +clone -fill khaki -colorize 50 -resize 75%% ) ^ -process 'replacespec color blue size 20x' ^ +append +repage ^ cu_repspec4.png |
|
My convention for modules is that verbose may give some information to stderr, including a list of the options seen by the module.
%IM7DEV%magick ^ toes.png ^ ( +clone -fill khaki -colorize 50 -resize 75%% ) ^ -process 'replacespec verbose' ^ +append +repage ^ NULL:
replacespec options: verbose input image size 267x233 input image size 200x175
Invalid options such as help give usage information to stdout, and cause IM to fail.
%IM7DEV%magick ^ toes.png ^ ( +clone -fill khaki -colorize 50 -resize 75%% ) ^ -process 'replacespec help' ^ +append +repage ^ dump.png 1>cu_rs_help.lis 2>&1 cmd /c exit /B 0
replacespec: ERROR: unknown option [help] magick: image filter signature mismatch 'replacespec': ffffffffffffffff != a20 @ error/module.c/InvokeDynamicImageFilter/1035. Usage: -process 'replacespec [OPTION]...' Replace each image. s, size WxH size of new image c, color string make new image this color
grad2.c replaces each image in the list with a two-way gradient the same size as the corresponding input image. The function visits every pixel in each output image, setting the pixel values according to a simple formula.
-process grad2 is equivalent to ??.
%IM7DEV%magick ^ toes.png ^ ( +clone -fill khaki -colorize 50 -resize 75%% ) ^ -process grad2 ^ +append +repage ^ cu_grad2.png |
|
drawcirc.c modifies each image in the list, drawing a blue circle on each one.
-process drawcirc is equivalent to -draw "fill Blue circle CX,CY CX,CY+RAD" where (CX,CY) is the centre and RAD is min(CX,CY).
%IM7DEV%magick ^ toes.png ^ ( +clone -fill khaki -colorize 50 -resize 75%% ) ^ -process drawcirc ^ +append +repage ^ cu_drawcirc.png |
|
geodist.c applies a geometric distortion to each image in the list.
This respects the -virtual-pixel and -interpolate settings. It uses InterpolateMagickPixelPacket() to find the colour at an arbitrary non-integer coordinate.
If any options are given, the filter name and its options must be quoted.
| Option | Description | |
|---|---|---|
| Short
form |
Long form | |
| s N | style N | Distortion style: 0, 1 or 2. See samples below. |
For example:
|
Style 0 Shift the image up and left. %IM7DEV%magick toes.png ^ -virtual-pixel Mirror ^ -process geodist ^ cu_gd0.png Virtual pixels fill-in bottom and right. |
|
|
Style 1 Shift horizontally according to saturation,
%IM7DEV%magick toes.png ^ -virtual-pixel Mirror ^ -process 'geodist style 1' ^ cu_gd1.png |
|
|
Style 2 Shift at an angle determined by hue,
%IM7DEV%magick toes.png ^ -virtual-pixel Mirror ^ -process 'geodist style 2' ^ cu_gd2.png |
|
rect2eqfish.c converts from a rectilinear image (eg from an ordinary camera) to an equidistant fisheye. There are many fisheye projections. The "equidistant" fisheye is also known as "linear". This module respects the -virtual-pixel and -interpolate settings.
This module is a direct translation (I hope) of part of Fred Weinhaus's fisheye script, with his permission. See Fred's fisheye page.
The main difference between my code and Fred's are:
| Option | Description | |
|---|---|---|
| Short
form |
Long form | |
| i N | ifov N | input field of view from corner to opposite corner in degrees, default 120. |
| o N | ofov N | output field of view from corner to opposite corner in degrees, default 180. |
| f string | format string | fullframe or circular. |
| r N | radius N | Output radius (overrides format). |
| v | verbose | Write some text output to stderr. |
Examples:
%IM7DEV%magick ^ toes.png ^ -process rect2eqfish ^ cu_fish1.jpg |
|
%IM7DEV%magick ^ toes.png ^ -virtual-pixel Black ^ -process 'rect2eqfish format circular' ^ cu_fish2.jpg |
|
%IM7DEV%magick ^ toes.png ^ -virtual-pixel Mirror ^ -process 'rect2eqfish format circular' ^ cu_fish3.jpg |
|
call StopWatch %IM7DEV%magick ^ toes.png ^ -virtual-pixel Black ^ -process 'rect2eqfish format circular o 120 i 120 v' ^ cu_fish4.jpg 2>cu_fish4.lis call StopWatch |
|
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:01
We can extract the -fx component and use it in a separate command:
for /F "usebackq tokens=*" %%F ^ in (`findstr fx cu_fish4.lis`) ^ do set FX=%%F call StopWatch %IMG7%magick ^ toes.png ^ -virtual-pixel Black ^ %FX% ^ cu_fishfx.jpg call StopWatch |
|
The -fx version took this long:
0 00:00:00
I can't see any good reason for doing this, apart from finding out how slow -fx is.
onewhite.c writes exactly one line to stderr for each image in the list. The line starts with "onewhite: ", then has either "none" or the integer x,y coordinates of the first white pixel. "White" is defined here as each of the red, green and blue channels being greater than or equal to QuantumRange minus the -fuzz setting. This can allow for -auto-level sometimes not making genuine white in HDRI.
It is not sensitive to -channels. Each image search stops as soon as the first white pixel is found.
For example:
%IM7DEV%magick ^ xc:black ^ xc:white ^ -process onewhite ^ NULL:
onewhite: none onewhite: 0,0
There are two images, so there are two lines of text.
%IM7DEV%magick ^ xc:black ^ -bordercolor White -border 1 ^ ( +clone ) ^ -process onewhite ^ NULL:
onewhite: 0,0 onewhite: 0,0
BEWARE: Even for grayscale images, -auto-level does not always make exactly white pixels. (I haven't investigated when this happens.) Consider using "-fuzz", or use onelightest instead.
If the module finds a white pixel, it sets the image property filter:onewhite to the coordinates.
For example uses, see Details, details, [Adaptive] Contrast-limited equalisation, Islands, Simple alignment by matching areas, Adding zing to photographs and Standalone programs.
nearestwhite.c writes exactly one line to stderr for each image in the list. The line starts with "nearestwhite: ", then has either "none" or the integer x,y coordinates of the white pixel that is nearest to the centre. "White" is defined here as each of the red, green and blue channels being greater than or equal to QuantumRange minus the -fuzz setting. This can allow for -auto-level sometimes not making genuine white in HDRI.
If the module finds a white pixel, it sets the image property filter:nearestwhite to the coordinates of the nearest.
| Option | Description | |
|---|---|---|
| Short
form |
Long form | |
| cx N | cx N | Central x-coordinate (pixels or % of width-1).
Default: 50%. |
| cy N | cy N | Central y-coordinate (pixels or % of height-1).
Default: 50%. |
| v | verbose | Write some text output. |
Each number can be suffixed by % or c, both meaning a percentage of the image dimension minus one, or p meaning a proportion of the image dimension minus one.
For example:
%IM7DEV%magick ^ xc:black ^ xc:white ^ -process nearestwhite ^ NULL:
nearestwhite: none nearestwhite: 0,0
There are two images, so there are two lines of text.
%IM7DEV%magick ^ xc:black ^ -bordercolor White -border 1 ^ ( +clone ) ^ -process nearestwhite ^ NULL:
nearestwhite: 1,0 nearestwhite: 1,0
FUTURE: We may know in advance that the nearest pixel will be within a certain radius of (cx,cy). So we might have an option to limit the search to that area.
allwhite.c lists all the white pixels in all the images in the list. The listing for each image starts with the line "allwhite:". Then it lists the coordinates of each white pixel (if any), one per line. If none are found, it does not write "none".
"White" is defined as for onewhite above. It is not sensitive to -fuzz or -channels.
For example:
%IM7DEV%magick ^ xc:black ^ ( +clone ) ^ -process allwhite ^ NULL:
allwhite: allwhite:
%IM7DEV%magick ^ xc:black ^ -bordercolor White -border 1 ^ ( +clone ) ^ -process allwhite ^ NULL:
allwhite: 0,0 1,0 2,0 0,1 2,1 0,2 1,2 2,2 allwhite: 0,0 1,0 2,0 0,1 2,1 0,2 1,2 2,2
onelightest.c writes exactly one line to stderr for each image in the list, containing the x,y coordinate of the lightest pixel, as defined by the current -intensity setting. If a number of pixels are equal-lightest, only the first one is found.
%IM7DEV%magick ^ -size 10x10 ^ xc:black ^ xc:white ^ -process onelightest ^ NULL:
0,0 0,0
Note that every image has a lightest pixel.
For example uses, see Histogram peaks and troughs.
midlightest.c is similar to onelightest.c. However, if a number of pixels are equal-lightest, this module finds which of them is closest to the centroid of the set.
%IM7DEV%magick ^ -size 10x10 ^ xc:black ^ xc:white ^ -process midlightest ^ NULL:
4,4 4,4
For an example use, see Membranes.
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: |
|
|
Sort each line, with darkest pixels on the left. %IM7DEV%magick ^ toes.png ^ -process sortpixels ^ cu_sl.png |
|
|
By rotating, we sort columns with darkest pixels at the bottom. %IM7DEV%magick ^ toes.png ^ -rotate 90 ^ -process sortpixels ^ -rotate -90 ^ cu_sl2.png |
|
|
We can sort all of the pixels by rearranging them into a single line,
for /F "usebackq" %%L in (`%IMG7%magick identify ^ -format "WW=%%w" ^ toes.png`) do set %%L %IM7DEV%magick ^ toes.png ^ -crop x1 +repage ^ +append +repage ^ -intensity Rec709Luminance ^ -process sortpixels ^ -flop ^ -crop %WW%x1 ^ -append +repage ^ cu_sl3.png |
|
The module can be used to find the median value, the value at which 50% of the pixels are darker and 50% are lighter, as defined by the -intensity setting:
%IM7DEV%magick ^ toes.png ^ -crop x1 +append +repage ^ -process sortpixels ^ -gravity Center -crop 1x1+0+0 +repage ^ txt:
# ImageMagick pixel enumeration: 1,1,0,65535,srgb 0,0: (34168,30512,32580) #857877307F44 srgb(52.137023%,46.558326%,49.713895%)
%IM7DEV%magick ^ toes.png ^ -colorspace Gray ^ -crop x1 +append +repage ^ -process sortpixels ^ -gravity Center -crop 1x1+0+0 +repage ^ txt:
# ImageMagick pixel enumeration: 1,1,0,65535,gray 0,0: (31439) #7ACF7ACF7ACF gray(47.972459%)
This returns the centre value if there is an odd number of pixels. For an even number of pixels, it returns the next pixel.
For an alternative method to find the median, probably faster, see invclut.
Update 22 January 2021: See also Command line options: sort-pixels. This seems to be a recent addition to ImageMagick. Perhaps it does the same as my "-process sortpixels".
sortpixelsblue.c is exactly like sort pixels above, except that the sort compares values in the blue channel only. Like sort pixels, it moves entire pixels, and it operates on each row independently of other rows.
set SRC=toes.png %IM7DEV%magick ^ %SRC% ^ -process sortpixelsblue ^ cu_spb.png |
|
By populating the red, green and blue channels with specific data, we can use the module to find the coordinates of the ten lightest pixels in toes.png.
for /F "usebackq" %%L in (`%IM7DEV%magick identify ^
-precision 19 ^
-format "WW=%%w\nWm1=%%[fx:w-1]\nHm1=%%[fx:h-1]" ^
%SRC%`) do set %%L
set IDENT=^
0,0,#008,^
%%[fx:w-1],0,#f08,^
0,%%[fx:h-1],#0f8,^
%%[fx:w-1],%%[fx:h-1],#ff8
%IM7DEV%magick ^
%SRC% ^
( -clone 0 ^
-sparse-color bilinear %IDENT% ^
-channel RG -separate +channel ^
) ^
( -clone 0 ^
-grayscale Rec709Luminance ^
) ^
-delete 0 ^
-combine ^
-crop x1 +append +repage ^
-process sortpixelsblue ^
-flop ^
+depth ^
+write cu_spb_map.miff ^
-crop 10x1+0+0 +repage ^
-crop 1x1 +repage ^
-format "[%%p]=%%[fx:int(%Wm1%*r+0.5)],%%[fx:int(%Hm1%*u.g+0.5)],%%[fx:u.b]\n" ^
info:
[0]=255,217,0.8337736 [1]=255,217,0.8337736 [2]=225,217,0.8337736 [3]=225,217,0.8337736 [4]=256,217,0.8337736 [5]=255,217,0.8337736 [6]=254,217,0.8337736 [7]=266,217,0.8337736 [8]=266,217,0.8337736 [9]=256,217,0.8337736
The command:
If you want all the pixels, omit the -crop 10x1+0+0 line.
The red and green channels are initialised to an identity absolute distortion map. After the sort, those channels in each pixel give the source coordinates for that pixel. So we can use them to distort the source image into the sorted order. The map cu_spb_map.miff is a single row, so first we need to chop it into lines that we reassemble vertically.
%IM7DEV%magick ^
%SRC% ^
( cu_spb_map.miff ^
-crop %WW%x +repage -append +repage ^
) ^
-compose Distort ^
-set option:compose:args 100%%x100%% ^
-composite ^
cu_spb_srt.png
|
|
This should be the same as the previous sort:
%IM7DEV%magick compare -metric RMSE cu_sl3.png cu_spb_srt.png NULL:
0 (0)
It is the same.
If the blue channel of pixels is set to different random numbers before the sort, the result will be a shuffle of the pixels.
Sorting moves each pixel to only one destination, and moves no other pixels to that destination. Hence the displacement map is 1:1, hence it is reversible without leaving any gaps. (More technically, it is bijective, and a permutation.) See below, Practical module: invert displacement map.
FUTURE: For use in making displacement maps, maybe we should have a tie-breaker when two pixels have equal values in the blue channels.
shadowsortpixels.c needs a list of exactly two images, of the same dimensions. The first image will be sorted in the same way as sortpixels above, so each row will be sorted in ascending order of intensity. Pixels in the second image will undergo exactly the same swaps as in the first image.
It respects the -intensity setting. It is not sensitive to a -channels setting.
An alternative name might be "guided sort pixels" or "proxy sort pixels".
The module was written for applications where we want to sort a Nx1 clut, and apply the same swaps to another Nx1 clut. See Heatmaps.
When the first image is an ordinary photo, the second image could be an identity displacement map. The swapped displacement map will not have gaps.
%IM7DEV%magick ^
toes.png ^
( +clone ^
-sparse-color Bilinear ^
0,0,#000,^
%%[fx:w-1],0,#f00,^
0,%%[fx:h-1],#0f0,^
%%[fx:w-1],%%[fx:h-1],#ff0 ^
) ^
-process shadowsortpixels ^
cu_ssp_%%d.png
|
|
We have displaced pixels from toes.png by sorting them, and displaced pixels from the identity displacement map in the same way. Hence the second result, cu_ssp_1.png, is the displacement map that has the same effect as the sort.
If we displace cu_ssp_0.png by the inverse of cu_ssp_1.png, the result should be the same as toes.png:
%IM7DEV%magick ^
cu_ssp_0.png ^
( cu_ssp_1.png ^
-process invdispmap ^
) ^
-compose Distort -composite ^
cu_ssp_t.png
|
|
%IM7DEV%magick compare -metric RMSE cu_ssp_t.png toes.png NULL: cmd /c exit /B 0
0 (0)
This confirms the round-trip.
fillholes.c modifies each image in the list, changing any fully-transparent pixels (alpha=0) to be fully opaque (alpha=1), setting their colours from elsewhere in the image. The technique is also known as "infilling" or "inpainting".
Caution: this is not fast. By default, the module runs a custom-built "subimage-search" across the entire image for every single non-transparent pixel. Various options will increase performance. These usually (but not always) decrease quality.
| Option | Description | |
|---|---|---|
| Short
form |
Long form | |
| wr N | window_radius N | Radius of window for searches, >= 1.
Window will be D*D square where D = radius*2+1. The minimum radius is one. Default 1. |
| lsr N | limit_search_radius N | Limit radius from transparent pixel to search for source, >= 0.
Default = 0 = no limit |
| als X | auto_limit_search X | Automatically limit the search radius.
X is on or off. Default = on |
| s X | search X | Mode for searching, where X is entire or random or skip.
Default = entire. |
| rs N | rand_searches N | For random searches, the number to make per attempted match.
Default = 0 = minimum of search field width and height |
| sn N | skip_num N | For skip searches, the number of positions to skip in x and y directions.
Default = 0 = window radius |
| hc N | hom_chk N | Homogeneity check.
N is either a number, typically 0 < N < 0.5, or off to remove the check. Default = 0.1 |
| e X | search_to_edges X | Whether to search for matches to image edges, so the result will be influenced by virtual pixels.
Set to on or off. Default = on |
| st N | similarity_threshold N | Stop searching for a window when RMSE <= N, when a match is "good enough".
Typically use 0.0 <= N <= 1.0, eg 0.01. Default = 0 = keep going to find the best match. |
| dt N | dissimilarity_threshold N | When a best match is found, if score > N, reject it, leaving pixels unfilled.
Typically use 0.0 <= N <= 1.0, eg 0.05. If zero, only perfect matches will be filled. Default = no threshold, so no rejections from bad scores. |
| cp X | copy X | Mode for copying, where X is either onepixel or window.
Default = onepixel. |
| cr N | copy_radius N | Radius of pixels to be copied from a matching window, >= 1.
Window will be D*D square where D = radius*2+1. Default: the value of window_radius. |
| c | favour_close | Favour search results close to destination pixel. EXPERIMENTAL. |
| w filename | write filename | Write the input image, then one frame per pass.
Example filename: frame_%06d.png |
| w2 filename | write2 filename | Write the input image, then one frame per changed pixel. |
| v | verbose | Write some text output to stderr. |
For example:
|
Make a test image, with a hole. %IMG7%magick ^
-size 100x100 xc:lime ^
-stroke Red -draw "line 0,0 99,99" ^
-stroke Blue -strokewidth 3 -draw "line 0,20 69,99" ^
( +clone -fill White -colorize 100 ^
-fill Black -draw "Rectangle 30,20 79,69" ^
-threshold 50%% ^
) ^
-alpha off ^
-compose CopyOpacity -composite ^
cu_fh_src1.png
|
|
|
Fill any holes. %IM7DEV%magick ^ cu_fh_src1.png ^ -process 'fillholes wr 2' ^ cu_fh_src1_fh.png |
|
See the page Filling holes for more details.
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.
img2knl.c writes an image as text to stdout, in a format suitable for a morphology kernel. The output text is:
WxH:n,n,...n
where W is the image width, H is the image height, and each n is the intensity of a pixel. When HDRI is used, intensities can be negative.
The output text contains no spaces, quotes or newlines.
The module respects the -precision and -intensity settings.
For example:
%IM7DEV%magick -size 1x5 gradient: -process img2knl NULL:
1x5:1,0.75,0.5,0.25,0
The result can be written to an environment variable ...
for /F "usebackq" %%L in (`%IM7DEV%magick ^ -size 1x5 gradient: -process img2knl NULL:`) do set KNL=%%L echo %KNL%
1x5:1,0.75,0.5,0.25,0
... and then used as a kernel:
%IMG7%magick ^ xc: ^ -define morphology:showkernel=1 -morphology convolve %KNL% ^ NULL:
Kernel "User Defined" of size 1x5+0+2 with values from 0 to 1 Forming a output range from 0 to 2.5 (Sum 2.5) 0: 1 1: 0.75 2: 0.5 3: 0.25 4: 0
At my request, IM can now read a kernel from a file. (See IM forum thread Kernels from text files.) If the text output from img2knl is directed to a file, IM can read that into a kernel.
Future: I may provide an option for a kernel offset, eg +2+3 and other stuff that can come before the colon ('@', '<' and '>'). Perhaps offsets would come from image attributes or canvas offsets. (However, canvas offsets default to 0,0 where kernel offsets default to w/2,h/2.) I may also provide the option to split the output into more readable lines.
This module might be better as a coder, so we can output to (say) KNL:myknl.txt. An input coder could also be written, so we could input from KNL:myknl.txt, adding an image to the current list. If the kernel contained a rotation symbol, would this add multiple images to the list? Perhaps this is best handled with image attributes.
The script knl2img.bat creates a kernel image from a kernel string.
call %PICTBAT%knl2img "%KNL%" cu_knl.png |
|
call %PICTBAT%blockPix cu_knl.png |
|
See also the scripts img2knl4.bat and img2knl4f.bat that write kernel strings to environment variable and text files respectively.
interppix.c returns the interpolated pixel values found at given coordinates. It provides a command-line inerface to IM's InterpolateMagickPixelPacket() function.
The module takes an even number of input arguments, the x- and y-coordinates of the desired points. These are floating-point values. Coordinates can be given as conventional pixel coordinates, or as percentages of image width-1 and height-1 by using a suffixed "%" or "c", or as proportions of image width-1 and height-1 by using a suffixed "p".
"c" is a synonym for "%". It avoids some complicated escaping in scripts and programs.
The suffixes only apply to the given coordinate. If they are both intended as percentages, they both must be suffixed.
Why are percentages and proportions based on image dimensions minus one? Because then:
If an image is 301x201 pixels, the following coordinate pairs refer to the same position:
234 123 78% 61.5% 78c 61.5c 0.78p 0.615p 78c 0.615p
The module can be called multiple times, once per coordinate-pair. But it is much faster to call it just once, listing all the coordinate pairs. Input arguments must be separated by spaces.
The module outputs one line with "interppix:" and 13 numbers to stderr:
The five channel values are RGBAK, CMYAK, or whatever comes out of InterpolateMagickPixelPacket(). However, for IM v6 the code subtracts the "opacity" value from QuantumRange.
set COORDS=-0.5 0 0.0 0 0.5 0 1.0 0 1.25 0 1.5 0 ^ 2.0 0 2.5 0 3.0 0 3.5 0 4.0 0 4.5 0 %IM7DEV%magick ^ -size 2x1 ^ xc:Black xc:White +append +repage ^ -alpha Opaque ^ -channel A -evaluate Multiply 0.25 +channel ^ +write txt: ^ +write cu_interppix.miff ^ -background Blue -virtual-pixel Background ^ -process 'interppix %COORDS%' ^ NULL: 1> cu_interppix.lis 2>&1
interppix: 0: @-0.5,0 (0,0,3.4359738e+09,2.6843546e+09,0) (0%,0%,80%,62.5%,0%) interppix: 0: @0,0 (0,0,0,1.0737418e+09,0) (0%,0%,0%,25%,0%) interppix: 0: @0.5,0 (0,0,0,1.0737418e+09,0) (0%,0%,0%,25%,0%) interppix: 0: @1,0 (0,0,0,1.0737418e+09,0) (0%,0%,0%,25%,0%) interppix: 0: @1.25,0 (1.0737418e+09,1.0737418e+09,1.0737418e+09,1.0737418e+09,0) (25%,25%,25%,25%,0%) interppix: 0: @1.5,0 (2.1474836e+09,2.1474836e+09,2.1474836e+09,1.0737418e+09,0) (50%,50%,50%,25%,0%) interppix: 0: @2,0 (4.2949673e+09,4.2949673e+09,4.2949673e+09,1.0737418e+09,0) (100%,100%,100%,25%,0%) interppix: 0: @2.5,0 (4.2949673e+09,4.2949673e+09,4.2949673e+09,1.0737418e+09,0) (100%,100%,100%,25%,0%) interppix: 0: @3,0 (4.2949673e+09,4.2949673e+09,4.2949673e+09,1.0737418e+09,0) (100%,100%,100%,25%,0%) interppix: 0: @3.5,0 (8.5899346e+08,8.5899346e+08,4.2949673e+09,2.6843546e+09,0) (20%,20%,100%,62.5%,0%) interppix: 0: @4,0 (0,0,4.2949673e+09,4.2949673e+09,0) (0%,0%,100%,100%,0%) interppix: 0: @4.5,0 (0,0,4.2949673e+09,4.2949673e+09,0) (0%,0%,100%,100%,0%) # ImageMagick pixel enumeration: 4,1,0,4294967295,srgba 0,0: (0,0,0,1073741824) #00000000000000000000000040000000 srgba(0,0,0,0.25) 1,0: (0,0,0,1073741824) #00000000000000000000000040000000 srgba(0,0,0,0.25) 2,0: (4294967295,4294967295,4294967295,1073741824) #FFFFFFFFFFFFFFFFFFFFFFFF40000000 srgba(255,255,255,0.25) 3,0: (4294967295,4294967295,4294967295,1073741824) #FFFFFFFFFFFFFFFFFFFFFFFF40000000 srgba(255,255,255,0.25)
The image has four pixels, with conventional x-coordinates 0, 1, 2 and 3. The module shows normal images values when it is given integer coordinates. At non-integer coordinates, it interpolates between image pixels. Beyond the coordinate width-1, it interpolates between the last pixel and the virtual colour, which is blue.
Repeat the test, but with spline interpolation:
%IM7DEV%magick ^ cu_interppix.miff ^ -background Blue -virtual-pixel Background ^ -interpolate Spline ^ -process 'interppix %COORDS%' ^ NULL: 1> cu_interppix2.lis 2>&1
interppix: 0: @-0.5,0 (0,0,2.8633115e+09,3.2212255e+09,0) (0%,0%,66.666667%,75%,0%) interppix: 0: @0,0 (0,0,1.9088744e+09,2.5053976e+09,0) (0%,0%,44.444444%,58.333333%,0%) interppix: 0: @0.5,0 (14913081,14913081,1.5062212e+09,2.1922229e+09,0) (0.34722222%,0.34722222%,35.069444%,51.041667%,0%) interppix: 0: @1,0 (1.1930465e+08,1.1930465e+08,1.5509604e+09,2.1474836e+09,0) (2.7777778%,2.7777778%,36.111111%,50%,0%) interppix: 0: @1.25,0 (2.2742448e+08,2.2742448e+08,1.6590802e+09,2.1474836e+09,0) (5.2951389%,5.2951389%,38.628472%,50%,0%) interppix: 0: @1.5,0 (3.5791394e+08,3.5791394e+08,1.7895697e+09,2.1474836e+09,0) (8.3333333%,8.3333333%,41.666667%,50%,0%) interppix: 0: @2,0 (5.9652324e+08,5.9652324e+08,2.028179e+09,2.1474836e+09,0) (13.888889%,13.888889%,47.222222%,50%,0%) interppix: 0: @2.5,0 (6.8600172e+08,6.8600172e+08,2.1773098e+09,2.1922229e+09,0) (15.972222%,15.972222%,50.694444%,51.041667%,0%) interppix: 0: @3,0 (5.9652324e+08,5.9652324e+08,2.5053976e+09,2.5053976e+09,0) (13.888889%,13.888889%,58.333333%,58.333333%,0%) interppix: 0: @3.5,0 (3.5791394e+08,3.5791394e+08,3.2212255e+09,3.2212255e+09,0) (8.3333333%,8.3333333%,75%,75%,0%) interppix: 0: @4,0 (1.1930465e+08,1.1930465e+08,3.9370534e+09,3.9370534e+09,0) (2.7777778%,2.7777778%,91.666667%,91.666667%,0%) interppix: 0: @4.5,0 (14913081,14913081,4.2502281e+09,4.2502281e+09,0) (0.34722222%,0.34722222%,98.958333%,98.958333%,0%)
With spline interpolation, the red and green channels don't reach 100% before falling down to the virtual colour.
The module always outputs a value for opacity ("alpha"). If alpha is currently off, the output value should be ignored.
Like other process modules, this processes all the images in the current list.
The module respects -virtual-pixel, -interpolate and -precision. It does not respect -channel settings.
(Can this currently be done with a command? Scrolling with distort SRT?)
%IM7DEV%magick ^ cu_interppix.miff ^ -background Blue -virtual-pixel Background ^ -interpolate Spline ^ -distort SRT 3.5,0,1,0,0,0 -crop 1x1+0+0 +repage ^ txt:
# ImageMagick pixel enumeration: 1,1,0,4294967295,srgba 0,0: (829902453,829902453,4294967295,2718884104) #31774E7531774E75FFFFFFFFA20EE108 srgba(19.322672%,19.322672%,100%,0.63303953)
Future: the module could create an image from the found values.
mkgauss.c adds a new image size Nx1 with the RGB channels set to a Gaussian curve, also known as General Normal Distribution, or bell curve. When cumul is specified, makes Cumulative Distribution Function (CDF). See Wikipedia: Normal distribution.
Like other process modules, this one requires that the image list already has at least one entry. However, the image itself is irrelevant. For the examples here, I create an image with xc: and delete it immediately after the process.
| Option | Description | |
|---|---|---|
| Short
form |
Long form | |
| w N | width N | Output width (pixels).
Default: 256. |
| m N | mean N | The x-coordinate of the mean (pixels or % of width).
Default: 50%. |
| sd N | standarddeviation N | The standard deviation x-interval (pixels or % of width).
Default: 10%. |
| k N | skew N | Skew to place the peak away from the specified mean
(pixels or % of width). Default: 0. |
| sm N | skewmethod N | Method of skewing: 1 or 2.
1: smooth transition left and right of peak. 2: different linear shift left and right of peak. Default: 1. |
| z | zeroize | Subtract the minimum calculated value so output has a zero. |
| c | cumul | Cumulate the values. |
| n | norm | Normalise the output so the maximum is Quantum. |
| v | verbose | Write some text output to stderr. |
But doesn't sigmoidal-contrast already do this? No. The sigmoidal-contrast curve is the cumulative of the logistic curve. See Wikipedia: Logistic distribution. De-cumulating the sigmoidal-contrast curve yields a similar result to the gaussian curve, but they are not identical.
Each value for mean, standard deviation and skew is in units of pixels unless it is suffixed with a percent sign (%) or 'c', which makes it a percentage of the width. In Windows BAT files, we need to double the percent sign: %%.
The module implements the usual formula for the distribution:
y = 1 / (sd * sqrt(2*pi)) * exp (-(x-mean)*(x-mean)/(2*sd*sd))
However, if a skew is given, the x-value is first modified with a power function. A power function shifts middle values, without changing values at 0 or 100%.
Zeroizing occurs before cumulation.
For the basic (unskewed, unzeroized) curve, about 68.3% of values will be within (mean ± sd); 95.4% will be within (mean ± 2*sd); 99.7% will be within (mean ± 3*sd);
A non-default mean will shift the entire graph to the left or right. A non-default skew will shift the peak to the left or right without shifting the ends of the graph; it will also change the mean and SD.
If not normalised, the mean pixel value of the entire function (from -infinity to +infinity) will be 1.0 * QuantumValue. Provided IM is compiled with HDRI, the module does not clamp or clip values to Quantum. For example:
%IM7DEV%magick ^ xc: ^ -process 'mkgauss v sd 1%%' ^ -format "mean=%%[fx:mean] sd=%%[fx:standard_deviation] max=%%[fx:maxima]\n" ^ info:
mean=1 sd=0 max=1 mean=0.99609375 sd=5.216645 max=39.134651
The first image is white so the maximum value is 1 * Quantum. The mkgauss image has a much larger maximum value.
The calculated mean value of the curve should be 1.0 (by definition of the function), but will be less as the graph does not extend to infinity in either direction.
To verify the module has made the desired standard deviation, we make a cumulative function and examine the value at the mean minus one standard deviation. As about 68.3% of values should be within (mean ± sd), (100-68.3)/2 = 15.85% of values should be below (mean - sd).
%IM7DEV%magick ^ xc: ^ -process 'mkgauss width 100000 v sd 1%% cumul norm' ^ -delete 0 ^ -crop 2x1+48999+0 ^ -format "mean=%%[fx:mean]\n" ^ info:
mean=0.15877387
Other examples, displaying the Nx1 image as a graph:
set IFFEXT=tiff %IM7DEV%magick ^ xc: ^ -process 'mkgauss sd 1%%' ^ -delete 0 ^ cu_mg0.%IFFEXT% call %PICTBAT%graphLineCol ^ cu_mg0.%IFFEXT% . . 0 cu_mg0_glc.png |
|
%IM7DEV%magick ^ xc: ^ -process 'mkgauss sd 30%%' ^ -delete 0 ^ cu_mg1.%IFFEXT% call %PICTBAT%graphLineCol ^ cu_mg1.%IFFEXT% . . 0 cu_mg1_glc.png |
|
%IM7DEV%magick ^ xc: ^ -process 'mkgauss sd 30%% norm' ^ -delete 0 ^ cu_mg2.%IFFEXT% call %PICTBAT%graphLineCol ^ cu_mg2.%IFFEXT% . . 0 cu_mg2_glc.png |
|
%IM7DEV%magick ^ xc: ^ -process 'mkgauss sd 30%% cumul norm' ^ -delete 0 ^ cu_mg3.%IFFEXT% call %PICTBAT%graphLineCol ^ cu_mg3.%IFFEXT% . . 0 cu_mg3_glc.png |
|
|
Shift the mean to 20% of the width. %IM7DEV%magick ^ xc: ^ -process 'mkgauss sd 30%% mean 20%% norm' ^ -delete 0 ^ cu_mg4.%IFFEXT% call %PICTBAT%graphLineCol ^ cu_mg4.%IFFEXT% . . 0 cu_mg4_glc.png The curve remains symmetrical, so the start and end points are not equal. |
|
|
Shift the peak 20% (of the width) left of the mean position. %IM7DEV%magick ^ xc: ^ -process 'mkgauss sd 30%% skew -20%% norm' ^ -delete 0 ^ cu_mg5.%IFFEXT% call %PICTBAT%graphLineCol ^ cu_mg5.%IFFEXT% . . 0 cu_mg5_glc.png |
|
|
Shift the peak 20% (of the width) right of the mean position. %IM7DEV%magick ^ xc: ^ -process 'mkgauss sd 30%% skew 20%% norm' ^ -delete 0 ^ cu_mg6.%IFFEXT% call %PICTBAT%graphLineCol ^ cu_mg6.%IFFEXT% . . 0 cu_mg6_glc.png This is a mirror image of -20%. |
|
|
Shift the peak 20% (of the width) left of the mean position, and cumulate. %IM7DEV%magick ^ xc: ^ -process 'mkgauss sd 30%% skew -20%% cumul norm' ^ -delete 0 ^ cu_mg7.%IFFEXT% call %PICTBAT%graphLineCol ^ cu_mg7.%IFFEXT% . . 0 cu_mg7_glc.png The steepest part of the cumulative clut is at
|
|
Repeat the previous two examples, with zeroize.
|
Shift the peak 20% (of the width) right of the mean position. %IM7DEV%magick ^ xc: ^ -process 'mkgauss sd 30%% skew 20%% zeroize norm' ^ -delete 0 ^ cu_mg6a.%IFFEXT% call %PICTBAT%graphLineCol ^ cu_mg6a.%IFFEXT% . . 0 cu_mg6a_glc.png Doing both zeroise and normalise has the same effect as -autolevel. |
|
%IM7DEV%magick ^ xc: ^ -process 'mkgauss sd 30%% skew -20%% zeroize cumul norm' ^ -delete 0 ^ cu_mg7a.%IFFEXT% call %PICTBAT%graphLineCol ^ cu_mg7a.%IFFEXT% . . 0 cu_mg7a_glc.png |
|
There are two methods for skewing. They have the same overall effect: the peak is shifted to left or right, with suitable adjustment of values between the peak and ends.
|
No skew. %IM7DEV%magick ^ xc: ^ -process 'mkgauss sd 30%% norm' ^ -delete 0 ^ cu_noskew.%IFFEXT% call %PICTBAT%graphLineCol ^ cu_noskew.%IFFEXT% . . 0 cu_noskew_glc.png |
|
|
Skew left by 20% with method 1. %IM7DEV%magick ^ xc: ^ -process 'mkgauss sd 30%% skew -20%% skewmethod 1 norm' ^ -delete 0 ^ cu_sm1.%IFFEXT% call %PICTBAT%graphLineCol ^ cu_sm1.%IFFEXT% . . 0 cu_sm1_glc.png Method 1 varies the compression/expansion across the image. |
|
|
Skew left by 20% with method 2. %IM7DEV%magick ^ xc: ^ -process 'mkgauss sd 30%% skew -20%% skewmethod 2 norm' ^ -delete 0 ^ cu_sm2.%IFFEXT% call %PICTBAT%graphLineCol ^ cu_sm2.%IFFEXT% . . 0 cu_sm2_glc.png Method 2 shrinks the left side evenly, and expands the right side evenly.
|
|
For a skewed zeroized normalised Gaussian curve, find the values at x=0, 20%, .. 100%, as percentages of maximum.
(for /F "usebackq tokens=10 delims=:()@,%% " %%V in (`%IM7DEV%magick ^ xc: ^ -process 'mkgauss w 1000 sd 30%% skew -20%% zeroize norm' ^ -delete 0 ^ -process 'interppix 0%% 0 20%% 0 40%% 0 60%% 0 80%% 0 100%% 0' ^ NULL: 2^>^&1`) do @echo %%V) >cu_gauss_vals.lis
0 92.21699 94.129833 62.169307 26.648452 0
mkgauss makes an image that represents a Gaussian curve with the given mean and SD. If we want an image with a Gaussian distribution of pixel values, we can follow mkgauss with invclut :
%IM7DEV%magick ^ xc: ^ -process 'mkgauss width 65536 mean 40%% sd 10%% cumul norm' ^ -delete 0 ^ -process 'invclut' ^ +write cu_gauss_dist.png ^ -format "mean=%%[fx:mean] SD=%%[fx:standard_deviation]" ^ info:
mean=0.40001599 SD=0.099998906
The result is not exact, especially for SD greater than 20% or so, because the curve does not extend to ±infinity.
The result, cu_gauss_dist.png, is 65536x1, which is difficult to see. We can crop it into rows and append them vertically:
|
Skew left by 20% with method 2. %IMG7%magick ^ cu_gauss_dist.png ^ -crop 256x ^ -append +repage ^ cu_gauss_dist_sq.png |
|
For example uses, see the pages Camera blur (unpublished) and Contrast-limited equalisation.
mkhisto.c replaces each image in the list with a histogram size Nx1, channel by channel. Each output file contains three separate histograms, one for each of the input RBG channels.
The process is reversible: from a histogram, we can easily make an image for which this would be the histogram. See Application: de-histogram below.
If any options are given, the filter name and its options must be quoted.
| Option | Description | |
|---|---|---|
| Short
form |
Long form | |
| b N | capnumbuckets N | Limit the number of buckets (pixel width) to N, where N>0.
Default: limit to 65536. |
| m N | multiply N | Multiply the count by N. Useful when output format has less precision than IM's Q-number.
Default: 1. |
| r | regardalpha | Multiply the count by alpha, so transparent pixels aren't counted. |
| l | log | Use logs of counts. |
| c | cumul | Cumulate the histogram values. |
| n | norm | Normalise the histogram so the maximum is Quantum. (The minimum may not be zero.) |
| v | verbose | Write some text output to stderr. |
Each histogram pixel represents the number of input pixels with values within a certain range. The histogram pixels are "buckets". The histogram width, or number of buckets, is determined by the possible number of values per channel in the input image. (A histogram shouldn't be unnecessarily wide, or it will contain many entries that cannot hold values.)
If the input has depth 8, the histogram will normally be 256 pixels wide. For depth 16, it will be 65536. For depth 32, it would be 4 billion pixels wide. IM could handle this, but horribly slowly if this exceeds memory.
The setting capnumbuckets limits the histogram width. This needs an integer from 1 upwards. The default is 65536, so histograms are limited to a width of 65536. For inputs that have more than 16 bits/channel/pixel, it will be more precise with larger numbers, such as 1000000 (one million). A small number might be wanted for special purposes. A single bucket is fairly pointless as all the input pixels will be counted in it, but you can have just one if you want.
Small images may benefit from a small capnumbuckets setting. For example, if a 16-bit/channel image has only 30,000 pixels then probably only half the buckets will have an entry, and the range of counts will be zero to one, or at least very small. This may make further processing meaningless. Perhaps a rule-of-thumb is that the number of buckets should be less than the number of pixels divided by 256.
Every input pixel increases the count in one red bucket, one green bucket and one blue bucket. If no options are given, the bucket is incremented by one for each input pixel.
If the multiply option is given, this will be the basic increment.
If the regardalpha option is given, the basic increment is multiplied by the alpha channel value (on a scale of 0.0 to 1.0). Hence fully transparent pixels will not increment the contents of any bucket. An opaque pixel will add multiply to the count in the appropriate bucket. If non-HDRI, a pixel will either be counted or not, according to whether alpha is > 0.5. For HDRI, each count of multiply is multiplied by alpha, so the histogram may contain non-integer values.
An input image value V (which is one of VR, VG or VB) is counted in bucket number min(int(V/(Q/N)),N-1). For example if there are 20 buckets, numbered 0 to 19, bucket[0] will contain count of input image pixels 0 to 4.999%, bucket[1] will contain 5% to 9.999%, and so on to the final bucket, bucket[19], which will contain 95% to 100%.
If neither cumulative nor normalised, the pixel values in the output represent a true count of the number of pixels, subject to Q which may cause clipping. If normalised (option norm), the output is multiplied such that the largest count becomes Quantum. This makes the graphical output more useful.
Clipping can occur if either of the following are true:
As from 27 September 2015, code in mkhisto.c checks for the first condition, and issues a warning if this occurs. It cannot check for the second condition.
If cumul alone is given, the result is a true cumulative count of the number of pixels at or below the value, again subject to clipping due to Q. If a photograph contains a million pixels, a Q16 IM build is not sufficient to contain an accurate cumulative count. The build needs to be Q32.
If options cumul and norm are both specified, the result is a traditional cumulative histogram, with all channels roughly zero in the first bucket (pixel coordinate 0,0), and theoretically 100% of quantum in the last bucket.
Beware: If the calculation is performed by a Q32 program, and the input is small, and the results are saved to a Q16 file, all result pixels are likely to be zero. Losing 16 bits of precision is equivalent to dividing by 65536 and rounding to the nearest integer. Any counts less than 32768 will be rounded to zero. If the program is Q32 but the output is to be Q16, use -evaluate LeftShift 16 -depth 16. Left-shifting may clip values to quantum. Eg, if a count is 80,000, it will be clipped to 65535 in a Q16 file. (Put it another way: when we save Q32 data in a Q16 file, we can lose either the bottom 16 bits or the top 16 bits.) TIFF files can be saved as -depth 32.
Another way around this is to use the mult option.
Options can be given in the short form (single letters) or long form. Where an option takes an argument, separate them with a space (not an equals sign "="). Options can be given in any order. If an invalid option is given, such as help, usage text will be written and the filter will fail. (Currently, this will not cause the program to fail.)
CAUTION: So far, I have tested this only in a Q32 HDRI build.
In the following examples, I limit the number of buckets to 500 for web display. For normal use, I wouldn't use such a small number. The script graphLineCol.bat displays the graphs with a height of 256 pixels, from a theoretical height of 232-1 = 4 billion.
%IM7DEV%magick ^ toes.png ^ -process 'mkhisto capnumbuckets 500' ^ -depth 32 ^ cu_h1.%IFFEXT% call %PICTBAT%graphLineCol ^ cu_h1.%IFFEXT% . . 0 cu_h1_glc.png The counts are too low to register on the graph. |
|
|
To make the counts visible, we normalise. %IM7DEV%magick ^ toes.png ^ -process 'mkhisto capnumbuckets 500 norm' ^ -depth 32 ^ cu_hn.%IFFEXT% call %PICTBAT%graphLineCol ^ cu_hn.%IFFEXT% . . 0 cu_hn_glc.png Normalising multiplies the three channels by the same factor,
|
|
|
Compare this to the conventional IM histogram: %IMG7%magick ^ toes.png ^ -density 256x256 ^ -define histogram:unique-colors=false ^ histogram:cu_toes_hist.png (In v6.9.0-6 and other versions,
|
|
%IM7DEV%magick ^ toes.png ^ -process 'mkhisto capnumbuckets 500 cumul' ^ -depth 32 ^ cu_hc.%IFFEXT% call %PICTBAT%graphLineCol ^ cu_hc.%IFFEXT% . . 0 cu_hc_glc.png The counts are too low to register on the graph. |
|
%IM7DEV%magick ^ toes.png ^ -process 'mkhisto capnumbuckets 500 cumul norm' ^ -depth 32 ^ cu_hcn.%IFFEXT% call %PICTBAT%graphLineCol ^ cu_hcn.%IFFEXT% . . 0 cu_hcn_glc.png Norm and cumul together will multiply the three channels by different factors,
|
|
|
We can sort a histogram (for no good reason that I can see): %IM7DEV%magick ^ toes.png ^ -process 'mkhisto capnumbuckets 500 norm' ^ -process sortpixels ^ -depth 32 ^ cu_hcns.%IFFEXT% call %PICTBAT%graphLineCol ^ cu_hcns.%IFFEXT% . . 0 cu_hcns_glc.png |
|
|
If we greyscale the image first, the resulting sorted histogram is: %IM7DEV%magick ^ toes.png ^ -modulate 100,0,100 ^ -process 'mkhisto capnumbuckets 500 norm' ^ -process sortpixels ^ -depth 32 ^ cu_hcngs.%IFFEXT% call %PICTBAT%graphLineCol ^ cu_hcngs.%IFFEXT% . . 0 cu_hcngs_glc.png |
|
PPM is a convenient format to extract numerical values. Use -compress None to get text output. (An IM option to get one PPM pixel per line would be useful, to aid post-processing. Likewise for sparse-color:.) For example, 4 pixels (buckets) near the centre of the normalised histogram:
%IM7DEV%magick cu_hn.%IFFEXT% -crop 4x1+250+0 -compress None ppm:
P3 # 4 1 4294967295 1575052672 2005243264 2060751616 1581991168 2053813120 2074628736 1602806912 2234215680 2032997504 1464035712 2206461440 2157891584
From the plain, un-normalised histogram:
%IM7DEV%magick cu_h1.%IFFEXT% -crop 4x1+250+0 -compress None ppm:>cu_h1.ppm
P3 # 4 1 4294967295 227 289 297 228 296 299 231 322 293 211 318 311
This tells us that from the original image, the red channel of 231 pixels fell into bucket number 250; the green channel of 287 pixels fell into bucket number 250; and the blue channel of 301 pixels fell into bucket number 250. These number are so low compared to quantum (300 compared to 4 billion) that they don't register on the graph.
By specifying a generous cap to the number of buckets, we try to ensure we get one value per bucket. We also give the verbose option to get some information, and -write info:.
%IM7DEV%magick ^ toes.png ^ -process 'mkhisto verbose capnumbuckets 1000000' ^ -depth 32 ^ -write info: ^ cu_h1wide.%IFFEXT% 1> cu_h1wide.lis 2>&1
mkhisto options: capnumbuckets 1000000 multiply 1 verbose mkhisto: Input image [toes.png] depth is 16 NumBuckets 65536 BucketSize 65536 counts: min_value 1, max_value 49 sum_red 62211, sum_green 62211, sum_blue 62211 toes.png PNG 65536x1 65536x1+0+0 32-bit sRGB 320268B 0.110u 0:00.103
This tells us the options we have specified. The input image has 16 bits/channel/pixel, so we have 65536 buckets. "BucketSize" is the range of input values, within the quantum of the program, per bucket. When IM reads 16-bit toes.png, it scales pixel values up to quantum, in this case 32 bits.
The other numbers are for debugging. toes.png has 62211 pixels. The lowest value in any histogram bucket that has any count is 1; the largest value is 49. The sum of the red, green and blue values is equal to the number of pixels. If the input image had any transparency, and the regardalpha option had been set, the sums would have been less.
As expected, the created histogram is 65536 pixels wide.
We can dump the contents of 4 buckets:
%IM7DEV%magick cu_h1wide.%IFFEXT% -crop 4x1+32768+0 -compress None ppm: >cu_h1wide.ppm
P3 # 4 1 4294967295 0 0 0 8 15 3 0 0 0 0 0 0
This tells us that no toes.png pixels have any RGB channel of 32768 or 32769. 8 pixels have 32770 in the red channel, 15 pixels have 32770 in the green channel, and 3 pixels have 32770 in the blue channel.
If the process module mkhisto were to be incorporated into conventional IM code as an ordinary command, I suggest the following syntax:
The command is named -mkhisto, with no arguments.
The -mkhisto command respects options, if specified before the command:
Or any or all of these might be arguments to the -mkhisto command, or -define attributes.
The -mkhisto command would respect the general -verbose setting. It might also respect the -channel setting, and work correctly for CMY(K)(A) images.
invclut.c inverts a clut file, so the input becomes the output and the output becomes the input.
Like most IM operations, it operates on all the images in the list. Each image will be replaced by another of the same size. If an input image has more than one row, each is processed separately, as if it were a clut.
(I might add an option to process just the last image.)
A cumulative normalised histogram can be regarded as a clut file: for any input value, it defines an output value. Importantly, every input defines exactly one output. (Possibly, every output is defined by exactly one input.) A general clut file might have more than one input defining the same output. A graph of the clut may rise, fall or be level. However, a cumulative normalised histogram increases monotonically; it may rise or be level, but cannot fall. See my Clut cookbook: Cluts from cumulative histogram.
Invclut assumes each channel increases monotonically. If this is not true, the result will be garbage. (The module could be modified to produce a good result for monotonically decreasing inputs, and to raise errors on inputs that both rise and fall.)
If the input contains an alpha channel, it is copied unchanged to the output. (test??)
We will invert cu_hcn.%IFFEXT%, the cumulative normalised histogram created above.
|
We will invert this: call %PICTBAT%graphLineCol ^ cu_hcn.%IFFEXT% . . 0 cu_hcn_glc.png |
|
We invert it in two ways. The first is by making a cumulative normalised histogram of cu_hcn.%IFFEXT%. The second uses the invclut method.
%IM7DEV%magick ^ cu_hcn.%IFFEXT% ^ -process 'mkhisto capnumbuckets 500 cumul norm' ^ -depth 32 ^ cu_hcn_i1.%IFFEXT% call %PICTBAT%graphLineCol ^ cu_hcn_i1.%IFFEXT% . . 0 cu_hcn_i1_glc.png |
|
%IM7DEV%magick ^ cu_hcn.%IFFEXT% ^ -process invclut ^ -depth 32 ^ cu_hcn_i2.%IFFEXT% call %PICTBAT%graphLineCol ^ cu_hcn_i2.%IFFEXT% . . 0 cu_hcn_i2_glc.png |
|
Visually, the curves appear identical, apart from the ends.
To check the result, we apply the same two processes to the results. An inverse of an inverse should take us back where we started.
%IM7DEV%magick ^ cu_hcn_i1.%IFFEXT% ^ -process 'mkhisto capnumbuckets 500 cumul norm' ^ -depth 32 ^ cu_hcn_i3.%IFFEXT% call %PICTBAT%graphLineCol ^ cu_hcn_i3.%IFFEXT% . . 0 cu_hcn_i3_glc.png |
|
%IM7DEV%magick ^ cu_hcn_i2.%IFFEXT% ^ -process invclut ^ -depth 32 ^ cu_hcn_i4.%IFFEXT% call %PICTBAT%graphLineCol ^ cu_hcn_i4.%IFFEXT% . . 0 cu_hcn_i4_glc.png |
|
Compare each with the original:
%IM7DEV%magick compare -metric RMSE cu_hcn.%IFFEXT% cu_hcn_i3.%IFFEXT% NULL: cmd /c exit /B 0
8014544.6 (0.0018660316)
%IM7DEV%magick compare -metric RMSE cu_hcn.%IFFEXT% cu_hcn_i4.%IFFEXT% NULL: cmd /c exit /B 0
8623753 (0.002007874)
Both results are close to the original. The histogram has 500 buckets, so we shouldn't expect an accuracy much better than 1/500 = 0.002.
Let's repeat the process, not limiting the number of buckets to 500. The first convert makes a cumulative normalised histogram of toes.png, saves it, and inverts it twice. The second convert takes the saved cumulative normalised histogram and inverts it twice, with the invclut module. Then we compare the two results with the saved cumulative normalised histogram.
%IM7DEV%magick ^ toes.png ^ -process 'mkhisto cumul norm' ^ -write cu_hcn_wide.%IFFEXT% ^ -process 'mkhisto cumul norm' ^ -process 'mkhisto cumul norm' ^ -depth 32 ^ cu_hcn_wide1.%IFFEXT% %IM7DEV%magick ^ cu_hcn_wide.%IFFEXT% ^ -process invclut ^ -process invclut ^ -depth 32 ^ cu_hcn_wide2.%IFFEXT% %IM7DEV%magick compare -metric RMSE cu_hcn_wide.%IFFEXT% cu_hcn_wide1.%IFFEXT% NULL: cmd /c exit /B 0
258629.3 (6.0216826e-05)
%IM7DEV%magick compare -metric RMSE cu_hcn_wide.%IFFEXT% cu_hcn_wide2.%IFFEXT% NULL: cmd /c exit /B 0
255069.6 (5.9388018e-05)
The results are better than capnumbuckets 500 by a factor of about a hundred. The histogram has 65536 buckets, so we shouldn't expect an accuracy much better than 1/65536 = 1.53e-05.
Only when writing these process modules and this page did I discover that inverting a clut is the same as taking a cumulative normalised histogram of the clut. This came as a great surprise to me, but I suppose it is documented in the literature, and may be a well-known fact. (It is related to the fact that clutting an image with its own cumulative histogram equalises the histogram.)
If a clut doesn't increase from black to white, but from a% to b%, then the inverse will have the first a% of buckets black, and the last (100-b%) buckets will be white.
For using the invclut module to displace one shape into another, see the (unpublished) page Shape to shape: displacement maps.
Inverting a normalised cumulative histogram by either method gives us the median, or any desired threshold:
%IM7DEV%magick ^ toes.png ^ -colorspace Gray ^ -process 'mkhisto cumul norm' ^ -process 'mkhisto cumul norm' ^ -gravity Center -crop 1x1+0+0 +repage ^ txt:
# ImageMagick pixel enumeration: 1,1,0,65535,gray 0,0: (31439) #7ACF7ACF7ACF gray(47.972107%)
Currently, histograms are calculated for each channel independently. So using this method on a colour image would give the median red, median green and median blue, which would not be the same as any form of median intensity.
For an alternative method to find the median, probably slower, see sortpixels.
cumulhisto.c makes cumulative histograms from non-cumulative histograms, or vice versa.
| Option | Description | |
|---|---|---|
| Short
form |
Long form | |
| r | regardalpha | Multiply input RGB values by alpha; cumulate alpha. |
| d | decumul | De-cumulate the histogram values, instead of cumulating them. |
| n | norm | Normalise the output histogram so the maximum is Quantum. (The minimum may not be zero.) |
| v | verbose | Write some text output to stderr. |
Cumulation is an integration of the input, defined as, for all y:
cumul[0,y] = in_image[0,y] cumul[x,y] = in_image[x,y] + cumul[x-1,y] for x > 0.
De-cumulation is the opposite, a differentiation:
decumul[0,y] = in_image[0,y] decumul[x,y] = in_image[x,y] - decumul[x-1,y] for x > 0.
The input is typically one image with a single row that represents a histogram. Multiple input images will result in corresponding multiple output images. If an image has multiple rows, each is treated as an independent histogram, and is cumulated into an output row.
Each output will be the same size as the corresponding input.
By default, the alpha channel is copied unchanged. If regardalpha is used, the cumulation process is modified so each colour channel value is multiplied by alpha (scaled to 0.0 to 1.0) before being cumulated, and the alpha channel is also cumulated.
Input histograms can be normalised or not. Normalisation effects the RGB channels only (not alpha); values are scaled so the maximum value is 100% of QuantumRange. Output histograms will be normalised if the norm option is used. If an input is normalised but the output isn't, cumulated results will be clipped.
CAUTION: If the input images aren't histograms, this module won't make histograms of any kind. The result may be quite pretty when normalised, or the un-normalised version can be useful, eg to make Integral images.
Examples of cumulation:
|
For the input, we use the non-cumulative histogram cu_hn.%IFFEXT% created above. call %PICTBAT%graphLineCol ^ cu_hn.%IFFEXT% . . 0 cu_hn_glc2.png |
|
|
From that input, we make a cumulative histogram. %IM7DEV%magick ^ cu_hn.%IFFEXT% ^ -process 'cumulhisto norm' ^ -depth 32 ^ cu_hn_c.%IFFEXT% call %PICTBAT%graphLineCol ^ cu_hn_c.%IFFEXT% . . 0 cu_hn_c_glc.png Where the input is highest, the output is steepest. Cumulation has smoothed the histogram's appearance because of the great difference in scale;
|
|
|
We can make a normalised cumulative histogram of a Gaussian distribution,
%IM7DEV%magick ^ -size 2x1 xc: -bordercolor Black -border 1x0 ^ -filter gaussian ^ -resize "500x1^!" ^ -process 'cumulhisto norm' ^ -depth 32 ^ -write cu_gauss_ch.%IFFEXT% ^ -process invclut ^ cu_gauss_ch_icl.%IFFEXT% call %PICTBAT%graphLineCol ^ cu_gauss_ch.%IFFEXT% . . 0 cu_gauss_ch_glc.png call %PICTBAT%graphLineCol ^ cu_gauss_ch_icl.%IFFEXT% . . 0 cu_gauss_ch_icl_glc.png |
|
|
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,
%IMG7%magick ^ cu_hn_c.%IFFEXT% ^ cu_gauss_ch_icl.%IFFEXT% ^ -clut ^ cu_to_gauss.%IFFEXT% call %PICTBAT%graphLineCol ^ cu_to_gauss.%IFFEXT% . . 0 cu_to_gauss_glc.png |
|
|
Apply the clut so each channel of toes.png becomes a Gaussian distribution. %IMG7%magick ^ toes.png ^ cu_to_gauss.%IFFEXT% ^ -clut ^ cu_toes_gauss.png %IM7DEV%magick ^ cu_toes_gauss.png ^ -process 'mkhisto capnumbuckets 500 norm' ^ cu_toes_gauss_hn.png call %PICTBAT%graphLineCol ^ cu_toes_gauss_hn.png . . 0 cu_toes_gauss_hn_glc.png |
|
|
Just for fun, apply the cumulhisto process to toes.png. %IM7DEV%magick ^ toes.png ^ -process 'cumulhisto norm' ^ cu_toes_ch.png The result is pretty, but junk. |
|
Examples of de-cumulation:
|
From cu_hn_c.%IFFEXT% created above, we make a de-cumulated histogram. %IM7DEV%magick ^ cu_hn_c.%IFFEXT% ^ -process 'cumulhisto decumul norm' ^ -depth 32 ^ cu_hn_dc.%IFFEXT% call %PICTBAT%graphLineCol ^ cu_hn_dc.%IFFEXT% . . 0 cu_hn_dc_glc.png We can verify the round-trip: %IMG7%magick compare -metric RMSE cu_hn.%IFFEXT% cu_hn_dc.%IFFEXT% NULL: cmd /c exit /B 0 0.111195 (1.69673e-06) The round-trip is accurate, within 32-bit integer quantum. |
|
|
De-cumulate a sigmoidal curve. %IM7DEV%magick ^ -size 1x500 gradient: -rotate 90 ^ -sigmoidal-contrast 10x50%% ^ -process 'cumulhisto decumul norm' ^ -depth 32 ^ cu_sig_hn.%IFFEXT% call %PICTBAT%graphLineCol ^ cu_sig_hn.%IFFEXT% . . 0 cu_sig_hn_glc.png |
|
|
De-cumulate a different sigmoidal curve. %IM7DEV%magick ^ -size 1x500 gradient: -rotate 90 ^ -sigmoidal-contrast 10x20%% ^ -process 'cumulhisto decumul norm' ^ -depth 32 ^ cu_sig2_hn.%IFFEXT% call %PICTBAT%graphLineCol ^ cu_sig2_hn.%IFFEXT% . . 0 cu_sig2_hn_glc.png |
|
For using these process modules to displace one shape into another, see the (unpublished) page Shape to shape: displacement maps.
ASIDE: After writing the above, IM has acquired an -integral operation. This has the same effect as "-process cumulhisto". We can also divide the result by the right-most pixel scaled to the full width to get the same effect as "-process 'cumulhisto norm'".
%IMG7%magick ^
cu_hn.%IFFEXT% ^
-set option:WW %%w ^
-integral ^
( +clone ^
-crop 1x1+%%[fx:w-1]+0 +repage ^
-scale "%%[WW]x1^!" ^
) ^
-compose DivideSrc -composite ^
cu_integ.%IFFEXT%
call %PICTBAT%graphLineCol ^
cu_integ.%IFFEXT% . . 0 cu_integ.png
|
|
Compare this to the result from "-process 'cumulhisto norm'":
%IMG7%magick compare ^ -metric RMSE ^ cu_hn_c.%IFFEXT% cu_integ.%IFFEXT% ^ NULL:
0.00931123 (1.4208e-07)
The result is practically identical.
invdispmap.c inverts displacement maps.
A displacement map will transform image A into image B. If we invert the displacement map, we can use the result to transform image B into image A.
| Option | Description | |
|---|---|---|
| Short
form |
Long form | |
| t string | type string | Declare the map type.
string must be one of: Absolute (which uses -compose Distort) or Relative (which uses -compose Displace). Default: Absolute. |
| v | verbose | Write some text output to stderr. |
In general, displacements are not 1:1 mappings. Some pixels in A may displace to more than one pixel in B. The resulting inverse map will have only one pixel in B displaced from that pixel of A. The other corresponding pixels in the inverse map will become transparent black. This will be obvious where pixels in A are squashed together in B. If desired, the inverse absolute (or relative) map may be filled in by one of the techniques shown in Filling holes.
Alternatively, supersampling may be used: resize the input up, then resize the output down to the original size. This will usually give better results. However, a resizing may not eliminate all holes, so it may be necessary to resize the input up, invert, fill holes, and finally resize the output down.
Like other modules and IM processes, this operates on every image in the current list.
This module assumes the red channel represents the horizontal component of a displacement; the green channel represents the vertical component of a displacement; a value of zero represents the left or top edge; a value of quantum represents the right or bottom edge. Input pixels that are fully transparent are ignored.
The input blue channel is ignored. The output blue channel is set to zero.
Note that:
A displacement map is a "pull" map: the value at each location specifies where the pixel colour for that location should be pulled from. Inverting a displacement map converts it to a "push" map: the value at each location specifies where the pixel colour at that location should be pushed to.
For example usage, see Follow line: inverse process, Straightening horizons: inverse follow-line and Pin or push.
Future: I may add an option to fill holes using seamless-blend composite.
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 |
|
|
Darkest path between top and bottom. %IM7DEV%magick ^ toes.png ^ -colorspace Gray ^ -process darkestpath ^ cu_dp1.png |
|
|
Check the opposite direction. %IM7DEV%magick ^ toes.png ^ -colorspace Gray ^ -flip ^ -process darkestpath ^ -flip ^ cu_dp2.png |
|
|
Darkest path between left and right. %IM7DEV%magick ^
toes.png ^
-colorspace Gray ^
( +clone ^
-rotate 90 ^
-process darkestpath ^
-rotate -90 ^
-colorspace sRGB ^
-fill Yellow -opaque White ^
-transparent Black ^
) ^
-composite ^
cu_dp3.png
|
|
For further explanation and examples, see Dark paths.
darkestmeander.c finds the darkest meandering path from the top edge to the bottom edge. It replaces the input image with one the same size, with white pixels showing the path and black pixels elsewhere.
| Option | Description | |
|---|---|---|
| Short
form |
Long form | |
| m N | maxiter N | Maximum number of iterations of each cycle.
0 = no limit. Default = 10. |
| a | autoiter | Stop iterating when path becomes stable. |
| c | cumulerr | Return cumulative image instead of path image. |
| s string | side string | Which sides to search for the minimum cumulative.
String may have one or more of LBRlbr for left side, bottom or right side. Default = b. |
| e X,Y | end_at X,Y | Find the path that ends at given X,Y coordinate. |
| v | verbose | Write some text output to stderr. |
| v2 | verbose2 | Write more text output to stderr. |
Unlike darkestpath above, a meandering path can move in any direction: up, down or sideways. It will not cross itself.
Possible meandering paths are of different lengths. The meander returned will be the one with the least total sum of the pixel red channels. In general, this will be different to a path that has the smallest average value of the pixel red channels.
The darkestmeander of an image, from one side to the opposite side, may be the same as the darkestpath of the same image. For ordinary photographs, this is often the case.
For further explanation and examples, see Dark paths.
I'd like to add an option for starting the path at a given location, but can't see how to do this. A workaround is to use a super-white gate, or to use the next module below.
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 | 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.
rmsealpha.c performs weighted RMSE comparisons or subimage searches. It searches for the second image as a subimage within the first, and finds the best result. The subimage is assumed neither scaled nor rotated. The module compares image pairs, testing all possible window positions within the first image, and returns the position that has the best score, and the value of that score. The score will be between 0.0 and 1.0 inclusive. The returned coordinates are the best position for the top-left of the second image within the first.
| Option | Description | |
|---|---|---|
| Short
form |
Long form | |
| ai | avoid_identical | Windows with all pixels identical to corresponding subimage pixels in each RGBA channel will score 1.0. |
| ddo | dont_decrease_opacity | Windows with lower opacity in any pixel than corresponding subimage pixel will score 1.0. |
| ms | multi_scale | Do a multi-scale search. This is faster, but potentially finds the wrong "best" match. |
| adj number | adjustLC number | Adjust lightness and contrast of windows to match the subimage before comparing.
Default: 0.0. |
| j | just_score | Write the score only, with no trailing \n. |
| so | stdout | Write data to stdout. |
| se | stderr | Write data to stderr. (Default.) |
| sis | saveInpScales | For testing only: run multiple times, saving resized input image. |
| sss | saveSubScales | For testing only: run multiple times, saving resized subimage. |
| z number | sizeDiv number | Size divider for multi_scale.
Default: 2.0. |
| md integer | minDim integer | Minimum dimension for multi_scale.
Default: 20. |
| v | verbose | Write some text output to stderr. |
(The multi-scale and adjustLC options were added 22-August-2017.)
The module doesn't modify images or add new ones.
It operates on all images in the current list, in pairs. If the list has no images, or an odd number of images, it raises a fatal error. Within each pair, the first should be at least as large as the second, in both width and height.
It always operates on R,G and B channels, weighted by alpha. It does not repect similarity or dissimilarity thresholds (but this might change). If the process finds a perfect score of zero, it stops searching. Otherwise, it performs an exhaustive search. It ignores the -metric setting, always using RMSE. The score is given according to the -precision setting.
The weighting is such that a fully-transparent pixel (alpha=0) in one image will exactly match any colour of pixel in the other image, whatever its alpha value. This is like a "wildcard" search.
The usual ImageMagick comparisons pre-multiply colour values by alpha before subtracting them, so two pixels with the same RGB values but different alphas will have a distance greater than 0.0, unless they are both black. By contrast, this module subtracts RGB values then multiplies by the product of the two alphas, so two pixels with the same RGB values but different alphas will have a distance of exactly 0.0.
If the multi_scale option is used, and the smallest dimension of both images and their difference is at least minDim (default 20) pixels the module resizes both images by factor sizeDiv (default 2.0, ie resize 50%) and searches recursively. This always returns correct results. Reducing the number will increase performance, but if reduced too far will return incorrect results. For graphics images it may be possible to reduce this to 5, which increases performance by 60%.
By default, the module writes two lines of text per image-pair to stderr. The lines are of the format:
rmsealpha: n @ x,y rmsealphaCrop: WxH+x+y
... where n is the score, 0<=n<=1, and x,y are integer coordinates within the first image for the top-left corner of the second, and WxH is the size of the subimage.
However, when the just_score option is chosen, the only output will be the score, with no trailing new-line.
V6 and v7 should produce the same result. It seems v7 gives the same coordinates but a different score. I haven't yet investigated this.
When the images are the same size, the best (and only) match will be at 0,0, so the score is the conventional RMSE score between two equal-sized images, but taking transparency into account.
For large images, the default processing is unusably slow. The multi_scale option is very much faster, but it can return the wrong "best" result. This works as described on Searching an image, but the image is resized by 50% at each level.
The process module searches for one subimage within a larger, main image. We often want to search for many different subimages within a single main image, or for one subimage within many different main images. The C code has an option to improve performance: instead of creating a hierarchy of resizes for each search, they can be saved in memory and re-used for subsequent searches. The pyramid of either the main image (saveInpScales) or the subimage (saveSubScales) can be saved. They can both be saved, though this is unlikely to be useful.
For example:
%IM7DEV%magick ^ toes.png ^ rose: ^ -process rmsealpha ^ NULL:
rmsealpha: 0.25165087 @ 107,166 rmsealphaCrop: 70x46+107+166
%IM7DEV%magick ^ toes.png ^ rose: ^ -process 'rmsealpha multi_scale' ^ NULL:
rmsealpha: 0.25165087 @ 107,166 rmsealphaCrop: 70x46+107+166
For comparison:
%IMG7%magick compare ^ -metric RMSE ^ -subimage-search ^ toes.png ^ rose: ^ NULL:
16491.9 (0.251651) @ 107,166
One use for this module is to search for non-rectangular shapes within images. For example, to search for a circle of red pixels within rose:, create an image with a red circle on a transparent background.
%IM7DEV%magick ^ rose: ^ ( -size 11x11 xc:None -fill Red -draw "translate 5,5 circle 0,0 0,5" ) ^ -process rmsealpha ^ NULL:
rmsealpha: 0.18507465 @ 32,9 rmsealphaCrop: 11x11+32+9
Another example, from the Multi-phase search page:
|
A main image to search. ms_toes_frogs.png |
|
|
A subimage to search for. frog.png This has transparency. |
|
|
Search for the subimage. %IM7DEV%magick ^ ms_toes_frogs.png ^ frog.png ^ -process 'rmsealpha' ^ +repage ^ NULL: rmsealpha: 0.054971276 @ 48,148 rmsealphaCrop: 45x45+48+148 |
[No image] |
The adjustLC option adjusts the lightness and contrast of the candidate window of the main image to roughly match the lightness and contrast of the subimage, by a gain-and-bias method, before comparing the window to the subimage. This takes a number parameter, typically 0.0 to 1.0, that determines how far to make the adjustment. The default is 0.0, which is no adjustment. 1.0 is full adjustment. Any (non-zero) adjustment adds to the processing time required.
The module finds one offset that gives the best score. If more than one offset gives the same best score, the module won't find the others.
The process module searches for one subimage within a larger, main image. We often want to search for many different subimages within a single main image, or for one subimage within many different main images. The C code has a mechanism to improve performance: instead of creating a hierarchy of resizes for each search, they can be saved in memory (as two lists of images) and re-used for subsequent searches. The pyramid of either the main image (saveInpScales) or the subimage (saveSubScales) can be saved. They can both be saved, though this is unlikely to be useful. The module contains options saveInpScales and saveSubScales to test this mechanism. For example:
%IM7DEV%magick ^ toes.png ^ rose: ^ -process 'rmsealpha ms sis sss' ^ NULL:
rmsealpha: 0.25165087 @ 107,166 rmsealphaCrop: 70x46+107+166 rmsealpha: 0.25165087 @ 107,166 rmsealphaCrop: 70x46+107+166 rmsealpha: 0.25165087 @ 107,166 rmsealphaCrop: 70x46+107+166
Possible variations for filling holes etc:
srchimg.c is similar to the srchimg.bat script. It searches within the first image for the second image, assumed neither scaled nor rotated, finds the best result, and replaces the two input images with a crop of the first. The search is "multi-scale", ie both images are reduced in size for approximate searches. The RMSE score will be between 0.0 and 1.0 inclusive. The returned coordinates are the best position for the top-left of the second image within the first.
| Option | Description | |
|---|---|---|
| Short
form |
Long form | |
| n | noCrop | Don't replace input images with crop. |
| z number | sizeDiv number | Size divider for multi_scale.
Default: 2.0. |
| md integer | minDim integer | Minimum dimension for multi_scale.
Default: 20. |
| ss integer | superSample integer | Factor for super-sampling.
Default: 1 (no super-sampling). |
| f string | file string | Write verbose text to stderr or stdout.
Default: stderr. |
| v | verbose | Write some text output to stderr. |
| version | Write version information to stderr. | |
This is similar to alpha-weighted RMSE search with the ms option.
The module needs exactly two input images. The second must be no larger, in either dimension, than the first.
By default, it replaces both input images with a cropped version of the first. (The crop has appropriate canvas settings; use +repage if these are not wanted.) The noCrop option prevents this, so the image list will not be changed. This saves some time when we want only the coordinates.
The module respects the -precision setting.
|
Make a subimage to search for. %IM7DEV%magick ^ toes.png ^ -crop 100x80+50+60 ^ +repage ^ -attenuate 0.5 ^ +noise Gaussian ^ cu_srchsub.png |
|
|
Search for the subimage. %IM7DEV%magick ^ toes.png ^ cu_srchsub.png ^ -precision 9 ^ -process 'srchimg' ^ +repage ^ cu_srchi.png 0.0390103447 @ 50,60 |
|
Another example, from the Multi-phase search page:
|
A main image to search. ms_toes_frogs.png |
|
|
A subimage to search for. frog.png This has transparency. |
|
|
Search for the subimage. %IM7DEV%magick ^ ms_toes_frogs.png ^ frog.png ^ -process 'srchimg' ^ +repage ^ cu_srchi2.png 0.054971276 @ 48,148 |
|
For more details, see Searching an image: process module.
aggrsrch.c searches aggressively for the second image (a subimage) within the first. Both images are scaled down: the sub-image to 1x1 pixel, and the main image by the same proportion. The 1x1 sub-image is then scaled up to the new size of the main image, and the difference between the two is found. We gray-scale the shrunken main image (with RMS method).
This difference would be black where the exact 1x1 sub-image is found.
| Option | Description | |
|---|---|---|
| Short
form |
Long form | |
| a N | aggression N | Aggression factor.
More than 0.0, typically no more than 1.0. Default = 1.0. |
| m | makeMask | Make a mask. |
| t N | threshold N | Threshold for the mask.
Typically 0.0 to 1.0. 0.0 will exclude all positions (which isn't useful). More than 1.0 will exclude no positions (which may be useful). Default = 0.75. |
| v | verbose | Write some text output to stderr. |
| version | Write version information. | |
By default, the output is the same size as the first input. The makeMask option crops the output to the sliding area of the second image within the first image, sets the red and green channels to zero, and sets the blue channel to 100% where it is above a given threshold.
A threshold greater than 1.0 (eg 1.1) will not eliminate any positions.
This module is useful when the subimage is significantly smaller than the main image. When this isn't true, the method may return the wrong result.
|
A main image to search. ms_toes_frogs.png |
|
|
A subimage to search for. frog.png This has transparency. |
|
|
Make a difference image. %IM7DEV%magick ^ ms_toes_frogs.png ^ frog.png ^ -process 'aggrsrch v' ^ cu_aggr1.png aggrsrch options: aggression 1 verbose aggrSrch Diff: main 267x233 sub 45x45 newMain 6x5 threshold 0.75 aggrsrch Res: 0.316605 @ 4,4 |
|
|
Make the mask. %IM7DEV%magick ^ ms_toes_frogs.png ^ frog.png ^ -process 'aggrsrch makeMask v' ^ cu_aggr2.png aggrsrch options: aggression 1 makeMask threshold 0.75 verbose aggrSrch Diff: main 267x233 sub 45x45 newMain 6x5 threshold 0.75 aggrsrch Res: 0.316605 @ 4,4 LightenNonSinks: nIter 1 aggrSrch Mask: crop to 223x189+22+22 aggrSrch Mask: removed 88.1225% |
|
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.
srt3d.c applies an arbitrary series of scales, rotations and translations in 3D, possibly with perspective.
Options:
| Option | Description | |
|---|---|---|
| Short
form |
Long form | |
| t string | transform string | Input transformation string.
Default: no string (so array is required). |
| a filename | array filename | Input transformation array: a text file with four lines, each with four numbers.
Can be "-" for stdin. Default: no array (so string is required). |
| A filename | out-array filename | Output filename for transformation array.
Can be "-" for stdout. Default: no output array. |
| c filename | coords filename | Input filename for coordinate list.
Note: the file is read multiple times, so "-" can't be used for stdin. Default: no input coordinates, so the image corners will be used. |
| C filename | out-coords filename | Output filename for coordinate list.
Can be "-" for stdout. Default: no output coordinates. |
| n integer | nOutCoords integer | Number of coordinates to output with C option.
Use 2 for just the x and y coordinates if you want to use the list in an IM "distort" command. Default: 3 (x, y and z coordinates). |
| f number | focal-length number | Focal length for perspective.
Default: 0, no perspective. |
| r | hide-reverse | Hide reversed polygons. |
| h | help | Write some help to stdout. |
| v | verbose | Write some text output to stderr. |
| version | Write version text to stdout. | |
The transform string is a list of scale, rotate and translate operations in any order.
The module is sensitive to -precision, -virtual-pixel and -mattecolor.
For example:
%IM7DEV%magick ^ toes.png ^ -virtual-pixel Black -mattecolor Black ^ -process 'srt3d transform ty-50c,tx-50c,rx30,ry60 f 600' ^ cu_s3d.jpg
For more details, see the Scale, rotate and translate in 3D page.
arctan2.c takes two same-size input images and replaces them with atan2(u,v) where u is the first input and v is the second. Inputs can be negative (which needs HDRI, of course). Output values are in the range 0 to 100%. With default parameters, it is the equivalent of "-fx atan2(u,v)/(2*pi)+0.5", but much faster.
| Option | Description | |
|---|---|---|
| Short
form |
Long form | |
| s number | scale number | Gain factor for distance from WP.
Default: 0.5 / pi. |
| b number | bias number | Bias for distance from WP.
Default: 0.5. |
| v | verbose | Write some text output to stderr. |
The module processes red, green and blue channels independently, and currently ignores the alpha channel.
output = QuantumRange * [atan2 (u, v) * scale + bias]
... where u is the first image, v is the second image, scale defaults to 1/2pi, and bias defaults to 0.5.
The function atan2() returns a value between -pi and +pi, so the default values of scale and bias normalises the output to the range 0 to QuantumRange.
|
Make two gradient images. set DIM=300 %IMG7%magick ^ -size %DIM%x%DIM% ^ gradient: -rotate 180 ^ ( +clone -rotate -90 ) ^ +swap ^ cu_2grad.png |
|
|
Find the arctangent. %IM7DEV%magick ^ cu_2grad-0.png cu_2grad-1.png ^ -process arctan2 ^ cu_atan2_1.png |
|
|
Subtract 50%, then find the arctangent. %IM7DEV%magick ^ cu_2grad-0.png cu_2grad-1.png ^ -evaluate Subtract 50%% ^ -process arctan2 ^ cu_atan2_2.png |
|
|
Subtract 50%, then find the arctangent, with no bias. %IM7DEV%magick ^ cu_2grad-0.png cu_2grad-1.png ^ -evaluate Subtract 50%% ^ -process 'arctan2 b 0' ^ -evaluate AddModulus 0 ^ cu_atan2_3.png |
|
|
Subtract 50%, then find the arctangent, with 25% bias. %IM7DEV%magick ^ cu_2grad-0.png cu_2grad-1.png ^ -evaluate Subtract 50%% ^ -process 'arctan2 b 0' ^ -evaluate AddModulus 25%% ^ cu_atan2_4.png |
|
|
Use "-negate" to invert the direction. %IM7DEV%magick ^ cu_2grad-0.png cu_2grad-1.png ^ -evaluate Subtract 50%% ^ -process 'arctan2 b 0' ^ -evaluate AddModulus 25%% ^ -negate ^ cu_atan2_5.png |
|
rhotheta.c processes all images in the list, replacing the first two channels. By default it reads these channels as cartesian coordinates x,y and replaces these with polar coordinates rho and theta. Optionally it does the inverse.
| Option | Description | |
|---|---|---|
| Short
form |
Long form | |
| s number | scale number | Scale factor for theta.
Default: 0.5 / pi. |
| b number | bias number | Bias for theta.
Default: 0.5. |
| o number,number | offset number,number | Offset for cartesian coordinates.
Default: 0,0. |
| inv | inverse | Inverse calculation (polar to cartesian). |
| v | verbose | Write some text output to stderr. |
The module reads and writes the first two colour channels (red and green) only. The blue and alpha (if any) channels are untouched.
Channels values 0 to QuantumRange are normalised to 0.0 to 1.0 before the calculations, and denormalised afterwards.
offset numbers should typically be in the range 0.0 to 1.0.
Cartesian to polar:
dx = x - offsetX dy = y - offsetY rho = hypot (dx,dy) theta = atan2 (dx, dy) * scale + bias
When 0 <= x,y <= 1, 0 <= rho <= sqrt(2)
Polar to cartesian:
t = (theta - bias) / scale x = rho * sin (t) + offsetX y = rho * cos (t) + offsetY
With default settings, at x=0 and y<0, theta is 0.0 or 100%.
|
Make a gradient image. call %PICTBAT%identAbsDispMap 300 300 cu_tst_rt.png |
|
|
Find rho and theta, with defaults. %IM7DEV%magick ^ cu_tst_rt.png ^ -process 'rhotheta' ^ cu_out_rt_1.png |
|
|
Subtract 50%, then find rho and theta. %IM7DEV%magick ^ cu_tst_rt.png ^ -evaluate Subtract 50%% ^ -process 'rhotheta' ^ cu_out_rt_2.png |
|
|
Subtract 50%, then find rho and theta, with no bias. %IM7DEV%magick ^ cu_tst_rt.png ^ -evaluate Subtract 50%% ^ -process 'rhotheta b 0' ^ -evaluate AddModulus 0 ^ cu_out_rt_3.png |
|
|
Subtract 50%, then find rho and theta, with 25% bias. %IM7DEV%magick ^ cu_tst_rt.png ^ -evaluate Subtract 50%% ^ -process 'rhotheta b 0' ^ -evaluate AddModulus 25%% ^ cu_out_rt_4.png |
|
|
As previous, but then negate green channel. %IM7DEV%magick ^ cu_tst_rt.png ^ -evaluate Subtract 50%% ^ -process 'rhotheta b 0' ^ -evaluate AddModulus 25%% ^ -channel G -negate +channel ^ cu_out_rt_5.png |
|
Examples of inverse:
|
Find x and y, with defaults. %IM7DEV%magick ^ cu_out_rt_1.png ^ -process 'rhotheta inverse' ^ cu_out_rt_1i.png |
|
|
Subtract 50%, then find x and y. %IM7DEV%magick ^ cu_out_rt_2.png ^ -process 'rhotheta inverse' ^ -evaluate Add 50%% ^ cu_out_rt_2i.png |
|
This module is used to calculate Colourfulness.
pause.c writes a message to stdout and waits for the 'return' key to be pressed. It takes no arguments. It has no effect on the image list.
This pauses IM while it is running, so temporary files may still exist. This can be useful in debugging or investigating weirdness.
Even if there are multiple images in the current list, the pause is executed only once.
shell.c attempts to execute its arguments as a shell command. IM waits for the command to finish. It has no effect on the image list. It respects the current "-verbose" setting, sending some text to stderr.
Even if there are multiple images in the current list, the command is executed only once.
The module can be used to call operating system utilities that monitor resource usage, such as temporary files and memory usage.
For example:
%IM7DEV%magick xc: -process 'shell pwd' NULL: >cu_shell1.lis 2>&1
/cygdrive/c/prose/pictures
%IM7DEV%magick xc: -verbose -process 'shell pwd' +verbose NULL: >cu_shell2.lis 2>&1
shell: sCmd = [pwd] /cygdrive/c/prose/pictures shell: status 0.
This can get information about the process while it is running:
%IM7DEV%magick ^ -size 1000x1000 xc:pink ^ -alpha opaque ^ -process 'shell tasklist ^|findstr magick' ^ -scale "2000x1000^!" ^ -process 'shell tasklist ^|findstr magick' ^ NULL: >cu_shell3.lis 2>&1
magick.exe 2700 Console 1 43,416 K magick.exe 2700 Console 1 74,736 K
Hence this version of IM needs 32 million bytes for 1 million pixels, so 8 bytes/channel/pixel.
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.
setmnsd.c sets the mean and standard deviation of each image by applying a suitable "-sigmoidal-contrast" and "-evaluate pow".
The module corresponds to the script setmnsd.bat, and works in the same way, but it is much quicker as it doesn't write to disk at each iteration.
| Option | Description | |
|---|---|---|
| Short
form |
Long form | |
| mn N | meanGoal N | Goal for the mean.
Proportion of quantum 0.0 to 1.0, or percentage suffixed by 'c' or '%', or pin. Default: no goal for mean value. |
| sd N | sdGoal N | Goal for standard deviation.
Proportion of quantum 0.0 to 0.5, or percentage suffixed by 'c' or '%', or pin. Default: no goal for SD. |
| t N | tolerance N | Tolerance for mean and standard deviation.
Proportion of quantum, or percentage suffixed by 'c' or '%'. Default: 0.00001 (0.001%). |
| m N | mid N | Mid-point for sigmoidal-contrast, as percentage of quantum (0.0 to 100.0)
or mean to use the mean of the image for the mid-point. Default: mean. |
| i0 N | initCon0 N | Lower initial guess for contrast.
Can be 0.0. Default: 0.0000001. |
| i2 N | initCon2 N | Upper initial guess for contrast.
Default: 30. |
| d string | direction string | One of:
incOnly to prevent the SD decreasing; decOnly to prevent the SD increasing; or both for no restriction. Default: both. |
| f string | file string | Write verbose text to stderr or stdout.
Default: stderr. |
| v | verbose | Write text information. |
| v2 | verbose2 | Write more text information. |
The process first calculates whether contrast needs to increase or decrease. Then it uses two initial guesses for the contrast, and calculates the SDs. If either calculated SD is within tolerance of the required SD, the task is done. Otherwise, the SDs should bracket the required SD. A geometric or arithmetic mean of the two contrast guesses gives a third, and the SD from this is either within tolerance or is too high or two low. So the third guessed contrast replaces one of the others, and the process iterates.
An ordinary image has all pixels in the range 0 to 100%. The standard deviation of the image has a range of 0.0 to 0.5. When the image is a constant colour, the SD is 0.0 When the image contains 0 and 100% only, in equal amounts, the SD is 0.5. Provided "mid mean" is used, with Q32 HDRI, the module can find SDs close to the theoretical limits of 0.0 and 0.5.
When the module doesn't find a solution, it reports this to stderr and causes IM to fail.
%IM7DEV%magick ^ toes.png ^ -process 'setmnsd sd 0 t 0.01 i2 1000 mid mean' ^ cu_sss0.png |
|
%IM7DEV%magick ^ toes.png ^ -process 'setmnsd sd 0.1 mid mean' ^ cu_sss1.png |
|
%IM7DEV%magick ^ toes.png ^ -process 'setmnsd sd 0.2 mid mean' ^ cu_sss2.png |
|
%IM7DEV%magick ^ toes.png ^ -process 'setmnsd sd 0.3 mid mean' ^ cu_sss3.png |
|
%IM7DEV%magick ^ toes.png ^ -process 'setmnsd sd 0.4 mid mean' ^ cu_sss4.png |
|
%IM7DEV%magick ^ toes.png ^ -process 'setmnsd sd 0.5 t 0.01 i2 1000 mid mean' ^ cu_sss5.png |
|
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.
integim.c converts ordinary images into integral images (summed area tables).
| Option | Description | |
|---|---|---|
| Short
form |
Long form | |
| pm string | premult string | Whether to pre-multiply RGB values by alpha.
One of: yes (do pre-multiply), no (don't pre-multiply), or auto (from current alpha setting). Default: yes. |
| f string | file string | Write verbose text to stderr or stdout.
Default: stderr. |
| v | verbose | Write text information. |
An integral of an image has pixel values of each coordinate set to the sum of the image's pixel values across a rectangle from the origin (0,0) to this coordinate. This is done for each channel. The integral image needs to be HDRI.
For opaque images, this creates in a single pass the same result we get from two orthogonal runs of cumulhisto.
Visually, integral images are mostly whiter than white. We can view them after "-auto-level":
%IM7DEV%magick ^ toes.png ^ -process 'integim' ^ -depth 32 ^ -define quantum:format=floating-point ^ +write cu_ii1.miff ^ -channel RGB -auto-level +channel ^ cu_ii1_al.png |
|
|
For images with transparency, when premult is used, each colour value is pre-multiplied by the alpha at that pixel. The alpha also accumulates to values greater than 100%.
%IM7DEV%magick ^ toes_holed.png ^ -process 'integim' ^ -depth 32 ^ -define quantum:format=floating-point ^ +write cu_th_ii1.miff ^ -channel RGB -auto-level +channel ^ cu_th_ii1_al.png |
|
|
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.
deintegim.c converts from integral images (summed area tables) into ordinary images.
| Option | Description | |
|---|---|---|
| Short
form |
Long form | |
| w string | window string | The window size, two numbers separated by "x".
Each number may be suffixed with "%" or "c" or "p". Default: 1x1. |
| pd string | postdiv string | Whether to post-divide RGB values by alpha.
One of: yes (do post-divide), no (don't post-divide), or auto (from current alpha setting). Default: yes. |
| ox integer | offsetX integer | Offset for window centre in X-direction.
Default: 0. |
| oy integer | offsetY integer | Offset for window centre in Y-direction.
Default: 0. |
| ae | adjedges | Adjust divisor near image edges (boundaries). |
| dd | dontdivide | Don't divide colour channels by anything. |
| f string | file string | Write verbose text to stderr or stdout.
Default: stderr. |
| v | verbose | Write text information. |
The windows width and height are integers from one upwards. When the window size is 1x1, this is the inverse process to integral image.
%IM7DEV%magick ^ cu_ii1.miff ^ -process 'deintegim' ^ -define quantum:format=floating-point ^ cu_ii1_di.png |
|
%IM7DEV%magick ^ cu_th_ii1.miff ^ -process 'deintegim' ^ -define quantum:format=floating-point ^ cu_th_ii1_di.png |
|
More usefully, a larger window size makes a mean (blurred) result:
%IM7DEV%magick ^ cu_ii1.miff ^ -process 'deintegim window 25x5' ^ cu_ii1_bl.png |
|
%IM7DEV%magick ^ cu_th_ii1.miff ^ -process 'deintegim window 25x5' ^ cu_th_ii1_bl.png |
|
We can use both processes in a single command:
%IM7DEV%magick ^ toes.png ^ -process 'integim' ^ -process 'deintegim window 5x25' ^ cu_id_bl.png |
|
For more details and examples, see the Integral images page.
deintegim2.c converts from integral images (summed area tables) into ordinary images.
deintegim2 is similar to deintegim, and takes the same options.
deintegim2 takes exactly two input images: a Integral Image that has been created by integim, and a map image of the same size. The red channel of the map image, divided by QuantumRange, is used as a multiplier for the window width, and the green channel as a multiplier for the window height.
The calculated window width and height are rounded to the nearest integer. This is crude. A more accurate method would use non-integral window sizes. For each corner, it would read at least four pixels from the Integral Image, and interpolate.
When the map is white, deintegim2 gives the same result as deintegim with the same options, but deintegim will be faster.
When the map is black, the window size is 1x1, so the result is the same as deintegim with a 1x1 window, which is the input to integim.
For example, we use a map where the red channel increase across the image, and the green channel increases down the image. (This is the same as an identity absolute displacement map.) The result has increasing horizontal blur across the image, and increasing vertical blur down the image.
%IM7DEV%magick ^
%SRC% ^
-process integim ^
( +clone ^
-sparse-color Bilinear ^
0,0,#000,^
%%[fx:w-1],0,#f00,^
0,%%[fx:h-1],#0f0,^
%%[fx:w-1],%%[fx:h-1],#ff0 ^
-evaluate Pow 3 ^
) ^
-process 'deintegim2 window 200x200' ^
cu_deinteg2.png
|
|
The effect is similar to that from a -compose Blur, although this also blurs with pixels beyond the image boundary, virtual pixels.
%IM7DEV%magick ^
%SRC% ^
-process integim ^
( +clone ^
-sparse-color Bilinear ^
0,0,#000,^
%%[fx:w-1],0,#f00,^
0,%%[fx:h-1],#0f0,^
%%[fx:w-1],%%[fx:h-1],#ff0 ^
-evaluate Pow 3 ^
) ^
-compose Blur ^
-set option:compose:args 80x80 -composite ^
cu_deinteg2b.png
|
|
kcluster.c implements clustering by k-means, fuzzy k-means and k-harmonic, with transparency.
| Option | Description | |
|---|---|---|
| Short
form |
Long form | |
| m string | method string | Method for converging clusters, one of:
Means "k-means" clustering; Fuzzy "fuzzy k-means" clustering; Harmonic "k-harmonic" clustering; Null no convergence; Default = means. |
| k N
k N-M |
kCols N
kCols N-M |
Number of clusters to find.
Parameter is one integer, or two integers separated by a hyphen. When two integers, finds k as the best value between N and M inclusive by the jump method. For example: kCols 10, kCols 5-15. Default = 10. |
| j N,M | jump N,M | DEPRECATED. Find k as the best value between N and M inclusive by the jump method.
For example: jump 1,10 |
| s | sdNorm | Transform input to equalise channel SDs, and invert this at the end. |
| a | regardAlpha | Process opacity. |
| i string | initialize string | Initialization for clusters, one of:
Colors IM's "-colors"; Forgy chooses random pixels from the image; RandPart assigns image pixels to random partitions; SdLine uses colours from a short line in the colour cube; FromList uses unique colours from last image in list; Default = colors. |
| y N | jumpY N | Y power parameter for "jump" iterations. A positive number.
Default = 0.5, 1.0 or 1.5. |
| r N | fuzzyR N | Power parameter r for fuzzy method. A positive number at least 1.0.
Default = 1.5. |
| p N | harmonicP N | Power parameter p for harmonic method. A positive number at least 2.0.
Default = 3.5. |
| t N | tolerance N | Stop iterating when worst channel change is less than or equal to this.
Default = 0.01. |
| x N | maxIter N | Stop after this many iterations.
Default = 100. |
| w filename | write filename | Write iterations as image files.
For example: write myfile_%06d.png |
| f string | file string | Write verbose text to stderr or stdout.
Default: stderr. |
| v | verbose | Write some text output to stderr. |
| v2 | verbose2 | Write more text output to stderr. |
| d | debug | Write debugging text output to stderr. |
For example:
%IM7DEV%magick ^ toes.png ^ -process 'kcluster' ^ cu_kc.png |
|
For more details and examples, see the K-clustering page.
centsmcrop.c takes two input images, and replaces each with a central crop of that image that has the smallest dimensions of the input images. Both outputs have zero canvases.
For example:
%IM7DEV%magick ^ toes.png ^ rose: ^ -process 'centsmcrop' ^ cu_csc_%%d.png |
|
%IM7DEV%magick ^ toes.png ^ ( +clone -rotate 90 ) ^ -process 'centsmcrop' ^ cu_csc2_%%d.png |
|
| Option | Description | |
|---|---|---|
| Short
form |
Long form | |
| p | findPeaks | Find peaks instead of sinks. |
| v | verbose | Write some text output to stderr. |
| version | Write version text to stdout. | |
findsinks.c finds local sinks or peaks. It makes non-sink pixels white (or non-peak pixels black). Colours of sink (or peak) pixels are unchanged. Alpha values are unchanged. For the purpose of deciding which pixels are sinks (or peaks), alpha is ignored.
Each pixel has up to eight neighbours. A pixel that is not neighboured by any darker (lighter) pixels is a sink (peak). In addition, when a pixel is part of a connected group of equal-value pixels, and that group is not neighboured by darker (lighter) pixels, the pixels in the group are all sinks (peaks).
With this definition, in an image of a single colour, all the pixels are both sinks and peaks.
White pixels can't be sinks (unless the image is entirely white), so we use it to denote non-sinks. Similarly for black pixels and peaks.
Lightness is defined by the "-intensity" setting. If we imagine the image represents an area of land, with lightness representing height above sea level, sinks are where water would collect after rainfall (forming lakes), and peaks are where rain would always flow away from. All other pixels represent the slopes of the hills, although connected pixels may have the same height so they are terraces or "shelves" in the hillside.
For ordinary photographs, which have no significant terraces, the process is fast.
For example:
%IM7DEV%magick ^ toes.png ^ -process 'findsinks' ^ cu_fsink.png |
|
%IM7DEV%magick ^ toes.png ^ -process 'findsinks p' ^ cu_fsinkp.png |
|
Ordinary photographs don't have sinks or peaks in connected groups. We can make an artificial example:
%IM7DEV%magick ^ toes.png ^ +dither -colors 20 ^ cu_fsink_col.png |
|
%IM7DEV%magick ^ cu_fsink_col.png ^ -process 'findsinks' ^ cu_fsink_col2.png |
|
%IM7DEV%magick ^ cu_fsink_col.png ^ -process 'findsinks p' ^ cu_fsinkp_col2.png |
|
The code that does the work, function LightenNonSinks in findsinks.inc, was written for a specific purpose and generalised for the process module. The function identifies pixels that are not sinks. A pixel that has a darker neighbour is not a sink. In addition, a pixel that is next to a not-sink and has the same intensity as that pixel is not a sink. Non-sink pixels are changed to QuantumRange. The function operates on the blue channel only.
avgconcrings.c creates a representation of an image at multiple scales, invariant to rotation.
| Option | Description | |
|---|---|---|
| Short
form |
Long form | |
| s number | minScale number | Minimum (lowest) scale in range.
Default: 0.5. |
| t number | maxScale number | Maximum (highest) scale in range.
Default: 1.5. |
| n integer | nScales integer | Number of scales.
Default: 100. |
| m string | method string | Processing method. One of:
Slow Quick Default: Quick. |
| v | verbose | Write some text output to stderr. |
| version | Write version text to stdout. | |
There are nScales output rows. Each row represents the image at a single scale, with minScale at the top and maxScale at the bottom. The pixels within each row are the mean colours of a ring around the image centre. From the left, the first pixel is the image center, the second is the average of the ring at radius=1, the next at radius=2, and so on. Pixels at a radius beyond the image's inscribed circle are increasingly transparent.
The result width is the semi-diagonal of the input image. Where the scale at a row is greater than one, the row is truncated on the right.
At row number y, the scale is:
scale = minScale * (maxScale/minScale)y/(nScales-1)
Thus:
y/(nScales-1) = log (scale / minScale) / log (maxScale / minScale)
... where the log is to any base.
method Slow depolarises each resized image, and scales those to one dimension. By contrast, method Quick depolarizes and scales to one dimension only once, for the largest scale. For the smaller scales, it downsamples (with distort SRT) the 1D image.
For example, we try both methods on the same input, and show the time taken for each:
%IM7DEV%magick ^
toes.png ^
-process ^
'avgconcrings' ^
cu_acr.png
0 00:00:01 |
|
%IM7DEV%magick ^
toes.png ^
-process ^
'avgconcrings method Slow' ^
cu_acr2.png
0 00:00:03 |
|
Check the results:
%IM7DEV%magick compare -metric RMSE cu_acr.png cu_acr2.png NULL:
15354525 (0.003575004)
The results are practically the same.
Currently, the rings are always centred on the centre of the image. I may provide a facility for a different centre, if this seems desirable.
Average concentric circles are used when searching for a subimage at a range of scales, invariant to rotation. See what scale below.
whatrot.c takes two images, replacing them with the rotation of the second image that best matches the first image, assuming there is no translation.
| Option | Description | |
|---|---|---|
| Short
form |
Long form | |
| a | approxOnly | Use only the first (approximate) method. |
| t number | tolerance number | Plus-or-minus tolerance for second (golden section) method, in degrees.
Default: 0.01. |
| x | noRotate | Don't replace images with rotation. |
| f string | file string | Write verbose text to stderr or stdout.
Default: stderr. |
| v | verbose | Write some text output to stderr. |
| v2 | verbose2 | Write more text output to stderr. |
| version | Write version text to stdout. | |
The module uses two phases. The first is a radial filter: both images are unrolled and scaled to a single row. A WxH image is summarised as Nx1 pixels, where N is the semi-diagonal of WxH multiplied by pi (3.141...). This search is invariant to scale. The method is fairly fast, but approximate (low precision, and possibly slightly wrong).
The second phase starts at the result of the first and searches for a more precise result with successive approximations using the golden section method. This is slower, but precise to any required tolerance. (However, numerical instability may prevent high tolerance being achieved. In addition, low-resolution images cannot yield high-resolution transformations. A third problem is that the method is not invariant to rotation, so if rotation is present, the method will either fail or return the wrong answer.)
The module respects the -precision setting for the text output of floating-point results.
The module needs exactly two input images. By default, it replaces both input images with a rotated version of the second. (The output has appropriate canvas settings; use +repage if these are not wanted.) The noRotate option prevents this, so the image list will not be changed. This saves some time when we want only the text results.
|
Make two images, rotations of a common source. %IM7DEV%magick ^ toes.png ^ -rotate 34.567 +repage ^ toes.png ^ -gravity Center ^ -crop 100x100+0+0 +repage ^ cu_wr_%%d.png |
|
|
Find the rotation that makes the second match the first. %IM7DEV%magick ^ cu_wr_0.png ^ cu_wr_1.png ^ -process 'whatrot' ^ cu_wr_out1.png whatrot: ang=34.859226 angPorm=0.0095968936 angScore=0.0083337867 |
|
The RMSE score is from 0.0 (perfect match) to 1.0 (pixels are totally different). A score better than (less than) 0.01, ie 1%, for ordinary photographs, means there is probably no visible difference.
These images have a "radius" of about 70 pixels, so a one-pixel displacement at the corners represents a rotation of arctan(1/70) = 0.8°, so we can't expect precision much better than this.
The result is more or less correct. However, the true result is not within the angle stated plus or minus the stated error margin.
In practice, the second phase might be used to confirm the correctness of the result from the first phase. However, the first phase is invariant to scale, and the second isn't.
|
Find the rotation that makes the second, resized, match the first. %IM7DEV%magick ^ cu_wr_0.png ^ ( cu_wr_1.png -resize 300%% ) ^ -process 'whatrot' ^ cu_wr_out2.png whatrot: ang=33.957526 angPorm=0.040653094 angScore=0.12014631 |
|
The score is poor because of the resize, but the found angle is correct, more or less. [[The gss message announces a failure in the golden section search. The found angle comes from the first phase, and it is correct, within the stated tolerance.]]
|
Make two images, rotations and scales of a common source. %IM7DEV%magick ^ toes.png ^ -rotate 34.567 +repage ^ -resize 120%% +repage ^ toes.png ^ -gravity Center ^ -crop 100x100+0+0 +repage ^ cu_wrws_%%d.png |
|
|
Find the rotation that makes the second match the first. %IM7DEV%magick ^ cu_wrws_0.png ^ cu_wrws_1.png ^ -process 'whatrot' ^ cu_wrws_out1.png whatrot: ang=32.223571 angPorm=0.0095968936 angScore=0.065099396 |
|
FUTURE: For performance, a facility to limit the range of searches would be useful, eg search only between -10° and +10°.
FUTURE: The module can waste time seeking a higher precision than small images can support. For performance (and accuracy of the plus-or-minus result), it should stop early.
whatscale.c takes two images, replacing them with a scaled version of the second image that best matches the first image, assuming there is no translation. This is very similar to the what rotation module, but the first phase scales the unrolled image to a single column instead of a single row.
| Option | Description | |
|---|---|---|
| Short
form |
Long form | |
| a | approxOnly | Use only the first (approximate) phase. |
| x | noScale | Don't replace images with rescaled. |
| s0 number | minScale number | Minimum scale.
Default: 0.5. |
| s1 number | minScale number | Maximum scale.
Default: 2.0. |
| n integer | nScales integer | Number of scales.
Default: 100. |
| m string | method string | Method for creating concentric rings. One of:
Slow Quick Default: Quick. |
| f string | file string | Write verbose text to stderr or stdout.
Default: stderr. |
| v | verbose | Write some text output to stderr. |
| v2 | verbose2 | Write more text output to stderr. |
| version | Write version text to stdout. | |
The module uses two phases. The first is a circular filter: the images are unrolled and scaled to a single column. A WxH image is summarised as 1xN, where N is the semi-diagonal of WxH. This search is invariant to rotation. The method is fairly fast, but approximate (low precision, and possibly slightly wrong). It uses code that lies behind the average concentric rings module.
The second phase starts at the result of the first and searches for a more precise result with successive approximations using the golden section method. This is slower, but precise to any required tolerance. (However, numerical instability may prevent high tolerance being achieved. In addition, low-resolution images cannot yield high-resolution transformations.)
The module respects the -precision setting for the text output of floating-point results.
The module needs exactly two input images. By default, it replaces both input images with a resized version of the second, effectively using +distort SRT. (The output has appropriate canvas settings; use +repage if these are not wanted.) The noScale option prevents this, so the image list will not be changed. This saves some time when we want only the text results.
For example, we create a pair of images, where the first is the second enlarged by a factor of 1.35.
|
Make two images, scales of a common source. %IM7DEV%magick ^ toes.png ^ -define distort:viewport=100x100+30+50 ^ ( -clone 0 -distort SRT 80,100,1.35,0,80,100 ) ^ ( -clone 0 -distort SRT 80,100,1.00,0,80,100 ) ^ -delete 0 ^ +repage ^ cu_ws_%%d.png |
|
|
Find the scale that makes the second match the first. %IM7DEV%magick ^ cu_ws_0.png ^ cu_ws_1.png ^ -process 'whatscale' ^ cu_ws_out1.png whatscale: scale=1.348578 scalePorm=0.0048481956 scaleScore=0.0010225431 |
|
These images have a "radius" of about 70 pixels, so a scale that displaces by one pixel in the corners is a ratio of about 71/70=1.014, so we can't expect the scale to be much more accurate than about 1.4%.
FUTURE: It would be useful if the module worked at any scale, rather than having to specify the search limits.
|
Use two images created above, cu_wrws_0.png and cu_wrws_1.png: |
|
|
Find the scale that makes the second match the first. %IM7DEV%magick ^ cu_wrws_0.png ^ cu_wrws_1.png ^ -process 'whatscale' ^ cu_wrws_out3.png whatscale: GoldSectScale failed whatscale: scale=1.2909392 scalePorm=0.097711945 scaleScore=0.0086793421 |
|
Because there is both rotation and scaling, the golden section search failed. But the first (approximate) phase has found the correct solution, within the stated precision.
The approximate search explores within a range. For best accuracy and performance, provide a sensible range for the expected resulting scale. (The default range is 0.5 to 2.0.) If the found result is at exactly the top or bottom of the range, the module will assume the true result may be beyond that found result, and will repeat the search with a different range. It will do this up to ten times, which allows for a magnification or reduction up to 210 = 1024.
%IM7DEV%magick ^ rose: -resize 23%% ^ rose: ^ -process 'whatscale noScale' ^ NULL:
whatscale: scale=0.22975392 scalePorm=0.0049849308 scaleScore=0.08625291
%IM7DEV%magick ^ rose: -resize 567%% ^ rose: ^ -process 'whatscale noScale' ^ NULL:
whatscale: scale=5.6989377 scalePorm=0.004578015 scaleScore=0.020585027
The first phase of whatrot finds the approximate rotation that is invariant to scale, and the first phase of whatscale finds the approximate scale that is invariant to rotation. We can run both modules in a single command on the same pair of images to get approximations of both scale and rotation.
|
Use two images created above, cu_wrws_0.png and cu_wrws_1.png: |
|
|
Find the rotation and scale that makes the second match the first. %IM7DEV%magick ^ cu_wrws_0.png ^ cu_wrws_1.png ^ -process 'whatrot approxOnly noRotate' ^ -process 'whatscale approxOnly noScale' ^ NULL: whatrot: ang=33.901345 angPorm=0.80717489 angScore=0.016217008 whatscale: scale=1.2001027 scalePorm=0.090836478 scaleScore=0.0068274963 |
[No image] |
The score for the found scale is less than 0.01, suggesting it is correct, and more correct than the rotation which has a larger (worse) score.
The returned values can be used to rotate and scale the second image, and the modules used again to calculate a more accurate transformation.
for /F "usebackq tokens=1,2" %%A in (`%IM7DEV%magick ^ cu_wrws_0.png ^ cu_wrws_1.png ^ -process 'whatrot approxOnly noRotate' ^ -process 'whatscale approxOnly noScale' ^ NULL: 2^>^&1`) do set %%B set ang1=%ang% set scale1=%scale% echo ang1 is %ang1% echo scale1 is %scale1%
ang1 is 33.901345 scale1 is 1.2001027
for /F "usebackq tokens=1,2" %%A in (`%IM7DEV%magick ^
cu_wrws_0.png ^
^( cu_wrws_1.png ^
-virtual-pixel None ^
+distort SRT "%scale1%,%ang1%" +repage ^) ^
-process 'whatrot noRotate' ^
-process 'whatscale noScale' ^
NULL: 2^>^&1`) do set %%B
echo ang is %ang%
echo scale is %scale%
ang is 0.99626821 scale is 0.98549912
whatrotscale.c combines the functionality of what rotation and what scale. It takes two images, replacing them with a scaled and rotated version of the second image that best matches the first image, assuming there is no translation.
|
Use two images created above, cu_wrws_0.png and cu_wrws_1.png: |
|
|
Find the rotation and scale that makes the second match the first. %IM7DEV%magick ^ cu_wrws_0.png ^ cu_wrws_1.png ^ -process 'whatrotscale' ^ cu_wrs_out.png WhatRotScale scale=1.1893139 angle=34.72907 score=0.01082364 |
|
cols2mat.c takes two equal-sized images and makes a 6x6 colour matrix for use in the "-color-matrix" operation. It also replaces the input images with the first image transformed by the matrix. The matrix will contain up to 12 terms that depend on the inputs.
| Option | Description | |
|---|---|---|
| Short
form |
Long form | |
| m string | method string | Method for calculating the matrix, one of:
Cross include cross-channel multipliers (12 terms); NoCross exclude cross-channel multipliers (6 terms); NoCrossPoly polynomial without cross-channel (3*degreePoly+3 terms); GainOnly include only this-channel multipliers (3 terms). Default = Cross. |
| d integer | degreePoly integer | For method NoCrossPoly, degree of polynomial.
For example: degree 3 gives v' = a*v3 + b*v2 + c*v + d. Default: 2. |
| w number | weightLast number | Weight for last line of image.
For example: more than 1.0 (eg 10, 100) to give greater weight to last line, between 0.0 and 1.0 to give less weight. Default: 1.0. |
| wa | weightAlpha | Multiplies weight by product of the pixel alphas. |
| x | noTrans | Don't replace images with transformation. |
| f string | file string | Write text data (the colour matrix) to stderr or stdout.
Default = stderr. |
| v | verbose | Write some text output to stderr. |
| version | Write version information to stdout. | |
If a third input image is provided, this (instead of the first) image will be transformed.
When the calculated colour matrix is applied to an image, it may cause clipping.
The module respects the "-precision" setting.
For example, what colour matrix gives the closest approximation for the transformation from toes.png to toes_x.jpg?
%IM7DEV%magick ^ toes.png ^ toes_x.jpg ^ -process 'cols2mat f stdout' ^ cu_c2m.png |
|
c2matrix=1.8136873,-0.41789439,0.37468949,0,0,-0.36273306,-0.086006068,1.721299,-0.069416049,0,0,-0.23137974,0.17349911,-0.19861703,1.695083,0,0,-0.37885636,0,0,0,1,0,0,0,0,0,0,1,0,0,0,0,0,0,1 Warning: may clip Red shadows Warning: may clip Red highlights Warning: may clip Green shadows Warning: may clip Green highlights
How close is the result?
%IM7DEV%magick compare -metric RMSE toes_x.jpg cu_c2m.png NULL:
2.1077115e+08 (0.049073983)
One application is in normalising (aka "grading") grayscale or colours in photographs or video using colour checker charts. More details on this module and example uses are on that page.
oogbox.c adjusts pixel values to put them in the range 0 to 100%.
| Option | Description | |
|---|---|---|
| Short
form |
Long form | |
| m string | method string | Method for adjusting the shadows and highlights, one of:
Linear Power Default = Power. |
| fmn number | forceMin number | Force minimum value (override calculation).
Default: use calculated value. |
| fmx number | forceMax number | Force maximum value (override calculation).
Default: use calculated value. |
| l number | loLimit number | Low limit for shadow processing.
Default: 0.1. |
| h number | hiLimit number | High limit for highlight processing.
Default: 0.9. |
| lg number | loGradient number | Gradient for shadows.
Default: use calculated value. |
| hg number | hiGradient number | Gradient for highlights.
Default: use calculated value. |
| v | verbose | Write some text output to stderr. |
| version | Write version information to stdout. | |
For more details, see Putting OOG back in the box.
paintpatches.c blah...
| Option | Description | |
|---|---|---|
| Short
form |
Long form | |
| c string | colfrom string | Method blah, one of:
Avg Cent Gradient Palette Sample Default = ??. |
| ps string | patchShape string | Method blah, one of:
Rect Ellipse Min Default = Rect. |
| o number | opacity number | Blah.
0.0 to 1.0. Default: 1.0. |
| ms number | multSal number | Blah.
0.0 to 1.0. Default: 0.5. |
| wt number | wrngThresh number | Blah.
0.0 to 1.0. Default: 0.1. |
| ac number | adjLcComp number | Adjust lightness and contrast for comparisons.
?? Default: 0.0. |
| ap number | adjLcPat number | Adjust lightness and contrast of patches.
?? Default: 0.0. |
| fe number | feather number | Feather patch edges (blur sigma).
?? Default: 0.0. |
| x integer | maxIter integer | Maximum iterations.
?? Default: ?1000. |
| m | mebc | Do minimum error boundary cuts. |
| rc | rectCanv | Set rectangle to canvas. |
| hs | hotSpot | Hotspot only (for ps Min). |
| w filename | write filename | Write each iteration to a file.
Eg frame_%06d.png Default: iterations are not written to files. |
| d | debug | Write debugging information. |
| v | verbose | Write some text output to stderr. |
| v2 | verbose2 | Write more text output to stderr. |
| version | Write version information to stdout. | |
For more details, see Painting with patches.
plotrg.c makes a plot of red versus green values. This is a two-dimensional histogram. The ouput is always fully opaque.
A conventional histogram (see mkhisto, make histogram) has a one-dimensional array of buckets, and each pixel is allocated to a bucket according to some criteria such as intensity or the value in a single channel. This module creates a two-dimensional array of buckets and allocates each pixel to a bucket according the values in its red and green channels. The blue channel of the input is ignored.
| Option | Description | |
|---|---|---|
| Short
form |
Long form | |
| d integer | dim integer | Width and height of square output.
Default: 256. |
| ra | regardAlpha | Increment count by alpha, instead of one. |
| n | norm | Normalise the histogram so the maximum is QuantumRange. (The minimum may not be zero.) |
| ns | normSum | Normalise the histogram so the sum is QuantumRange. |
| co | calcOnly | Calculate only; don't replace the image. |
| f string | file string | Write text data (the colour matrix) to stderr or stdout.
Default = stderr. |
| v | verbose | Write some text output to stderr. |
| version | Write version information to stdout. | |
By default the output image contains counts of pixels on a scale of zero to QuantumRange. When we use Q32 these counts are too close to zero to be visible. The normalise option expands the range so the maximum count is 100%.
The verbose text output respects the current -precison setting.
For example:
%IM7DEV%magick ^
toes.png ^
-precision 15 ^
-process ^
'plotrg norm f stdout verbose' ^
cu_prg.png
|
|
totalCnt=62211 minR=0.0917677576867323 maxR=1 minG=0.061875333791104 maxG=0.914885175860227 minVal=0.061875333791104 maxVal=1 numOOG=0 minCnt=0 maxCnt=65
By default, each image in the input list is replaced by a square image.
The output square image represents increasing red from left to right, and increasing green from top to bottom. The top-right corner is red (with no green) and the bottom-left corner is green (with no red). The diagonal line from top-left to bottom-right represents grays from black to white. toes.png has low saturation, so the counts are clustered near this diagonal.
The output text shows the range of values in the input red and green channels and the minimum and maximum values found in either channel (on a nominal scale of 0.0 to 1.0), the number of pixels that were outside the range 0.0 to 1.0 (OOG = out of gamut), and the minimum and maximum counts in the buckets.
By default, each pixel will increment the contents of a bucket by one. The regardAlpha option will increment according to the pixel's alpha, on a scale of zero to one, so fully transparent pixels will not increment any bucket. If regardAlpha is used and the input is fully transparent, all buckets will be empty so the output will be entirely black.
%IM7DEV%magick ^
toes_holed.png ^
-precision 15 ^
-process ^
'plotrg regardAlpha norm f stdout verbose' ^
cu_prg2.png
|
|
totalCnt=23042 minR=0 maxR=0.886274509803922 minG=0 maxG=0.835294117647059 minVal=0 maxVal=0.886274509803922 numOOG=0 minCnt=0 maxCnt=42
Any pair of channels can be plotted by arranging them to be the first two of three channels.
|
Plot hue horizontally versus chroma vertically.
%IM7DEV%magick ^
toes.png ^
-colorspace HCL ^
-set colorspace sRGB ^
-process ^
'plotrg dim 256 norm' ^
cu_prg3.png
|
|
For reference, we make an image where the corresponding pixels have the given hue and chroma:
%IM7DEV%magick ^ -size 256x256 xc: ^ -sparse-color Bilinear ^ 0,0,#008,^ %%[fx:w-1],0,#f08,^ 0,%%[fx:h-1],#0f8,^ %%[fx:w-1],%%[fx:h-1],#ff8 ^ -set colorspace HCL ^ -colorspace sRGB ^ cu_prg3a.png |
|
So we can see pixels in toes.png have low chroma (though virtually no pixels at zero chroma), with clusters at red and green, and magenta to purple.
We can process the hue-chroma diagram in various ways, such as finding a smoothed maximum:
%IM7DEV%magick ^ cu_prg3.png ^ -fill White +opaque Black ^ -morphology dilate:-1 rectangle:1x2+0+1 ^ -morphology Convolve Blur:0x2 ^ cu_prg_3b.png |
|
This could be used as data for increasing the chroma of hues that have low maximums. (HCL is a primitive colorspace. In practice, LCH or Jzbzbz are better for this purpose.) For example, for each pixel we set the chroma to the maximum chroma of any pixel with the same hue:
%IM7DEV%magick ^
toes.png ^
-colorspace HCL ^
-separate ^
( -clone 0 ^
( cu_prg_3b.png -scale "256x1^!" ) ^
-clut ^
) ^
-swap 1,3 +delete ^
-combine ^
-set colorspace HCL -colorspace sRGB ^
cu_prg_3c.png
|
|
We can check the result by plotting the hue versus the chroma, as before, but changing non-black to white:
|
Plot hue horizontally versus chroma vertically.
%IM7DEV%magick ^
cu_prg_3c.png ^
-colorspace HCL ^
-set colorspace sRGB ^
-process ^
'plotrg dim 256 norm' ^
-fill White +opaque Black ^
cu_prg_3d.png
|
|
Apparently some hues have a number of chroma values. I suspect this is because we are using 256 buckets, a very small number. 4000 buckets would be more reasonable.
More examples:
|
Plot chroma horizontally versus lightness vertically. %IM7DEV%magick ^
toes.png ^
-colorspace HCL ^
-channel RGB ^
-separate -insert 0 -insert 0 -combine ^
+channel ^
-set colorspace sRGB ^
-process ^
'plotrg dim 256 norm' ^
cu_prg4.png
|
|
|
Convert to xyY colorspace;
%IM7DEV%magick ^
toes.png ^
-colorspace xyY ^
-set colorspace sRGB ^
-process ^
'plotrg dim 256 norm' ^
-flip ^
cu_prg5.png
|
|
|
As previous,
%IM7DEV%magick ^
toes.png ^
-colorspace xyY ^
-set colorspace sRGB ^
-process ^
'plotrg dim 256 norm' ^
-flip ^
-fill White +opaque Black ^
cu_prg6.png
|
|
The last two examples are chromaticity diagrams, and can be compared to CIE "horseshoe" diagrams. Most pixels are close to x=y=0.33, which is (speaking loosely and approximately) the location of zero saturation on chromaticity diagrams.
centroid.c calculates the centroid of the pixels, according to their intensities. This is where the centre of weight would be if intensities represented weights.
The centroid location (Cx,Cy) is defined by:
Cx = sum (x*Ixy) / sum(Ixy) Cy = sum (y*Ixy) / sum(Ixy)
... where Ixy is the intensity of the pixel at (x,y); x is its x-coordinate and y is its y-cooordinate.
The coordinates are written to stderr, in a line that starts with "centroid: ".
An image always has a centroid, with coordinates that are within the image, though the pixel at that location may be black.
If all the pixels have equal intensity, the centroid is at (w-1)/2,(h-1)/2.
The module respects -intensity and -precision settings.
%IM7DEV%magick ^ -size 10x10 ^ xc:black ^ xc:white ^ toes.png ^ -precision 9 ^ -process centroid ^ NULL:
centroid: 4.5,4.5 centroid: 4.5,4.5 centroid: 138.015153,111.419086
When the image is a single group of connected white pixels on a black background, "-connected-components" will give the same result (but writes to stdout):
%IMG7%magick ^ -size 10x10 xc:white ^ -define connected-components:verbose=true ^ -connected-components 4 ^ NULL:
Objects (id: bounding-box centroid area mean-color): 0: 10x10+0+0 4.5,4.5 100 srgb(255,255,255)
barymap.c takes an image encoded as xyY or XYZ or RGB, manipulates colour chromaticities (x and y components only), making the result image in xyY, XYZ or RGB space.
| Option | Description | |
|---|---|---|
| Short
form |
Long form | |
| ic string | inChannels string | Channels (colour model) of input.
String is one of RGB, xyY or XYZ. Default: from input image. |
| ip string | inPrim string | Input primaries.
String is one of: a named set of primaries, eg sRGB; or a comma-separated list of six numbers. Default: from input image. |
| iw string | inWP string | Input white point.
String is one of: a named white point, eg D60; a number suffixed with K or k, eg 5000K or 5000k; or a comma-separated list of two numbers, the xy coordinates. Default: taken from inPrim. |
| it number | inTransfer number | Gamma of input, or named transfer function.
Default: taken from inPrim. |
| xw string | XYZwp string | xy coordinates of XYZ white point.
String is one of: a named white point, eg D60; a number suffixed with K or k, eg 5000K or 5000k; or a comma-separated list of two numbers, the xy coordinates. Default: taken from inWP. |
| gn2 number | gain2 number | Gain factor for squared distance from WP.
Default: 0.0. |
| gn number | gain number | Gain factor for distance from WP.
Default: 1.0. |
| bs number | bias number | Bias added to distance from WP.
Default: 0.0. |
| pow number | power number | Power factor for distance from WP.
Default: 1.0. |
| am string | angleMult string | Input white point.
String is list of tuples separated by semi-colon ';'. Each tuple is three numbers separated by comma ','. The three numbers are: distance width factor Default: angleMult is not used. |
| st | skipTri | Skip triangle processing. |
| ign | ignoreWP | Ignore white point for triangulation. |
| clC | clampCartesian | Clamp cartesian coordinates. |
| clB | clampBarycentric | Clamp barycentric coordinates. |
| oc string | outChannels string | Channels (colour model) of output.
String is one of RGB, xyY or XYZ. Default: inChannels. |
| op string | outPrim string | Output primaries.
String is one of: a named set of primaries, eg sRGB; or a comma-separated list of six numbers. Default: from inPrim. |
| ow string | outWP string | Output white point.
String is one of: a named white point, eg D60; a number suffixed with K or k, eg 5000K or 5000k; or a comma-separated list of two numbers, the xy coordinates. Default: taken from outPrim. |
| ot number | outTransfer number | Gamma of output, or named transfer function.
Default: taken from outPrim. |
| ca string | chromAdap string | Name of Chromatic Adaptation Transform (CAT).
Default: Bradford. Use list cats to see available CATs |
| r16 | round16 | Round some fractions to 16 binary places. |
| list string | list string | Write list to stderr or stdout.
String is one of list, primaries, illuminants, transfers, cats, or wpnums. |
| f string | file string | Write text data to stderr or stdout.
Default = stderr. |
| v | verbose | Write some text output to stderr. |
| v2 | verbose2 | Write more text. |
| v9 | verbose9 | Write voluminous text (every pixel). |
| version | Write version information to stdout. | |
See Colours as barycentric coordinates for further details and examples.
polypix.c pixelates an image into polygons or other shapes. It needs two input images: the first contains colours thart are to be used; the second contains the shapes, identified by ("labelled with") shades of gray.
| Option | Description | |
|---|---|---|
| Short
form |
Long form | |
| m string | method string | Method, one of:
mean centroid Default = mean. |
| f string | file string | Write verbose text to stderr or stdout.
Default: stderr. |
| v | verbose | Write some text output. |
For example:
|
Input images |
|
|
Default method: mean %IM7DEV%magick ^ toes.png ppix_proto_c2.png ^ -process 'polypix' ^ cu_polypix1.png |
|
|
Centroid method %IM7DEV%magick ^ toes.png ppix_proto_c2.png ^ -process 'polypix method centroid' ^ cu_polypix2.png |
|
See Polygonal pixelation for details and examples.
From a sparse 1D or 2D image, polyreg.c calculates polynomials that pass through the non-transparent input pixels.
| Option | Description | |
|---|---|---|
| Short
form |
Long form | |
| ld integer | limitDegree integer | Limit polynomial degree.
Default: no limit. |
| lt integer | limitTerms integer | Limit number of terms.
If given, must be at least 1. Default: no limit. |
| ow integer | outputWidth integer | Width of generated output.
0 (zero) means same as width of input. Default: zero. |
| oh integer | outputHeight integer | Height of generated output.
0 (zero) means same as height of input. Default: zero. |
| co | calcOnly | Calculate only; don't replace the image. |
| sy string | syntax string | Syntax for verbose text, one of:
bash windows Default = bash. |
| f string | file string | Write verbose text to stderr or stdout.
Default: stderr. |
| v | verbose | Write some text output to stderr. |
| v2 | verbose2 | Write more text output to stderr. |
| version | Write version information to stdout. | |
For example:
%IM7DEV%magick ^
toes.png ^
-seed 1234 ^
-alpha opaque ^
-channel A ^
-seed 1234 -fx "rand()>0.99 ? 1 : 0" ^
+channel ^
-write cu_preg_sp.png ^
-process polyreg ^
cu_preg_sp_out.png
The image cu_preg_sp.png is mostly transparent, with a few opaque pixels. |
|
See Polynomial regression for details and source code.
This creates a Delaunay triangulation from the non-transparent pixels of an input image. This is useful for creating gradients, displacing images from sparse displacement maps, simplifying images, and other purposes.
See Delaunay triangulation for details and examples.
| Option | Description | |
|---|---|---|
| Short
form |
Long form | |
| ml integer | minLen integer | Remove points until no edges are shorter than this.
Default: no limit. |
| md integer | minDist integer | Remove points that are closer than this to opposite edge in triangles.
Default: no limit. |
| mr integer | maxRatio integer | Remove points where triangle ratio is more than this.
Default: no limit. |
| ph | protectHull | Don't remove points on the convex hull. |
| t | triangle | What to draw in triangles.
One of: transparent, label, mean, coords, barycentric. Default: barycentric. |
| o | outside | What to draw outside triangles.
One of: transparent, label, mean, coords, barycentric. Default: barycentric. |
| dp | drawPoints | Draw points on main image. |
| de | drawEdges | Draw edges on a second image. |
| dt | drawText | Write text on the edge image (if there is one), otherwise on the main image. |
| nc | noClampBary | Don't clamp barycentric coordinates outside convex hull to closest point on convex hull. |
| co | calcOnly | Calculate only; don't replace the image. |
| f string | file string | Write verbose text to stderr or stdout.
Default: stderr. |
| v | verbose | Write some text output to stderr. |
| v2 | verbose2 | Write more text output to stderr. |
| version | Write version information to stdout. | |
For example:
%IM7DEV%magick ^ toes.png ^ -seed 1234 ^ -process 'deltri minLen 20' ^ cu_deltri.png |
|
We can follow the same process given in Clut cookbook: Application: Matching histograms.
As inputs, we take toes.png and toes_x.jpg.
Make cumulative normalised histograms, and the inverse of both. We limit to 500 buckets only for the web display.
We need the "+" version of +write. If we used -write, in-memory pixels would be be reduced to 16 bits.%IM7DEV%magick ^
( toes.png ^
-process 'mkhisto verbose capnumbuckets 500 cumul norm' ^
-depth 32 +write cu_toes_chist.png ^
-process 'mkhisto verbose capnumbuckets 500 cumul norm' ^
-depth 32 +write cu_toes_chist_icl.png ^
+delete ^
) ^
( toes_x.jpg ^
-process 'mkhisto verbose capnumbuckets 500 cumul norm' ^
-depth 32 +write cu_toes_x_chist.png ^
-process 'mkhisto verbose capnumbuckets 500 cumul norm' ^
-depth 32 +write cu_toes_x_chist_icl.png ^
) ^
NULL:
mkhisto options: capnumbuckets 500 multiply 1 cumul norm verbose
mkhisto: Input image [toes.png] depth is 16
NumBuckets 500 BucketSize 8589934.6
counts: min_value 1, max_value 619
sum_red 62211, sum_green 62211, sum_blue 62211
Cumulating and normalising...
max_sum=62211 mult_fact=69038.712
mkhisto options: capnumbuckets 500 multiply 1 cumul norm verbose
mkhisto: Input image [toes.png] depth is 32
NumBuckets 500 BucketSize 8589934.6
counts: min_value 1, max_value 85
sum_red 500, sum_green 500, sum_blue 500
Cumulating and normalising...
max_sum=500 mult_fact=8589934.6
mkhisto options: capnumbuckets 500 multiply 1 cumul norm verbose
mkhisto: Input image [toes_x.jpg] depth is 8
NumBuckets 256 BucketSize 16777216
counts: min_value 1, max_value 1423
sum_red 62211, sum_green 62211, sum_blue 62211
Cumulating and normalising...
max_sum=62211 mult_fact=69038.712
mkhisto options: capnumbuckets 500 multiply 1 cumul norm verbose
mkhisto: Input image [toes_x.jpg] depth is 32
NumBuckets 500 BucketSize 8589934.6
counts: min_value 1, max_value 9
sum_red 256, sum_green 256, sum_blue 256
Cumulating and normalising...
max_sum=256 mult_fact=16777216
|
The cumulative histograms call %PICTBAT%graphLineCol ^ cu_toes_chist.png call %PICTBAT%graphLineCol ^ cu_toes_x_chist.png |
|
|
The inverses of the cumulative histograms call %PICTBAT%graphLineCol ^ cu_toes_chist_icl.png call %PICTBAT%graphLineCol ^ cu_toes_x_chist_icl.png |
|
The right-hand graphs, from toes_x.jpg, are stepped. This is because toes_x.jpg is an 8-bit file. The channels do not have 500 values.
From the CHs and their inverses, we calculate two transformation clut files. They are inverses of each other.
|
Apply the inverse of toes CH to toes_x CH ... %IM7DEV%magick ^ cu_toes_x_chist.png ^ cu_toes_chist_icl.png ^ -clut ^ cu_tx_it.png call %PICTBAT%graphLineCol cu_tx_it.png |
|
|
... and the inverse of toes_x CH to toes CH. %IM7DEV%magick ^ cu_toes_chist.png ^ cu_toes_x_chist_icl.png ^ -clut ^ cu_t_ixt.png call %PICTBAT%graphLineCol cu_t_ixt.png |
|
We have created two cluts. One will transform from toes.png to toes_x.jpg. The other will transform in the opposite direction.
|
Apply these to toes and toes_x. %IMG7%magick ^ toes.png ^ cu_t_ixt.png ^ -clut ^ cu_toes_txit.png %IMG7%magick ^ toes_x.jpg ^ cu_tx_it.png ^ -clut ^ cu_toes_x_txit.png |
|
Check the results:
%IM7DEV%magick compare -metric RMSE toes.png cu_toes_x_txit.png NULL: echo. %IM7DEV%magick compare -metric RMSE toes_x.jpg cu_toes_txit.png NULL:
31504745 (0.0073352701)20795968 (0.0048419385)
The results are similar to those at Clut cookbook: Application: Matching histograms.
We do it again, but not limiting the buckets to 500:
%IM7DEV%magick ^
( toes.png ^
-process 'mkhisto verbose cumul norm' ^
-depth 32 +write cu_w_toes_chist.png ^
-process 'mkhisto verbose cumul norm' ^
-depth 32 +write cu_w_toes_chist_icl.png ^
+delete ^
) ^
( toes_x.jpg ^
-process 'mkhisto verbose cumul norm' ^
-depth 32 +write cu_w_toes_x_chist.png ^
-process 'mkhisto verbose cumul norm' ^
-depth 32 +write cu_w_toes_x_chist_icl.png ^
) ^
NULL:
mkhisto options: capnumbuckets 65536 multiply 1 cumul norm verbose
mkhisto: Input image [toes.png] depth is 16
NumBuckets 65536 BucketSize 65536
counts: min_value 1, max_value 49
sum_red 62211, sum_green 62211, sum_blue 62211
Cumulating and normalising...
max_sum=62211 mult_fact=69038.712
mkhisto options: capnumbuckets 65536 multiply 1 cumul norm verbose
mkhisto: Input image [toes.png] depth is 32
NumBuckets 65536 BucketSize 65536
counts: min_value 1, max_value 6014
sum_red 65536, sum_green 65536, sum_blue 65536
Cumulating and normalising...
max_sum=65536 mult_fact=65536
mkhisto options: capnumbuckets 65536 multiply 1 cumul norm verbose
mkhisto: Input image [toes_x.jpg] depth is 8
NumBuckets 256 BucketSize 16777216
counts: min_value 1, max_value 1423
sum_red 62211, sum_green 62211, sum_blue 62211
Cumulating and normalising...
max_sum=62211 mult_fact=69038.712
mkhisto options: capnumbuckets 65536 multiply 1 cumul norm verbose
mkhisto: Input image [toes_x.jpg] depth is 32
NumBuckets 65536 BucketSize 65536
counts: min_value 1, max_value 2
sum_red 256, sum_green 256, sum_blue 256
Cumulating and normalising...
max_sum=256 mult_fact=16777216
Calculate the two transformation cluts:
%IMG7%magick ^ cu_w_toes_x_chist.png ^ cu_w_toes_chist_icl.png ^ -clut ^ cu_w_tx_it.png %IMG7%magick ^ cu_w_toes_chist.png ^ cu_w_toes_x_chist_icl.png ^ -clut ^ cu_w_t_ixt.png
We have created two cluts. One will transform from toes.png to toes_x.jpg. The other will transform in the opposite direction.
|
Apply these to toes and toes_x. %IMG7%magick ^ toes.png ^ cu_w_t_ixt.png ^ -clut ^ cu_w_toes_txit.png %IMG7%magick ^ toes_x.jpg ^ cu_w_tx_it.png ^ -clut ^ cu_w_toes_x_txit.png |
|
Check the results:
%IM7DEV%magick compare -metric RMSE toes.png cu_w_toes_x_txit.png NULL: echo. %IM7DEV%magick compare -metric RMSE toes_x.jpg cu_w_toes_txit.png NULL:
29408242 (0.00684714)17833878 (0.0041522734)
The result isn't much more accurate than the 500-bucket version.
Summarising the process to make the histogram of image_A match that of image_B:
magick ^ image_A ^ -process 'mkhisto cumul norm' ^ image_A_chist.png magick ^ image_B ^ -process 'mkhisto cumul norm' ^ -process 'mkhisto cumul norm' ^ image_B_chist_icl.png magick ^ image_A_chist.png ^ image_B_chist_icl.png ^ -clut ^ cu_w_t_ixt.png magick image_A cu_w_t_ixt.png -clut out_image
A bug in Q32 v6.8.9-6, (see forum report -clut in Q32), and probably in all previous Q32 versions, prevented me from combining these commands. That bug is now fixed, so we can do this:
magick ^
image_A ^
( -clone 0 ^
-process 'mkhisto cumul norm' ^
) ^
( image_B ^
-process 'mkhisto cumul norm' ^
-process 'mkhisto cumul norm' ^
) ^
( -clone 1-2 -clut ) ^
-delete 1-2 ^
-clut ^
out_image
This is equivalent to taking image_A and clutting it with its own cumulative histogram (which equalises it), and clutting that with the inverted cumulative histogram of image_B. For convenience, this is put into a script matchHisto.bat.
See Implementation of the Midway Image Equalization, Thierry Guillemot, Julie Delon, 2016.
We can change the histograms of two images to match a midway histogram. For "midway", we use the harmonic mean of the two normalised cumulative histograms. The harmonic mean is defined as the reciprocal of the mean of the reciprocals of the inputs.
H = 1/((1/v1 + 1/v2)/2)
Or:
H = 2.v1.v2
v2 + v1
Using the normalised cumulative histograms of toes.png and toes_x.jpg (resizing them to be the same size):
|
Find the harmonic mean: %IMG7%magick ^ ( cu_toes_chist.png -resize "500x1^!" +write mpr:ONE ) ^ ( cu_toes_x_chist.png -resize "500x1^!" +write mpr:TWO ) ^ -compose Mathematics -define compose:args=0,0.5,0.5,0 -composite ^ ( mpr:ONE mpr:TWO -compose Multiply -composite ) ^ -compose DivideDst -composite ^ cu_midway.png call %PICTBAT%graphLineCol cu_midway.png |
|
We can apply this midway histogram to toes.png and toes_x.jpg by clutting each one with a clut of its own histogram clutted with the inverse of the midway histogram.
|
Give toes this midway histogram: %IM7DEV%magick ^
toes.png ^
( cl_toes_chist.png ^
( cu_midway.png -process invclut ) ^
-clut ^
) ^
-clut ^
cu_toes_mw.png
|
|
|
Give toes_x this midway histogram: %IM7DEV%magick ^
toes_x.jpg ^
( cl_toes_x_chist.png ^
( cu_midway.png -process invclut ) ^
-clut ^
) ^
-clut ^
cu_toes_x_mw.png
|
|
The results are almost exactly the same.
Clutting an image with its 3-channel cumulative histogram (CH) will equalise the histogram of the image. As three different cluts are applied to the channels, this shifts the hue.
|
Equalise with the built-in method,
%IMG7%magick ^ toes.png ^ -channel RGB ^ -equalize ^ cu_eq.png |
|
|
Equalise by using the cumulative histogram. %IMG7%magick ^ toes.png ^ cu_toes_chist.png ^ -clut ^ cu_cheq.png |
|
|
De-equalise by using the inverse cumulative histogram. %IMG7%magick ^ cu_eq.png ^ cu_toes_chist_icl.png ^ -clut ^ cu_cheq_i.png |
|
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,
%IMG7%magick ^ toes.png ^ -equalize ^ cu_eq_sync.png |
|
|
Equalise by using the cumulative histogram. %IM7DEV%magick ^ toes.png ^ -modulate 100,0,100 ^ -process 'mkhisto cumul norm' ^ cu_chsync.png %IMG7%magick ^ toes.png ^ cu_chsync.png ^ -clut ^ cu_cheq_sync.png |
|
As we have the histogram data in an image format, we can use ordinary IM tools to limit the contrast of the histogram equalisation (Contrast-Limited Histogram Equalisation, CLHE). Here is the normalised but not cumulative histogram of greyscaled toes.png. As usual, we capnumbuckets for the web dispay.
|
Equalise by using the cumulative histogram. %IM7DEV%magick ^ toes.png ^ -modulate 100,0,100 ^ -process 'mkhisto capnumbuckets 500 norm' ^ cu_gch.png call %PICTBAT%graphLineCol ^ cu_gch.png . . 0 cu_gch_glc.png |
|
We could cumulate and normalise this, to create the clut that can be used for equalisation ...
|
Equalise by using the cumulative histogram. %IM7DEV%magick ^ cu_gch.png ^ -process 'cumulhisto norm' ^ cu_gchc.png call %PICTBAT%graphLineCol ^ cu_gchc.png . . 0 cu_gchc_glc.png %IMG7%magick ^ toes.png ^ cu_gchc.png ^ -clut ^ cu_gchc_cl.png |
|
... but the resulting contrast may be too high, exaggerating noise in areas of flat colour (not a problem in toes.png). We could mix the result with the original, or we can cap the value of the non-cumulative histogram, which limits the slope of the cumulative histogram. (This limits the overall contrast, rather than a local contrast. A related technique is used for that: CLAHE (A=Adaptive). See below Contrast Limited Adaptive Histogram Equalisation.)
|
Equalise by using the capped cumulative histogram. %IM7DEV%magick ^ cu_gch.png ^ -channel RGB ^ -evaluate Min 70%% ^ +channel ^ +write cu_gchc_cap.png ^ -process 'cumulhisto norm' ^ cu_gchc_clhe.png call %PICTBAT%graphLineCol ^ cu_gchc_cap.png . . 0 cu_gchc_cap_glc.png call %PICTBAT%graphLineCol ^ cu_gchc_clhe.png . . 0 cu_gchc_clhe_glc.png %IMG7%magick ^ toes.png ^ cu_gchc_clhe.png ^ -clut ^ cu_gchc_clhe_cl.png |
|
Rather than capping at a particular percentage of the maximum, it is generally better to base the cap on the mean and standard deviation, eg mean + 2 * SD.
for /F "usebackq" %%L in (`%IMG7%magick ^ cu_gch.png ^ -format "statMEAN=%%[fx:mean]\nstatSD=%%[fx:standard_deviation]\nhistcap=%%[fx:(mean+2*standard_deviation)*100]" ^ info:`) do set %%L echo statMEAN=%statMEAN% echo statSD=%statSD% echo histcap=%histcap%
statMEAN=0.254442 statSD=0.264227 histcap=78.2896
So we could use the value of %histcap% instead of 70% above.
It is said (see Wikipedia: Contrast Limited AHE) that the counts removed from the histogram should be redistributed among all the buckets, before cumulating. The mean of the difference gives us this, multiplied by 100 to get a percentage.
for /F "usebackq" %%L in (`%IMG7%magick ^ cu_gch.png ^ cu_gchc_cap.png ^ -compose MinusSrc -composite ^ -format "MeanDiffPC=%%[fx:mean*100]" ^ info:`) do set %%L echo MeanDiffPC=%MeanDiffPC%
MeanDiffPC=1.07066
|
Equalise by using the capped cumulative histogram, redistributing the excess. %IM7DEV%magick ^ cu_gch.png ^ -channel RGB ^ -evaluate Min 70%% ^ +channel ^ -evaluate Add %MeanDiffPC%%% ^ +write cu_gchc_capred.png ^ -process 'cumulhisto norm' ^ cu_gchc_clhe_red.png call %PICTBAT%graphLineCol ^ cu_gchc_capred.png . . 0 cu_gchc_capred_glc.png call %PICTBAT%graphLineCol ^ cu_gchc_clhe_red.png . . 0 cu_gchc_clhe_red_glc.png %IMG7%magick ^ toes.png ^ cu_gchc_clhe_red.png ^ -clut ^ cu_gchc_clhe_red_cl.png |
|
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 |
|
The script is explained more fully in [Adaptive] Contrast-limited equalisation and also demonstrated in Adding zing to photographs: equalising the histogram.
From a histogram H we can derive an image I that contains the same distribution of channel values as the source image S of which H is the histogram. An almost infinite number of images will make the same histogram. This method makes one of them.
To do this, we apply the general method above of histogram-matching, where the source image is a grayscale gradient. The process can be simplified by noting that:
So the process simplifies to: take the histogram H, cumulate it to make a cumulative histogram, invert this, and the result is the required image I. (See also Clut cookbook: Histogram algebra.)
For example, suppose we want an image that has the same histogram as toes.png. We make a cumulative histogram, and from that we make another cumulative histogram which inverts it. The result should have the same histogram as toes.png.
We also make a 500-bucket histogram of a clone of toes.png, purely for display on this web page.
%IM7DEV%magick ^
toes.png ^
( +clone ^
-process 'mkhisto capnumbuckets 500 norm' ^
+write cu_dhh.%IFFEXT% ^
-delete 0 ^
) ^
-process 'mkhisto cumul norm' ^
-process 'mkhisto cumul norm' ^
cu_dehist.%IFFEXT%
call %PICTBAT%graphLineCol ^
cu_dhh.%IFFEXT% . . 0 cu_dhh_glc.png
|
|
|
Show the histogram of cu_dehist.%IFFEXT%. %IM7DEV%magick ^ cu_dehist.%IFFEXT% ^ -process 'mkhisto capnumbuckets 500 norm' ^ cu_dehist_h.%IFFEXT% call %PICTBAT%graphLineCol ^ cu_dehist_h.%IFFEXT% . . 0 cu_dehist_h_glc.png |
|
The histograms are the same. We have created a small image with the same histogram of what could be a very large image. This small image can then be used as a proxy for the large image, for some purposes.
From toes.png, we did "-process 'mkhisto cumul norm'" twice. The first time was to make a cumulative histogram. The second time was to invert it. For the second, we could have used "-process 'invclut'".
If the input had been a cumulative histogram, we wouldn't have needed the first mkhisto.
If the input had been a non-cumulative histogram, the first operation could have been "-process 'cumulhisto norm verbose'".
The created small image is size 65536x1. If we resize it, we can see it:
%IM7DEV%magick ^ cu_dehist.%IFFEXT% ^ -resize "500x100^!" ^ cu_dehist_web.png |
|
Resizing loses data. Instead, we can chop it into lines and append these, after flopping to get the lighter tones at top-left:
%IM7DEV%magick ^ cu_dehist.%IFFEXT% ^ -flop ^ -crop 256x1 +repage ^ -append +repage ^ cu_dehist_lines.png |
|
The distribution of channel values in this image is the same as in the source image. This doesn't mean the colours are the same. In an ordinary photograph, without heavily saturated colours, they will be similar. But we can't expect them to be the same.
For comparison, here is toes.png, resized to be the same size, with the pixels sorted and arranged in the same way.
%IM7DEV%magick ^ toes.png ^ -crop x1 +repage ^ +append +repage ^ -process sortpixels ^ -flop ^ -resize "65536x1^!" ^ -crop 256x1 +repage ^ -append +repage ^ cu_toes_lines.png |
|
Process modules could be written for many purposes.
Like -unique-colors but with alpha channel set to the (normalised) count of the pixels at that colour. Also allow sorting on alpha channel.
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.
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).
Make one pixel that is sum of all pixels. Or image 1xN that sums each row, equivalent to -scale 1x{height} -evaluate Multiply {width}.
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.
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.
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.
See my Innertrim page.
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.
Find the gamma that maximises the standard deviation of an image.
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.
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').)
This would shell to a specified command (or script).
Various techniques for image segmentation could be written.
A skeleton program, or test harness, could be written.
Probably based on Wikipedia: Fisher-Yates shuffle.
diff and patch
To statically link process modules, we edit module.c and static.c. To ensure we can easily patch future versions of IM source code, we can use diff and patch.
set IMCYGDEVDIR=/home/Alan/imdevsrc diff -u --strip-trailing-cr %IMCYGDEVDIR%/../ImageMagick-6.8.9-6/magick/static.c %IMCYGDEVDIR%/magick/static.c >ci_static.diff
--- /home/Alan/imdevsrc/../ImageMagick-6.8.9-6/magick/static.c 2014-04-26 19:21:31.000000000 +0100
+++ /home/Alan/imdevsrc/magick/static.c 2014-08-23 18:19:33.789354600 +0100
@@ -108,7 +108,25 @@
#else
{
extern size_t
- analyzeImage(Image **,const int,char **,ExceptionInfo *);
+ analyzeImage(Image **,const int,char **,ExceptionInfo *),
+ hellowImage(Image **,const int,const char **,ExceptionInfo *),
+ echostuffImage(Image **,const int,const char **,ExceptionInfo *),
+ addendImage(Image **,const int,const char **,ExceptionInfo *),
+ replacelastImage(Image **,const int,const char **,ExceptionInfo *),
+ replacefirstImage(Image **,const int,const char **,ExceptionInfo *),
+ replaceallImage(Image **,const int,const char **,ExceptionInfo *),
+ replaceeachImage(Image **,const int,const char **,ExceptionInfo *),
+ grad2Image(Image **,const int,const char **,ExceptionInfo *),
+ drawcircImage(Image **,const int,const char **,ExceptionInfo *),
+ sortpixelsImage(Image **,const int,char **,ExceptionInfo *),
+ sortlinesImage(Image **,const int,char **,ExceptionInfo *),
+ appendlinesImage(Image **,const int,char **,ExceptionInfo *),
+ mkhistoImage(Image **,const int,char **,ExceptionInfo *),
+ cumulhistoImage(Image **,const int,const char **,ExceptionInfo *),
+ geodistImage(Image **,const int,char **,ExceptionInfo *),
+ invclutImage(Image **,const int,char **,ExceptionInfo *),
+ rect2eqfishImage(Image **,const int,const char **,ExceptionInfo *),
+ eqfish2rectImage(Image **,const int,const char **,ExceptionInfo *);
ImageFilterHandler
*image_filter;
@@ -116,6 +134,43 @@
image_filter=(ImageFilterHandler *) NULL;
if (LocaleCompare("analyze",tag) == 0)
image_filter=(ImageFilterHandler *) analyzeImage;
+ else if (LocaleCompare("hellow",tag) == 0)
+ image_filter=(ImageFilterHandler *) hellowImage;
+ else if (LocaleCompare("echostuff",tag) == 0)
+ image_filter=(ImageFilterHandler *) echostuffImage;
+ else if (LocaleCompare("addend",tag) == 0)
+ image_filter=(ImageFilterHandler *) addendImage;
+ else if (LocaleCompare("replacelast",tag) == 0)
+ image_filter=(ImageFilterHandler *) replacelastImage;
+ else if (LocaleCompare("replacefirst",tag) == 0)
+ image_filter=(ImageFilterHandler *) replacefirstImage;
+ else if (LocaleCompare("replaceall",tag) == 0)
+ image_filter=(ImageFilterHandler *) replaceallImage;
+ else if (LocaleCompare("replaceeach",tag) == 0)
+ image_filter=(ImageFilterHandler *) replaceeachImage;
+ else if (LocaleCompare("grad2",tag) == 0)
+ image_filter=(ImageFilterHandler *) grad2Image;
+ else if (LocaleCompare("drawcirc",tag) == 0)
+ image_filter=(ImageFilterHandler *) drawcircImage;
+ else if (LocaleCompare("sortpixels",tag) == 0)
+ image_filter=(ImageFilterHandler *) sortpixelsImage;
+ else if (LocaleCompare("sortlines",tag) == 0)
+ image_filter=(ImageFilterHandler *) sortlinesImage;
+ else if (LocaleCompare("applines",tag) == 0)
+ image_filter=(ImageFilterHandler *) appendlinesImage;
+ else if (LocaleCompare("mkhisto",tag) == 0)
+ image_filter=(ImageFilterHandler *) mkhistoImage;
+ else if (LocaleCompare("cumulhisto",tag) == 0)
+ image_filter=(ImageFilterHandler *) cumulhistoImage;
+ else if (LocaleCompare("geodist",tag) == 0)
+ image_filter=(ImageFilterHandler *) geodistImage;
+ else if (LocaleCompare("invclut",tag) == 0)
+ image_filter=(ImageFilterHandler *) invclutImage;
+ else if (LocaleCompare("rect2eqfish",tag) == 0)
+ image_filter=(ImageFilterHandler *) rect2eqfishImage;
+ else if (LocaleCompare("eqfish2rect",tag) == 0)
+ image_filter=(ImageFilterHandler *) eqfish2rect;
+
if (image_filter == (ImageFilterHandler *) NULL)
(void) ThrowMagickException(exception,GetMagickModule(),ModuleError,
"UnableToLoadModule","`%s'",tag);
diff -u --strip-trailing-cr %IMCYGDEVDIR%/../ImageMagick-6.8.9-6/magick/module.c %IMCYGDEVDIR%/magick/module.c >ci_module.diff cmd /c exit /B 0
--- /home/Alan/imdevsrc/../ImageMagick-6.8.9-6/magick/module.c 2014-04-04 14:59:23.000000000 +0100
+++ /home/Alan/imdevsrc/magick/module.c 2014-08-23 18:19:25.585751900 +0100
@@ -1616,7 +1616,25 @@
#if !defined(MAGICKCORE_BUILD_MODULES)
extern size_t
- analyzeImage(Image **,const int,const char **,ExceptionInfo *);
+ analyzeImage(Image **,const int,const char **,ExceptionInfo *),
+ hellowImage(Image **,const int,const char **,ExceptionInfo *),
+ echostuffImage(Image **,const int,const char **,ExceptionInfo *),
+ addendImage(Image **,const int,const char **,ExceptionInfo *),
+ replacelastImage(Image **,const int,const char **,ExceptionInfo *),
+ replacefirstImage(Image **,const int,const char **,ExceptionInfo *),
+ replaceallImage(Image **,const int,const char **,ExceptionInfo *),
+ replaceeachImage(Image **,const int,const char **,ExceptionInfo *),
+ grad2Image(Image **,const int,const char **,ExceptionInfo *),
+ drawcircImage(Image **,const int,const char **,ExceptionInfo *),
+ sortpixelsImage(Image **,const int,const char **,ExceptionInfo *),
+ sortlinesImage(Image **,const int,const char **,ExceptionInfo *),
+ appendlinesImage(Image **,const int,const char **,ExceptionInfo *),
+ mkhistoImage(Image **,const int,const char **,ExceptionInfo *),
+ cumulhistoImage(Image **,const int,const char **,ExceptionInfo *),
+ geodistImage(Image **,const int,const char **,ExceptionInfo *),
+ invclutImage(Image **,const int,const char **,ExceptionInfo *),
+ rect2eqfishImage(Image **,const int,const char **,ExceptionInfo *),
+ eqfish2rectImage(Image **,const int,const char **,ExceptionInfo *);
#endif
MagickExport MagickBooleanType ListModuleInfo(FILE *magick_unused(file),
@@ -1658,6 +1676,43 @@
image_filter=(ImageFilterHandler *) NULL;
if (LocaleCompare("analyze",tag) == 0)
image_filter=(ImageFilterHandler *) analyzeImage;
+ else if (LocaleCompare("hellow",tag) == 0)
+ image_filter=(ImageFilterHandler *) hellowImage;
+ else if (LocaleCompare("echostuff",tag) == 0)
+ image_filter=(ImageFilterHandler *) echostuffImage;
+ else if (LocaleCompare("addend",tag) == 0)
+ image_filter=(ImageFilterHandler *) addendImage;
+ else if (LocaleCompare("replacelast",tag) == 0)
+ image_filter=(ImageFilterHandler *) replacelastImage;
+ else if (LocaleCompare("replacefirst",tag) == 0)
+ image_filter=(ImageFilterHandler *) replacefirstImage;
+ else if (LocaleCompare("replaceall",tag) == 0)
+ image_filter=(ImageFilterHandler *) replaceallImage;
+ else if (LocaleCompare("replaceeach",tag) == 0)
+ image_filter=(ImageFilterHandler *) replaceeachImage;
+ else if (LocaleCompare("grad2",tag) == 0)
+ image_filter=(ImageFilterHandler *) grad2Image;
+ else if (LocaleCompare("drawcirc",tag) == 0)
+ image_filter=(ImageFilterHandler *) drawcircImage;
+ else if (LocaleCompare("sortpixels",tag) == 0)
+ image_filter=(ImageFilterHandler *) sortpixelsImage;
+ else if (LocaleCompare("sortlines",tag) == 0)
+ image_filter=(ImageFilterHandler *) sortlinesImage;
+ else if (LocaleCompare("applines",tag) == 0)
+ image_filter=(ImageFilterHandler *) appendlinesImage;
+ else if (LocaleCompare("mkhisto",tag) == 0)
+ image_filter=(ImageFilterHandler *) mkhistoImage;
+ else if (LocaleCompare("cumulhisto",tag) == 0)
+ image_filter=(ImageFilterHandler *) cumulhistoImage;
+ else if (LocaleCompare("geodist",tag) == 0)
+ image_filter=(ImageFilterHandler *) geodistImage;
+ else if (LocaleCompare("invclut",tag) == 0)
+ image_filter=(ImageFilterHandler *) invclutImage;
+ else if (LocaleCompare("rect2eqfish",tag) == 0)
+ image_filter=(ImageFilterHandler *) rect2eqfishImage;
+ else if (LocaleCompare("eqfish2rect",tag) == 0)
+ image_filter=(ImageFilterHandler *) eqfish2rectImage;
+
if (image_filter == (ImageFilterHandler *) NULL)
(void) ThrowMagickException(exception,GetMagickModule(),ModuleError,
"UnableToLoadModule","`%s'",tag);
[patch: Not yet written.]
Process modules are powerful tools that can be easily inserted into magick commands. Modules are fairly easy to write.
I expect to add further modules over time.
For convenience, these .bat scripts and .c code and makefile.am are also available in a single zip file. See Zipped BAT files.
By including this file, we can often write code that is independent of v6 or v7.
This file is in both directories %IMSRC% and %IM7SRC%.
// First written: 21-May-2017
//
// Last updated:
// 21-July-2017 Added IS_ALPHA_CH
// 9-September-2017 Added WRITEIMAGE, COMPOSITE, COPY_OPACITY, CLAMP
// 3-April-2018 Added ACQUIRE_KERNEL and MAGICK_THREADS
// 14-March-2020 Added NumColChannels()
// If we haven't defined the version, do this.
#ifndef IMV6OR7
#include "imv6or7.h"
#ifndef IMV6OR7
#error IMV6OR7 not defined
#endif
#if IMV6OR7==6
#include "magick/MagickCore.h"
#define MAGICK_CORE_SIG MagickSignature
#define VIEW_PIX_PTR PixelPacket
#define PIX_INFO MagickPixelPacket
#define GET_PIX_INFO(image,p) GetMagickPixelPacket(image, p)
#define GET_PIXEL_RED(image,p) GetPixelRed(p)
#define GET_PIXEL_GREEN(image,p) GetPixelGreen(p)
#define GET_PIXEL_BLUE(image,p) GetPixelBlue(p)
#define GET_PIXEL_ALPHA(image,p) GetPixelAlpha(p)
#define SET_PIXEL_RED(image,val,p) SetPixelRed(p,val)
#define SET_PIXEL_GREEN(image,val,p) SetPixelGreen(p,val)
#define SET_PIXEL_BLUE(image,val,p) SetPixelBlue(p,val)
#define SET_PIXEL_ALPHA(image,val,p) SetPixelAlpha(p,val)
#define IS_ALPHA_CH(image) ((image)->matte==MagickTrue)
#define LAYER_METHOD ImageLayerMethod
#define GET_PIX_INF_ALPHA(p) (QuantumRange - (p)->opacity)
#define SET_PIX_INF_ALPHA(p,v) (p)->opacity=QuantumRange - (v)
#define WRITEIMAGE(ii,image,excep) WriteImage (ii, image)
#define COMPOSITE(dst,op,src,x,y,exc) \
CompositeImage(dst, op, src, x,y)
#define COPY_OPACITY CopyOpacityCompositeOp
#define CLAMP(img,exc) ClampImage(img)
#define RESIZEIMG(img,wi,ht,exc) ResizeImage(img,wi,ht,UndefinedFilter,1.0,exc)
#define ACQUIRE_KERNEL(str,exc) AcquireKernelInfo(str)
#define VIRT_NONE(img,exc) \
SetImageVirtualPixelMethod (img, TransparentVirtualPixelMethod);\
SetImageAlphaChannel (img, ActivateAlphaChannel);
#define MAGICK_THREADS(source,destination,chunk,expression) \
num_threads((expression) == 0 ? 1 : \
(((chunk) > (32*GetMagickResourceLimit(ThreadResource))) && \
(GetImagePixelCacheType(source) != DiskCache)) && \
(GetImagePixelCacheType(destination) != DiskCache) ? \
GetMagickResourceLimit(ThreadResource) : \
GetMagickResourceLimit(ThreadResource) < 2 ? 1 : 2)
static size_t inline Inc_ViewPixPtr (const Image * image)
{
return 1;
}
static MagickBooleanType inline SetNoPalette (
Image * image, ExceptionInfo *exception)
{
return SetImageStorageClass (image, DirectClass);
}
static void inline SetAllBlack (Image * image, ExceptionInfo *exception)
{
MagickPixelPacket
mppBlack;
GetMagickPixelPacket (image, &mppBlack);
SetImageColor(image, &mppBlack);
}
static void inline SetAllOneCol (Image * image, char * sCol, ExceptionInfo *exception)
{
PIX_INFO mpp;
GetMagickPixelPacket (image, &mpp);
QueryMagickColor (sCol, &mpp, exception);
SetImageColor (image, &mpp);
}
static MagickBooleanType inline NumColChannels (const Image * image)
{
return 3;
}
static MagickBooleanType inline Has3Channels (const Image * image)
{
return MagickTrue;
}
static MagickBooleanType inline Ensure3Channels (
Image * image,
ExceptionInfo *exception)
{
return MagickTrue;
}
#elif IMV6OR7==7
#include "MagickCore/MagickCore.h"
//#include "MagickCore/cache-private.h" // for SetPixelCacheVirtualMethod()
#define MAGICK_CORE_SIG MagickCoreSignature
#define VIEW_PIX_PTR Quantum
#define PIX_INFO PixelInfo
#define GET_PIX_INFO(image,p) GetPixelInfo(image, p)
#define GET_PIXEL_RED(image,p) GetPixelRed(image,p)
#define GET_PIXEL_GREEN(image,p) GetPixelGreen(image,p)
#define GET_PIXEL_BLUE(image,p) GetPixelBlue(image,p)
#define GET_PIXEL_ALPHA(image,p) GetPixelAlpha(image,p)
#define SET_PIXEL_RED(image,val,p) SetPixelRed(image,val,p)
#define SET_PIXEL_GREEN(image,val,p) SetPixelGreen(image,val,p)
#define SET_PIXEL_BLUE(image,val,p) SetPixelBlue(image,val,p)
#define SET_PIXEL_ALPHA(image,val,p) SetPixelAlpha(image,val,p)
#define IS_ALPHA_CH(image) ((image)->alpha_trait!=UndefinedPixelTrait)
#define LAYER_METHOD LayerMethod
#define GET_PIX_INF_ALPHA(p) ((p)->alpha)
#define SET_PIX_INF_ALPHA(p,v) (p)->alpha=(v)
#define WRITEIMAGE(ii,image,excep) WriteImage (ii, image, excep)
#define COMPOSITE(dst,op,src,x,y,exc) \
CompositeImage(dst, src, op, MagickFalse, x,y, exc)
#define COPY_OPACITY CopyAlphaCompositeOp
#define CLAMP(img,exc) ClampImage(img,exc)
#define RESIZEIMG(img,wi,ht,exc) ResizeImage(img,wi,ht,UndefinedFilter,exc)
#define ACQUIRE_KERNEL(str,exc) AcquireKernelInfo(str,exc)
#define VIRT_NONE(img,exc) \
SetImageVirtualPixelMethod (img, TransparentVirtualPixelMethod, exc);\
SetImageAlphaChannel (img, ActivateAlphaChannel, exc);
#define MAGICK_THREADS(source,destination,chunk,multithreaded) \
num_threads((multithreaded) == 0 ? 1 : \
((GetImagePixelCacheType(source) != MemoryCache) && \
(GetImagePixelCacheType(source) != MapCache)) || \
((GetImagePixelCacheType(destination) != MemoryCache) && \
(GetImagePixelCacheType(destination) != MapCache)) ? \
MagickMax(MagickMin(GetMagickResourceLimit(ThreadResource),2),1) : \
MagickMax(MagickMin((ssize_t) GetMagickResourceLimit(ThreadResource),(ssize_t) (chunk)/64),1));
static size_t inline Inc_ViewPixPtr (const Image * image)
{
return GetPixelChannels (image);
}
static MagickBooleanType inline SetNoPalette (
Image * image, ExceptionInfo *exception)
{
return SetImageStorageClass (image, DirectClass, exception);
}
static void inline SetAllBlack (Image * image, ExceptionInfo *exception)
{
PixelInfo
mppBlack;
GetPixelInfo (image, &mppBlack);
SetImageColor (image, &mppBlack, exception);
}
static void inline SetAllOneCol (Image * image, char * sCol, ExceptionInfo *exception)
{
PIX_INFO mpp;
GetPixelInfo (image, &mpp);
QueryColorCompliance (sCol, AllCompliance, &mpp, exception);
SetImageColor (image, &mpp, exception);
}
static int inline NumColChannels (const Image * image)
{
// Following possibly naff if IM ever handles more than 3 colour channels.
if (GetPixelChannels (image) >= 3) return 3;
return GetPixelChannels (image);
}
static MagickBooleanType inline Has3Channels (const Image * image)
{
return (GetPixelChannels (image) >= 3);
}
static MagickBooleanType inline Ensure3Channels (
Image * image,
ExceptionInfo *exception)
// Returns MagickFalse if unable to perform.
{
if (GetPixelChannels (image) >= 3) return MagickTrue;
// Following is naff for linear grayscale images; should not be sRGB.
return TransformImageColorspace (image, sRGBColorspace, exception);
}
#else
#error IMV6OR7 defined but not valid
#endif
#endif // ifndef IMV6OR7
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.
#include <stdio.h>
#include <stdlib.h>
#include "MagickCore/magick-config.h"
#include "MagickCore/studio.h"
#include "vsn_defines.h"
#include "MagickCore/opencl-private.h"
ModuleExport size_t hellowImage(Image **images,const int argc,
const char **argv,ExceptionInfo *exception)
{
int
precision;
// Prevent compilers warning about unused parameters:
(void) images;
(void) argc;
(void) argv;
(void) exception;
precision = GetMagickPrecision();
printf ("Greetings to wizards of the ImageMagick world!\n\n");
printf ("MAGICKCORE_QUANTUM_DEPTH %.*g\n",
precision, (double)MAGICKCORE_QUANTUM_DEPTH);
printf ("QuantumRange %.*g\n",
precision, (double)QuantumRange);
printf (" V/V-1 %.*g\n",
precision, (double)QuantumRange/(double)QuantumRange-1.0);
printf ("QuantumScale %.*g\n", precision, QuantumScale);
printf ("MagickEpsilon %.*g\n", precision, MagickEpsilon);
printf ("Quantum %s\n", MagickStringify(Quantum));
printf ("sizeof:\n");
printf (" MagickRealType %i\n", (int)sizeof(MagickRealType));
printf (" MagickSizeType %i\n", (int)sizeof(MagickSizeType));
printf (" int %i\n", (int)sizeof(int));
printf (" long int %i\n", (int)sizeof(long int));
printf (" long long int %i\n", (int)sizeof(long long int));
printf (" float %i\n", (int)sizeof(float));
printf (" double %i\n", (int)sizeof(double));
printf (" long double %i\n", (int)sizeof(long double));
printf (" _Float128 %i\n", (int)sizeof(_Float128));
printf (" Image %i\n", (int)sizeof(Image));
printf (" ImageInfo %i\n", (int)sizeof(ImageInfo));
printf (" Quantum %i\n", (int)sizeof(Quantum));
printf ("MagickSizeType [%s]\n", MagickStringify(MagickSizeType));
printf ("MagickSizeFormat [%s]\n", MagickSizeFormat);
printf ("QuantumFormat: [%s]\n", QuantumFormat);
printf ("MaxMap %i\n", (int)MaxMap);
printf ("MaxTextExtent %i\n", (int)MaxTextExtent);
#if IMV6OR7==7
printf ("MagickPathExtent %i\n", (int)MagickPathExtent);
#endif
#if defined(MAGICKCORE_HDRI_SUPPORT)
printf ("Includes HDRI\n");
#else
printf ("Does not includes HDRI\n");
#endif
#if defined(MAGICKCORE_WINDOWS_SUPPORT)
printf ("MAGICKCORE_WINDOWS_SUPPORT defined\n");
#else
printf ("MAGICKCORE_WINDOWS_SUPPORT not defined\n");
#endif
#if defined(WIN32)
printf ("WIN32 defined\n");
#else
printf ("WIN32 not defined\n");
#endif
#if defined(WIN64)
printf ("WIN64 defined\n");
#else
printf ("WIN64 not defined\n");
#endif
#if defined(MAGICKCORE_OPENMP_SUPPORT)
printf ("MAGICKCORE_OPENMP_SUPPORT defined\n");
#else
printf ("MAGICKCORE_OPENMP_SUPPORT not defined\n");
#endif
#if defined(MAGICKCORE_HAVE_CL_CL_H)
printf ("MAGICKCORE_HAVE_CL_CL_H defined\n");
#else
printf ("MAGICKCORE_HAVE_CL_CL_H not defined\n");
#endif
#if defined(MAGICKCORE_HAVE_OPENCL_CL_H)
printf ("MAGICKCORE_HAVE_OPENCL_CL_H defined\n");
#else
printf ("MAGICKCORE_HAVE_OPENCL_CL_H not defined\n");
#endif
#if defined(MAGICKCORE_OPENCL_SUPPORT)
printf ("MAGICKCORE_OPENCL_SUPPORT defined\n");
DumpOpenCLProfileData();
printf ("Written ImageMagickOpenCL.log\n");
if (GetOpenCLEnabled()) {
printf ("GetOpenCLEnabled() = true\n");
} else {
printf ("GetOpenCLEnabled() = false\n");
}
DumpOpenCLProfileData();
printf ("Written ImageMagickOpenCL.log\n");
MagickCLEnv mcle = GetCurrentOpenCLEnv();
if (!mcle) {
printf ("GetCurrentOpenCLEnv failed\n");
}
if (mcle == (MagickCLEnv) NULL) {
printf ("default_CLEnv is null\n");
} else {
size_t i;
printf ("mcle->number_devices = %li\n", mcle->number_devices);
for (i = 0; i < mcle->number_devices; i++)
{
MagickCLDevice
device;
device=mcle->devices[i];
if ((device->profile_kernels == MagickFalse) ||
(device->profile_records == (KernelProfileRecord *) NULL))
continue;
printf("Device: %s\n",device->name);
printf("Version: %s\n",device->version);
}
}
#else
printf ("MAGICKCORE_OPENCL_SUPPORT not defined\n");
#endif
return(MagickImageFilterSignature);
}
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include "vsn_defines.h"
static void WrPixelTraits (PixelTrait pt)
{
printf ("traits: %i=(", pt);
if (pt == UndefinedPixelTrait) printf ("undefined ");
if ((pt & CopyPixelTrait) != 0) printf ("copy ");
if ((pt & UpdatePixelTrait) != 0) printf ("update ");
if ((pt & BlendPixelTrait) != 0) printf ("blend ");
printf (")");
}
static void WrPixelChannel (PixelChannel pc)
{
switch (pc) {
/* Instead of fixed RGB, we should use CMY, HSI, Lab etc as appropriate.
*/
case RedPixelChannel: printf ("R"); break;
case GreenPixelChannel: printf ("G"); break;
case BluePixelChannel: printf ("B"); break;
case BlackPixelChannel: printf ("K"); break;
case AlphaPixelChannel: printf ("A"); break;
case IndexPixelChannel: printf ("Index"); break;
case ReadMaskPixelChannel: printf ("Rdmsk"); break;
case WriteMaskPixelChannel: printf ("Wrmsk"); break;
case MetaPixelChannel: printf ("Meta"); break;
case CompositeMaskPixelChannel: printf ("Compmsk"); break;
case MetaPixelChannels: printf ("Metas"); break;
case IntensityPixelChannel: printf ("Intensity"); break;
case SyncPixelChannel: printf ("Sync"); break;
default:
if (pc >= MetaPixelChannels) {
printf ("meta%i", pc - MetaPixelChannels);
} else {
printf ("?");
}
}
}
// 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 %lu size %lux%lu\n",
count,
image->filename, image->depth,
image->columns, 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 (" alpha is on: %s\n", IS_ALPHA_CH(image) ? "yes" : "no");
printf (" channels %li meta_channels %li\n", image->number_channels, image->number_meta_channels);
printf (" ( MaxPixelChannels = %i )\n", MaxPixelChannels);
printf (" GetPixelChannels() = %li\n", GetPixelChannels(image));
{
ssize_t
i;
size_t
channels;
channels=0;
for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
{
PixelChannel channel = GetPixelChannelChannel(image,i);
PixelTrait traits = GetPixelChannelTraits(image,channel);
if ((traits & UpdatePixelTrait) != 0)
channels++;
}
printf (" Number of channels with update trait = %li\n", channels);
}
{
ssize_t
i;
PixelChannel
channel;
PixelTrait
traits;
printf ("Image channels:\n");
printf (" i, channel, name, traits \n");
for (i=0; i < (ssize_t) GetPixelChannels(image); i++) {
channel = GetPixelChannelChannel (image,i);
traits = GetPixelChannelTraits (image,channel);
printf (" %lu, %i, ", i, channel);
WrPixelChannel (channel);
printf (", ");
WrPixelTraits (traits);
printf ("\n");
}
}
{
ssize_t
i;
printf ("channel_map:\n");
printf (" i, channel, offset, traits\n");
/* Note: channel_map has MaxPixelChannels+1 entries,
numbered 0 to MaxPixelChannels.
*/
for (i=0; i <= MaxPixelChannels; i++) {
PixelChannelMap * pcm = &image->channel_map[i];
printf (" %li, %i, %li ", i, pcm->channel, pcm->offset);
WrPixelTraits (pcm->traits);
printf ("\n");
}
}
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);
}
#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);
}
#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);
}
#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);
}
#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);
}
#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);
}
#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);
}
#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);
}
/* Updated:
3-April-2018 for v7.0.7-28
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <assert.h>
#include <math.h>
#include "vsn_defines.h"
// The next function corresponds in style to functions in transform.c
// It takes one image, and returns an image.
//
static Image *CopyGrad2(const Image *image,
ExceptionInfo *exception)
{
Image
*new_image;
CacheView
*image_view;
ssize_t
y;
MagickBooleanType
status;
assert(image != (Image *) NULL);
assert(image->signature == MAGICK_CORE_SIG);
if (image->debug != MagickFalse)
(void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
// Make a clone of this image, same size but undefined pixel values:
//
new_image=CloneImage(image, image->columns, image->rows, MagickTrue, exception);
if (new_image == (Image *) NULL)
return(new_image);
if (SetNoPalette (new_image, exception) == MagickFalse)
return (Image *)NULL;
image_view = AcquireAuthenticCacheView(new_image,exception);
status = MagickTrue;
#if defined(MAGICKCORE_OPENMP_SUPPORT)
#pragma omp parallel for schedule(static,4) shared(status) \
MAGICK_THREADS(new_image,new_image,new_image->rows,1)
#endif
for (y=0; y < (ssize_t) new_image->rows; y++)
{
register VIEW_PIX_PTR
*q;
register ssize_t
x;
if (status == MagickFalse)
continue;
q=GetCacheViewAuthenticPixels(image_view,0,y,new_image->columns,1,exception);
if (q == (const VIEW_PIX_PTR *) NULL)
{
status=MagickFalse;
continue;
}
for (x=0; x < (ssize_t) image->columns; x++)
{
SET_PIXEL_RED (new_image, QuantumRange * (x/(image->columns-1.0)), q);
SET_PIXEL_GREEN (new_image, QuantumRange * (y/(image->rows-1.0)), q);
SET_PIXEL_BLUE (new_image, QuantumRange/2, q);
q += Inc_ViewPixPtr (new_image);
}
if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
status=MagickFalse;
}
image_view=DestroyCacheView(image_view);
return (new_image);
}
ModuleExport size_t grad2Image(Image **images,const int argc,
const char **argv,ExceptionInfo *exception)
{
Image
*image,
*new_image;
assert(images != (Image **) NULL);
assert(*images != (Image *) NULL);
assert((*images)->signature == MAGICK_CORE_SIG);
(void) argc;
(void) argv;
for (image=(*images); image != (Image *) NULL; image=GetNextImageInList(image))
{
new_image = CopyGrad2(image,exception);
if (new_image == (Image *) NULL)
return(-1);
ReplaceImageInList(&image,new_image);
// Replace messes up the images pointer. Make it good:
*images=GetFirstImageInList(image);
}
return(MagickImageFilterSignature);
}
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include "vsn_defines.h"
// The next function corresponds in style to functions in enhance.c
// It takes one image, and returns a status.
//
static MagickBooleanType DrawCircleImage(Image *image,
ExceptionInfo *exception)
{
double
cx,
cy,
rad;
MagickBooleanType
status;
DrawInfo
*draw_info;
char
primitive[MaxTextExtent];
assert(image != (Image *) NULL);
assert(image->signature == MAGICK_CORE_SIG);
if (image->debug != MagickFalse)
(void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
// We don't need SetImageStorageClass().
cx = (image->columns -1) / 2.0;
cy = (image->rows -1) / 2.0;
rad = cx;
if (rad > cy) rad = cy;
status=MagickTrue;
draw_info = AcquireDrawInfo();
//
// Or:
// draw_info=CloneDrawInfo((ImageInfo *) NULL,(DrawInfo *) NULL);
// ??
#if IMV6OR7==6
QueryColorDatabase (
"blue", &draw_info->fill, exception);
#else
QueryColorCompliance (
"blue", AllCompliance, &draw_info->fill, exception);
#endif
(void) FormatLocaleString(primitive,MaxTextExtent,
"circle %g,%g %g,%g", cx, cy, cx, cy+rad);
(void) CloneString(&draw_info->primitive,primitive);
#if IMV6OR7==6
status = DrawImage (image, draw_info);
#else
status = DrawImage (image, draw_info, exception);
#endif
draw_info = DestroyDrawInfo(draw_info);
return(status);
}
// Style of next is for "-process".
// It loops through all images in a list, processing each in-place.
//
ModuleExport size_t drawcircImage(Image **images,const int argc,
const char **argv,ExceptionInfo *exception)
{
Image
*image;
MagickBooleanType
status;
(void) argc;
(void) argv;
assert(images != (Image **) NULL);
assert(*images != (Image *) NULL);
assert((*images)->signature == MAGICK_CORE_SIG);
for (image=(*images); image != (Image *) NULL; image=GetNextImageInList(image))
{
status = DrawCircleImage(image,exception);
if (status == MagickFalse)
continue;
}
assert (status == MagickTrue);
return(MagickImageFilterSignature);
}
/* Updated:
1-October-2016 Use GetPixelIntensity() for both v6 and v7. (Previously,
v6 used PixelPacketIntensity()).
3-April-2018 for v7.0.7-28
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include "vsn_defines.h"
static Image
*gimage;
static int compare_pixels (const void *a, const void *b)
{
//#if IMV6OR7==6
// const Quantum intensity_a = PixelPacketIntensity( (const VIEW_PIX_PTR *) a);
// const Quantum intensity_b = PixelPacketIntensity( (const VIEW_PIX_PTR *) b);
//#else
// const Quantum intensity_a = GetPixelIntensity(gimage, (const VIEW_PIX_PTR *) a);
// const Quantum intensity_b = GetPixelIntensity(gimage, (const VIEW_PIX_PTR *) b);
//#endif
const Quantum intensity_a = GetPixelIntensity(gimage, (const VIEW_PIX_PTR *) a);
const Quantum intensity_b = GetPixelIntensity(gimage, (const VIEW_PIX_PTR *) b);
return (intensity_a > intensity_b) - (intensity_a < intensity_b);
}
// The next function corresponds in style to functions in enhance.c
// It takes one image, and returns a status.
//
static MagickBooleanType SortPixImage(Image *image,
ExceptionInfo *exception)
{
CacheView
*image_view;
ssize_t
y;
MagickBooleanType
status;
assert(image != (Image *) NULL);
assert(image->signature == MAGICK_CORE_SIG);
if (image->debug != MagickFalse)
(void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
if (SetNoPalette (image, exception) == MagickFalse)
return MagickFalse;
gimage = image;
status=MagickTrue;
image_view=AcquireAuthenticCacheView(image,exception);
#if defined(MAGICKCORE_OPENMP_SUPPORT)
#pragma omp parallel for schedule(static,4) shared(status) \
MAGICK_THREADS(image,image,image->rows,1)
#endif
for (y=0; y < (ssize_t) image->rows; y++)
{
register const VIEW_PIX_PTR
*p;
if (status == MagickFalse)
continue;
p=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
if (p == (const VIEW_PIX_PTR *) NULL)
{
status=MagickFalse;
continue;
}
#if IMV6OR7==6
qsort ((void *)p, image->columns, sizeof (VIEW_PIX_PTR), compare_pixels);
#else
qsort (
(void *)p,
image->columns,
sizeof (VIEW_PIX_PTR) * GetPixelChannels(image),
compare_pixels);
#endif
if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
status=MagickFalse;
}
image_view=DestroyCacheView(image_view);
return(status);
}
// Style of next is for "-process".
// It loops through all images in a list, processing each in-place.
//
ModuleExport size_t sortpixelsImage(Image **images,const int argc,
const char **argv,ExceptionInfo *exception)
{
Image
*image;
MagickBooleanType
status;
(void) argc;
(void) argv;
assert(images != (Image **) NULL);
assert(*images != (Image *) NULL);
assert((*images)->signature == MAGICK_CORE_SIG);
for (image=(*images); image != (Image *) NULL; image=GetNextImageInList(image))
{
status = SortPixImage(image,exception);
if (status == MagickFalse)
continue;
}
assert (status == MagickTrue);
return(MagickImageFilterSignature);
}
/* Updated:
3-April-2018 for v7.0.7-28
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include "vsn_defines.h"
static Image
*gimage;
static int compare_pixels (const void *a, const void *b)
{
Quantum intensity_a = GET_PIXEL_BLUE(gimage, (const VIEW_PIX_PTR *) a);
Quantum intensity_b = GET_PIXEL_BLUE(gimage, (const VIEW_PIX_PTR *) b);
int r = (intensity_a > intensity_b) - (intensity_a < intensity_b);
if (r != 0) return r;
intensity_a = GET_PIXEL_GREEN(gimage, (const VIEW_PIX_PTR *) a);
intensity_b = GET_PIXEL_GREEN(gimage, (const VIEW_PIX_PTR *) b);
r = (intensity_a > intensity_b) - (intensity_a < intensity_b);
if (r != 0) return r;
intensity_a = GET_PIXEL_RED(gimage, (const VIEW_PIX_PTR *) a);
intensity_b = GET_PIXEL_RED(gimage, (const VIEW_PIX_PTR *) b);
return (intensity_a > intensity_b) - (intensity_a < intensity_b);
}
// The next function corresponds in style to functions in enhance.c
// It takes one image, and returns a status.
//
static MagickBooleanType SortPixImageBlue(Image *image,
ExceptionInfo *exception)
{
CacheView
*image_view;
ssize_t
y;
MagickBooleanType
status;
assert(image != (Image *) NULL);
assert(image->signature == MAGICK_CORE_SIG);
if (image->debug != MagickFalse)
(void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
if (SetNoPalette (image, exception) == MagickFalse)
return MagickFalse;
gimage = image;
status=MagickTrue;
image_view=AcquireAuthenticCacheView(image,exception);
#if defined(MAGICKCORE_OPENMP_SUPPORT)
#pragma omp parallel for schedule(static,4) shared(status) \
MAGICK_THREADS(image,image,image->rows,1)
#endif
for (y=0; y < (ssize_t) image->rows; y++)
{
register const VIEW_PIX_PTR
*p;
if (status == MagickFalse)
continue;
p=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
if (p == (const VIEW_PIX_PTR *) NULL)
{
status=MagickFalse;
continue;
}
#if IMV6OR7==6
qsort ((void *)p, image->columns, sizeof (VIEW_PIX_PTR), compare_pixels);
#else
qsort (
(void *)p,
image->columns,
sizeof (VIEW_PIX_PTR) * GetPixelChannels(image),
compare_pixels);
#endif
if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
status=MagickFalse;
}
image_view=DestroyCacheView(image_view);
return(status);
}
// Style of next is for "-process".
// It loops through all images in a list, processing each in-place.
//
ModuleExport size_t sortpixelsblueImage(Image **images,const int argc,
const char **argv,ExceptionInfo *exception)
{
Image
*image;
MagickBooleanType
status;
(void) argc;
(void) argv;
assert(images != (Image **) NULL);
assert(*images != (Image *) NULL);
assert((*images)->signature == MAGICK_CORE_SIG);
for (image=(*images); image != (Image *) NULL; image=GetNextImageInList(image))
{
status = SortPixImageBlue(image,exception);
if (status == MagickFalse)
continue;
}
assert (status == MagickTrue);
return(MagickImageFilterSignature);
}
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include "vsn_defines.h"
static void inline SwapPixels (const VIEW_PIX_PTR *a, const VIEW_PIX_PTR *b, int nBytes)
{
char * pa = (char *) a;
char * pb = (char *) b;
char t;
int i;
for (i=0; i < nBytes; i++) {
t = *pa;
*pa++ = *pb;
*pb++ = t;
}
}
// The next function corresponds in style to functions in enhance.c
// It takes two images, modifies both, and returns a status.
//
static MagickBooleanType ShadowSortPixImage(Image **images,
ExceptionInfo *exception)
{
CacheView
*imageA_view, *imageB_view;
ssize_t
y;
MagickBooleanType
status = MagickTrue;
Image * imageA = *images;
assert(imageA != (Image *) NULL);
assert(imageA->signature == MAGICK_CORE_SIG);
if (imageA->debug != MagickFalse)
(void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",imageA->filename);
Image * imageB = GetNextImageInList (imageA);
if (!imageB) {
fprintf (stderr, "shadowsortpixels: needs two images, the same size\n");
return MagickFalse;
}
if (imageA->columns != imageB->columns ||
imageA->rows != imageB->rows)
{
fprintf (stderr, "shadowsortpixels: the two images must be the same size\n");
return MagickFalse;
}
if (SetNoPalette (imageA, exception) == MagickFalse)
return MagickFalse;
if (SetNoPalette (imageB, exception) == MagickFalse)
return MagickFalse;
const ssize_t incA = Inc_ViewPixPtr (imageA);
const ssize_t incB = Inc_ViewPixPtr (imageB);
const int nBytesA = sizeof (VIEW_PIX_PTR) * incA;
const int nBytesB = sizeof (VIEW_PIX_PTR) * incB;
imageA_view=AcquireAuthenticCacheView(imageA,exception);
imageB_view=AcquireAuthenticCacheView(imageB,exception);
#if defined(MAGICKCORE_OPENMP_SUPPORT)
#pragma omp parallel for schedule(static,4) shared(status) \
MAGICK_THREADS(imageA,imageA,imageA->rows,1)
#endif
for (y=0; y < (ssize_t) imageA->rows; y++)
{
VIEW_PIX_PTR
*pA, *pB;
if (status == MagickFalse)
continue;
pA=GetCacheViewAuthenticPixels(imageA_view,0,y,imageA->columns,1,exception);
if (!pA)
{
status=MagickFalse;
continue;
}
pB=GetCacheViewAuthenticPixels(imageB_view,0,y,imageB->columns,1,exception);
if (!pB)
{
status=MagickFalse;
continue;
}
// Shell sort.
ssize_t x, xSwitch, xLimit;
ssize_t xOffset = imageA->columns / 2;
while (xOffset) {
xLimit = imageA->columns - xOffset - 1 ;
do {
xSwitch = 0; // Assume no switches at this offset.
// Compare elements and switch ones out of order.
for( x = 0; x <= xLimit; x++ ) {
const VIEW_PIX_PTR * a = pA + x*incA;
const VIEW_PIX_PTR * b = pA + (x+xOffset)*incA;
if (GetPixelIntensity(imageA, a) > GetPixelIntensity(imageA, b)) {
SwapPixels (a, b, nBytesA);
SwapPixels (pB + x*incB, pB + (x+xOffset)*incB, nBytesB);
xSwitch = x;
}
}
// Sort on next pass only to where last switch was made.
xLimit = xSwitch - xOffset;
} while (xSwitch);
// No switches at last offset, try one half as big.
xOffset /= 2;
}
if (SyncCacheViewAuthenticPixels(imageA_view,exception) == MagickFalse)
status=MagickFalse;
if (SyncCacheViewAuthenticPixels(imageB_view,exception) == MagickFalse)
status=MagickFalse;
}
imageA_view=DestroyCacheView(imageA_view);
imageB_view=DestroyCacheView(imageB_view);
return(status);
}
ModuleExport size_t shadowsortpixelsImage(Image **images,const int argc,
const char **argv,ExceptionInfo *exception)
{
(void) argc;
(void) argv;
assert(images != (Image **) NULL);
assert(*images != (Image *) NULL);
assert((*images)->signature == MAGICK_CORE_SIG);
int ListLen = (int)GetImageListLength(*images);
if (ListLen != 2) {
fprintf (stderr, "shadowsortpixels: needs 2 images\n");
return (-1);
}
if (!ShadowSortPixImage (images, exception)) return (-1);
return(MagickImageFilterSignature);
}
/*
Reference: http://im.snibgo.com/fillholes.htm
Update 21-Nov-2015
Added copy_radius option.
Normalise MSE result from CompareWindow to 0.0-1.0, for threshold option.
Added threshold option.
Added "unfilled" warning.
Moved most code to fillholescommon.inc.
Updated
2-Feb-2016 for v7.
3-April-2018 for v7.0.7-28
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <ctype.h>
#include "vsn_defines.h"
#include "fillholescommon.inc"
#define DEBUG 0
// The next function corresponds in style to functions in transform.c
// It takes one image, and returns an image with filled holes.
//
static Image *fillholes (
Image *image,
FillHoleT * pfh,
ExceptionInfo *exception)
{
Image
*holed_image,
*new_image;
CacheView
*copy_inp_view,
*copy_new_view2,
*transp_view,
*new_view;
ssize_t
y,
holedXmult;
int
cols_plus_3,
nIter = 0,
frameNum = 0;
MagickBooleanType
unfilled,
changedAny,
status = MagickTrue;
if (image->debug != MagickFalse)
(void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
if (pfh->do_verbose) {
fprintf (stderr, "fillholes: Input image [%s] %ix%i depth is %i\n",
image->filename,
(int)image->columns, (int)image->rows,
(int)image->depth);
}
ResolveImageParams (image, pfh);
InitAutoLimit (pfh);
// Clone the image, same size, copied pixels:
//
holed_image=CloneImage(image, 0, 0, MagickTrue, exception);
if (holed_image == (Image *) NULL)
return(holed_image);
if (SetNoPalette (holed_image, exception) == MagickFalse)
return (Image *)NULL;
holedXmult = Inc_ViewPixPtr (holed_image);
// Clone holed_image into new_image.
new_image=CloneImage(holed_image, 0, 0, MagickTrue, exception);
if (new_image == (Image *) NULL)
return(new_image);
if (pfh->write_frames > 0) {
WriteFrame (pfh, new_image, frameNum++, exception);
}
do { // one onion-ring
#if DEBUG==1
printf ("Do one onion-ring\n");
#endif
changedAny = MagickFalse;
unfilled = MagickFalse;
// FIXME: performance: we can acquire/release some of these outside do loop?
// inp_view = AcquireVirtualCacheView (image, exception);
// srch_holed_view = AcquireVirtualCacheView (holed_image, exception);
pfh->CompWind.ref_image = image;
pfh->CompWind.sub_image = holed_image;
pfh->CompWind.ref_view = AcquireVirtualCacheView (image, exception);
pfh->CompWind.sub_view = AcquireVirtualCacheView (holed_image, exception);
transp_view = AcquireVirtualCacheView (holed_image, exception);
new_view = AcquireAuthenticCacheView (new_image, exception);
copy_inp_view = CloneCacheView (pfh->CompWind.ref_view);
copy_new_view2 = AcquireVirtualCacheView (new_image, exception);
cols_plus_3 = holed_image->columns + 3;
#if defined(MAGICKCORE_OPENMP_SUPPORT)
#pragma omp parallel for schedule(static,4) shared(status,changedAny,frameNum) \
MAGICK_THREADS(image,new_image,image->rows,1)
#endif
for (y=0; y < (ssize_t) new_image->rows; y++)
{
ssize_t
x;
const VIEW_PIX_PTR
*pp_this_pix,
*pp_transp3,
*pp_line0,
*pp_line1,
*pp_line2;
CopyWhereT
cw;
if (pfh->copyWhat == copyWindow) {
cw.wi = pfh->sqCopyDim;
cw.ht = pfh->sqCopyDim;
} else {
cw.wi = 1;
cw.ht = 1;
}
if (status == MagickFalse) continue;
pp_this_pix = GetCacheViewVirtualPixels (
copy_new_view2,0,y,holed_image->columns,1,exception); // FIXME: need diff view?
if (pp_this_pix == (const VIEW_PIX_PTR *) NULL)
{
status=MagickFalse;
continue;
}
pp_transp3 = GetCacheViewVirtualPixels (
transp_view,-1,y-1,holed_image->columns+2,3,exception);
if (pp_transp3 == (const VIEW_PIX_PTR *) NULL)
{
status=MagickFalse;
continue;
}
// FIXME: offsets are wrong for v7.
// pp_line0 = pp_transp3 + 1;
// pp_line1 = pp_transp3 + cols_plus_3;
// pp_line2 = pp_transp3 + 2 * cols_plus_3 - 1;
pp_line0 = pp_transp3 + holedXmult;
pp_line1 = pp_transp3 + holedXmult * cols_plus_3;
pp_line2 = pp_transp3 + holedXmult * (2 * cols_plus_3 - 1);
#if DEBUG==1
printf ("y=%li ", (long int)y);
#endif
for (x=0; x < (ssize_t) new_image->columns; x++)
{
// FIXME: get the 9 alphas from new_image, so we can copy a window-full each time.
// But then we do too many.
// FIXME: offsets are wrong for v7.
//if (GetPixelAlpha (pp_line1 + x) <= 0) {
if (GET_PIXEL_ALPHA (new_image, pp_this_pix + holedXmult * x) <= 0) {
// The pixel is fully transparent.
MagickBooleanType HasAdjOpaq =
(GET_PIXEL_ALPHA (holed_image, pp_line0 + holedXmult*(x-1)) > 0)
|| (GET_PIXEL_ALPHA (holed_image, pp_line0 + holedXmult*x ) > 0)
|| (GET_PIXEL_ALPHA (holed_image, pp_line0 + holedXmult*(x+1)) > 0)
|| (GET_PIXEL_ALPHA (holed_image, pp_line1 + holedXmult*(x-1)) > 0)
|| (GET_PIXEL_ALPHA (holed_image, pp_line1 + holedXmult*(x+1)) > 0)
|| (GET_PIXEL_ALPHA (holed_image, pp_line2 + holedXmult*(x-1)) > 0)
|| (GET_PIXEL_ALPHA (holed_image, pp_line2 + holedXmult*x ) > 0)
|| (GET_PIXEL_ALPHA (holed_image, pp_line2 + holedXmult*(x+1)) > 0);
if (HasAdjOpaq == MagickTrue) {
// The pixel is fully transparent,
// with at least one opaque 8-neighbour.
status = MatchAndCopy (
pfh,
new_image,
new_view,
copy_inp_view,
x,
y,
&cw,
&frameNum,
&unfilled,
&changedAny,
exception);
/*==
ssize_t
i0, i1,
j0, j1,
hx, hy,
i, j,
besti=0, bestj=0;
hx = x - pfh->WindowRad;
hy = y - pfh->WindowRad;
if (pfh->SearchToEdges) {
i0 = -pfh->WindowRad;
i1 = (ssize_t) holed_image->rows - pfh->WindowRad;
j0 = -pfh->WindowRad;
j1 = (ssize_t) holed_image->columns - pfh->WindowRad;
} else {
i0 = 0;
i1 = (ssize_t) holed_image->rows - pfh->sqDim + 1;
j0 = 0;
j1 = (ssize_t) holed_image->columns - pfh->sqDim + 1;
}
if (pfh->LimSrchRad) {
ssize_t limj0 = x - pfh->LimSrchRad - pfh->WindowRad;
ssize_t limj1 = x + pfh->LimSrchRad - pfh->WindowRad;
ssize_t limi0 = y - pfh->LimSrchRad - pfh->WindowRad;
ssize_t limi1 = y + pfh->LimSrchRad - pfh->WindowRad;
if (i0 < limi0) i0 = limi0;
if (i1 > limi1) i1 = limi1;
if (j0 < limj0) j0 = limj0;
if (j1 > limj1) j1 = limj1;
}
double BestScore = pfh->WorstCompare;
for (i = i0; i < i1; i++) {
for (j = j0; j < j1; j++) {
double v = CompareWindow (
pfh, inp_view, srch_holed_view, exception,
hx, hy, j, i);
if (BestScore > v) {
BestScore = v;
besti = i;
bestj = j;
if (BestScore <= pfh->thresholdSq) break;
}
}
if (BestScore <= pfh->thresholdSq) break;
}
//if (x==30)
// printf ("Best ji=%i,%i %g => hxy %i,%i\n",
// (int)bestj, (int)besti, BestScore, (int)hx, (int)hy);
if (BestScore < pfh->WorstCompare) {
if (pfh->copyWhat == copyOnePixel)
CopyOnePix (pfh, new_view, copy_inp_view, hx, hy, bestj, besti, exception);
else
CopyWindow (pfh, new_view, copy_inp_view, hx, hy, bestj, besti, exception);
if (SyncCacheViewAuthenticPixels (new_view,exception) == MagickFalse)
status=MagickFalse;
changedAny = MagickTrue;
if (pfh->write_frames == 2) {
WriteFrame (pfh, new_image, frameNum++, exception);
}
} else {
unfilled = MagickTrue;
}
==*/
}
}
} // loop x
} // loop y
#if DEBUG==1
printf ("Done x,y\n");
#endif
if (pfh->Match.SetAutoLimit && pfh->Match.nLimitCnt <= 0) UseAutoLimit (pfh);
copy_inp_view = DestroyCacheView (copy_inp_view);
new_view = DestroyCacheView (new_view);
transp_view = DestroyCacheView (transp_view);
copy_new_view2 = DestroyCacheView (copy_new_view2);
pfh->CompWind.sub_view = DestroyCacheView (pfh->CompWind.sub_view);
pfh->CompWind.ref_view = DestroyCacheView (pfh->CompWind.ref_view);
if (changedAny) {
DestroyImage (holed_image);
holed_image = CloneImage(new_image, 0, 0, MagickTrue, exception);
if (pfh->write_frames == 1) {
WriteFrame (pfh, new_image, frameNum++, exception);
}
}
nIter++;
#if DEBUG==1
printf ("Done one onion-ring\n");
#endif
} while (changedAny);
if (pfh->do_verbose) {
fprintf (stderr, "Finished fillholes nIter=%i\n", nIter);
if (unfilled) {
fprintf (stderr, "Warning: some pixels were not filled\n");
}
if (pfh->write_frames > 0) {
fprintf (stderr, " numFrames=%i\n", frameNum);
}
}
DestroyImage (new_image);
return (holed_image);
}
ModuleExport size_t fillholesImage (
Image **images,
const int argc,
const char **argv,
ExceptionInfo *exception)
{
Image
*image,
*new_image;
MagickBooleanType
status;
FillHoleT
fh;
assert(images != (Image **) NULL);
assert(*images != (Image *) NULL);
assert((*images)->signature == MAGICK_CORE_SIG);
status = menu (argc, argv, &fh);
if (status == MagickFalse)
return (-1);
InitRand (&fh);
for (image=(*images); image != (Image *) NULL; image=GetNextImageInList(image))
{
new_image = fillholes (image, &fh, exception);
ReplaceImageInList(&image,new_image);
*images=GetFirstImageInList(image);
}
DeInitRand (&fh);
return(MagickImageFilterSignature);
}
/*
Reference: http://im.snibgo.com/fillholes.htm
Created 21-Nov-2015
Updated:
17-July-2016 AutoRepeat.
3-April-2018 for v7.0.7-28
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <ctype.h>
#include "vsn_defines.h"
#include "fillholescommon.inc"
#define DEBUG 0
#define VERIFY 0
#define CONF_FACT 0.9
typedef double fhpValueT;
typedef enum {
psOpaq, // Input pixel was opaque, or has become opaque.
psEdge, // Currently pixel is transparent but has an opaque neighbour.
psInner, // Currently pixel is transparent and has only transparent neghbours.
psCantFill // Pixel is transparent and can't be filled, so remains transparent.
} pixStateT;
// Valid state transitions:
// Inner -> Edge -> Opaq
// Inner -> Opaq
// Edge -> CantFill
typedef struct {
pixStateT
pixState;
fhpValueT
confidence,
energy,
priority;
} fhPriPixT;
typedef struct {
ssize_t
x,
y;
} pqNodeT;
typedef struct {
ssize_t
width,
height;
fhPriPixT
**fhPriPix;
ssize_t
pqNumNodes;
ssize_t
pqMaxNodes;
pqNodeT
*pqNodes;
} fhPriT;
//---------------------------------------------------------------------------
// Priority queue functions.
// This is a MAX queue: higher values will come out first.
#define LCHILD(x) 2 * x + 1
#define RCHILD(x) 2 * x + 2
#define PARENT(x) (x - 1) / 2
static fhpValueT inline pqDataOfNode (fhPriT * fhp, ssize_t NodeNum)
{
ssize_t x = fhp->pqNodes[NodeNum].x;
ssize_t y = fhp->pqNodes[NodeNum].y;
return fhp->fhPriPix[y][x].priority;
}
static void pqVerify (fhPriT * fhp, MagickBooleanType verbose)
{
ssize_t
i;
if (verbose) printf ("pqVerify pqNumNodes=%li\n", fhp->pqNumNodes);
for (i=0; i < fhp->pqNumNodes; i++) {
ssize_t lch = LCHILD(i);
ssize_t rch = RCHILD(i);
MagickBooleanType HasL = lch < fhp->pqNumNodes;
MagickBooleanType HasR = rch < fhp->pqNumNodes;
if (HasL && pqDataOfNode (fhp, lch) > pqDataOfNode (fhp, i))
printf ("** Bad %li left\n", i);
if (HasR && pqDataOfNode (fhp, rch) > pqDataOfNode (fhp, i))
printf ("** Bad %li right\n", i);
}
}
#if 0
static void pqDump (FillHoleT * fh, fhPriT * fhp)
{
ssize_t
i;
printf ("pqNumNodes=%li\n", fhp->pqNumNodes);
printf (" # x,y, state, confidence, energy, priority\n");
for (i=0; i < fhp->pqNumNodes; i++) {
pqNodeT * pqn = &fhp->pqNodes[i];
fhPriPixT * pn = &(fhp->fhPriPix[pqn->y][pqn->x]);
char cState;
if (pn->pixState == psOpaq) cState = 'o';
else if (pn->pixState == psEdge) cState = 'e';
else cState = 'i';
printf (" %li %li,%li %c %g %g %g\n",
i, pqn->x, pqn->y, cState, pn->confidence, pn->energy, pn->priority);
}
pqVerify (fhp, fh->debug);
}
#endif
static void inline pqSwapNodes (fhPriT * fhp, ssize_t n1, ssize_t n2)
{
ssize_t t = fhp->pqNodes[n1].x;
fhp->pqNodes[n1].x = fhp->pqNodes[n2].x;
fhp->pqNodes[n2].x = t;
t = fhp->pqNodes[n1].y;
fhp->pqNodes[n1].y = fhp->pqNodes[n2].y;
fhp->pqNodes[n2].y = t;
}
static void pqBalanceNode (fhPriT * fhp, ssize_t NodeNum)
// "Bubble-down".
// Finds the smallest of this node and its children.
// If the node isn't the smallest of the three,
// swaps data with that child and recursively balances that child.
{
ssize_t largest = NodeNum;
ssize_t lch = LCHILD(NodeNum);
ssize_t rch = RCHILD(NodeNum);
fhpValueT SmData = pqDataOfNode (fhp, NodeNum);
if (lch < fhp->pqNumNodes) {
fhpValueT ldist = pqDataOfNode (fhp, lch);
if (ldist > SmData) {
SmData = ldist;
largest = lch;
}
}
if (rch < fhp->pqNumNodes) {
fhpValueT rdist = pqDataOfNode (fhp, rch);
if (rdist > SmData) {
SmData = rdist;
largest = rch;
}
}
if (largest != NodeNum) {
pqSwapNodes (fhp, largest, NodeNum);
pqBalanceNode (fhp, largest);
}
}
static void pqInsertNew (fhPriT * fhp, ssize_t x, ssize_t y)
{
//printf ("pqIns %li,%li ", x, y);
if (fhp->pqNumNodes >= fhp->pqMaxNodes) {
printf ("pq bust\n");
}
fhpValueT newPri = fhp->fhPriPix[y][x].priority;
// Priority could be zero, eg for graphics files with flat colours.
ssize_t n = fhp->pqNumNodes;
// "Bubble up".
// If this data is more than node's parent,
// drop parent data into this node
// and consider putting new data into that parent.
while (n && newPri > pqDataOfNode (fhp, PARENT(n))) {
fhp->pqNodes[n] = fhp->pqNodes[PARENT(n)];
n = PARENT(n);
}
fhp->pqNodes[n].x = x;
fhp->pqNodes[n].y = y;
fhp->pqNumNodes++;
//pqVerify (fhp, MagickTrue);
}
static ssize_t pqFindNode (
fhPriT * fhp, ssize_t x, ssize_t y, fhpValueT val, ssize_t NodeStart)
// Returns -1 if not present.
// Note: recursive.
{
if (fhp->pqNumNodes==0) return -1;
pqNodeT * pqn = &fhp->pqNodes[NodeStart];
if (pqn->x == x && pqn->y == y) return NodeStart;
if (pqDataOfNode (fhp, NodeStart) < val) return -1;
ssize_t lch = LCHILD(NodeStart);
if (lch < fhp->pqNumNodes) {
ssize_t nch = pqFindNode (fhp, x, y, val, lch);
if (nch >= 0) return nch;
}
ssize_t rch = RCHILD(NodeStart);
if (rch < fhp->pqNumNodes) {
ssize_t nch = pqFindNode (fhp, x, y, val, rch);
if (nch >= 0) return nch;
}
return -1;
}
static ssize_t pqDelNode (
fhPriT * fhp, ssize_t x, ssize_t y)
// Returns -1 if not present.
{
fhpValueT oldPri = fhp->fhPriPix[y][x].priority;
ssize_t n = pqFindNode (fhp, x, y, oldPri, 0);
if (n < 0) {
printf ("** Can't find to del %li,%li %g\n", x, y, oldPri);
return n;
}
// Move last to here, delete last, rebalance.
pqNodeT * pqn = &fhp->pqNodes[n];
pqNodeT * pqLast = &fhp->pqNodes[fhp->pqNumNodes-1];
pqn->x = pqLast->x;
pqn->y = pqLast->y;
fhp->pqNumNodes--;
fhpValueT newPri = fhp->fhPriPix[pqn->y][pqn->x].priority;
// "Bubble up".
// If this data is more than node's parent,
// swap with parent and iterate.
ssize_t bn = n;
while (bn && newPri > pqDataOfNode (fhp, PARENT(bn))) {
pqSwapNodes (fhp, bn, PARENT(bn));
bn = PARENT(bn);
}
// Bubble down from here.
pqBalanceNode (fhp, n);
// Bubble down from the top.
// FIXME: do we need this?
pqBalanceNode (fhp, 0);
#if VERIFY==1
pqVerify (fhp, MagickFalse);
#endif
return n;
}
/*==
static void pqFindMax (fhPriT * fhp, ssize_t *x, ssize_t *y)
{
if (!fhp->pqNumNodes) printf ("** Bad: fm: empty");
pqNodeT * pqn = &fhp->pqNodes[0];
*x = pqn->x;
*y = pqn->y;
}
==*/
static int pqRemoveMax (fhPriT * fhp, ssize_t *x, ssize_t *y)
// Returns 1 if okay, or 0 if no data.
{
if (!fhp->pqNumNodes) {
//printf ("** Bad: rm: empty");
return 0;
}
pqNodeT * pqn = &fhp->pqNodes[0];
*x = pqn->x;
*y = pqn->y;
// Put last into root, and rebalance.
fhp->pqNodes[0] = fhp->pqNodes[--fhp->pqNumNodes];
pqBalanceNode (fhp, 0);
return 1;
}
static void UpdIfData (
FillHoleT * fh,
fhPriT * fhp, ssize_t x, ssize_t y, fhpValueT oldPri, fhpValueT newPri)
// If the pixel is in the queue, updates the priority.
{
//assert(newPri >= oldPri);
printf ("UpdIfData %li,%li: ", x, y);
ssize_t n = pqFindNode (fhp, x, y, oldPri, 0);
if (n < 0) {
// Not in queue.
return;
} else {
printf ("%li\n", n);
}
fhp->fhPriPix[y][x].priority = newPri;
// "Bubble up".
// If this data is more than node's parent,
// swap with parent and iterate.
while (n && newPri > pqDataOfNode (fhp, PARENT(n))) {
pqSwapNodes (fhp, n, PARENT(n));
n = PARENT(n);
}
// Bubble down from the top.
// FIXME: do we need this?
pqBalanceNode (fhp, 0);
#if VERIFY==1
pqVerify (fhp, fh->do_verbose);
if (fh->debug==MagickTrue) {
ssize_t n = pqFindNode (fhp, x, y, newPri, 0);
printf (" UpdIfData: n=%li\n", n);
assert (n >= 0);
}
#endif
}
static void UpdInsData (
FillHoleT * fh,
fhPriT * fhp, ssize_t x, ssize_t y, fhpValueT oldPri, fhpValueT newPri)
// Updates the priority of a pixel, or inserts if not already present.
{
ssize_t n = pqFindNode (fhp, x, y, oldPri, 0);
if (fh->debug==MagickTrue) printf ("UpdInsData %li,%li: %li\n", x, y, n);
fhp->fhPriPix[y][x].priority = newPri;
if (n < 0) {
pqInsertNew (fhp, x, y);
} else {
if (fh->debug==MagickTrue) printf ("UpdInsData %g -> %g\n", oldPri, newPri);
//assert(newPri >= oldPri);
if (newPri >= oldPri) {
// "Bubble up".
// If this data is more than node's parent,
// swap with parent and iterate.
while (n && newPri > pqDataOfNode (fhp, PARENT(n))) {
pqSwapNodes (fhp, n, PARENT(n));
n = PARENT(n);
}
} else {
pqBalanceNode (fhp, n);
}
// Bubble down from the top.
// FIXME: do we need this?
pqBalanceNode (fhp, 0);
#if VERIFY==1
pqVerify (fhp, fh->debug);
#endif
}
if (fh->debug==MagickTrue) {
ssize_t n = pqFindNode (fhp, x, y, newPri, 0);
printf (" UpdInsData: n=%li\n", n);
assert (n >= 0);
}
}
//---------------------------------------------------------------------------
/* Energy of a pixel is defined as the maximum gradient between that pixel
and an 8-connected neighbour.
The gradient is delta(channel)/distance,
where distance is 1 for adjacent NSEW or sqrt(2) for NW, NE, SW, SE.
Knowing dRed, dGreen and dBlue, do we use the max of these, or RMS or what? Max, I think.
Neighbours that are fully transparent are ignored.
Neighbours that are partially transparent???
NO!!! We need the energy of a transparent (unknown) pixel, as defined by its neighbours.
Bugger.
Maybe we could take "this pixel" to be the average of is neighbours, then calc as above.
Or, we say above definition is for opaque pixels.
For edge pixels, energy is the maximum energy of its opaque neighbours.
*/
/*
We initially put only the edge pixels in the queue.
The others have zero priority, so no need to put them in until we know the priority.
*/
#if 0
static void DumpPriPix (
fhPriT * fhp
)
{
ssize_t x, y;
printf ("x,y, state, confidence, energy, priority\n");
for (y = 0; y < fhp->height; y++) {
for (x = 0; x < fhp->width; x++) {
fhPriPixT * pn = &(fhp->fhPriPix[y][x]);
printf ("%li,%li %i %g %g %g\n",
x, y, pn->pixState, pn->confidence, pn->energy, pn->priority);
}
}
}
#endif
#define CALC_CE \
if (pno->pixState == psOpaq) { \
if (pno->confidence > 0 && confid > pno->confidence * CONF_FACT) confid = pno->confidence * CONF_FACT; \
if (energy < pno->energy) energy = pno->energy; \
num_opaq++; \
}
static fhpValueT inline CalcPixConfEn (
FillHoleT * fh,
fhPriT * fhp,
ssize_t x,
ssize_t y
// Calculate pixel confidence and energy.
// Returns calculated priority (but doesn't set it).
)
{
return 0;
}
static void inline CalcOnePixEdgeData (
FillHoleT * fh,
fhPriT * fhp,
ssize_t x,
ssize_t y
)
// Confidence of an edge pixel is the lowest confidence of 8-connected opaque neighbours,
// multiplied by a factor.
// Energy of an edge pixel is the highest energy of 8-connected opaque neighbours.
// Priority is confidence * energy * num_opaq/8.
{
if (fh->debug==MagickTrue) printf (" coped %li,%li", x, y);
fhPriPixT * pno;
fhPriPixT * pn = &(fhp->fhPriPix[y][x]);
fhpValueT oldPri = pn->priority; // In case we need to update queue.
pn->priority = 0;
fhpValueT confid = 99;
fhpValueT energy = 0;
int num_opaq = 0;
if (y > 0) {
if (x > 0) {
pno = &(fhp->fhPriPix[y-1][x-1]);
CALC_CE;
}
fhPriPixT * pno = &(fhp->fhPriPix[y-1][x]);
CALC_CE;
if (x < fhp->width-1) {
pno = &(fhp->fhPriPix[y-1][x+1]);
CALC_CE;
}
}
if (x > 0) {
pno = &(fhp->fhPriPix[y][x-1]);
CALC_CE;
}
if (x < fhp->width-1) {
pno = &(fhp->fhPriPix[y][x+1]);
CALC_CE;
}
if (y < fhp->height-1) {
if (x > 0) {
pno = &(fhp->fhPriPix[y+1][x-1]);
CALC_CE;
}
pno = &(fhp->fhPriPix[y+1][x]);
CALC_CE;
if (x < fhp->width-1) {
pno = &(fhp->fhPriPix[y+1][x+1]);
CALC_CE;
}
}
assert (num_opaq > 0);
assert (confid > 0);
pn->confidence = confid;
pn->energy = energy;
if (pn->energy==0) pn->energy=0.00001; // FIXME?
// If this is edge pixel, we need to update queue.
fhpValueT newPri = pn->confidence * pn->energy * num_opaq/8.0;
if (fh->debug==MagickTrue) printf (" co=%g en=%g no=%i ", pn->confidence, pn->energy, num_opaq);
if (fh->debug==MagickTrue) printf (" coped %g->%g \n", oldPri, newPri);
if (pn->pixState == psEdge) {
if (fh->debug==MagickTrue) printf (" edge\n");
UpdInsData (fh, fhp, x, y, oldPri, newPri);
} else {
if (fh->debug==MagickTrue) printf (" notEdge\n");
//pn->priority = newPri;
// Pixel may be in queue, even though no longer an edge.
assert (1==0);
if (oldPri != newPri) UpdIfData (fh, fhp, x, y, oldPri, newPri);
}
}
static void CalcEdgeData (
FillHoleT * fh,
fhPriT * fhp
)
// Calculates confidence, energy and priority of all edge pixels,
// and insert them into queue.
{
ssize_t
x, y;
for (y = 0; y < fhp->height; y++) {
for (x = 0; x < fhp->width; x++) {
fhPriPixT * pn = &(fhp->fhPriPix[y][x]);
if (pn->pixState == psEdge) {
if (fh->debug==MagickTrue) printf ("ced %li,%li ", x, y);
CalcOnePixEdgeData (fh, fhp, x, y);
}
}
}
#if VERIFY==1
pqVerify (fhp, fh->debug);
#endif
}
static fhpValueT CalcEnergy (
const Image * image,
const VIEW_PIX_PTR * p1,
const VIEW_PIX_PTR * p2,
fhpValueT dist)
{
fhpValueT
d,
dMax;
dMax = fabs(GET_PIXEL_RED(image,p1) - GET_PIXEL_RED(image,p2));
d = fabs(GET_PIXEL_GREEN(image,p1) - GET_PIXEL_GREEN(image,p2));
if (dMax < d) dMax = d;
d = fabs(GET_PIXEL_BLUE(image,p1) - GET_PIXEL_BLUE(image,p2));
if (dMax < d) dMax = d;
dMax = dMax / (QuantumRange * dist) * (GET_PIXEL_ALPHA(image,p2) / QuantumRange);
return dMax;
}
/*===
static void CalcEdgePixelData (
FillHoleT * fh,
fhPriT * fhp)
// Edge pixels are exactly those in the priority queue.
{
int i;
for (i=0; i < fhp->pqNumNodes; i++) {
pqNodeT * pqn = &fhp->pqNodes[i];
CalcOnePixEdgeData (fh, fhp, pqn->x, pqn->y);
}
}
===*/
static MagickBooleanType Initialise (const Image *image,
FillHoleT * fh,
fhPriT * fhp,
ExceptionInfo *exception
)
{
ssize_t
x, y, xMult;
MagickBooleanType
status = MagickTrue;
CacheView
*image_view;
fhp->width = image->columns;
fhp->height = image->rows;
// Allocate memory
if (fh->do_verbose) printf ("Alloc %lix%li\n", fhp->width, fhp->height);
fhp->pqMaxNodes = fhp->height * fhp->width;
fhp->pqNodes = (pqNodeT *) AcquireQuantumMemory(fhp->pqMaxNodes, sizeof(pqNodeT));
if (fhp->pqNodes == (pqNodeT *) NULL) {
return MagickFalse;
}
fhp->pqNumNodes = 0;
fhp->fhPriPix = (fhPriPixT **) AcquireQuantumMemory(fhp->height, sizeof(*fhp->fhPriPix));
if (fhp->fhPriPix == (fhPriPixT **) NULL) {
RelinquishMagickMemory(fhp->pqNodes);
return MagickFalse;
}
for (y = 0; y < fhp->height; y++) {
fhp->fhPriPix[y] = (fhPriPixT *) AcquireQuantumMemory(fhp->width, sizeof(**fhp->fhPriPix));
if (fhp->fhPriPix[y] == (fhPriPixT *) NULL) break;
}
if (y < fhp->height) {
for (y--; y >= 0; y--) {
if (fhp->fhPriPix[y] != (fhPriPixT *) NULL)
fhp->fhPriPix[y] = (fhPriPixT *) RelinquishMagickMemory(fhp->fhPriPix[y]);
}
fhp->fhPriPix = (fhPriPixT **) RelinquishMagickMemory(fhp->fhPriPix);
RelinquishMagickMemory(fhp->pqNodes);
return MagickFalse;
}
xMult = Inc_ViewPixPtr (image);
// Populate values
InitAutoLimit (fh);
if (fh->do_verbose) printf ("Populate\n");
image_view = AcquireVirtualCacheView(image,exception);
#if defined(MAGICKCORE_OPENMP_SUPPORT)
#pragma omp parallel for schedule(static,4) shared(status) \
MAGICK_THREADS(image,image,image->rows,1)
#endif
for (y = 0; y < fhp->height; y++) {
VIEW_PIX_PTR const
*p, *pxy, *pup, *pdn;
if (status == MagickFalse)
continue;
// We use virtual pixels for energy calculation.
p=GetCacheViewVirtualPixels(image_view,-1,y-1,image->columns+2,3,exception);
if (p == (const VIEW_PIX_PTR *) NULL)
status=MagickFalse;
// 31 Jan 2015: FIXME: Eek. Following were wrong for v7?
pup = p + xMult;
pxy = p + xMult * (image->columns + 3);
pdn = p + xMult * (2*image->columns + 5);
for (x = 0; x < fhp->width; x++) {
fhpValueT
e;
//if (fh->debug==MagickTrue) printf ("Pop %i,%i ", (int)x, (int)y);
fhPriPixT * pn = &(fhp->fhPriPix[y][x]);
pn->confidence = GET_PIXEL_ALPHA(image,pxy)/QuantumRange;
pn->energy = CalcEnergy (image, pxy, pup-xMult, M_SQRT2);
e = CalcEnergy (image, pxy, pup, 1);
if (pn->energy < e) pn->energy = e;
e = CalcEnergy (image, pxy, pup+xMult, M_SQRT2);
if (pn->energy < e) pn->energy = e;
e = CalcEnergy (image, pxy, pxy-xMult, 1);
if (pn->energy < e) pn->energy = e;
e = CalcEnergy (image, pxy, pxy+xMult, 1);
if (pn->energy < e) pn->energy = e;
e = CalcEnergy (image, pxy, pdn-xMult, M_SQRT2);
if (pn->energy < e) pn->energy = e;
e = CalcEnergy (image, pxy, pdn, 1);
if (pn->energy < e) pn->energy = e;
e = CalcEnergy (image, pxy, pdn+xMult, M_SQRT2);
if (pn->energy < e) pn->energy = e;
//if (fh->debug==MagickTrue) printf (" e=%g\n", pn->energy);
if (pn->confidence <= 0) {
// FIXME? If any 8-connected neighbour is opaque, this is an edge.
if (GET_PIXEL_ALPHA(image,pup-xMult)>0
|| GET_PIXEL_ALPHA(image,pup)>0
|| GET_PIXEL_ALPHA(image,pup+xMult)>0
|| GET_PIXEL_ALPHA(image,pxy-xMult)>0
|| GET_PIXEL_ALPHA(image,pxy+xMult)>0
|| GET_PIXEL_ALPHA(image,pdn-xMult)>0
|| GET_PIXEL_ALPHA(image,pdn)>0
|| GET_PIXEL_ALPHA(image,pdn+xMult)>0)
{
pn->pixState = psEdge;
fh->Match.nLimitCnt++;
} else {
pn->pixState = psInner;
}
} else {
pn->pixState = psOpaq;
}
if (pn->confidence == 0 ) pn->energy = 0;
pn->priority = 0; // Until we know better.
// We can't find data for edge pixels until we know data for all edge neighbours.
// FIXME? following were wrong for v7.
p += xMult;
pup += xMult;
pxy += xMult;
pdn += xMult;
}
}
if (fh->debug==MagickTrue) printf ("fh->Match.nLimitCnt=%li\n", fh->Match.nLimitCnt);
image_view=DestroyCacheView(image_view);
pqVerify (fhp, fh->debug);
CalcEdgeData (fh, fhp);
//if (fh->debug==MagickTrue) pqDump (fh, fhp);
pqVerify (fhp, fh->debug);
//if (fh->debug==MagickTrue) DumpPriPix (fhp);
if (fh->do_verbose) printf ("End populate\n");
return (status);
}
static void deInitialise(
FillHoleT * fh,
fhPriT * fhp
)
{
ssize_t
y;
for (y = 0; y < fhp->height; y++) {
fhp->fhPriPix[y] = (fhPriPixT *) RelinquishMagickMemory(fhp->fhPriPix[y]);
}
fhp->fhPriPix = (fhPriPixT **) RelinquishMagickMemory(fhp->fhPriPix);
RelinquishMagickMemory(fhp->pqNodes);
}
static MagickBooleanType inline IsStateOpaque (
fhPriT * fhp,
ssize_t x,
ssize_t y)
{
if (x >= 0 && x < fhp->width &&
y >= 0 && y < fhp->height)
{
return fhp->fhPriPix[y][x].pixState == psOpaq;
}
return MagickFalse;
}
static void inline MakeEdgePix (
FillHoleT * fh,
fhPriT * fhp,
ssize_t x,
ssize_t y)
// If not outside image, and inner, set to edge and calc priority and add to queue.
// If it's already an edge, re-calc data and update queue,
{
if (fh->debug==MagickTrue) printf (" mep %li,%li\n", x, y);
if (x >= 0 && x < fhp->width &&
y >= 0 && y < fhp->height)
{
fhPriPixT * pn = &(fhp->fhPriPix[y][x]);
if (pn->pixState == psInner) {
// FIXME: only if 4-connected neighbours are opaque?
if (
IsStateOpaque (fhp, x, y-1) ||
IsStateOpaque (fhp, x-1, y) ||
IsStateOpaque (fhp, x+1, y) ||
IsStateOpaque (fhp, x, y+1)
/*=== ||
IsStateOpaque (fhp, x-1, y-1) ||
IsStateOpaque (fhp, x+1, y-1) ||
IsStateOpaque (fhp, x-1, y+1) ||
IsStateOpaque (fhp, x+1, y+1) ===*/
)
{
if (fh->debug==MagickTrue) printf (" mepI %li,%li\n", x, y);
pn->pixState = psEdge;
CalcOnePixEdgeData (fh, fhp, x, y);
// FIXME: again, batch entry probably quicker.
} else {
if (fh->debug==MagickTrue) printf (" mepU %li,%li no_opaque_neighbours", x, y);
}
} else if (pn->pixState == psEdge) {
if (fh->debug==MagickTrue) printf (" mepU %li,%li already_edge", x, y);
CalcOnePixEdgeData (fh, fhp, x, y);
}
}
}
static MagickBooleanType inline IsPixelOpaque (
Image * image,
CacheView * view,
ssize_t x,
ssize_t y,
ExceptionInfo *exception)
{
const VIEW_PIX_PTR
*src;
src = GetCacheViewVirtualPixels(view,x,y,1,1,exception);
if (src == (const VIEW_PIX_PTR *) NULL) {
printf ("bad src");
return MagickFalse;
}
return (GET_PIXEL_ALPHA (image, src) > 0);
}
static fhpValueT inline PixelEnergy (
Image * image,
CacheView * view,
ssize_t x,
ssize_t y,
ExceptionInfo *exception)
{
const VIEW_PIX_PTR
*src,
*this,
*other;
int
i;
src = GetCacheViewVirtualPixels(view,x-1,y-1,3,3,exception);
if (src == (const VIEW_PIX_PTR *) NULL) { printf ("bad src"); return MagickFalse; }
// FIXME: for v7
ssize_t xMult = Inc_ViewPixPtr (image);
other = src;
this = src + xMult*4;
fhpValueT e = 0;
for (i = 0; i < 9; i++) {
if (other != this && GET_PIXEL_ALPHA (image, other) > 0) {
fhpValueT v = CalcEnergy (image, this, other,
(i==0 || i==2 || i==6 || i==8) ? M_SQRT2 : 1);
if (e < v) e = v;
}
other += Inc_ViewPixPtr (image);
}
return (e);
}
static MagickBooleanType ProcessPixels (
Image *image,
Image *new_image,
FillHoleT * fh,
fhPriT * fhp,
MagickBooleanType *ChangedSome,
MagickBooleanType *unfilled,
ExceptionInfo *exception)
{
Image
*holed_image;
CacheView
*copy_inp_view,
*new_view;
ssize_t
x, y;
int
GotOne,
frameNum = 0;
MagickBooleanType
changedAny,
status=MagickTrue;
CopyWhereT
cw;
if (fh->copyWhat == copyWindow) {
cw.wi = fh->sqCopyDim;
cw.ht = fh->sqCopyDim;
} else {
cw.wi = 1;
cw.ht = 1;
}
// Clone the image, same size, copied pixels:
//
holed_image=CloneImage(image, 0, 0, MagickTrue, exception);
if (holed_image == (Image *) NULL)
return MagickFalse;
if (SetNoPalette (holed_image, exception) == MagickFalse)
return MagickFalse;
fh->CompWind.ref_image = image;
fh->CompWind.ref_view = AcquireVirtualCacheView (image, exception);
new_view = AcquireAuthenticCacheView (new_image, exception);
fh->CompWind.sub_image = new_image;
fh->CompWind.sub_view = new_view;
copy_inp_view = CloneCacheView (fh->CompWind.ref_view);
*ChangedSome = MagickFalse;
changedAny = MagickFalse;
*unfilled = MagickFalse;
//#if DEBUG==1
if (fh->debug==MagickTrue) printf ("ProcessPixels: start do\n");
//#endif
do {
GotOne = pqRemoveMax (fhp, &x, &y);
if (fh->debug==MagickTrue) printf ("GotOne? %i\n", GotOne);
if (GotOne)
{
assert (fhp->fhPriPix[y][x].pixState == psEdge);
if (fh->debug==MagickTrue) printf ("pp %li,%li\n", x, y);
changedAny = MagickFalse;
fh->Match.nLimitCnt--;
status = MatchAndCopy (
fh,
new_image,
new_view,
copy_inp_view,
x,
y,
&cw,
&frameNum,
unfilled,
&changedAny,
exception);
if (!changedAny) {
if (fh->debug==MagickTrue) printf (" Can't change %li,%li r=%i\n", x, y, (int)fh->CompWind.Reason);
fhp->fhPriPix[y][x].pixState = psCantFill;
} else {
ssize_t i, j;
if (fh->debug==MagickTrue)
printf (" doneMaC st=%i %lix%li+%li+%li\n", status, cw.wi, cw.ht, cw.dstX, cw.dstY);
*ChangedSome = MagickTrue;
// We may not have changed them all.
MagickBooleanType OpaqAny = MagickFalse;
if (fh->debug==MagickTrue) printf ("toOpaq ");
assert (cw.dstX >= 0 && cw.dstX+cw.wi <= fh->Width);
assert (cw.dstY >= 0 && cw.dstY+cw.ht <= fh->Height);
fhpValueT conf = 99;
for (j=0; j < cw.ht; j++) {
ssize_t adjY = cw.dstY+j;
for (i=0; i < cw.wi; i++) {
ssize_t adjX = cw.dstX+i;
fhPriPixT * pn = &(fhp->fhPriPix[adjY][adjX]);
if (pn->pixState != psOpaq) {
if (IsPixelOpaque (new_image, new_view, adjX, adjY, exception)) {
if (fh->debug==MagickTrue) printf (" %li,%li\n", adjX, adjY);
if (pn->pixState == psEdge && (adjX != x || adjY != y)) {
if (pqDelNode (fhp, adjX, adjY) < 0) status=MagickFalse;
fh->Match.nLimitCnt--;
}
pn->pixState = psOpaq; // FIXME: or "filled"?
if (pn->confidence > 0 && conf > pn->confidence) conf = pn->confidence;
// We will also update energy, at least of pixels on edge of copied block.
// We can only do this after we know which neighbours are opaque.
OpaqAny = MagickTrue;
}
}
}
}
if (fh->debug==MagickTrue) printf ("done toOpaq\n");
if (conf == 99) conf = 0.001;
else conf *= CONF_FACT;
if (OpaqAny) {
// Calc energies of pixels on edge of copied block.
// FIXME: only for cw.wi>1 or cw.ht>1?
// Possible optimisation: If a pixel is surrounded by opaque pixels,
// we will never care what energy it has.
if (fh->debug==MagickTrue) printf ("calcE\n");
for (i=0; i < cw.wi; i++) {
// Top
fhPriPixT * pn = &(fhp->fhPriPix[cw.dstY][cw.dstX+i]);
pn->energy = PixelEnergy (new_image, new_view, cw.dstX+i, cw.dstY, exception);
if (pn->confidence==0) pn->confidence = conf;
if (cw.ht > 1) {
// Bottom
pn = &(fhp->fhPriPix[cw.dstY+cw.ht-1][cw.dstX+i]);
pn->energy = PixelEnergy (new_image, new_view, cw.dstX+i, cw.dstY+cw.ht-1, exception);
if (pn->confidence==0) pn->confidence = conf;
}
}
for (j=1; j < cw.ht-1; j++) {
// Left
fhPriPixT * pn = &(fhp->fhPriPix[cw.dstY+j][cw.dstX]);
pn->energy = PixelEnergy (new_image, new_view, cw.dstX, cw.dstY+j, exception);
if (pn->confidence==0) pn->confidence = conf;
if (cw.wi > 1) {
// Right
pn = &(fhp->fhPriPix[cw.dstY+j][cw.dstX+cw.wi-1]);
pn->energy = PixelEnergy (new_image, new_view, cw.dstX+cw.wi-1, cw.dstY+j, exception);
if (pn->confidence==0) pn->confidence = conf;
}
}
// Walk around border outside the copied pixels.
// If not outside image, and inner, set to edge and calc priority and add to queue.
// But if we haven't changed them all, some of these may not really be edges.
if (fh->debug==MagickTrue) printf ("\nWalk border from %li,%li\n", cw.dstX-1, cw.dstY-1);
for (i=0; i < cw.wi+2; i++) {
MakeEdgePix (fh, fhp, cw.dstX-1+i, cw.dstY-1);
MakeEdgePix (fh, fhp, cw.dstX-1+i, cw.dstY+cw.ht);
}
for (j=0; j < cw.ht; j++) {
MakeEdgePix (fh, fhp, cw.dstX-1, cw.dstY+j);
MakeEdgePix (fh, fhp, cw.dstX+cw.wi, cw.dstY+j);
}
if (fh->debug==MagickTrue) printf ("End walk border\n");
// FIXME: should we also update data on pixels that are already edges, updating queue?
// I think so.
}
}
if (fh->debug==MagickTrue) printf ("\n");
}
//if (fh->Match.SetAutoLimit) printf ("fh->Match.nLimitCnt=%li\n", fh->Match.nLimitCnt);
if (fh->Match.SetAutoLimit && fh->Match.nLimitCnt <= 0) UseAutoLimit (fh);
} while (GotOne==1 && status==MagickTrue);
#if DEBUG==1
printf ("ProcessPixels: done do\n");
#endif
if (status==MagickFalse) printf ("** Bug: ProcessPixels: status==MagickFalse\n");
if (*unfilled) printf ("** Some pixels are not filled\n");
copy_inp_view = DestroyCacheView (copy_inp_view);
new_view = DestroyCacheView (new_view);
fh->CompWind.ref_view = DestroyCacheView (fh->CompWind.ref_view);
// srch_holed_view = DestroyCacheView (srch_holed_view);
// inp_view = DestroyCacheView (inp_view);
if (fh->debug==MagickTrue) printf ("unfilled=%i\n", *unfilled);
return status;
}
// The next function corresponds in style to functions in transform.c
// It takes one image, and returns an image with filled holes.
//
static Image *fillholespri (
Image *image,
FillHoleT * fh,
ExceptionInfo *exception)
{
Image
*new_image;
fhPriT
fhPri;
MagickBooleanType
ChangedSome,
unfilled;
ResolveImageParams (image, fh);
if (fh->debug==MagickTrue) printf ("sizeof(fhPriPixT)=%li sizeof(pqNodeT)=%li\n",
sizeof(fhPriPixT), sizeof(pqNodeT));
if (fh->debug==MagickTrue) printf ("Initialise ...");
if (Initialise (image, fh, &fhPri, exception) == MagickFalse)
return (Image *) NULL;
// Clone the image, same size, copied pixels:
//
new_image=CloneImage(image, 0, 0, MagickTrue, exception);
if (new_image == (Image *) NULL)
return(new_image);
if (fh->debug==MagickTrue) printf ("Process pixels ...");
ProcessPixels (image, new_image, fh, &fhPri, &ChangedSome, &unfilled, exception);
if (fh->debug==MagickTrue) printf ("Deinitialise ...\n");
deInitialise (fh, &fhPri);
while (unfilled && ChangedSome && fh->AutoRepeat) {
if (fh->do_verbose) printf ("fillholespri: doing it again.\n");
Image *tmp_copy = CloneImage(new_image, 0, 0, MagickTrue, exception);
if (tmp_copy == (Image *) NULL) return(tmp_copy);
if (Initialise (tmp_copy, fh, &fhPri, exception) == MagickFalse)
return (Image *) NULL;
ProcessPixels (tmp_copy, new_image, fh, &fhPri, &ChangedSome, &unfilled, exception);
tmp_copy = DestroyImage (tmp_copy);
if (fh->debug==MagickTrue) printf ("Deinitialise ...\n");
deInitialise (fh, &fhPri);
}
return (new_image);
}
ModuleExport size_t fillholespriImage (
Image **images,
const int argc,
const char **argv,
ExceptionInfo *exception)
{
Image
*image,
*new_image;
MagickBooleanType
status;
FillHoleT
fh;
assert(images != (Image **) NULL);
assert(*images != (Image *) NULL);
assert((*images)->signature == MAGICK_CORE_SIG);
status = menu (argc, argv, &fh);
if (status == MagickFalse)
return (-1);
InitRand (&fh);
for (image=(*images); image != (Image *) NULL; image=GetNextImageInList(image))
{
new_image = fillholespri (image, &fh, exception);
ReplaceImageInList(&image,new_image);
*images=GetFirstImageInList(image);
}
DeInitRand (&fh);
return(MagickImageFilterSignature);
}
/*
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);
}
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;
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;
}
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;
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;
}
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <assert.h>
#include <math.h>
#include "vsn_defines.h"
/* Updated:
3-April-2018 for v7.0.7-28
*/
/* Maybe see transform.c TransformImages and TransformImage.
*/
ModuleExport size_t appendlinesImage(Image **images,const int argc,
const char **argv,ExceptionInfo *exception)
{
(void) argc;
(void) argv;
Image
*image;
Image
*new_image,
*transform_images;
assert(images != (Image **) NULL);
assert(*images != (Image *) NULL);
assert((*images)->signature == MAGICK_CORE_SIG);
transform_images=NewImageList();
image=(*images);
for ( ; image != (Image *) NULL; image=GetNextImageInList(image))
{
CacheView
*image_view,
*out_view;
ssize_t
y;
MagickBooleanType
status;
VIEW_PIX_PTR
*q_out;
printf ("applines: in loop\n");
new_image=CloneImage(image, image->rows*image->columns, 1, MagickTrue, exception);
if (new_image == (Image *) NULL)
return(-1);
out_view=AcquireVirtualCacheView(new_image,exception); // FIXME: or authentic?
status=MagickTrue;
q_out=GetCacheViewAuthenticPixels(out_view,0,0,new_image->columns,1,exception);
if (q_out == (const VIEW_PIX_PTR *) NULL)
status=MagickFalse;
image_view=AcquireVirtualCacheView(image,exception);
#if defined(MAGICKCORE_OPENMP_SUPPORT)
#pragma omp parallel for schedule(static,4) shared(status) \
MAGICK_THREADS(image,image,image->rows,1)
#endif
for (y=0; y < (ssize_t) image->rows; y++)
{
register const VIEW_PIX_PTR
*p;
register ssize_t
x;
if (status == MagickFalse)
continue;
p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
if (p == (const VIEW_PIX_PTR *) NULL)
{
status=MagickFalse;
continue;
}
for (x=0; x < (ssize_t) image->columns; x++)
{
SET_PIXEL_RED (new_image,GET_PIXEL_RED(image,p), q_out);
SET_PIXEL_GREEN (new_image,GET_PIXEL_GREEN(image,p), q_out);
SET_PIXEL_BLUE (new_image,GET_PIXEL_BLUE(image,p), q_out);
// p++;
// q_out++;
p += Inc_ViewPixPtr (image);
q_out += Inc_ViewPixPtr (new_image);
}
if (SyncCacheViewAuthenticPixels(out_view,exception) == MagickFalse)
status=MagickFalse;
}
AppendImageToList(&transform_images,new_image);
image_view=DestroyCacheView(image_view);
out_view=DestroyCacheView(out_view);
}
*images=transform_images;
return(MagickImageFilterSignature);
}
/* Updated:
3-April-2018 for v7.0.7-28
18-July-2024 to remove include MagickCore/gem-private.h; use HSL instead of HSB.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <assert.h>
#include <math.h>
#include "vsn_defines.h"
#include "MagickCore/magick.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;
}
}
++*/
ConvertRGBToHSL(
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;
ConvertRGBToHSL(
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);
}
#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);
}
#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);
}
#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);
}
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include "vsn_defines.h"
// Updated:
// 19-August-2017 Use "virtual", not "authentic".
// The next function corresponds in style to functions in enhance.c
// It takes one image, and returns a status.
//
static MagickBooleanType onelightest(const Image *image,
ExceptionInfo *exception)
{
MagickBooleanType
status;
CacheView
*image_view;
ssize_t
y;
ssize_t
xLightest,
yLightest;
long double
val,
valLightest;
assert(image != (Image *) NULL);
assert(image->signature == MAGICK_CORE_SIG);
if (image->debug != MagickFalse)
(void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
image_view = AcquireVirtualCacheView(image,exception);
status = MagickTrue;
valLightest = 0;
xLightest = yLightest = 0;
for (y=0; y < (ssize_t) image->rows; y++)
{
VIEW_PIX_PTR const *p;
register ssize_t x;
if (status == MagickFalse) continue;
p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
if (p == (const VIEW_PIX_PTR *) NULL) status=MagickFalse;
for (x=0; x < (ssize_t) image->columns; x++) {
val = GetPixelIntensity(image, p);
if (valLightest < val) {
valLightest = val;
xLightest = x;
yLightest = y;
}
p += Inc_ViewPixPtr (image);
}
}
fprintf (stderr, "%lu,%lu\n", xLightest, yLightest);
image_view = DestroyCacheView (image_view);
return MagickTrue;
}
ModuleExport size_t onelightestImage(Image **images,const int argc,
const char **argv,ExceptionInfo *exception)
{
Image
*image;
MagickBooleanType
status;
(void) argc;
(void) argv;
assert(images != (Image **) NULL);
assert(*images != (Image *) NULL);
assert((*images)->signature == MAGICK_CORE_SIG);
for (image=(*images); image != (Image *) NULL; image=GetNextImageInList(image))
{
status = onelightest(image, exception);
if (status == MagickFalse)
continue;
}
assert (status == MagickTrue);
return(MagickImageFilterSignature);
}
#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);
}
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <ctype.h>
#include "vsn_defines.h"
/* Updated:
3-April-2018 for v7.0.7-28
*/
/* When an output pixel is outside the circle,
the value will come from the "-virtual-pixel" setting.
*/
typedef enum {
fullframe,
circular
} FormatT;
typedef struct {
double
ifov,
ofov,
rad;
FormatT
format;
MagickBooleanType
do_verbose;
} FishSpecT;
static void usage (void)
{
printf ("Usage: -process 'rect2eqfish [OPTION]...'\n");
printf ("From an rectilinear image, makes equidistant (linear) fisheye image.\n");
printf ("\n");
printf (" i, ifov N input field of view, default 120\n");
printf (" o, ofov N output field of view, default 180\n");
printf (" f, format string fullframe [default] or circular\n");
printf (" r, radius N output radius (overrides format)\n");
printf (" v, verbose write text information to stdout\n");
printf ("\n");
printf ("Fields of view are angles in degrees from a corner to opposite corner.\n");
}
static MagickBooleanType IsArg (char * pa, char * ShtOpt, char * LongOpt)
{
if ((LocaleCompare(pa, ShtOpt)==0) || (LocaleCompare(pa, LongOpt)==0))
return MagickTrue;
return MagickFalse;
}
static MagickBooleanType menu(
const int argc,
const char **argv,
FishSpecT * pfs
)
// Returns MagickTrue if okay.
{
int
i;
MagickBooleanType
status;
status = MagickTrue;
pfs->ifov = 120;
pfs->ofov = 180;
pfs->do_verbose = MagickFalse;
pfs->format = fullframe;
pfs->rad = 0;
for (i=0; i < argc; i++) {
char * pa = (char *)argv[i];
if (IsArg (pa, "i", "ifov")==MagickTrue) {
i++;
if (!isdigit ((int)*argv[i]))
{
fprintf (stderr, "Non-numeric argument to 'ifov'.\n");
status = MagickFalse;
}
else pfs->ifov = atof(argv[i]);
if (pfs->ifov <= 0 || pfs->ifov >= 180)
{
fprintf (stderr, "'ifov' must be > 0, < 180.\n");
status = MagickFalse;
}
} else if (IsArg (pa, "o", "ofov")==MagickTrue) {
i++;
if (!isdigit ((int)*argv[i]))
{
fprintf (stderr, "Non-numeric argument to 'ofov'.\n");
status = MagickFalse;
}
else pfs->ofov = atof(argv[i]);
if (pfs->ofov <= 0 || pfs->ofov > 180)
{
fprintf (stderr, "'ofov' must be > 0, <= 180.\n");
status = MagickFalse;
}
} else if (IsArg (pa, "r", "radius")==MagickTrue) {
i++;
if (!isdigit ((int)*argv[i]))
{
fprintf (stderr, "Non-numeric argument to 'radius'.\n");
status = MagickFalse;
}
else pfs->rad = atof(argv[i]);
if (pfs->rad <= 0)
{
fprintf (stderr, "'radius' must be > 0.\n");
status = MagickFalse;
}
} else if (IsArg (pa, "f", "format")==MagickTrue) {
i++;
char * pa = (char *)argv[i];
if (LocaleCompare(pa, "circular" )==0) pfs->format = circular;
else if (LocaleCompare(pa, "fullframe")==0) pfs->format = fullframe;
else {
fprintf (stderr, "rect2eqfish: ERROR: unknown 'format' [%s]\n", pa);
status = MagickFalse;
}
} else if (IsArg (pa, "v", "verbose")==MagickTrue) {
pfs->do_verbose = MagickTrue;
} else {
fprintf (stderr, "rect2eqfish: ERROR: unknown option [%s]\n", pa);
status = MagickFalse;
}
}
if (pfs->do_verbose) {
fprintf (stderr, "rect2eqfish options:");
if (pfs->do_verbose) fprintf (stderr, " verbose");
// FIXME: thoughout, %g should respect "-precision"
fprintf (stderr, " ifov %g", pfs->ifov);
fprintf (stderr, " ofov %g", pfs->ofov);
fprintf (stderr, " format ");
if (pfs->format == circular)
fprintf (stderr, "circular");
else if (pfs->format == fullframe)
fprintf (stderr, "fullframe");
else
fprintf (stderr, "??");
fprintf (stderr, "\n");
}
if (status == MagickFalse)
usage ();
return (status);
}
// The next function corresponds in style to functions in transform.c
// It takes one image, and returns an image.
//
static Image *rect2eqfish(const Image *image,
FishSpecT * pfs,
ExceptionInfo *exception)
{
Image
*new_image;
CacheView
*image_view,
*out_view;
ssize_t
y;
MagickBooleanType
status;
double
CentX,
CentY,
dim,
//dim2,
ifoc,
ofocinv;
assert(image != (Image *) NULL);
assert(image->signature == MAGICK_CORE_SIG);
if (image->debug != MagickFalse)
(void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
// Make a clone of this image, same size but undefined pixel values:
//
new_image=CloneImage(image, image->columns, image->rows, MagickTrue, exception);
if (new_image == (Image *) NULL)
return(new_image);
if (SetNoPalette (new_image, exception) == MagickFalse)
return (Image *)NULL;
image_view = AcquireVirtualCacheView(image,exception);
out_view = AcquireAuthenticCacheView(new_image,exception);
status = MagickTrue;
dim = 0.0;
if (pfs->format == circular) {
dim = image->columns;
if (dim > image->rows) dim = image->rows;
} else if (pfs->format == fullframe) {
dim = hypot (image->columns, image->rows);
}
if (pfs->rad > 0) dim = 2 * pfs->rad;
if (dim == 0.0) {
fprintf (stderr, "BUG");
return (Image *) NULL;
}
//dim2 = dim / 2.0; // But not used??
CentX = (image->columns - 1) / 2.0;
CentY = (image->rows - 1) / 2.0;
ifoc = dim / (2 * tan(pfs->ifov*M_PI/360));
ofocinv = (pfs->ofov*M_PI)/(dim * 180);
if (pfs->do_verbose) {
fprintf (stderr, "CentX=%g", CentX);
fprintf (stderr, " CentY=%g", CentY);
fprintf (stderr, " dim=%g", dim);
fprintf (stderr, " ifoc=%g", ifoc);
fprintf (stderr, " ofocinv=%g (1/%g)\n", ofocinv, 1.0 / ofocinv);
fprintf (stderr, "\nFX equivalent:\n");
fprintf (stderr, " -fx \"");
fprintf (stderr, "xd=i-%g;", CentX);
fprintf (stderr, "yd=j-%g;", CentY);
fprintf (stderr, "rd=hypot(xd,yd);");
fprintf (stderr, "phiang=%g*rd;", ofocinv);
// (phiang would have a different formula for other fisheye projections.)
fprintf (stderr, "rr=%g*tan(phiang);", ifoc);
fprintf (stderr, "xs=(rd?rr/rd:0)*xd+%g;", CentX);
fprintf (stderr, "ys=(rd?rr/rd:0)*yd+%g;", CentY);
//fprintf (stderr, "(rd>$dim2)?$bgc:u.p{xs,ys}";
fprintf (stderr, "u.p{xs,ys}\"\n");
}
#if defined(MAGICKCORE_OPENMP_SUPPORT)
#pragma omp parallel for schedule(static,4) shared(status) \
MAGICK_THREADS(new_image,new_image,new_image->rows,1)
#endif
for (y=0; y < (ssize_t) new_image->rows; y++)
{
register VIEW_PIX_PTR
*q;
register ssize_t
x;
if (status == MagickFalse)
continue;
q=GetCacheViewAuthenticPixels(out_view,0,y,new_image->columns,1,exception);
if (q == (const VIEW_PIX_PTR *) NULL)
{
status=MagickFalse;
continue;
}
for (x=0; x < (ssize_t) image->columns; x++)
{
double
xd,
yd,
phi,
rd,
rr,
mult,
xs,
ys;
PIX_INFO
src_pixel;
xd = x - CentX;
yd = y - CentY;
rd = hypot (xd, yd);
phi = ofocinv * rd;
// (phi would have a different formula for other fisheye projections.)
rr = ifoc * tan(phi);
if (rd == 0) {
xs = CentX;
ys = CentY;
} else {
mult = rr / rd;
xs = CentX + mult * xd;
ys = CentY + mult * yd;
}
#if IMV6OR7==6
InterpolateMagickPixelPacket (
image, image_view,
image->interpolate,
xs, ys, &src_pixel, exception);
#else
InterpolatePixelInfo (
image, image_view,
image->interpolate,
xs, ys, &src_pixel, exception);
#endif
SET_PIXEL_RED (new_image, src_pixel.red, q);
SET_PIXEL_GREEN (new_image, src_pixel.green, q);
SET_PIXEL_BLUE (new_image, src_pixel.blue, q);
#if IMV6OR7==6
SET_PIXEL_ALPHA (new_image, QuantumRange - src_pixel.opacity, q);
#else
SET_PIXEL_ALPHA (new_image, src_pixel.alpha, q);
#endif
q += Inc_ViewPixPtr (new_image);
}
if (SyncCacheViewAuthenticPixels(out_view,exception) == MagickFalse)
status=MagickFalse;
}
out_view=DestroyCacheView(out_view);
image_view=DestroyCacheView(image_view);
return (new_image);
}
/*
We replace each image as we come across it.
An alternative technique is to create a new list, and add new images to it;
at the end, replace the original list with the new one.
That alternative is slightly more complex
and needs more memory as it would hold all input and output images in memory.
*/
ModuleExport size_t rect2eqfishImage(Image **images,const int argc,
const char **argv,ExceptionInfo *exception)
{
Image
*image,
*new_image;
MagickBooleanType
status;
FishSpecT
fs;
assert(images != (Image **) NULL);
assert(*images != (Image *) NULL);
assert((*images)->signature == MAGICK_CORE_SIG);
status = menu (argc, argv, &fs);
if (status == MagickFalse)
return (-1);
for (image=(*images); image != (Image *) NULL; image=GetNextImageInList(image))
{
new_image = rect2eqfish(image, &fs, exception);
if (new_image == (Image *) NULL)
return(-1);
ReplaceImageInList(&image,new_image);
// Replace messes up the images pointer. Make it good:
*images=GetFirstImageInList(image);
}
return(MagickImageFilterSignature);
}
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include "vsn_defines.h"
// Updated:
// 16-September-2016 Output "-" for pixels with alpha < 50%.
// 6-September-2017 for v7.
// The next function corresponds in style to functions in enhance.c
// It takes one image, and returns a status.
//
static MagickBooleanType WriteKernel(Image *image,
ExceptionInfo *exception)
{
CacheView
*image_view;
ssize_t
y;
MagickBooleanType
status;
int
precision;
assert(image != (Image *) NULL);
assert(image->signature == MAGICK_CORE_SIG);
if (image->debug != MagickFalse)
(void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
precision = GetMagickPrecision();
if (SetNoPalette (image, exception) == MagickFalse)
return MagickFalse;
status=MagickTrue;
image_view=AcquireAuthenticCacheView(image,exception);
#define OUTFILE stdout
fprintf (OUTFILE, "%ix%i:",
(int)image->columns, (int)image->rows);
long double SemiQuantum = QuantumRange / 2.0;
for (y=0; y < (ssize_t) image->rows; y++)
{
register const VIEW_PIX_PTR
*p;
register ssize_t
x;
if (status == MagickFalse)
continue;
p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
if (p == (const VIEW_PIX_PTR *) NULL)
{
status=MagickFalse;
continue;
}
for (x=0; x < (ssize_t) image->columns; x++)
{
if (x || y) fprintf (OUTFILE, ",");
if (IS_ALPHA_CH(image) && GET_PIXEL_ALPHA(image,p) < SemiQuantum) {
fprintf (OUTFILE, "-");
} else {
fprintf (OUTFILE, "%.*g",
precision, GetPixelIntensity(image, p) / QuantumRange);
}
p += Inc_ViewPixPtr (image);
}
}
fprintf (OUTFILE, "\n");
image_view=DestroyCacheView(image_view);
return(status);
}
// Style of next is for "-process".
// It loops through all images in a list, processing each in-place.
//
ModuleExport size_t img2knlImage(Image **images,const int argc,
const char **argv,ExceptionInfo *exception)
{
Image
*image;
MagickBooleanType
status;
(void) argc;
(void) argv;
assert(images != (Image **) NULL);
assert(*images != (Image *) NULL);
assert((*images)->signature == MAGICK_CORE_SIG);
for (image=(*images); image != (Image *) NULL; image=GetNextImageInList(image))
{
status = WriteKernel (image,exception);
if (status == MagickFalse)
continue;
}
assert (status == MagickTrue);
return(MagickImageFilterSignature);
}
#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);
}
#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);
}
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <assert.h>
#include <math.h>
#include <ctype.h>
#include "vsn_defines.h"
// Latest update:
// 27 September 2015
// 1 February 2016: for v7.
// 3 April 2018 for v7.0.7-28
// 9 April 2018 added log option.
// 14 March 2020 Limit processing to 1, 2 or 3 channels.
typedef struct {
int
cap_numbuckets;
int
mult;
MagickBooleanType
do_alpha,
do_log,
do_cumul,
do_norm,
do_verbose;
} mkhistoT;
/* Maybe see transform.c TransformImages and TransformImage.
*/
#define DEFAULT_CAP 65536
static void usage (void)
{
printf ("Usage: -process 'mkhisto [OPTION]...'\n");
printf ("Make a histogram image.\n");
printf ("\n");
printf (" b, capnumbuckets N limit number of buckets to N\n");
printf (" m, multiply N multiply counts by N\n");
printf (" r, regardalpha pre-multiply count by alpha\n");
printf (" l, log use logs of counts\n");
printf (" c, cumul make cumulative histogram\n");
printf (" n, norm normalise output so maximum count is quantum\n");
printf (" v, verbose write text information to stdout\n");
printf ("\n");
printf ("Default capnumbuckets is %i.\n", DEFAULT_CAP);
printf ("Default multiply is 1.\n");
}
static MagickBooleanType IsArg (char * pa, char * ShtOpt, char * LongOpt)
{
if ((LocaleCompare(pa, ShtOpt)==0) || (LocaleCompare(pa, LongOpt)==0))
return MagickTrue;
return MagickFalse;
}
static MagickBooleanType menu(
const int argc,
const char **argv,
mkhistoT * mkh
)
// Returns MagickTrue if okay.
{
int
i;
MagickBooleanType
status;
status = MagickTrue;
mkh->cap_numbuckets = DEFAULT_CAP;
mkh->mult = 1;
mkh->do_norm =
mkh->do_alpha =
mkh->do_cumul =
mkh->do_verbose =
mkh->do_log =
MagickFalse;
//printf ("mkhisto argc=%i\n", argc);
for (i=0; i < argc; i++) {
char * pa = (char *)argv[i];
//printf ("Arg %i [%s]\n", i, pa);
if (IsArg (pa, "n", "norm")==MagickTrue) {
mkh->do_norm = MagickTrue;
} else if (IsArg (pa, "r", "regardalpha")==MagickTrue) {
mkh->do_alpha = MagickTrue;
} else if (IsArg (pa, "l", "log")==MagickTrue) {
mkh->do_log = MagickTrue;
} else if (IsArg (pa, "c", "cumul")==MagickTrue) {
mkh->do_cumul = MagickTrue;
} else if (IsArg (pa, "b", "capnumbuckets")==MagickTrue) {
i++;
if (!isdigit ((int)*argv[i]))
{
fprintf (stderr, "Non-numeric argument to 'capnumbuckets'.\n");
status = MagickFalse;
}
else mkh->cap_numbuckets = atoi (argv[i]);
} else if (IsArg (pa, "m", "multiply")==MagickTrue) {
i++;
if (!isdigit ((int)*argv[i]))
{
fprintf (stderr, "Non-numeric argument to 'multiply'.\n");
status = MagickFalse;
}
else mkh->mult = atoi (argv[i]);
} else if (IsArg (pa, "v", "verbose")==MagickTrue) {
mkh->do_verbose = MagickTrue;
} else {
fprintf (stderr, "mkhisto: ERROR: unknown option [%s]\n", pa);
status = MagickFalse;
}
}
if (mkh->cap_numbuckets < 1)
status = MagickFalse;
if (mkh->do_verbose) {
fprintf (stderr,
"mkhisto options: capnumbuckets %i multiply %i ",
mkh->cap_numbuckets, mkh->mult);
if (mkh->do_alpha) fprintf (stderr, "regardalpha ");
if (mkh->do_log) fprintf (stderr, "log ");
if (mkh->do_cumul) fprintf (stderr, "cumul ");
if (mkh->do_norm) fprintf (stderr, "norm ");
if (mkh->do_verbose) fprintf (stderr, "verbose ");
fprintf (stderr, "\n");
}
if (status == MagickFalse)
usage ();
return (status);
}
static MagickBooleanType MultiplyColor (
const Image *new_image,
CacheView *out_view,
long double mult_fact,
ExceptionInfo *exception)
/* For all pixels, multiply RGB channels by a factor.
*/
{
ssize_t
y;
MagickBooleanType
status = MagickTrue;
int numChannels = NumColChannels (new_image);
if (mult_fact != 1.0) {
#if defined(MAGICKCORE_OPENMP_SUPPORT)
#pragma omp parallel for schedule(static,4) shared(status)\
MAGICK_THREADS(new_image,new_image,new_image->rows,1)
#endif
for (y=0; y < (ssize_t) new_image->rows; y++)
{
register VIEW_PIX_PTR
*q;
register ssize_t
x;
if (status == MagickFalse)
continue;
q=GetCacheViewAuthenticPixels(out_view,0,y,new_image->columns,1,exception);
if (q == (const VIEW_PIX_PTR *) NULL)
status=MagickFalse;
for (x=0; x < (ssize_t) new_image->columns; x++)
{
#if defined(MAGICKCORE_HDRI_SUPPORT)
SET_PIXEL_RED (new_image,
GET_PIXEL_RED (new_image, q) * mult_fact,
q);
if (numChannels >= 2) SET_PIXEL_GREEN (new_image,
GET_PIXEL_GREEN (new_image, q) * mult_fact,
q);
if (numChannels >= 3) SET_PIXEL_BLUE (new_image,
GET_PIXEL_BLUE (new_image, q) * mult_fact,
q);
#else
SET_PIXEL_RED (new_image,
GET_PIXEL_RED (new_image, q) * mult_fact + 0.5,
q);
if (numChannels >= 2) SET_PIXEL_GREEN (new_image,
GET_PIXEL_GREEN (new_image, q) * mult_fact + 0.5,
q);
if (numChannels >= 3) SET_PIXEL_BLUE (new_image,
GET_PIXEL_BLUE (new_image, q) * mult_fact + 0.5,
q);
#endif
q += Inc_ViewPixPtr (new_image);
}
if (SyncCacheViewAuthenticPixels(out_view,exception) == MagickFalse)
status=MagickFalse;
}
}
return (status);
}
static MagickBooleanType CumulateColor (
const Image *image,
CacheView *out_view,
long double mult_fact,
ExceptionInfo *exception)
/* For all pixels, cumulate RGB channels.
The accumulation is across the entire image, not each row separately.
*/
{
ssize_t
y;
MagickBooleanType
status = MagickTrue;
int numChannels = NumColChannels (image);
long double
cumul_red, cumul_green, cumul_blue;
cumul_red = cumul_green = cumul_blue = 0;
// I suppose multi-threading wouldn't work here.
//
//#if defined(MAGICKCORE_OPENMP_SUPPORT)
// #pragma omp parallel for schedule(static,4) shared(status)
// MAGICK_THREADS(image,image,image->rows,1)
//#endif
for (y=0; y < (ssize_t) image->rows; y++)
{
register VIEW_PIX_PTR
*q;
register ssize_t
x;
if (status == MagickFalse)
continue;
q=GetCacheViewAuthenticPixels(out_view,0,y,image->columns,1,exception);
if (q == (const VIEW_PIX_PTR *) NULL)
status=MagickFalse;
for (x=0; x < (ssize_t) image->columns; x++)
{
cumul_red += GET_PIXEL_RED (image, q);
cumul_green += GET_PIXEL_GREEN (image, q);
cumul_blue += GET_PIXEL_BLUE (image, q);
SET_PIXEL_RED (image, mult_fact * cumul_red, q);
if (numChannels >= 2) SET_PIXEL_GREEN (image, mult_fact * cumul_green, q);
if (numChannels >= 3) SET_PIXEL_BLUE (image, mult_fact * cumul_blue, q);
q += Inc_ViewPixPtr (image);
}
if (SyncCacheViewAuthenticPixels(out_view,exception) == MagickFalse)
status=MagickFalse;
}
return (status);
}
#if defined(MAGICKCORE_HDRI_SUPPORT)
#define ADD_HALF
#else
#define ADD_HALF +0.5
#endif
// The next function corresponds in style to functions in transform.c
// It takes one image, and returns an image.
//
static Image *mkhImage(const Image *image,
mkhistoT * pmkh,
ExceptionInfo *exception)
{
Image
*new_image;
CacheView
*image_view,
*out_view;
ssize_t
y,
NumBuckets,
outXmult;
MagickBooleanType
status;
VIEW_PIX_PTR
*q_out;
MagickRealType
BucketSize;
long double
Increment,
value,
min_value,
max_value,
sum_red,
sum_green,
sum_blue;
int
precision;
int numChannels = NumColChannels (image);
if (image->debug != MagickFalse)
(void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
if (pmkh->do_verbose) {
fprintf (stderr, "mkhisto: Input image [%s] depth is %i\n",
image->filename, (int)image->depth);
}
precision = GetMagickPrecision();
NumBuckets = exp2 (image->depth);
if (NumBuckets > pmkh->cap_numbuckets) NumBuckets = pmkh->cap_numbuckets;
Increment = pmkh->mult;
BucketSize = exp2 (MAGICKCORE_QUANTUM_DEPTH) /(MagickRealType)NumBuckets;
if (pmkh->do_verbose) {
fprintf (stderr, " NumBuckets %i BucketSize %.*g\n",
(int)NumBuckets, precision, (double)BucketSize);
}
new_image=CloneImage(image, NumBuckets, 1, MagickTrue, exception);
if (new_image == (Image *) NULL)
return(new_image);
// FIXME: set new_image depth to Quantum?
// new_image->depth=MAGICKCORE_QUANTUM_DEPTH;
if (SetNoPalette (new_image, exception) == MagickFalse)
return (Image *)NULL;
SetAllBlack (new_image, exception);
#if IMV6OR7==6
outXmult = 1;
#else
outXmult = GetPixelChannels (new_image);
#endif
out_view=AcquireAuthenticCacheView(new_image,exception);
status=MagickTrue;
q_out=GetCacheViewAuthenticPixels(out_view,0,0,new_image->columns,1,exception);
if (q_out == (const VIEW_PIX_PTR *) NULL) {
fprintf (stderr, "mkhisto: bad GetCacheViewAuthenticPixels out_view\n");
status=MagickFalse;
}
image_view=AcquireVirtualCacheView(image,exception);
sum_red = sum_green = sum_blue = 0.0;
max_value = -QuantumRange;
min_value = QuantumRange + 1.0;
/* I'm unsure about the multi-threading.
What if two threads want to update the same histogram bucket,
and sum_red etc?
*/
//#if defined(MAGICKCORE_OPENMP_SUPPORT)
// #pragma omp parallel for schedule(static,4) shared(status)
// MAGICK_THREADS(image,image,image->rows,1)
//#endif
for (y=0; y < (ssize_t) image->rows; y++)
{
register const VIEW_PIX_PTR
*p;
ssize_t
x,
out_x;
if (status == MagickFalse)
continue;
p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
if (p == (const VIEW_PIX_PTR *) NULL)
{
fprintf (stderr, "bad GetCacheViewAuthenticPixels image_view\n");
status=MagickFalse;
continue;
}
for (x=0; x < (ssize_t) image->columns; x++)
{
if (pmkh->do_alpha) {
Increment =
(pmkh->mult * GET_PIXEL_ALPHA(image,p) / (long double)QuantumRange) ADD_HALF ;
}
out_x = (GET_PIXEL_RED(image,p) / BucketSize);
if (out_x >= NumBuckets) out_x = NumBuckets-1;
else if (out_x < 0) out_x = 0;
out_x *= outXmult;
value = GET_PIXEL_RED(image,q_out+out_x)+Increment;
if (min_value > value) min_value = value;
if (max_value < value) max_value = value;
sum_red += Increment;
SET_PIXEL_RED (new_image, value, q_out+out_x);
if (numChannels >= 2) {
out_x = (GET_PIXEL_GREEN(image,p) / BucketSize);
if (out_x >= NumBuckets) out_x = NumBuckets-1;
else if (out_x < 0) out_x = 0;
out_x *= outXmult;
value = GET_PIXEL_GREEN(image,q_out+out_x)+Increment;
if (min_value > value) min_value = value;
if (max_value < value) max_value = value;
sum_green += Increment;
SET_PIXEL_GREEN (new_image, value, q_out+out_x);
}
if (numChannels >= 3) {
out_x = (GET_PIXEL_BLUE(image,p) / BucketSize);
if (out_x >= NumBuckets) out_x = NumBuckets-1;
else if (out_x < 0) out_x = 0;
out_x *= outXmult;
value = GET_PIXEL_BLUE(image,q_out+out_x)+Increment;
if (min_value > value) min_value = value;
if (max_value < value) max_value = value;
sum_blue += Increment;
SET_PIXEL_BLUE (new_image, value, q_out+out_x);
}
p += Inc_ViewPixPtr (image);
}
if (SyncCacheViewAuthenticPixels(out_view,exception) == MagickFalse) {
fprintf (stderr, "bad sync\n");
status=MagickFalse;
}
}
if (pmkh->do_log) {
ssize_t
x;
max_value = -QuantumRange;
min_value = QuantumRange + 1.0;
VIEW_PIX_PTR *ql = q_out;
for (x=0; x < (ssize_t) new_image->columns; x++)
{
value = GET_PIXEL_RED(new_image, ql);
value = (value <= 1) ? 0 : log(value);
if (min_value > value) min_value = value;
if (max_value < value) max_value = value;
SET_PIXEL_RED (new_image, value, ql);
if (numChannels >= 2) {
value = GET_PIXEL_GREEN(new_image, ql);
value = (value <= 1) ? 0 : log(value);
if (min_value > value) min_value = value;
if (max_value < value) max_value = value;
SET_PIXEL_GREEN (new_image, value, ql);
}
if (numChannels >= 3) {
value = GET_PIXEL_BLUE(new_image, ql);
value = (value <= 1) ? 0 : log(value);
if (min_value > value) min_value = value;
if (max_value < value) max_value = value;
SET_PIXEL_BLUE (new_image, value, ql);
}
ql += Inc_ViewPixPtr (new_image);
}
sum_red = (sum_red <= 1) ? 0 : log(sum_red);
sum_green = (sum_green <= 1) ? 0 : log(sum_green);
sum_blue = (sum_blue <= 1) ? 0 : log(sum_blue);
if (SyncCacheViewAuthenticPixels(out_view,exception) == MagickFalse) {
fprintf (stderr, "bad sync\n");
status=MagickFalse;
}
}
if (pmkh->do_verbose) {
fprintf (stderr, " counts: min_value %.*g, max_value %.*g\n",
precision, (double)min_value,
precision, (double)max_value);
fprintf (stderr, " sum_red %.*g, sum_green %.*g, sum_blue %.*g\n",
precision, (double)sum_red,
precision, (double)sum_green,
precision, (double)sum_blue);
}
#if !defined(MAGICKCORE_HDRI_SUPPORT)
if (max_value >= (long double)QuantumRange) {
fprintf (stderr, "Warning: max_value >= QuantumRange (%.*g >= %.*g)\n",
(double)max_value, (double)QuantumRange);
}
#endif
// FIXME: but we don't care about min_value??
// As required: cumulate, normalise or both.
//
if (pmkh->do_cumul==MagickTrue && pmkh->do_norm==MagickTrue) {
long double
max_sum,
mult_fact;
max_sum = sum_red;
if (max_sum < sum_green) max_sum = sum_green;
if (max_sum < sum_blue) max_sum = sum_blue;
if (max_sum != 0.0) {
mult_fact = QuantumRange / max_sum;
if (pmkh->do_verbose) {
fprintf (stderr, " Cumulating and normalising...\n");
fprintf (stderr, " max_sum=%.*g mult_fact=%.*g\n",
precision, (double)max_sum,
precision, (double)mult_fact);
}
status = CumulateColor (new_image, out_view, mult_fact, exception);
}
} else if (pmkh->do_norm==MagickTrue) {
long double
mult_fact;
if (pmkh->do_verbose) {
fprintf (stderr, " Normalising...\n");
}
if (max_value != 0.0) {
mult_fact = QuantumRange / max_value;
if (pmkh->do_verbose) {
fprintf (stderr, " max_value=%.*g mult_fact=%.*g\n",
precision, (double)max_value,
precision, (double)mult_fact);
}
MultiplyColor (new_image, out_view, mult_fact, exception);
}
} else if (pmkh->do_cumul==MagickTrue) {
if (pmkh->do_verbose) {
fprintf (stderr, " Cumulating...\n");
}
status = CumulateColor (new_image, out_view, 1.0, exception);
}
image_view=DestroyCacheView(image_view);
out_view=DestroyCacheView(out_view);
return (new_image);
}
ModuleExport size_t mkhistoImage(Image **images,const int argc,
const char **argv,ExceptionInfo *exception)
{
Image
*image,
*new_image;
MagickBooleanType
status;
mkhistoT
mkh;
assert(images != (Image **) NULL);
assert(*images != (Image *) NULL);
assert((*images)->signature == MAGICK_CORE_SIG);
status = menu (argc, argv, &mkh);
if (status == MagickFalse)
return (-1);
for (image=(*images); image != (Image *) NULL; image=GetNextImageInList(image))
{
new_image = mkhImage (image, &mkh, exception);
ReplaceImageInList(&image,new_image);
*images=GetFirstImageInList(image);
}
return(MagickImageFilterSignature);
}
/* This needs more work.
7-October-2014 Cope with multiple lines of output.
1-February-2016 For v7.
3-April-2018 for v7.0.7-28
27-September-2023 change (image->columns) to (image->columns-1).
*/
#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"
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);
}
// 27-September-2023 change (image->columns) to (image->columns-1).
quant_over_width = QuantumRange/(long double)(image->columns-1);
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);
}
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include "vsn_defines.h"
/*
Latest update:
1-Feb-2016 for V7.
20-July-2017 added regardalpha.
3-April-2018 for v7.0.7-28
*/
typedef struct {
MagickBooleanType
do_regardalpha,
do_decumul,
do_norm,
do_verbose;
} cumulhistoT;
static void usage (void)
{
printf ("Usage: -process 'cumulhisto [OPTION]...'\n");
printf ("Cumulate (or decumulate) a histogram image.\n");
printf ("\n");
printf (" r, regardalpha pre-multiply RGB values by alpha\n");
printf (" d, decumul de-cumulate histogram\n");
printf (" n, norm normalise output so maximum count is quantum\n");
printf (" v, verbose write text information to stdout\n");
printf ("\n");
}
static MagickBooleanType IsArg (char * pa, char * ShtOpt, char * LongOpt)
{
if ((LocaleCompare(pa, ShtOpt)==0) || (LocaleCompare(pa, LongOpt)==0))
return MagickTrue;
return MagickFalse;
}
static MagickBooleanType menu(
const int argc,
const char **argv,
cumulhistoT * pch
)
// Returns MagickTrue if okay.
{
int
i;
MagickBooleanType
status;
status = MagickTrue;
pch->do_regardalpha =
pch->do_decumul =
pch->do_norm =
pch->do_verbose = MagickFalse;
for (i=0; i < argc; i++) {
char * pa = (char *)argv[i];
if (IsArg (pa, "r", "regardalpha")==MagickTrue) {
pch->do_regardalpha = MagickTrue;
} else if (IsArg (pa, "n", "norm")==MagickTrue) {
pch->do_norm = MagickTrue;
} else if (IsArg (pa, "d", "decumul")==MagickTrue) {
pch->do_decumul = MagickTrue;
} else if (IsArg (pa, "v", "verbose")==MagickTrue) {
pch->do_verbose = MagickTrue;
} else {
fprintf (stderr, "cumulhisto: ERROR: unknown option\n");
status = MagickFalse;
}
}
if (pch->do_verbose) {
fprintf (stderr, "cumulhisto options: ");
if (pch->do_regardalpha) fprintf (stderr, "regardalpha ");
if (pch->do_decumul) fprintf (stderr, "decumul ");
if (pch->do_norm) fprintf (stderr, "norm ");
if (pch->do_verbose) fprintf (stderr, "verbose ");
fprintf (stderr, "\n");
}
if (status == MagickFalse)
usage ();
return (status);
}
#if defined(MAGICKCORE_HDRI_SUPPORT)
#define ADD_HALF
#else
#define ADD_HALF +0.5
#endif
// The next function corresponds in style to functions in transform.c
// It takes one image, and returns an image.
//
static Image *cumulhist (
const Image *image,
cumulhistoT * pch,
ExceptionInfo *exception)
{
Image
*new_image;
CacheView
*image_view,
*out_view;
ssize_t
y,
inXmult;
MagickBooleanType
status;
if (image->debug != MagickFalse)
(void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
if (pch->do_verbose) {
fprintf (stderr, "cumulhisto: Input image [%s] depth is %i\n",
image->filename, (int)image->depth);
}
// De-cumul needs a new image.
// Without this option, we could over-write the existing image.
new_image=CloneImage(image, 0, 0, MagickTrue, exception);
if (new_image == (Image *) NULL)
return(new_image);
// Just in case image had a palette.
if (SetNoPalette (new_image, exception) == MagickFalse)
return (Image *)NULL;
SetAllBlack (new_image, exception);
//#if IMV6OR7==6
// inXmult = 1;
// outXmult = 1;
//#else
// inXmult = GetPixelChannels (image);
// outXmult = GetPixelChannels (new_image);
//#endif
inXmult = Inc_ViewPixPtr (image);
status=MagickTrue;
out_view=AcquireAuthenticCacheView(new_image,exception);
image_view=AcquireVirtualCacheView(image,exception);
#if defined(MAGICKCORE_OPENMP_SUPPORT)
#pragma omp parallel for schedule(static,4) shared(status) \
MAGICK_THREADS(image,image,image->rows,1)
#endif
for (y=0; y < (ssize_t) image->rows; y++)
{
const VIEW_PIX_PTR
*pSave,
*p;
VIEW_PIX_PTR
*q_out;
ssize_t
x;
long double
mult_fact,
alpha;
if (status == MagickFalse)
continue;
p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
pSave = p;
if (p == (const VIEW_PIX_PTR *) NULL)
{
fprintf (stderr, "bad GetCacheViewVirtualPixels image_view\n");
status=MagickFalse;
continue;
}
q_out=GetCacheViewAuthenticPixels(out_view,0,y,new_image->columns,1,exception);
if (q_out == (const VIEW_PIX_PTR *) NULL) {
fprintf (stderr, "cumulhisto: bad GetCacheViewAuthenticPixels out_view\n");
status=MagickFalse;
}
mult_fact = 1.0;
alpha = 1.0;
// FIXME: we need first pass only if normalising.
if (pch->do_decumul) {
long double
diff,
max_diff;
max_diff = 0;
p += Inc_ViewPixPtr (image);
for (x=1; x < (ssize_t) image->columns; x++)
{
// FIXME: subtract xfactor.
diff = GET_PIXEL_RED(image,p) - GET_PIXEL_RED(image,p-inXmult);
if (max_diff < diff) max_diff = diff;
diff = GET_PIXEL_GREEN(image,p) - GET_PIXEL_GREEN(image,p-inXmult);
if (max_diff < diff) max_diff = diff;
diff = GET_PIXEL_BLUE(image,p) - GET_PIXEL_BLUE(image,p-inXmult);
if (max_diff < diff) max_diff = diff;
p += Inc_ViewPixPtr (image);
}
if (pch->do_verbose) {
fprintf (stderr, " row=%i max_diff=%g\n",
(int)y,
(double)max_diff);
}
if (pch->do_norm && max_diff != 0.0) {
mult_fact = QuantumRange / max_diff;
if (pch->do_verbose) {
fprintf (stderr, " mult_fact=%g\n",
(double)mult_fact);
}
}
} else {
// We are not decumulating.
long double
sum_red,
sum_green,
sum_blue,
max_sum;
sum_red = sum_green = sum_blue = 0.0;
for (x=0; x < (ssize_t) image->columns; x++)
{
if (pch->do_regardalpha)
alpha = GET_PIXEL_ALPHA(image,p) / (long double)QuantumRange;
sum_red += alpha * GET_PIXEL_RED(image,p);
sum_green += alpha * GET_PIXEL_GREEN(image,p);
sum_blue += alpha * GET_PIXEL_BLUE(image,p);
p += Inc_ViewPixPtr (image);
}
max_sum = sum_red;
if (max_sum < sum_green) max_sum = sum_green;
if (max_sum < sum_blue) max_sum = sum_blue;
if (pch->do_verbose) {
fprintf (stderr, " row=%i sum_red=%g sum_green=%g sum_blue=%g\n",
(int)y,
(double)sum_red, (double)sum_green, (double)sum_blue);
}
if (pch->do_norm && max_sum != 0.0) {
mult_fact = QuantumRange / max_sum;
}
if (pch->do_verbose) {
fprintf (stderr, " max_sum=%g mult_fact=%g\n",
(double)max_sum, (double)mult_fact);
}
}
p = pSave;
if (pch->do_decumul) {
SET_PIXEL_RED (new_image, GET_PIXEL_RED(image,p) * mult_fact ADD_HALF,q_out);
SET_PIXEL_GREEN (new_image, GET_PIXEL_GREEN(image,p) * mult_fact ADD_HALF,q_out);
SET_PIXEL_BLUE (new_image, GET_PIXEL_BLUE(image,p) * mult_fact ADD_HALF,q_out);
p += Inc_ViewPixPtr (image);
q_out += Inc_ViewPixPtr (new_image);
for (x=1; x < (ssize_t) image->columns; x++)
{
SET_PIXEL_RED
(new_image,
(GET_PIXEL_RED(image,p) - GET_PIXEL_RED(image,p-inXmult)) * mult_fact ADD_HALF,
q_out);
SET_PIXEL_GREEN
(new_image,
(GET_PIXEL_GREEN(image,p) - GET_PIXEL_GREEN(image,p-inXmult)) * mult_fact ADD_HALF,
q_out);
SET_PIXEL_BLUE
(new_image,
(GET_PIXEL_BLUE(image,p) - GET_PIXEL_BLUE(image,p-inXmult)) * mult_fact ADD_HALF,
q_out);
p += Inc_ViewPixPtr (image);
q_out += Inc_ViewPixPtr (new_image);
}
} else {
long double
cumul_red,
cumul_green,
cumul_blue,
cumul_alpha,
alp_q;
cumul_red = cumul_green = cumul_blue = cumul_alpha = 0;
for (x=0; x < (ssize_t) image->columns; x++)
{
if (pch->do_regardalpha) {
alp_q = GET_PIXEL_ALPHA(image,p);
cumul_alpha += alp_q;
SET_PIXEL_ALPHA (new_image, cumul_alpha ADD_HALF, q_out);
alpha = alp_q / (long double)QuantumRange;
} else {
SET_PIXEL_ALPHA (new_image, GET_PIXEL_ALPHA(image,p), q_out);
}
cumul_red += (alpha * GET_PIXEL_RED(image,p));
SET_PIXEL_RED (new_image, cumul_red * mult_fact ADD_HALF, q_out);
cumul_green += (alpha * GET_PIXEL_GREEN(image,p));
SET_PIXEL_GREEN (new_image, cumul_green * mult_fact ADD_HALF, q_out);
cumul_blue += (alpha * GET_PIXEL_BLUE(image,p));
SET_PIXEL_BLUE (new_image, cumul_blue * mult_fact ADD_HALF, q_out);
p += Inc_ViewPixPtr (image);
q_out += Inc_ViewPixPtr (new_image);
}
}
if (SyncCacheViewAuthenticPixels(out_view,exception) == MagickFalse) {
fprintf (stderr, "bad sync\n");
status=MagickFalse;
}
}
image_view=DestroyCacheView(image_view);
out_view=DestroyCacheView(out_view);
return (new_image);
}
ModuleExport size_t cumulhistoImage(Image **images,const int argc,
const char **argv,ExceptionInfo *exception)
{
Image
*image,
*new_image;
MagickBooleanType
status;
cumulhistoT
cumul_histo;
assert(images != (Image **) NULL);
assert(*images != (Image *) NULL);
assert((*images)->signature == MAGICK_CORE_SIG);
status = menu (argc, argv, &cumul_histo);
if (status == MagickFalse)
return (-1);
for (image=(*images); image != (Image *) NULL; image=GetNextImageInList(image))
{
new_image = cumulhist (image, &cumul_histo, exception);
ReplaceImageInList(&image,new_image);
*images=GetFirstImageInList(image);
}
return(MagickImageFilterSignature);
}
/* Updated:
3-April-2018 for v7.0.7-28
9-September-2023 Ensure new_image has alpha channel, all transparent.
*/
#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
// Ensure new_image has alpha channel, all transparent.
if (!SetImageAlphaChannel (new_image, TransparentAlphaChannel, exception))
return (Image *)NULL;
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);
}
/* 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);
}
/* Written by: Alan Gibson, 18 October 2015.
References:
http://im.snibgo.com/darkpath.htm
Updated:
21-April-2023 for IM v7.
*/
#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 - Inc_ViewPixPtr(image)); // was p-1
if (minVal > prevVal) {
minVal = prevVal;
pv = -1;
}
}
if (x < image->columns-1) {
prevVal = GET_PIXEL_RED(image,p + Inc_ViewPixPtr(image)); // was 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);
}
/* Written by: Alan Gibson, 18 October 2015.
References:
https://en.wikipedia.org/wiki/Dijkstra's_algorithm
Wikipedia: Dijkstra's algorithm.
http://im.snibgo.com/darkpath.htm
Updated:
9-May-2016 added "data" option.
15-October-2016 fixed incorrect warning that start or end exceeds threshold.
16-December-2016 Added "print" option.
14-January-2017 Added coordList stuff.
23-August-2017 Moved user-coordinate stuff to external file.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <float.h>
#ifndef IMV6OR7
#include "vsn_defines.h"
#endif
#include "chklist.h"
#define VERIFY 0
#include "usercoord.inc"
//---------------------------------------------------------------------------
// Coordinate list data structures.
typedef struct {
double x;
double y;
} CoordT;
typedef struct {
int listInitSize;
int listSize;
int numUsed;
CoordT * coords;
} CoordListT;
//---------------------------------------------------------------------------
// Data structures.
typedef double dppValueT;
// Neighbours are numbered 1 to 9 except 5.
// Up-left is 1, up-right is 3, etc.
#define NUM_NEIGHB 9
typedef struct {
Quantum
value;
dppValueT
tentativeDistance;
MagickBooleanType
visited;
int
parent;
} NodeT;
#define INFINITE_DIST DBL_MAX
typedef struct {
ssize_t
x,
y;
} pqNodeT;
typedef struct {
MagickBooleanType
stopAtEnd,
wrData,
suppressImage,
printPix,
do_verbose,
do_warnings,
hasVisitedThreshold,
foundEnd;
dppValueT
visitedThreshold;
UserCoordXyT
start,
end;
ssize_t
width,
height;
FILE *
fh_data;
int
precision;
dppValueT
neighbourWeights[NUM_NEIGHB+1];
NodeT
**nodes;
ssize_t
pqNumNodes;
ssize_t
pqMaxNodes;
pqNodeT
*pqNodes;
CoordListT
*coordList; // If not null, list will be added to.
} darkestPntPntT;
static void InitDarkestPntPnt (darkestPntPntT * dpp)
{
dpp->do_verbose = MagickFalse;
dpp->do_warnings = MagickTrue;
dpp->hasVisitedThreshold = MagickFalse;
dpp->start.x.Pix = dpp->start.y.Pix = 0;
dpp->start.x.cs = dpp->start.y.cs = csPix;
dpp->end.x.Pix = dpp->end.y.Pix = 0;
dpp->end.x.cs = dpp->end.y.cs = csPix;
dpp->wrData = MagickFalse;
dpp->suppressImage = MagickFalse;
dpp->stopAtEnd = MagickTrue;
dpp->printPix = MagickFalse;
dpp->fh_data = stderr;
dpp->coordList = NULL;
int i;
for (i=0; i <= NUM_NEIGHB; i++) dpp->neighbourWeights[i] = 1;
dpp->neighbourWeights[1]
= dpp->neighbourWeights[3]
= dpp->neighbourWeights[7]
= dpp->neighbourWeights[9] = sqrt (2.0);
}
//---------------------------------------------------------------------------
// Coordinate list functions.
static CoordListT * CreateCoordList (int initSize)
// If oom (or initSize is <= zero), returns NULL.
{
if (initSize <= 0) return NULL;
CoordListT * pcl = (CoordListT *)malloc(sizeof(CoordListT));
if (pcl==NULL) return NULL;
pcl->listInitSize = initSize;
pcl->listSize = initSize;
pcl->numUsed = 0;
pcl->coords = (CoordT *)malloc(pcl->listSize*sizeof(CoordT));
if (pcl->coords==NULL) {
free (pcl);
return NULL;
}
return pcl;
}
static CoordListT * DestroyCoordList (CoordListT * pcl)
{
if (pcl) {
if (pcl->coords) free (pcl->coords);
free (pcl);
}
return NULL;
}
static MagickBooleanType AddToCoordList (CoordListT * pcl, double x, double y)
// If list full, expands it.
// If oom, returns false.
{
if (!pcl) return MagickFalse;
if (pcl->numUsed > pcl->listSize) {
printf ("AddToCoordList: bug\n");
return MagickFalse;
}
if (pcl->numUsed == pcl->listSize) {
// Expand it.
pcl->listSize += pcl->listInitSize;
CoordT * tmp = (CoordT *)realloc(pcl->coords, pcl->listSize*sizeof(CoordT));
if (tmp==NULL) {
free (pcl->coords);
free (pcl);
return MagickFalse;
}
pcl->coords = tmp;
}
pcl->coords[pcl->numUsed].x = x;
pcl->coords[pcl->numUsed].y = y;
pcl->numUsed++;
return MagickTrue;
}
static MagickBooleanType AddToCoordListIf (CoordListT * pcl, double x, double y)
// If list empty, or new coords different to last entry,
// adds to list.
{
if (!pcl) return MagickFalse;
if (pcl->numUsed == 0
|| pcl->coords[pcl->numUsed-1].x != x
|| pcl->coords[pcl->numUsed-1].y != y)
{
return AddToCoordList (pcl, x, y);
}
printf ("f");
return MagickTrue;
}
static void InvertCoordList (CoordListT * pcl, int nStart)
{
if (!pcl) return;
printf ("InvertCoordList %i\n", pcl->numUsed);
int i;
int q = pcl->numUsed-1;
double tx, ty;
for (i = nStart; i < q; i++) {
tx = pcl->coords[i].x;
ty = pcl->coords[i].y;
pcl->coords[i].x = pcl->coords[q].x;
pcl->coords[i].y = pcl->coords[q].y;
pcl->coords[q].x = tx;
pcl->coords[q].y = ty;
q--;
}
}
static void EmptyCoordList (CoordListT * pcl)
{
if (!pcl) return;
pcl->numUsed = 0;
}
static void WrCoords (CoordListT * pcl, FILE * fhOut)
{
if (!pcl) return;
int i;
for (i = 0; i < pcl->numUsed; i++) {
fprintf (fhOut, "%g,%g\n",
(double)pcl->coords[i].x, (double)pcl->coords[i].y);
}
}
static void DumpCoordList (CoordListT * pcl, FILE * fhOut)
{
if (!pcl) return;
fprintf (fhOut, "Init=%i Size=%i numUsed=%i\n",
pcl->listInitSize, pcl->listSize, pcl->numUsed);
WrCoords (pcl, fhOut);
}
//---------------------------------------------------------------------------
// Priority queue functions.
#define LCHILD(x) 2 * x + 1
#define RCHILD(x) 2 * x + 2
#define PARENT(x) (x - 1) / 2
static dppValueT inline pqDataOfNode (darkestPntPntT * dpp, ssize_t NodeNum)
{
ssize_t x = dpp->pqNodes[NodeNum].x;
ssize_t y = dpp->pqNodes[NodeNum].y;
return dpp->nodes[y][x].tentativeDistance;
}
#if VERIFY==1
static void pqVerify (darkestPntPntT * dpp)
{
ssize_t
i;
//if (dpp->do_verbose) printf ("pqVerify pqNumNodes=%li\n", dpp->pqNumNodes);
for (i=0; i < dpp->pqNumNodes; i++) {
ssize_t lch = LCHILD(i);
ssize_t rch = RCHILD(i);
MagickBooleanType HasL = lch < dpp->pqNumNodes;
MagickBooleanType HasR = rch < dpp->pqNumNodes;
if (HasL && pqDataOfNode (dpp, lch) < pqDataOfNode (dpp, i))
printf ("** Bad %li left\n", i);
if (HasR && pqDataOfNode (dpp, rch) < pqDataOfNode (dpp, i))
printf ("** Bad %li right\n", i);
}
}
static void pqDumpOrder (darkestPntPntT * dpp, ssize_t NodeNum)
{
//printf("%d ", hp->elem[i].data) ;
ssize_t x = dpp->pqNodes[NodeNum].x;
ssize_t y = dpp->pqNodes[NodeNum].y;
dppValueT v = dpp->nodes[y][x].tentativeDistance;
printf (" %li %li,%li %.*g\n", NodeNum, x, y, dpp->precision, v);
ssize_t lch = LCHILD(NodeNum);
ssize_t rch = RCHILD(NodeNum);
MagickBooleanType
LFirst = MagickTrue,
HasL = lch < dpp->pqNumNodes,
HasR = rch < dpp->pqNumNodes;
if (HasL && HasR) {
LFirst = pqDataOfNode (dpp, lch) < pqDataOfNode (dpp, rch);
}
if(HasL && LFirst) {
pqDumpOrder (dpp, lch) ;
}
if(HasR) {
pqDumpOrder (dpp, rch) ;
}
if(HasL && !LFirst) {
pqDumpOrder (dpp, lch) ;
}
}
static void pqDump (darkestPntPntT * dpp)
{
ssize_t
i;
printf ("pqNumNodes=%li\n", dpp->pqNumNodes);
for (i=0; i < dpp->pqNumNodes; i++) {
pqNodeT * pqn = &dpp->pqNodes[i];
NodeT * pn = &(dpp->nodes[pqn->y][pqn->x]);
printf (" %li %li,%li %.*g\n",
i, pqn->x, pqn->y, dpp->precision, pn->tentativeDistance);
}
//printf ("In order:\n");
//pqDumpOrder (dpp, 0);
pqVerify (dpp);
}
#endif
static void inline pqSwapNodes (darkestPntPntT * dpp, ssize_t n1, ssize_t n2)
{
ssize_t t = dpp->pqNodes[n1].x;
dpp->pqNodes[n1].x = dpp->pqNodes[n2].x;
dpp->pqNodes[n2].x = t;
t = dpp->pqNodes[n1].y;
dpp->pqNodes[n1].y = dpp->pqNodes[n2].y;
dpp->pqNodes[n2].y = t;
}
static void pqBalanceNode (darkestPntPntT * dpp, ssize_t NodeNum)
// "Bubble-down".
// Finds the smallest of this node and its children.
// If the node isn't the smallest of the three,
// swaps data with that child and recursively balances that child.
{
ssize_t smallest = NodeNum;
ssize_t lch = LCHILD(NodeNum);
ssize_t rch = RCHILD(NodeNum);
dppValueT SmData = pqDataOfNode (dpp, NodeNum);
if (lch < dpp->pqNumNodes) {
dppValueT ldist = pqDataOfNode (dpp, lch);
if (ldist < SmData) {
SmData = ldist;
smallest = lch;
}
}
if (rch < dpp->pqNumNodes) {
dppValueT rdist = pqDataOfNode (dpp, rch);
if (rdist < SmData) {
SmData = rdist;
smallest = rch;
}
}
if (smallest != NodeNum) {
pqSwapNodes (dpp, smallest, NodeNum);
pqBalanceNode (dpp, smallest);
}
}
static void pqInsertNew (darkestPntPntT * dpp, ssize_t x, ssize_t y)
{
//printf ("pqIns %li,%li ", x, y);
if (dpp->pqNumNodes >= dpp->pqMaxNodes) {
fprintf (stderr, "pq bust\n");
}
dppValueT newDist = dpp->nodes[y][x].tentativeDistance;
ssize_t n = dpp->pqNumNodes;
// "Bubble up".
// If this data is less than node's parent,
// drop parent data into this node
// and consider putting new data into that parent.
while (n && newDist < pqDataOfNode (dpp, PARENT(n))) {
dpp->pqNodes[n] = dpp->pqNodes[PARENT(n)];
n = PARENT(n);
}
dpp->pqNodes[n].x = x;
dpp->pqNodes[n].y = y;
dpp->pqNumNodes++;
#if VERIFY==1
pqVerify (dpp);
#endif
}
static ssize_t pqFindNode (
darkestPntPntT * dpp,
ssize_t x, ssize_t y,
dppValueT val, ssize_t NodeStart)
// Returns -1 if not present.
// Note: recursive.
{
if (dpp->pqNumNodes==0) return -1;
pqNodeT * pqn = &dpp->pqNodes[NodeStart];
if (pqn->x == x && pqn->y == y) return NodeStart;
if (pqDataOfNode (dpp, NodeStart) > val) return -1;
ssize_t lch = LCHILD(NodeStart);
if (lch < dpp->pqNumNodes) {
ssize_t nch = pqFindNode (dpp, x, y, val, lch);
if (nch >= 0) return nch;
}
ssize_t rch = RCHILD(NodeStart);
if (rch < dpp->pqNumNodes) {
ssize_t nch = pqFindNode (dpp, x, y, val, rch);
if (nch >= 0) return nch;
}
return -1;
}
#if VERIFY==1
static void pqFindMin (darkestPntPntT * dpp, ssize_t *x, ssize_t *y)
{
if (!dpp->pqNumNodes) fprintf (stderr, "** Bad pqfm: empty");
pqNodeT * pqn = &dpp->pqNodes[0];
*x = pqn->x;
*y = pqn->y;
}
#endif
static int pqRemoveMin (darkestPntPntT * dpp, ssize_t *x, ssize_t *y)
// Returns 1 if okay, or 0 if no data.
{
if (!dpp->pqNumNodes) {
//fprintf (stderr, "** Bad pqrm: empty");
return 0;
}
pqNodeT * pqn = &dpp->pqNodes[0];
*x = pqn->x;
*y = pqn->y;
// Put largest into root, and rebalance.
dpp->pqNodes[0] = dpp->pqNodes[--dpp->pqNumNodes];
pqBalanceNode (dpp, 0);
return 1;
}
static void inline UpdateDist (
darkestPntPntT * dpp,
ssize_t nodeNum,
ssize_t x, ssize_t y,
//dppValueT oldVal,
dppValueT newVal)
// Updates the tentative distance of a pixel.
{
//printf ("UpdDist %li,%li: ", x, y);
/*==
assert(newVal <= oldVal);
ssize_t n = pqFindNode (dpp, x, y, oldVal, 0);
if (n < 0) {
fprintf (stderr, "** BUG: UpdateDist can't find %li,%li\n", x, y);
return;
}
==*/
ssize_t n = nodeNum;
dpp->nodes[y][x].tentativeDistance = newVal;
// "Bubble up".
// If this data is less than node's parent,
// swap with parent and iterate.
while (n && newVal < pqDataOfNode (dpp, PARENT(n))) {
pqSwapNodes (dpp, n, PARENT(n));
n = PARENT(n);
}
// Bubble down from the top.
// FIXME: do we need this?
// pqBalanceNode (dpp, 0);
#if VERIFY==1
pqVerify (dpp);
#endif
}
//---------------------------------------------------------------------------
static void usage (void)
{
printf ("Usage: -process 'darkestpntpnt [OPTION]...'\n");
printf ("Finds darkest path between two points.\n");
printf ("\n");
printf (" s, start_at X,Y start the path at (integer) coordinates\n");
printf (" e, end_at X,Y end the path at (integer) coordinates\n");
printf (" t, threshold_visited N where initial values above N, don't visit\n");
printf (" n, no_end don't stop processing at path end\n");
printf (" d, data write data image instead of path image\n");
printf (" p, print write each visited coordinate to stderr\n");
printf (" f, file string write to file stream stdout or stderr\n");
printf (" v, verbose write text information to stdout\n");
printf ("\n");
printf ("sizeof(NodeT)=%i\n", (int)sizeof(NodeT));
printf ("sizeof(pqNodeT)=%i\n", (int)sizeof(pqNodeT));
printf ("INFINITE_DIST=%g\n", INFINITE_DIST);
printf ("\n");
}
static MagickBooleanType IsArg (char * pa, char * ShtOpt, char * LongOpt)
{
if ((LocaleCompare(pa, ShtOpt)==0) || (LocaleCompare(pa, LongOpt)==0))
return MagickTrue;
return MagickFalse;
}
static MagickBooleanType menu(
const int argc,
const char **argv,
darkestPntPntT * dpp
)
// Returns MagickTrue if okay.
{
int
i;
MagickBooleanType
status;
status = MagickTrue;
InitDarkestPntPnt (dpp);
for (i=0; i < argc; i++) {
char * pa = (char *)argv[i];
if (IsArg (pa, "s", "start_at")==MagickTrue) {
i++;
int r = ParseXy (argv[i], &dpp->start);
if (!r) {
printf ("Failed to parse start [%s]\n", argv[i]);
status = MagickFalse;
}
} else if (IsArg (pa, "e", "end_at")==MagickTrue) {
i++;
int r = ParseXy (argv[i], &dpp->end);
if (!r) {
printf ("Failed to parse end [%s]\n", argv[i]);
status = MagickFalse;
}
} else if (IsArg (pa, "t", "threshold_visited")==MagickTrue) {
dpp->hasVisitedThreshold = MagickTrue;
i++;
dpp->visitedThreshold = atof (argv[i]);
} else if (IsArg (pa, "n", "no_end")==MagickTrue) {
dpp->stopAtEnd = MagickFalse;
} else if (IsArg (pa, "d", "data")==MagickTrue) {
dpp->wrData = MagickTrue;
} else if (IsArg (pa, "p", "print")==MagickTrue) {
dpp->printPix = MagickTrue;
} else if (IsArg (pa, "f", "file")==MagickTrue) {
i++;
if (strcasecmp (argv[i], "stdout")==0) dpp->fh_data = stdout;
else if (strcasecmp (argv[i], "stderr")==0) dpp->fh_data = stderr;
else status = MagickFalse;
} else if (IsArg (pa, "v", "verbose")==MagickTrue) {
dpp->do_verbose = MagickTrue;
} else {
fprintf (stderr, "darkestpntpnt: ERROR: unknown option\n");
status = MagickFalse;
}
}
if (dpp->do_verbose) {
fprintf (stderr, "darkestpntpnt options: ");
fprintf (stderr, " start_at ");
WrUserCoord (&dpp->start);
fprintf (stderr, " end_at ");
WrUserCoord (&dpp->end);
if (dpp->hasVisitedThreshold)
fprintf (stderr, " threshold_visited %.*g",
dpp->precision, dpp->visitedThreshold);
if (dpp->wrData) fprintf (stderr, " data");
if (dpp->printPix) fprintf (stderr, " print");
if (dpp->fh_data == stdout) fprintf (stderr, " file stdout");
if (!dpp->stopAtEnd) fprintf (stderr, " no_end");
if (dpp->do_verbose) fprintf (stderr, " verbose");
fprintf (stderr, "\n");
}
if (status == MagickFalse)
usage ();
return (status);
}
static MagickBooleanType initialise(const Image *image,
darkestPntPntT * dpp,
ExceptionInfo *exception
)
{
ssize_t
x, y;
MagickBooleanType
status = MagickTrue;
CacheView
*image_view;
dpp->width = image->columns;
dpp->height = image->rows;
// Allocate memory
//if (dpp->do_verbose) printf ("Alloc %ix%i\n", (int)dpp->width, (int)dpp->height);
dpp->pqMaxNodes = dpp->height * dpp->width;
dpp->pqNodes = (pqNodeT *) AcquireQuantumMemory(dpp->pqMaxNodes, sizeof(pqNodeT));
if (dpp->pqNodes == (pqNodeT *) NULL) {
return MagickFalse;
}
dpp->pqNumNodes = 0;
dpp->nodes = (NodeT **) AcquireQuantumMemory(dpp->height, sizeof(*dpp->nodes));
if (dpp->nodes == (NodeT **) NULL) {
RelinquishMagickMemory(dpp->pqNodes);
return MagickFalse;
}
for (y = 0; y < dpp->height; y++) {
dpp->nodes[y] = (NodeT *) AcquireQuantumMemory(dpp->width, sizeof(**dpp->nodes));
if (dpp->nodes[y] == (NodeT *) NULL) break;
}
if (y < dpp->height) {
for (y--; y >= 0; y--) {
if (dpp->nodes[y] != (NodeT *) NULL)
dpp->nodes[y] = (NodeT *) RelinquishMagickMemory(dpp->nodes[y]);
}
dpp->nodes = (NodeT **) RelinquishMagickMemory(dpp->nodes);
RelinquishMagickMemory(dpp->pqNodes);
return MagickFalse;
}
// Populate values
//if (dpp->do_verbose) printf ("Populate\n");
image_view = AcquireVirtualCacheView(image,exception);
for (y = 0; y < dpp->height; y++) {
VIEW_PIX_PTR const
*p;
if (status == MagickFalse)
continue;
p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
if (p == (const VIEW_PIX_PTR *) NULL)
status=MagickFalse;
for (x = 0; x < dpp->width; x++) {
//printf ("Pop %i,%i\n", (int)x, (int)y);
NodeT * pn = &(dpp->nodes[y][x]);
pn->value = GetPixelIntensity (image, p) / (dppValueT)QuantumRange;
pn->tentativeDistance = INFINITE_DIST;
pn->visited = MagickFalse;
if (dpp->hasVisitedThreshold) {
if (pn->value > dpp->visitedThreshold) {
pn->visited = MagickTrue;
}
}
pn->parent = 0;
p += Inc_ViewPixPtr (image);
}
}
image_view=DestroyCacheView(image_view);
NodeT * pn = &dpp->nodes[dpp->start.y.Pix][dpp->start.x.Pix];
pn->tentativeDistance = 0;
if (dpp->hasVisitedThreshold && pn->value > dpp->visitedThreshold) {
if (dpp->do_warnings) fprintf (stderr,
"Problem: start pixel has value (%.*g) above threshold.\n",
dpp->precision, pn->value);
}
pn = &dpp->nodes[dpp->end.y.Pix][dpp->end.x.Pix];
if (dpp->hasVisitedThreshold && pn->value > dpp->visitedThreshold) {
if (dpp->do_warnings) fprintf (stderr,
"Problem: end pixel has value (%.*g) above threshold.\n",
dpp->precision, pn->value);
}
return (status);
}
static void deIinitialise(
darkestPntPntT * dpp
)
{
ssize_t
y;
for (y = 0; y < dpp->height; y++) {
dpp->nodes[y] = (NodeT *) RelinquishMagickMemory(dpp->nodes[y]);
}
dpp->nodes = (NodeT **) RelinquishMagickMemory(dpp->nodes);
RelinquishMagickMemory(dpp->pqNodes);
}
#if VERIFY==1
static void DumpNodes (
darkestPntPntT * dpp
)
{
ssize_t
x, y;
printf ("DumpNodes %lix%li\n", dpp->width, dpp->height);
for (y = 0; y < dpp->height; y++) {
for (x = 0; x < dpp->width; x++) {
NodeT * pn = &dpp->nodes[y][x];
printf ("%li,%li %.*g %.*g %s %i\n",
x, y,
dpp->precision, (double)pn->value,
dpp->precision, (double)pn->tentativeDistance,
pn->visited? "V": "nv",
pn->parent);
}
}
}
#endif
static MagickBooleanType NearestUnvNode(
darkestPntPntT * dpp,
ssize_t * nx,
ssize_t * ny
)
// Returns whether okay.
// If none, returns FALSE.
{
// This needs to be fast.
/*===
//
// This is how it would be done without a priority queue.
// Much simpler, but very much slower.
//
// dppValueT
// nearestDist;
//
// ssize_t
// x, y;
//
// nearestDist = INFINITE_DIST;
//
// // This could be parallelised.
//
// for (y = 0; y < dpp->height; y++) {
// for (x = 0; x < dpp->width; x++) {
// NodeT * pn = &dpp->nodes[y][x];
// if (!pn->visited) {
// if (nearestDist > pn->tentativeDistance) {
// nearestDist = pn->tentativeDistance;
// *nx = x;
// *ny = y;
// *allDone = MagickFalse;
// }
// }
// }
// }
// printf ("nun %li,%li ad=%i\n", *nx, *ny, (int)*allDone);
//
===*/
ssize_t pqX=0, pqY=0;
if ( pqRemoveMin (dpp, &pqX, &pqY) == 0) return MagickFalse;
// *allDone = (r == 1) ? MagickFalse : MagickTrue;
//if (*nx != pqX || *ny != pqY) {
// printf ("NearestUnvNode: %li %li %li %li\n", *nx, pqX, *ny, pqY);
//}
*nx = pqX;
*ny = pqY;
return MagickTrue;
}
static void inline ProcessNeighbour (
darkestPntPntT * dpp,
dppValueT thisDist,
int nbx,
int nby,
int ParentNum)
{
NodeT * pnb = &dpp->nodes[nby][nbx];
if (!pnb->visited) {
dppValueT newDist = thisDist + pnb->value * dpp->neighbourWeights[ParentNum];
if (pnb->tentativeDistance > newDist) {
ssize_t pqNode = -1;
if (pnb->tentativeDistance < INFINITE_DIST) {
pqNode = pqFindNode (dpp, nbx, nby, pnb->tentativeDistance, 0);
}
if (pqNode == -1) {
pnb->tentativeDistance = newDist;
pqInsertNew (dpp, nbx, nby);
} else {
UpdateDist (dpp, pqNode, nbx, nby, /* pnb->tentativeDistance, */ newDist);
}
pnb->parent = ParentNum;
}
}
}
static MagickBooleanType VisitNodes (
darkestPntPntT * dpp
)
{
MagickBooleanType
finished = MagickFalse,
DoCoordList = MagickFalse;
ssize_t
nx, ny;
int nVisited = 0;
nx = dpp->start.x.Pix;
ny = dpp->start.y.Pix;
if (dpp->printPix) {
fprintf (dpp->fh_data, "%li,%li\n", nx, ny);
}
dpp->foundEnd = MagickFalse;
if (dpp->coordList != NULL && dpp->wrData == MagickTrue) {
DoCoordList = MagickTrue;
// Record this start pixel.
// FIXME: but not if out of threshold?
AddToCoordList (dpp->coordList, nx, ny);
}
while (finished == MagickFalse) {
//printf ("vn %li,%li\n", nx, ny);
NodeT * pn = &dpp->nodes[ny][nx];
// Process the 8 neighbours.
if (ny > 0) {
if (nx > 0) {
ProcessNeighbour (dpp, pn->tentativeDistance, nx-1, ny-1, 1);
}
ProcessNeighbour (dpp, pn->tentativeDistance, nx, ny-1, 2);
if (nx < dpp->width-1) {
ProcessNeighbour (dpp, pn->tentativeDistance, nx+1, ny-1, 3);
}
}
if (nx > 0) {
ProcessNeighbour (dpp, pn->tentativeDistance, nx-1, ny, 4);
}
if (nx < dpp->width-1) {
ProcessNeighbour (dpp, pn->tentativeDistance, nx+1, ny, 6);
}
if (ny < dpp->height-1) {
if (nx > 0) {
ProcessNeighbour (dpp, pn->tentativeDistance, nx-1, ny+1, 7);
}
ProcessNeighbour (dpp, pn->tentativeDistance, nx, ny+1, 8);
if (nx < dpp->width-1) {
ProcessNeighbour (dpp, pn->tentativeDistance, nx+1, ny+1, 9);
}
}
//printf ("vn2 %li,%li\n", nx, ny);
if ( pn->visited == MagickTrue
&& ( nx != dpp->start.x.Pix
|| ny != dpp->start.y.Pix ))
{
fprintf (stderr, "Bug: already visited %li,%li\n", nx, ny);
finished = MagickTrue;
#if VERIFY==1
DumpNodes (dpp);
pqVerify (dpp);
pqDump (dpp);
#endif
}
pn->visited = MagickTrue;
if (dpp->do_verbose && ((++nVisited % 100000) == 0)) {
printf (".\n");
}
if (nx == dpp->end.x.Pix && ny == dpp->end.y.Pix) {
dpp->foundEnd = MagickTrue;
if (dpp->stopAtEnd) {
finished = MagickTrue;
}
}
if (finished == MagickFalse) {
if (NearestUnvNode (dpp, &nx, &ny)) {
if (dpp->printPix && !finished) {
fprintf (dpp->fh_data, "%li,%li\n", nx, ny);
}
if (DoCoordList && !finished) {
AddToCoordList (dpp->coordList, nx, ny);
}
} else {
finished = MagickTrue;
}
} else if (dpp->printPix) {
fprintf (dpp->fh_data, "%li,%li\n", nx, ny);
if (DoCoordList) {
AddToCoordList (dpp->coordList, nx, ny);
}
}
}
if (dpp->do_verbose) fprintf (stderr, "nVisited: %i\n", nVisited);
return (MagickTrue);
}
static Image * writePath(const Image *image,
darkestPntPntT * dpp,
ExceptionInfo *exception
)
{
Image *
path_image;
CacheView
*path_view;
VIEW_PIX_PTR
*q_path;
MagickBooleanType
status = MagickTrue;
ssize_t
x, y;
//if (dpp->do_verbose) printf ("writePath: WH=%li,%li\n", image->columns, image->rows);
path_image=CloneImage(image, image->columns, image->rows, MagickTrue, exception);
if (path_image == (Image *) NULL)
return(path_image);
if (SetNoPalette (path_image, exception) == MagickFalse)
return (Image *)NULL;
SetAllBlack (path_image, exception);
path_view=AcquireAuthenticCacheView(path_image,exception);
x = dpp->end.x.Pix;
y = dpp->end.y.Pix;
ssize_t nInPath = 0;
dppValueT totalValue = 0.0;
MagickBooleanType DoCoordList;
int clNumStart = 0;
if (dpp->coordList==NULL) {
DoCoordList = MagickFalse;
} else {
DoCoordList = MagickTrue;
clNumStart = dpp->coordList->numUsed;
}
if (dpp->foundEnd == MagickTrue) {
if (dpp->do_verbose) {
NodeT * pn = &dpp->nodes[y][x];
fprintf (stderr, "maxDist: %.*g\n",
dpp->precision, pn->tentativeDistance * QuantumRange);
fprintf (stderr, "maxDistPc: %.*g\n",
dpp->precision, pn->tentativeDistance * 100.0);
}
do {
//printf ("xy=%li,%li\n", x, y);
// Get just the one pixel we need.
q_path=GetCacheViewAuthenticPixels(path_view,x,y,1,1,exception);
if (q_path == (const VIEW_PIX_PTR *) NULL) {
fprintf (stderr, "darkestpntpng: bad GetCacheViewAuthenticPixels q_path\n");
status=MagickFalse;
break;
}
if (GET_PIXEL_RED (path_image, q_path) != 0) {
fprintf (stderr, "Bug: cycle\n");
status=MagickFalse;
break;
}
SET_PIXEL_RED (path_image, QuantumRange, q_path);
SET_PIXEL_GREEN (path_image, QuantumRange, q_path);
SET_PIXEL_BLUE (path_image, QuantumRange, q_path);
if (SyncCacheViewAuthenticPixels(path_view,exception) == MagickFalse) {
fprintf (stderr, "bad sync\n");
status=MagickFalse;
break;
}
if (DoCoordList) {
if (!AddToCoordList (dpp->coordList, x, y)) {
status=MagickFalse;
}
}
if (x == dpp->start.x.Pix && y == dpp->start.y.Pix) break;
NodeT * pn = &dpp->nodes[y][x];
nInPath++;
totalValue += pn->value;
switch (pn->parent) {
case 1: y+=1; x+=1; break;
case 2: y+=1; break;
case 3: y+=1; x-=1; break;
case 4: x+=1; break;
case 6: x-=1; break;
case 7: y-=1; x+=1; break;
case 8: y-=1; break;
case 9: y-=1; x-=1; break;
default:
if (x != dpp->start.x.Pix || y != dpp->start.y.Pix) {
fprintf (stderr, "bad parent\n");
status=MagickFalse;
}
}
} while (status==MagickTrue);
} else {
if (dpp->do_warnings) fprintf (stderr, "Problem: end is not on path.\n");
}
if (DoCoordList) {
// FIXME: Oops, we can do this only if it was empty.
InvertCoordList (dpp->coordList, clNumStart);
}
path_view=DestroyCacheView(path_view);
if (dpp->do_verbose)
fprintf (stderr, "nInPath: %li\nTotalValue: %.*g\nAvgValue: %.*g\n",
nInPath,
dpp->precision, totalValue,
dpp->precision, totalValue / nInPath);
return path_image;
}
static Image * writeData (const Image *image,
darkestPntPntT * dpp,
ExceptionInfo *exception
)
{
// Copy values to red channel;
// copy distances to green channel;
// copy parent number to blue channel.
Image *
data_image;
ssize_t
x, y;
MagickBooleanType
status = MagickTrue;
CacheView
*data_view;
data_image=CloneImage(image, image->columns, image->rows, MagickTrue, exception);
if (data_image == (Image *) NULL)
return(data_image);
if (SetNoPalette (data_image, exception) == MagickFalse)
return (Image *)NULL;
data_view = AcquireAuthenticCacheView(data_image,exception);
ssize_t nInPath = 0;
dppValueT maxDist = -1;
// FIXME: parallelise
for (y = 0; y < dpp->height; y++) {
VIEW_PIX_PTR
*p;
if (status == MagickFalse)
continue;
p=GetCacheViewAuthenticPixels(data_view,0,y,image->columns,1,exception);
if (p == (const VIEW_PIX_PTR *) NULL)
status=MagickFalse;
for (x = 0; x < dpp->width; x++) {
NodeT * pn = &(dpp->nodes[y][x]);
SET_PIXEL_RED (data_image, pn->value * QuantumRange, p);
if (pn->tentativeDistance == INFINITE_DIST) {
SET_PIXEL_GREEN (data_image, -1, p);
} else {
dppValueT dist = pn->tentativeDistance * QuantumRange;
if (maxDist < dist) maxDist = dist;
SET_PIXEL_GREEN (data_image, dist, p);
nInPath++;
}
SET_PIXEL_BLUE (data_image, pn->parent, p);
p += Inc_ViewPixPtr (data_image);
}
if (SyncCacheViewAuthenticPixels(data_view,exception) == MagickFalse) {
fprintf (stderr, "bad sync\n");
status=MagickFalse;
break;
}
}
data_view=DestroyCacheView(data_view);
if (dpp->do_verbose) {
fprintf (stderr, "nInPath: %li\n", nInPath);
if (maxDist > -1) {
fprintf (stderr, "maxDist: %.*g\n",
dpp->precision, maxDist);
fprintf (stderr, "maxDistPc: %.*g\n",
dpp->precision, maxDist * 100.0 / QuantumRange);
}
}
return (data_image);
}
// The next function corresponds in style to functions in transform.c
// It takes one image, and returns an image.
//
static Image *darkestpntpnt(Image *image,
darkestPntPntT * dpp,
ExceptionInfo *exception)
{
Image
*out_image;
MagickBooleanType
status;
dpp->precision = GetMagickPrecision();
if (image->debug != MagickFalse)
(void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
status = initialise(image, dpp, exception);
if (status==MagickFalse) return (Image *) NULL;
//if (dpp->do_verbose) printf ("DumpNodes.\n");
//DumpNodes (dpp);
//if (dpp->do_verbose) printf ("VisitNodes.\n");
VisitNodes (dpp);
#if VERIFY==1
//if (dpp->do_verbose) printf ("Verify pq.\n");
pqVerify (dpp);
#endif
//if (dpp->do_verbose) printf ("DumpNodes again.\n");
//DumpNodes (dpp);
//pqDump (dpp);
if (dpp->suppressImage) {
if (dpp->do_verbose) printf ("Image suppressed.\n");
out_image = NULL;
} else {
if (dpp->wrData) {
if (dpp->do_verbose) printf ("writeData.\n");
out_image = writeData (image, dpp, exception);
} else {
if (dpp->do_verbose) printf ("writePath.\n");
out_image = writePath (image, dpp, exception);
}
}
deIinitialise(dpp);
return (out_image);
}
ModuleExport size_t darkestpntpntImage(Image **images,const int argc,
const char **argv,ExceptionInfo *exception)
{
Image
*image,
*new_image;
MagickBooleanType
status;
darkestPntPntT
dpp;
assert(images != (Image **) NULL);
assert(*images != (Image *) NULL);
assert((*images)->signature == MAGICK_CORE_SIG);
InitDarkestPntPnt (&dpp);
status = menu (argc, argv, &dpp);
if (status == MagickFalse)
return (-1);
for (image=(*images); image != (Image *) NULL; image=GetNextImageInList(image))
{
int r;
r = ResolveUserCoords (&dpp.start, image->columns, image->rows, 1.0);
if (!r) return -1;
r = ResolveUserCoords (&dpp.end, image->columns, image->rows, 1.0);
if (!r) return -1;
new_image = darkestpntpnt (image, &dpp, exception);
ReplaceImageInList(&image,new_image);
*images=GetFirstImageInList(image);
}
return(MagickImageFilterSignature);
}
This file contains code specific to the process module, including the call(s) to DistortImage() to process the image(s). The mathematics and other file-handling is done by code in srt3d.h below.
/*
Reference: http://im.snibgo.com/srt3d.htm
Last update: 27-May-2017.
7-September-2017 for v7.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <math.h>
#include <ctype.h>
#include "vsn_defines.h"
#include "srt3d.h"
#define VERSION "srt3d v1.0 Copyright (c) 2017 Alan Gibson"
static void usage (void)
{
printf ("Usage: -process 'srt3d [OPTION]...'\n");
printf ("Scale, rotate and translate in 3D.\n");
printf ("\n");
printf (" t, transform string transformation string\n");
printf (" a, array filename read array from file\n");
printf (" A, out-array filename write array to file\n");
printf (" c, coords filename read coords from file\n");
printf (" C, out-coords filename write coords to file\n");
printf (" n, nOutCoords integer number of output coordinates (0-3)\n");
printf (" f, focal-length number for perspective\n");
printf (" r, hide-reversed hide reversed polygons\n");
printf (" v, verbose write text information to stdout\n");
printf (" version write version information to stdout\n");
printf ("\n");
}
static MagickBooleanType IsArg (char * pa, char * ShtOpt, char * LongOpt)
{
// LocaleCompare is not case-sensitive,
// so we use strcmp for the short option.
if ((strcmp(pa, ShtOpt)==0) || (LocaleCompare(pa, LongOpt)==0))
return MagickTrue;
return MagickFalse;
}
#define NEXTARG \
if (i < argc-1) i++; \
else { \
fprintf (stderr, "srt3d: ERROR: [%s] needs argument\n", pa); \
status = MagickFalse; \
}
static MagickBooleanType menu(
const int argc,
const char **argv,
srt3dT * ps3d
)
// Returns MagickTrue if okay.
{
int
i;
MagickBooleanType
status = MagickTrue;
char ** pargv = (char **)argv;
srt3dInit (ps3d);
for (i=0; i < argc; i++) {
char * pa = (char *)argv[i];
//printf ("Arg %i [%s]\n", i, pa);
if (IsArg (pa, "t", "transform")==MagickTrue) {
NEXTARG;
ps3d->transStr = pargv[i];
} else if (IsArg (pa, "a", "array")==MagickTrue) {
NEXTARG;
ps3d->fInArray = pargv[i];
} else if (IsArg (pa, "A", "out-array")==MagickTrue) {
NEXTARG;
ps3d->fOutArray = pargv[i];
} else if (IsArg (pa, "c", "coords")==MagickTrue) {
NEXTARG;
ps3d->fInCoords = pargv[i];
} else if (IsArg (pa, "C", "out-coords")==MagickTrue) {
NEXTARG;
ps3d->fOutCoords = pargv[i];
} else if (IsArg (pa, "n", "nOutCoords")==MagickTrue) {
NEXTARG;
ps3d->numCoordsOut = atoi(pargv[i]);
} else if (IsArg (pa, "f", "focal-length")==MagickTrue) {
NEXTARG;
ps3d->focalLen = atof (pargv[i]);
} else if (IsArg (pa, "r", "hide-reversed")==MagickTrue) {
ps3d->calcHidden = MagickTrue;
} else if (IsArg (pa, "v", "verbose")==MagickTrue) {
ps3d->verbose = MagickTrue;
} else if (IsArg (pa, "version", "version")==MagickTrue) {
fprintf (stdout, "%s\n", VERSION);
} else {
fprintf (stderr, "srt3d: ERROR: unknown option [%s]\n", pa);
status = MagickFalse;
}
}
if (ps3d->verbose) {
fprintf (stderr, "srt3d options: ");
if (ps3d->transStr) fprintf (stderr, " transform %s", ps3d->transStr);
if (ps3d->fInArray) fprintf (stderr, " array %s", ps3d->fInArray);
if (ps3d->fOutArray) fprintf (stderr, " out-array %s", ps3d->fOutArray);
if (ps3d->fInCoords) fprintf (stderr, " coords %s", ps3d->fInCoords);
if (ps3d->fOutCoords) {
fprintf (stderr, " out-coords %s", ps3d->fOutCoords);
fprintf (stderr, " nOutCoords %i", ps3d->numCoordsOut);
}
if (ps3d->calcHidden) fprintf (stderr, " hide-reversed");
if (ps3d->focalLen!=0) fprintf (stderr, " focal-length %.*g",
ps3d->precision, ps3d->focalLen);
if (ps3d->verbose) fprintf (stderr, " verbose");
fprintf (stderr, "\n");
}
if (status == MagickFalse)
usage ();
return (status);
}
// The next function corresponds in style to functions in transform.c
// It takes one image, and returns an image.
//
static Image *srt3d (
Image *image,
srt3dT * s3d,
ExceptionInfo *exception)
{
if (image->debug != MagickFalse)
(void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
if (s3d->verbose) {
fprintf (stderr, "srt3d: Input image [%s] %ix%i depth is %i\n",
image->filename,
(int)image->columns, (int)image->rows,
(int)image->depth);
}
s3d->imgWidth = image->columns;
s3d->imgHeight = image->rows;
if (!srt3dCalc (s3d)) {
return NULL;
}
if (s3d->verbose) {
fprintf (stderr, "nEntries = %i\n", s3d->nEntries);
}
double * distArray = (double *)malloc (4 * s3d->nEntries * sizeof (double));
int i;
double *pd = distArray;
for (i=0; i < s3d->nEntries; i++) {
*(pd++) = s3d->coordPrimes[i].x;
*(pd++) = s3d->coordPrimes[i].y;
*(pd++) = s3d->coordPrimes[i].xPrime;
*(pd++) = s3d->coordPrimes[i].yPrime;
}
Image *out_image = NULL;
if (!s3d->isHidden) {
out_image = DistortImage (
image, PerspectiveDistortion,
4 * s3d->nEntries, distArray, MagickTrue, exception);
}
if (!out_image) {
ClearMagickException (exception);
// Create an image with one transparent pixel.
if (s3d->verbose) {
fprintf (stderr, "Creating 1x1 transparent.\n");
}
out_image = CloneImage (image, 1,1, MagickFalse, exception);
if (!out_image) {
fprintf (stderr, "srt3d: DistortImage and CloneImage failed\n");
return NULL;
}
#if IMV6OR7==6
SetImageOpacity (out_image, TransparentOpacity);
#else
SetImageAlpha (out_image, TransparentAlpha, exception);
#endif
}
if (s3d->verbose) {
fprintf (stderr, "Finished srt3d\n");
}
return (out_image);
}
ModuleExport size_t srt3dImage (
Image **images,
const int argc,
const char **argv,
ExceptionInfo *exception)
{
Image
*image,
*new_image;
MagickBooleanType
status;
srt3dT s3d;
assert(images != (Image **) NULL);
assert(*images != (Image *) NULL);
assert((*images)->signature == MAGICK_CORE_SIG);
status = menu (argc, argv, &s3d);
if (status == MagickFalse)
return (-1);
s3d.precision = GetMagickPrecision();
printf ("s3d.precision=%i\n", s3d.precision);
for (image=(*images); image != (Image *) NULL; image=GetNextImageInList(image))
{
new_image = srt3d (image, &s3d, exception);
if (!new_image) return (-1);
ReplaceImageInList(&image,new_image);
*images=GetFirstImageInList(image);
}
srt3dDeInit (&s3d);
return(MagickImageFilterSignature);
}
// 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;
}
/* Updated:
7-September-2017 for v7.
3-April-2018 for v7.0.7-28
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <ctype.h>
#include "vsn_defines.h"
typedef struct {
double
scale,
bias;
MagickBooleanType
do_verbose;
} arctanT;
static void usage (void)
{
printf ("Usage: -process 'arctan2 [OPTION]...'\n");
printf ("From two equal-size images, calculates the arctangent.\n");
printf ("\n");
printf (" s, scale N scale [1/2pi]\n");
printf (" b, bias N bias [0.5]\n");
printf (" v, verbose write text information to stdout\n");
printf ("\n");
}
static MagickBooleanType IsArg (char * pa, char * ShtOpt, char * LongOpt)
{
if ((LocaleCompare(pa, ShtOpt)==0) || (LocaleCompare(pa, LongOpt)==0))
return MagickTrue;
return MagickFalse;
}
#define NEXTARG \
if (i < argc-1) i++; \
else { \
fprintf (stderr, "baryc: ERROR: [%s] needs argument\n", pa); \
status = MagickFalse; \
}
static MagickBooleanType menu(
const int argc,
const char **argv,
arctanT * pat
)
// Returns MagickTrue if okay.
{
int
i;
MagickBooleanType
status;
status = MagickTrue;
pat->do_verbose = MagickFalse;
pat->scale = 1 / (2.0 * M_PI);
pat->bias = 0.5;
for (i=0; i < argc; i++) {
char * pa = (char *)argv[i];
if (IsArg (pa, "s", "scale")==MagickTrue) {
NEXTARG;
pat->scale = atof(argv[i]);
} else if (IsArg (pa, "b", "bias")==MagickTrue) {
NEXTARG;
pat->bias = atof(argv[i]);
} else if (IsArg (pa, "v", "verbose")==MagickTrue) {
pat->do_verbose = MagickTrue;
} else {
fprintf (stderr, "arctan2: ERROR: unknown option [%s]\n", pa);
status = MagickFalse;
}
}
if (pat->do_verbose) {
fprintf (stderr, "arctan2 options:");
if (pat->do_verbose) fprintf (stderr, " verbose");
// FIXME: thoughout, %g should respect "-precision"
fprintf (stderr, " scale %g", pat->scale);
fprintf (stderr, " bias %g", pat->bias);
fprintf (stderr, "\n");
}
if (status == MagickFalse)
usage ();
return (status);
}
// The next function corresponds in style to functions in transform.c
// It takes one image, and returns an image.
//
static Image *arctan2(
Image *image1,
Image *image2,
arctanT * pat,
ExceptionInfo *exception)
{
Image
*new_image;
CacheView
*in_view1,
*in_view2,
*out_view;
ssize_t
y;
MagickBooleanType
status;
assert(image1 != (Image *) NULL);
assert(image1->signature == MAGICK_CORE_SIG);
if (image1->debug != MagickFalse)
(void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image1->filename);
if (image1->columns != image2->columns || image1->rows != image2->rows ) {
fprintf (stderr, "arctan2: needs same-sized images");
}
if (SetNoPalette (image1, exception) == MagickFalse)
return (Image *)NULL;
if (SetNoPalette (image2, exception) == MagickFalse)
return (Image *)NULL;
// Make a clone of this image, same size but undefined pixel values:
//
new_image=CloneImage(image1, image1->columns, image1->rows, MagickTrue, exception);
if (new_image == (Image *) NULL)
return(new_image);
if (SetNoPalette (new_image, exception) == MagickFalse)
return (Image *)NULL;
in_view1 = AcquireVirtualCacheView (image1,exception);
in_view2 = AcquireVirtualCacheView (image2,exception);
out_view = AcquireAuthenticCacheView (new_image,exception);
status = MagickTrue;
#if defined(MAGICKCORE_OPENMP_SUPPORT)
#pragma omp parallel for schedule(static,4) shared(status) \
MAGICK_THREADS(new_image,new_image,new_image->rows,1)
#endif
for (y=0; y < (ssize_t) new_image->rows; y++)
{
register VIEW_PIX_PTR
*p1, *p2, *q;
register ssize_t
x;
if (status == MagickFalse)
continue;
p1=GetCacheViewAuthenticPixels(in_view1,0,y,new_image->columns,1,exception);
if (p1 == (const VIEW_PIX_PTR *) NULL)
{
status=MagickFalse;
continue;
}
p2=GetCacheViewAuthenticPixels(in_view2,0,y,new_image->columns,1,exception);
if (p2 == (const VIEW_PIX_PTR *) NULL)
{
status=MagickFalse;
continue;
}
q=GetCacheViewAuthenticPixels(out_view,0,y,new_image->columns,1,exception);
if (q == (const VIEW_PIX_PTR *) NULL)
{
status=MagickFalse;
continue;
}
for (x=0; x < (ssize_t) new_image->columns; x++)
{
SET_PIXEL_RED (new_image,
QuantumRange *
(atan2 (GET_PIXEL_RED(image1,p1),
GET_PIXEL_RED(image2,p2)
) * pat->scale + pat->bias),
q);
SET_PIXEL_GREEN (new_image,
QuantumRange *
(atan2 (GET_PIXEL_GREEN(image1,p1),
GET_PIXEL_GREEN(image2,p2)
) * pat->scale + pat->bias),
q);
SET_PIXEL_BLUE (new_image,
QuantumRange *
(atan2 (GET_PIXEL_BLUE(image1,p1),
GET_PIXEL_BLUE(image2,p2)
) * pat->scale + pat->bias),
q);
p1 += Inc_ViewPixPtr (image1);
p2 += Inc_ViewPixPtr (image2);
q += Inc_ViewPixPtr (new_image);
}
if (SyncCacheViewAuthenticPixels(out_view,exception) == MagickFalse)
status=MagickFalse;
}
out_view = DestroyCacheView (out_view);
in_view2 = DestroyCacheView (in_view2);
in_view1 = DestroyCacheView (in_view1);
if (status == MagickFalse) return NULL;
return (new_image);
}
ModuleExport size_t arctan2Image(Image **images,const int argc,
const char **argv,ExceptionInfo *exception)
{
Image
*image1,
*image2,
*new_image;
arctanT
at;
assert(images != (Image **) NULL);
assert(*images != (Image *) NULL);
assert((*images)->signature == MAGICK_CORE_SIG);
// Replace each pair of images with atan2(u,v).
// The images must be the same size.
int ListLen = (int)GetImageListLength(*images);
if (ListLen !=2) {
fprintf (stderr, "arctan2 needs 2 images\n");
return (-1);
}
MagickBooleanType status = menu (argc, argv, &at);
if (status == MagickFalse)
return (-1);
image1 = (*images);
image2 = GetNextImageInList (image1);
new_image = arctan2 (image1, image2, &at, exception);
if (new_image == (Image *) NULL)
return(-1);
DeleteImageFromList (&image2);
ReplaceImageInList (&image1, new_image);
// Replace messes up the images pointer. Make it good:
*images = GetFirstImageInList (image1);
return (MagickImageFilterSignature);
}
/* Updated:
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <ctype.h>
#include "vsn_defines.h"
typedef struct {
double
scale,
bias,
offsetX,
offsetY;
MagickBooleanType
inverse,
do_verbose;
} rhothetaT;
static void usage (void)
{
printf ("Usage: -process 'rhotheta [OPTION]...'\n");
printf ("From x and y channels, calculates the arctangent and polar distance.\n");
printf ("\n");
printf (" s, scale N scale [1/2pi]\n");
printf (" b, bias N bias [0.5]\n");
printf (" o, offset N,N offset [0,0]\n");
printf (" inv, inverse inverse (rho,theta to x,y)\n");
printf (" v, verbose write text information to stdout\n");
printf ("[And xy of origin?]\n");
printf ("\n");
}
static MagickBooleanType IsArg (char * pa, char * ShtOpt, char * LongOpt)
{
if ((LocaleCompare(pa, ShtOpt)==0) || (LocaleCompare(pa, LongOpt)==0))
return MagickTrue;
return MagickFalse;
}
static MagickBooleanType ParseCoord (
const char * s, double * px, double * py)
{
int n;
char * p = (char *)s;
sscanf (p, "%lg%n", px, &n);
if (!n) return MagickFalse;
p += n;
if (*p != ',') return MagickFalse;
p++;
sscanf (p, "%lg%n", py, &n);
if (!n) return MagickFalse;
p += n;
if (*p != '\0') return MagickFalse;
return MagickTrue;
}
#define NEXTARG \
if (i < argc-1) i++; \
else { \
fprintf (stderr, "baryc: ERROR: [%s] needs argument\n", pa); \
status = MagickFalse; \
}
static MagickBooleanType menu(
const int argc,
const char **argv,
rhothetaT * prt
)
// Returns MagickTrue if okay.
{
int
i;
MagickBooleanType
status;
status = MagickTrue;
prt->do_verbose = MagickFalse;
prt->scale = 1 / (2.0 * M_PI);
prt->bias = 0.5;
prt->offsetX = 0;
prt->offsetY = 0;
prt->inverse = MagickFalse;
for (i=0; i < argc; i++) {
char * pa = (char *)argv[i];
if (IsArg (pa, "s", "scale")==MagickTrue) {
NEXTARG;
prt->scale = atof(argv[i]);
} else if (IsArg (pa, "b", "bias")==MagickTrue) {
NEXTARG;
prt->bias = atof(argv[i]);
} else if (IsArg (pa, "o", "offset")==MagickTrue) {
NEXTARG;
if (!ParseCoord (argv[i], &prt->offsetX, &prt->offsetY)) {
status = MagickFalse;
}
} else if (IsArg (pa, "inv", "inverse")==MagickTrue) {
prt->inverse = MagickTrue;
} else if (IsArg (pa, "v", "verbose")==MagickTrue) {
prt->do_verbose = MagickTrue;
} else {
fprintf (stderr, "rhotheta: ERROR: unknown option [%s]\n", pa);
status = MagickFalse;
}
}
if (prt->do_verbose) {
fprintf (stderr, "rhotheta options:");
// FIXME: thoughout, %g should respect "-precision"
fprintf (stderr, " scale %g", prt->scale);
fprintf (stderr, " bias %g", prt->bias);
if (prt->offsetX != 0 || prt->offsetY != 0) {
fprintf (stderr, " offset %g,%g", prt->offsetX, prt->offsetY);
}
if (prt->inverse) fprintf (stderr, " inverse");
if (prt->do_verbose) fprintf (stderr, " verbose");
fprintf (stderr, "\n");
}
if (status == MagickFalse)
usage ();
return (status);
}
// The next function takes an image, and returns it.
//
static Image *rhotheta(
Image *image,
rhothetaT * pat,
ExceptionInfo *exception)
{
// This function reads and writes the R and G channels.
// It leaves the B and alpha channels unchanged.
ssize_t
y;
MagickBooleanType
status;
assert(image != (Image *) NULL);
assert(image->signature == MAGICK_CORE_SIG);
if (image->debug != MagickFalse)
(void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
if (SetNoPalette (image, exception) == MagickFalse)
return (Image *)NULL;
CacheView * image_view = AcquireAuthenticCacheView (image,exception);
status = MagickTrue;
#if defined(MAGICKCORE_OPENMP_SUPPORT)
#pragma omp parallel for schedule(static,4) shared(status) \
MAGICK_THREADS(image,image,image->rows,1)
#endif
for (y=0; y < (ssize_t) image->rows; y++)
{
register VIEW_PIX_PTR
*p;
ssize_t
x;
if (status == MagickFalse)
continue;
p=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
if (!p)
{
status=MagickFalse;
continue;
}
double vx, vy, rho, theta;
for (x=0; x < (ssize_t) image->columns; x++)
{
if (pat->inverse) {
rho = GET_PIXEL_RED (image, p) / (MagickRealType)QuantumRange;
theta = (GET_PIXEL_GREEN (image, p) / (MagickRealType)QuantumRange
- pat->bias)
/ pat->scale;
vx = rho * sin (theta) + pat->offsetX;
vy = rho * cos (theta) + pat->offsetY;
SET_PIXEL_RED (image,
QuantumRange * vx,
p);
SET_PIXEL_GREEN (image,
QuantumRange * vy,
p);
} else {
vx = GET_PIXEL_RED (image, p) / (MagickRealType)QuantumRange - pat->offsetX;
vy = GET_PIXEL_GREEN (image, p) / (MagickRealType)QuantumRange - pat->offsetY;
rho = hypot (vx, vy);
theta = atan2 (vx, vy) * pat->scale + pat->bias;
SET_PIXEL_RED (image,
QuantumRange * rho,
p);
SET_PIXEL_GREEN (image,
QuantumRange * theta,
p);
}
p += Inc_ViewPixPtr (image);
}
if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
status=MagickFalse;
}
image_view = DestroyCacheView (image_view);
if (!status) return NULL;
return (image);
}
ModuleExport size_t rhothetaImage(Image **images,const int argc,
const char **argv,ExceptionInfo *exception)
{
rhothetaT
rt;
assert(images != (Image **) NULL);
assert(*images != (Image *) NULL);
assert((*images)->signature == MAGICK_CORE_SIG);
MagickBooleanType status = menu (argc, argv, &rt);
if (status == MagickFalse)
return (-1);
Image * image;
for (image=(*images); image != (Image *) NULL; image=GetNextImageInList(image))
{
Image * newImg = rhotheta (image, &rt, exception);
if (!newImg) return (-1);
if (newImg != image) {
ReplaceImageInList (&image, newImg);
*images=GetFirstImageInList (newImg);
}
}
return (MagickImageFilterSignature);
}
/* Updated:
3-April-2018 for v7.0.7-28
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include "vsn_defines.h"
#include "rmsealpha.inc"
/* Takes two images.
Does a subimage search of the second image within the first:
finds the position of smallest score,
and returns the score at that location.
The weighting is such that a fully-transparent pixel effectively matches
any other pixel exactly, whether that pixel is opaque or not,
and contributes nothing to the scrore.
At a given position:
imageScore = sqrt (sigma(pixelScore) / sigma(pixelmAlpha) / 3)
pixelScore = (dRed^2 + dGreen^2 + dBlue^2 ) * pixelmAlpha
dRed = GET_PIXEL_RED(image,a) - GET_PIXEL_RED(image,b)
etc for green and blue.
pixelmAlpha = GET_PIXEL_ALPHA(image,a) * GET_PIXEL_ALPHA(image,b)
(Alternative pixelmAlpha = (min (GET_PIXEL_ALPHA(image,a), GET_PIXEL_ALPHA(image,b)))^2
Updated:
12-August-2017 for v7.
22-August-2017 added multi-scale and adjustLC; moved code to rmsealpha.inc
*/
// FIXME? Also option to process all images after first as searchimages?
static void usage (void)
{
printf ("Usage: -process 'rmsealpha [OPTION]...'\n");
printf ("Searches for lowest alpha-weighted RMSE score.\n");
printf ("\n");
printf (" ai, avoid_identical patches with identical RGBA score 1.0\n");
printf (" ddo, dont_decrease_opacity patches with decreased opacity in any pixel score 1.0\n");
printf (" ms, multi_scale multi-scale search\n");
printf (" adj, adjustLC number adjust lightness and contrast\n");
printf (" j, just_score write the score only, with no \\n\n");
printf (" cnv, canvasOffset add canvas offset to result\n");
printf (" so, stdout write data to stdout\n");
printf (" se, stderr write data to stderr (default)\n");
printf (" sis, saveInpScales save input scales\n");
printf (" sss, saveSubScales save subimage scales\n");
printf (" z, sizeDiv number size divider for ms\n");
printf (" md, minDim integer minimum dimension for ms\n");
printf (" v, verbose write text information to stdout\n");
printf ("\n");
}
static MagickBooleanType IsArg (char * pa, char * ShtOpt, char * LongOpt)
{
if ((LocaleCompare(pa, ShtOpt)==0) || (LocaleCompare(pa, LongOpt)==0))
return MagickTrue;
return MagickFalse;
}
#define NEXTARG \
if (i < argc-1) i++; \
else { \
fprintf (stderr, "paintpatches: ERROR: [%s] needs argument\n", pa); \
status = MagickFalse; \
}
static MagickBooleanType menu(
const int argc,
const char **argv,
RmseAlphaT * pra
)
// Returns MagickTrue if okay.
{
int
i;
MagickBooleanType
status;
status = MagickTrue;
for (i=0; i < argc; i++) {
char * pa = (char *)argv[i];
if (IsArg (pa, "ai", "avoid_identical")==MagickTrue) {
pra->avoidIdentical = MagickTrue;
} else if (IsArg (pa, "ddo", "dont_decrease_opacity")==MagickTrue) {
pra->dontDecreaseOpacity = MagickTrue;
} else if (IsArg (pa, "j", "just_score")==MagickTrue) {
pra->outJustScore = MagickTrue;
} else if (IsArg (pa, "cnv", "canvasOffset")==MagickTrue) {
pra->addCanvOffs = MagickTrue;
} else if (IsArg (pa, "ms", "multi_scale")==MagickTrue) {
pra->multiScale = MagickTrue;
} else if (IsArg (pa, "adj", "adjustLC")==MagickTrue) {
NEXTARG;
pra->adjustMeanSd = atof(argv[i]);
} else if (IsArg (pa, "z", "sizeDiv")==MagickTrue) {
NEXTARG;
pra->sizeDivider = atof(argv[i]);
} else if (IsArg (pa, "md", "minDim")==MagickTrue) {
NEXTARG;
pra->minDim = atoi(argv[i]);
} else if (IsArg (pa, "so", "stdout")==MagickTrue) {
pra->outhandle = stdout;
} else if (IsArg (pa, "se", "stderr")==MagickTrue) {
pra->outhandle = stderr;
} else if (IsArg (pa, "sis", "saveInpScales")==MagickTrue) {
pra->saveInpScales = MagickTrue;
} else if (IsArg (pa, "sss", "saveSubScales")==MagickTrue) {
pra->saveSubScales = MagickTrue;
} else if (IsArg (pa, "v", "verbose")==MagickTrue) {
pra->do_verbose = MagickTrue;
} else {
fprintf (stderr, "rmsealpha: ERROR: unknown option [%s]\n", pa);
status = MagickFalse;
}
}
if (pra->do_verbose) {
fprintf (stderr, "rmsealpha options:");
if (pra->avoidIdentical) fprintf (stderr, " avoid_identical");
if (pra->dontDecreaseOpacity) fprintf (stderr, " dont_decrease_opacity");
if (pra->outJustScore) fprintf (stderr, " just_score");
if (pra->addCanvOffs) fprintf (stderr, " canvasOffset");
if (pra->multiScale) fprintf (stderr, " multi_scale");
if (pra->adjustMeanSd > 0) fprintf (stderr, " adjustLC %g", pra->adjustMeanSd);
fprintf (stderr, " sizeDiv %g", pra->sizeDivider);
fprintf (stderr, " minDim %i", pra->minDim);
if (pra->outhandle == stdout) fprintf (stderr, " stdout");
if (pra->outhandle == stderr) fprintf (stderr, " stderr");
if (pra->saveInpScales) fprintf (stderr, " saveInpScales");
if (pra->saveSubScales) fprintf (stderr, " saveSubScales");
if (pra->do_verbose) fprintf (stderr, " verbose");
fprintf (stderr, "\n");
}
if (status == MagickFalse)
usage ();
return (status);
}
// The next function corresponds in style to functions in enhance.c
// It takes one image, and returns a status.
//
static MagickBooleanType rmsealpha (
RmseAlphaT *pra,
Image *inp_image, Image *sub_image,
ExceptionInfo *exception)
{
int
precision;
assert(inp_image != (Image *) NULL);
assert(inp_image->signature == MAGICK_CORE_SIG);
assert(sub_image != (Image *) NULL);
assert(sub_image->signature == MAGICK_CORE_SIG);
if (inp_image->debug != MagickFalse)
(void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",inp_image->filename);
precision = GetMagickPrecision();
/*==
pra->solnX = pra->solnY = 0;
pra->score = 0;
nPosY = inp_image->rows - ht_sub + 1;
nPosX = inp_image->columns - wi_sub + 1;
for (y=0; y < nPosY; y++)
{
register ssize_t
x;
double
score;
if (status == MagickFalse)
continue;
for (x=0; x < nPosX; x++)
{
score = CompareWindow (
inp_image, sub_image,
pra, inp_view, sub_view, wi_sub, ht_sub, x, y, exception);
if (score < 0) status = MagickFalse;
if (pra->score > score) {
pra->score = score;
pra->solnX = x;
pra->solnY = y;
}
if (pra->score == 0.0) break;
}
if (pra->score == 0.0) break;
}
==*/
pra->warnSubSd = MagickFalse;
if (pra->multiScale) {
if (!subRmseAlphaMS (pra, inp_image, sub_image, exception)) {
fprintf (stderr, "subRmseAlphaMS failed\n");
return MagickFalse;
}
} else {
if (!subRmseAlpha (pra, inp_image, sub_image, exception)) {
fprintf (stderr, "subRmseAlpha failed\n");
return MagickFalse;
}
}
if (pra->warnSubSd)
fprintf (stderr, "rmsealpha: Warning: subimage SD==0, so adjustLC matches all\n");
if (pra->outJustScore) {
fprintf (pra->outhandle, "%.*g",
precision, pra->score);
} else {
if (pra->addCanvOffs) {
pra->solnX += inp_image->page.x;
pra->solnY += inp_image->page.y;
}
fprintf (pra->outhandle, "rmsealpha: %.*g @ %lu,%lu\n",
precision, pra->score, pra->solnX, pra->solnY);
fprintf (pra->outhandle, "rmsealphaCrop: %lix%li+%lu+%lu\n",
sub_image->columns, sub_image->rows, pra->solnX, pra->solnY);
}
return MagickTrue;
}
ModuleExport size_t rmsealphaImage(Image **images,const int argc,
const char **argv,ExceptionInfo *exception)
{
Image
*image,
*inp_image,
*sub_image;
MagickBooleanType
status;
RmseAlphaT
ra;
(void) argc;
(void) argv;
assert(images != (Image **) NULL);
assert(*images != (Image *) NULL);
assert((*images)->signature == MAGICK_CORE_SIG);
InitRmseAlpha (&ra);
status = menu (argc, argv, &ra);
if (status == MagickFalse)
return (-1);
// Compare the images in pairs.
// If we don't have even number of images, fatal error.
for (image=(*images); image != (Image *) NULL; image=GetNextImageInList(image))
{
inp_image = image;
image=GetNextImageInList(image);
sub_image = image;
if (sub_image == (Image *) NULL) {
status = MagickFalse;
} else {
status = rmsealpha(&ra, inp_image, sub_image, exception);
if (ra.saveInpScales || ra.saveSubScales) {
// Do it again, to test the saving mechanism.
status = rmsealpha(&ra, inp_image, sub_image, exception);
// Do it again, to test the saving mechanism.
status = rmsealpha(&ra, inp_image, sub_image, exception);
}
}
ReInitRmseAlpha (&ra);
if (status == MagickFalse)
continue;
}
DeInitRmseAlpha (&ra);
assert (status == MagickTrue);
return(MagickImageFilterSignature);
}
/*
Updated:
17-September-2017: if a dimension difference is zero,
exclude it as a limiter.
19-September-2017: sub-image larger than main image is no longer fatal.
If sub-image is larger in either dimension,
only one search will be made in that dimension.
22-September-2017: added doSlideX and doSlideY. When false,
even if sub-image smaller than main, windows will not slide in
that direction.
3-April-2018 for v7.0.7-28
14-April-2018 added offset processing to subRmseAlphaMS
*/
#ifndef RMSEALPHA_INC
#define RMSEALPHA_INC
#include "calcmnsd.inc"
/* Possible optimisations:
- When searching a large image multiple times,
use integral images to speed up mean and SD calcs.
*/
#define RMSE_ALPHA_SIGNATURE 54321
typedef struct {
MagickBooleanType
do_verbose,
avoidIdentical,
dontDecreaseOpacity,
outJustScore,
doSlideX,
doSlideY,
addCanvOffs, // whether caller will add canvas offset to result
multiScale, // Not used.
saveInpScales, // Whether to save and reuse scaled versions of inp_image
saveSubScales; // Whether to save and reuse scaled versions of sub_image
FILE * outhandle;
double
adjustMeanSd,
sizeDivider;
int
minDim;
int
supSampFact;
// Returned values:
ssize_t
solnX,
solnY;
double
solnXf,
solnYf;
double
score; // RMSE, 0.0 to 1.0.
MagickBooleanType
warnSubSd;
// Used internally:
MeanSdT
meanSd_inp,
meanSd_sub;
MagickBooleanType
inpScalesSaved, // whether inp scale have been scaled, so can be reused
subScalesSaved, // whether sub scale have been scaled, so can be reused
mainHasAlpha,
subHasAlpha;
Image
*inp_list,
*sub_list;
int
signature;
} RmseAlphaT;
typedef struct {
double gainR;
double gainG;
double gainB;
double biasR;
double biasG;
double biasB;
} GainBiasT;
static void InitRmseAlpha (RmseAlphaT * pra)
{
pra->do_verbose = pra->avoidIdentical = pra->dontDecreaseOpacity = MagickFalse;
pra->outJustScore = MagickFalse;
pra->multiScale = MagickFalse;
pra->saveInpScales = MagickFalse;
pra->saveSubScales = MagickFalse;
pra->doSlideX = pra->doSlideY = MagickTrue;
pra->addCanvOffs = MagickFalse;
pra->outhandle = stderr;
pra->adjustMeanSd = 0.0;
pra->minDim = 20;
pra->supSampFact = 1;
pra->sizeDivider = 2.0;
pra->inpScalesSaved = MagickFalse;
pra->subScalesSaved = MagickFalse;
pra->inp_list = pra->sub_list = NULL;
pra->warnSubSd = MagickFalse;
pra->solnX = pra->solnY = -1;
pra->solnXf = pra->solnYf = -1;
pra->score = -1;
pra->mainHasAlpha = MagickFalse;
pra->subHasAlpha = MagickFalse;
pra->signature = RMSE_ALPHA_SIGNATURE;
}
#define CHK_RA_SIG(pra) { \
if ((pra)->signature != RMSE_ALPHA_SIGNATURE) \
fprintf (stderr, "\n** bad RMSE_ALPHA_SIGNATURE **\n"); \
}
static void ReInitRmseAlpha (RmseAlphaT * pra)
// After this, the returned values are still available.
{
// When we record resizes, this will free them,
// allowing for fresh searches.
CHK_RA_SIG(pra);
if (pra->signature != RMSE_ALPHA_SIGNATURE)
fprintf (stderr, "ReInitRmseAlpha: bad sig\n");
if (pra->inp_list) {
if (pra->do_verbose) fprintf (stderr, "rmsealpha: DestroyImageList\n");
pra->inp_list = DestroyImageList (pra->inp_list);
}
if (pra->sub_list) {
if (pra->do_verbose) fprintf (stderr, "rmsealpha: DestroyImageList\n");
pra->sub_list = DestroyImageList (pra->sub_list);
}
pra->inpScalesSaved = MagickFalse;
pra->subScalesSaved = MagickFalse;
}
static void DeInitRmseAlpha (RmseAlphaT * pra)
// After this, the returned values are still available.
{
CHK_RA_SIG(pra);
ReInitRmseAlpha (pra);
pra->signature = 0;
}
static void inline MeanSdToGainBiasOne (
double adjustMeanSd,
double mnA,
double sdA,
double mnB,
double sdB,
double * gain,
double * bias
)
{
double gn, bs;
if (sdA > 0) {
gn = sdB / sdA;
} else {
gn = 1.0;
}
bs = mnB - mnA * gn;
*gain = (1 - adjustMeanSd + adjustMeanSd*gn);
*bias = adjustMeanSd * bs * QuantumRange;
}
static void inline MeanSdToGainBias (
double adjustMeanSd,
MeanSdT * mnsdA,
MeanSdT * mnsdB,
GainBiasT * gb)
// Sets gb to required gain and bias to make A look like B.
{
MeanSdToGainBiasOne (adjustMeanSd,
mnsdA->mnR, mnsdA->sdR, mnsdB->mnR, mnsdB->sdR,
&gb->gainR, &gb->biasR);
MeanSdToGainBiasOne (adjustMeanSd,
mnsdA->mnG, mnsdA->sdG, mnsdB->mnG, mnsdB->sdG,
&gb->gainG, &gb->biasG);
MeanSdToGainBiasOne (adjustMeanSd,
mnsdA->mnB, mnsdA->sdB, mnsdB->mnB, mnsdB->sdB,
&gb->gainB, &gb->biasB);
}
static double CompareWindow (
const Image *inp_image,
const Image *sub_image,
RmseAlphaT *pra,
CacheView * inp_view,
CacheView * subimage_view,
ssize_t width, ssize_t height,
ssize_t offsX, ssize_t offsY,
ExceptionInfo *exception)
//
// Compares width x height of subimage with same-size window in inp,
// starting at top-left location (offsX,offsY) of inp.
// Returns number between 0.0 and 1.0 inclusive.
// 0.0 means exact match
// (but could be because sigma(alpha) is 0.0.
//
{
GainBiasT gb = {1.0,1.0,1.0,0.0,0.0,0.0};
if (pra->adjustMeanSd != 0.0) {
if (!CalcMeanSdVP (
inp_image, inp_view, width, height, offsX, offsY,
&pra->meanSd_inp, exception))
{
return MagickFalse; // FIXME: flag error?
}
if (pra->meanSd_inp.isMeanSdDefined) {
if (pra->meanSd_inp.sdR == 0
&& pra->meanSd_inp.sdG == 0
&& pra->meanSd_inp.sdB == 0)
{
pra->warnSubSd = MagickTrue;
}
// We make the window on the input look more like the subimage.
// We set gainX relative to 1.0, but biasX relative to QuantumRange.
MeanSdToGainBias (pra->adjustMeanSd,
&pra->meanSd_inp, &pra->meanSd_sub, &gb);
}
}
double
sigScore = 0,
sigmAlpha = 0;
MagickBooleanType
isIdentical = MagickTrue,
okay = MagickTrue;
ssize_t y;
#if defined(MAGICKCORE_OPENMP_SUPPORT)
#pragma omp parallel for schedule(static,4) \
MAGICK_THREADS(inp_image,sub_image,height,1)
#endif
for (y = 0; y < height; y++) {
MagickBooleanType okayY = MagickTrue;
//if (!okay) continue;
const VIEW_PIX_PTR *inpy = GetCacheViewVirtualPixels (
inp_view,offsX,y+offsY,width,1,exception);
const VIEW_PIX_PTR *suby = GetCacheViewVirtualPixels (
subimage_view,0,y,width,1,exception);
if (inpy == (const VIEW_PIX_PTR *) NULL || suby == (const VIEW_PIX_PTR *) NULL) {
fprintf (stderr, "CompareWindow: badgcvvp\n");
okayY = MagickFalse;
continue;
}
double sigScoreY=0, sigmAlphaY=0;
double inpa = 1.0, suba = 1.0;
ssize_t x;
for (x = 0; x < width; x++) {
if (pra->mainHasAlpha) inpa = GET_PIXEL_ALPHA(inp_image, inpy)/(double)QuantumRange;
if (pra->subHasAlpha) suba = GET_PIXEL_ALPHA(sub_image, suby)/(double)QuantumRange;
double mAlpha = inpa * suba;
double dRed =
((GET_PIXEL_RED(inp_image,inpy)*gb.gainR+gb.biasR)
- GET_PIXEL_RED(sub_image,suby))/QuantumRange;
double dGreen =
((GET_PIXEL_GREEN(inp_image,inpy)*gb.gainG+gb.biasG)
- GET_PIXEL_GREEN(sub_image,suby))/QuantumRange;
double dBlue =
((GET_PIXEL_BLUE(inp_image,inpy)*gb.gainB+gb.biasB)
- GET_PIXEL_BLUE(sub_image,suby))/QuantumRange;
if (pra->dontDecreaseOpacity && inpa < suba) {
okayY = MagickFalse;
}
if (dRed!=0 || dGreen!=0 || dBlue!=0 || inpa!=suba) {
isIdentical = MagickFalse;
}
if (mAlpha > 0) {
sigScoreY += mAlpha * (dRed*dRed + dGreen*dGreen + dBlue*dBlue);
sigmAlphaY += mAlpha;
}
inpy += Inc_ViewPixPtr (inp_image);
suby += Inc_ViewPixPtr (sub_image);
}
#if defined(MAGICKCORE_OPENMP_SUPPORT)
#pragma omp atomic
#endif
sigScore += sigScoreY;
#if defined(MAGICKCORE_OPENMP_SUPPORT)
#pragma omp atomic
#endif
sigmAlpha += sigmAlphaY;
#if defined(MAGICKCORE_OPENMP_SUPPORT)
#pragma omp atomic
#endif
okay &= okayY;
}
if (!okay) return 1.0;
if (pra->avoidIdentical && isIdentical) return 1.0;
if (sigmAlpha==0) {
if (pra->do_verbose) fprintf (stderr, " CompareWindow: All transparent ");
return 0.0;
}
return sqrt (sigScore / sigmAlpha / 3.0);
}
static MagickBooleanType subRmseAlpha (
RmseAlphaT *pra,
const Image *inp_image, const Image *sub_image,
ExceptionInfo *exception)
{
/* If sub_image is larger than inp_image in either direction,
the extra pixels at right/bottom of sub_image are ignored,
so there is only one search in that direction.
Effectively, sub_image is cropped at right/bottom.
*/
assert(inp_image != (Image *) NULL);
assert(inp_image->signature == MAGICK_CORE_SIG);
assert(sub_image != (Image *) NULL);
assert(sub_image->signature == MAGICK_CORE_SIG);
if (pra->signature != RMSE_ALPHA_SIGNATURE) return MagickFalse;
ssize_t wi_sub = sub_image->columns;
ssize_t ht_sub = sub_image->rows;
/*== Test replaced 19-September-2017.
if (wi_sub > inp_image->columns || ht_sub > inp_image->rows) {
fprintf (stderr, "subRmseAlpha: Subimage %lix%li larger than input %lix%li.\n",
wi_sub, ht_sub,
inp_image->columns, inp_image->rows);
return MagickFalse;
}
==*/
if (wi_sub > inp_image->columns) wi_sub = inp_image->columns;
if (ht_sub > inp_image->rows) ht_sub = inp_image->rows;
CacheView * inp_view = AcquireVirtualCacheView(inp_image,exception);
CacheView * sub_view = AcquireVirtualCacheView(sub_image,exception);
pra->solnX = pra->solnY = 0;
pra->score = 99e9;
if (pra->adjustMeanSd != 0.0) {
if (!CalcMeanSdVP (
sub_image, sub_view, wi_sub, ht_sub, 0, 0,
&pra->meanSd_sub, exception))
{
return MagickFalse;
}
if (pra->do_verbose) WrMeanSd (&pra->meanSd_sub, stderr);
if (!pra->meanSd_sub.isMeanSdDefined) {
pra->adjustMeanSd = 0.0;
}
}
ssize_t nPosX = inp_image->columns - wi_sub + 1;
ssize_t nPosY = inp_image->rows - ht_sub + 1;
if (!pra->doSlideX) nPosX = 1;
if (!pra->doSlideY) nPosY = 1;
pra->mainHasAlpha = IS_ALPHA_CH(inp_image);
pra->subHasAlpha = IS_ALPHA_CH(sub_image);
pra->warnSubSd = MagickFalse;
if (pra->do_verbose) {
fprintf (stderr, "subRmseAlpha: inp %lix%li (%s alpha) sub %lix%li (%s alpha) nPos %lix%li",
inp_image->columns, inp_image->rows, (pra->mainHasAlpha) ? "has" : "no",
wi_sub, ht_sub, (pra->subHasAlpha) ? "has" : "no",
nPosX, nPosY);
}
ssize_t y;
MagickBooleanType okay = MagickTrue;
for (y=0; y < nPosY; y++)
{
ssize_t
x;
double
score;
if (okay == MagickFalse)
continue;
for (x=0; x < nPosX; x++)
{
score = CompareWindow (
inp_image, sub_image,
pra, inp_view, sub_view, wi_sub, ht_sub, x, y, exception);
if (score < 0) okay = MagickFalse;
else if (pra->score > score) {
pra->score = score;
pra->solnX = x;
pra->solnY = y;
if (pra->score == 0.0) break;
}
}
if (pra->score == 0.0) break;
}
if (pra->do_verbose) {
fprintf (stderr, " %g @ %li,%li\n",
pra->score, pra->solnX, pra->solnY);
}
pra->solnXf = pra->solnX;
pra->solnYf = pra->solnY;
sub_view = DestroyCacheView (sub_view);
inp_view = DestroyCacheView (inp_view);
return okay;
}
static void WrList (Image * img, FILE * fh)
{
if (!img) {
fprintf (fh, "List empty\n");
} else {
while (img) {
fprintf (fh, "%lix%li\n", img->columns, img->rows);
img = GetNextImageInList (img);
}
}
}
// Each image can be in only one list.
// We don't want to mess with the main list of 5,
// so clone images for rmse lists.
static MagickBooleanType subRmseAlphaMSOne (
RmseAlphaT *pra,
const Image *inp_image, const Image *sub_image,
ExceptionInfo *exception)
// Multi-scale subimage search.
// This calls itself recursively.
{
assert(inp_image != (Image *) NULL);
assert(inp_image->signature == MAGICK_CORE_SIG);
assert(sub_image != (Image *) NULL);
assert(sub_image->signature == MAGICK_CORE_SIG);
if (pra->signature != RMSE_ALPHA_SIGNATURE) return MagickFalse;
ssize_t smDim = inp_image->columns;
if (smDim > inp_image->rows) smDim = inp_image->rows;
if (smDim > sub_image->columns) smDim = sub_image->columns;
if (smDim > sub_image->rows) smDim = sub_image->rows;
// 17-September-2017: if a diff is zero, exclude it as a limiter.
ssize_t dimDiff = inp_image->columns-sub_image->columns;
if (dimDiff && smDim > dimDiff) smDim = dimDiff;
dimDiff = inp_image->rows-sub_image->rows;
if (dimDiff && smDim > dimDiff) smDim = dimDiff;
if (pra->do_verbose) {
fprintf (stderr, "subRmseAlphaMSOne: %lix%li+%li+%li %lix%li+%li+%li smDim %li\n",
inp_image->columns, inp_image->rows,
inp_image->page.x, inp_image->page.y,
sub_image->columns, sub_image->rows,
sub_image->page.x, sub_image->page.y,
smDim);
}
if (pra->saveInpScales && !pra->inpScalesSaved) {
// Save clone of this image to end of inp_list.
Image * copy_img = CloneImage(inp_image, 0, 0, MagickTrue, exception);
if (!copy_img) return MagickFalse;
AppendImageToList (&pra->inp_list, copy_img);
if (pra->do_verbose) {
fprintf (stderr, "AppendImage inp %lix%li to %lix%li\n",
copy_img->columns, copy_img->rows,
pra->inp_list->columns, pra->inp_list->rows);
WrList (pra->inp_list, stderr);
}
}
if (pra->saveSubScales && !pra->subScalesSaved) {
// Save clone of this image to end of sub_list.
Image * copy_img = CloneImage(sub_image, 0, 0, MagickTrue, exception);
if (!copy_img) return MagickFalse;
AppendImageToList (&pra->sub_list, copy_img);
if (pra->do_verbose) {
fprintf (stderr, "AppendImage sub %lix%li to %lix%li\n",
copy_img->columns, copy_img->rows,
pra->sub_list->columns, pra->sub_list->rows);
WrList (pra->sub_list, stderr);
}
}
if (smDim < pra->minDim)
return subRmseAlpha (pra, inp_image, sub_image, exception);
Image *inp_sm, *sub_sm;
if (pra->saveInpScales && pra->inpScalesSaved) {
// Get small version from list.
// It is the next one after inp_image.
if (pra->do_verbose) {
fprintf (stderr, "GetNextImage inp after %lix%li\n",
inp_image->columns, inp_image->rows);
}
inp_sm = GetNextImageInList (inp_image);
// If we don't have a small image,
// create it. (This can happen if subimages are different sizes.)
if (!inp_sm) {
fprintf (stderr, "subRmseAlphaMSOne: no small inp\n");
WrList (pra->inp_list, stderr);
// FIXME: temp:
inp_sm = RESIZEIMG (inp_image,
inp_image->columns/pra->sizeDivider, inp_image->rows/pra->sizeDivider,
exception);
// FIXME: Append this to the list?
Image * copy_img = CloneImage(inp_sm, 0, 0, MagickTrue, exception);
if (!copy_img) return MagickFalse;
AppendImageToList (&pra->inp_list, copy_img);
}
if (pra->do_verbose) {
fprintf (stderr, "Got Image inp %lix%li\n",
inp_sm->columns, inp_sm->rows);
}
} else {
if (pra->do_verbose) {
fprintf (stderr, "Resizing Image inp\n");
}
inp_sm = RESIZEIMG (inp_image,
inp_image->columns/pra->sizeDivider, inp_image->rows/pra->sizeDivider,
exception);
}
if (!inp_sm) return MagickFalse;
if (pra->saveSubScales && pra->subScalesSaved) {
// Get small version from list.
// It is the next one after sub_image.
if (pra->do_verbose) {
fprintf (stderr, "GetNextImage sub after %lix%li\n",
sub_image->columns, sub_image->rows);
}
sub_sm = GetNextImageInList (sub_image);
// If we don't have a small image,
// create it. (This can happen if subimages are different sizes.)
if (!sub_sm) {
fprintf (stderr, "subRmseAlphaMSOne: no small sub\n");
WrList (pra->sub_list, stderr);
}
if (pra->do_verbose) {
fprintf (stderr, "Got Image sub %lix%li\n",
sub_sm->columns, sub_sm->rows);
}
} else {
if (pra->do_verbose) {
fprintf (stderr, "Resizing Image sub\n");
}
sub_sm = RESIZEIMG (sub_image,
sub_image->columns/pra->sizeDivider, sub_image->rows/pra->sizeDivider,
exception);
}
if (!sub_sm) return MagickFalse;
if (!subRmseAlphaMSOne (pra, inp_sm, sub_sm, exception))
return MagickFalse;
if (!pra->subScalesSaved) sub_sm = DestroyImage (sub_sm);
if (!pra->inpScalesSaved) inp_sm = DestroyImage (inp_sm);
int plusMinus = floor (pra->sizeDivider + 2.5);
RectangleInfo geom;
geom.x = pra->solnX * pra->sizeDivider - plusMinus;
geom.y = pra->solnY * pra->sizeDivider - plusMinus;
if (geom.x < 0) geom.x = 0;
if (geom.y < 0) geom.y = 0;
geom.width = sub_image->columns + 2*plusMinus;
geom.height = sub_image->rows + 2*plusMinus;
if (geom.x + geom.width > inp_image->columns)
geom.width = inp_image->columns - geom.x;
if (geom.y + geom.height > inp_image->rows)
geom.height = inp_image->rows - geom.y;
if (pra->do_verbose) {
fprintf (stderr,
"subRmseAlphaMSOne: crop geom %lix%li+%li+%li out of %lix%li %lix%li+%li+%li \n",
geom.width, geom.height, geom.x, geom.y,
inp_image->columns, inp_image->rows,
inp_image->page.width, inp_image->page.height,
inp_image->page.x, inp_image->page.y);
}
Image * inp_crp = CropImage (inp_image, &geom, exception);
if (!inp_crp) return MagickFalse;
inp_crp->page.width = inp_crp->page.height
= inp_crp->page.x = inp_crp->page.y = 0;
if (!subRmseAlpha (pra, inp_crp, sub_image, exception))
return MagickFalse;
inp_crp = DestroyImage (inp_crp);
if (pra->do_verbose) {
WrList (pra->inp_list, stderr);
WrList (pra->sub_list, stderr);
}
// Add the geometry offsets back
pra->solnX += geom.x;
pra->solnY += geom.y;
pra->solnXf = pra->solnX;
pra->solnYf = pra->solnY;
return MagickTrue;
}
static MagickBooleanType subRmseSuper (
RmseAlphaT *pra,
Image *inp_image, Image *sub_image,
ExceptionInfo *exception)
// Supersampling subimage search.
// Assumes pra->solnX and pra->solnY are the best integral solutions.
// Sets pra->solnXf and pra->solnYf.
{
// Clone cropped inp_image, and resize it.
// Clone sub_image, and resize it.
// Search.
// Adjust the numbers.
int
dl=0,
dt=0,
dr=0,
db=0;
if (pra->solnX > 0) dl = 1;
if (pra->solnY > 0) dt = 1;
if (dl + pra->solnX + sub_image->columns < inp_image->columns) dr = 1;
if (dt + pra->solnY + sub_image->rows < inp_image->rows ) db = 1;
RectangleInfo geom;
geom.x = pra->solnX - dl;
geom.y = pra->solnY - dt;
geom.width = sub_image->columns + dl + dr;
geom.height = sub_image->rows + dt + db;
if (pra->do_verbose)
fprintf (stderr, "subRmseSuper geom: %lix%li+%li+%li\n",
geom.width, geom.height, geom.x, geom.y);
Image * inp_crp = CropImage (inp_image, &geom, exception);
if (!inp_crp) return MagickFalse;
inp_crp->page.width = inp_crp->page.height
= inp_crp->page.x = inp_crp->page.y = 0;
// Resize both.
Image * inp_res = RESIZEIMG (inp_crp,
inp_crp->columns*pra->supSampFact, inp_crp->rows*pra->supSampFact,
exception);
if (!inp_res) {
return MagickFalse;
}
inp_crp = DestroyImage (inp_crp);
Image * sub_res = RESIZEIMG (sub_image,
sub_image->columns*pra->supSampFact, sub_image->rows*pra->supSampFact,
exception);
if (!sub_res) {
return MagickFalse;
}
if (!subRmseAlphaMSOne (pra, inp_res, sub_res, exception))
return MagickFalse;
sub_res = DestroyImage (sub_res);
inp_res = DestroyImage (inp_res);
pra->solnXf = geom.x + pra->solnX / (double)pra->supSampFact;
pra->solnYf = geom.y + pra->solnY / (double)pra->supSampFact;
pra->solnX = floor (pra->solnXf + 0.5);
pra->solnY = floor (pra->solnYf + 0.5);
return MagickTrue;
}
static MagickBooleanType subRmseAlphaMS (
RmseAlphaT *pra,
Image *inp_image, Image *sub_image,
ExceptionInfo *exception)
// Multi-scale subimage search.
{
CHK_RA_SIG(pra);
if (pra->signature != RMSE_ALPHA_SIGNATURE) {
fprintf (stderr, "rmsealpha: bad signature\n");
return MagickFalse;
}
Image * inp_i = inp_image;
if (pra->inpScalesSaved) {
if (pra->do_verbose) fprintf (stderr, "subRmseAlphaMS: inpScalesSaved\n");
inp_i = pra->inp_list;
}
Image * sub_i = sub_image;
if (pra->subScalesSaved) {
if (pra->do_verbose) fprintf (stderr, "subRmseAlphaMS: subScalesSaved\n");
sub_i = pra->sub_list;
}
ssize_t ix = inp_i->page.x;
ssize_t iy = inp_i->page.y;
ssize_t sx = sub_i->page.x;
ssize_t sy = sub_i->page.y;
inp_i->page.x = sub_i->page.x =
inp_i->page.y = sub_i->page.y = 0;
MagickBooleanType r = subRmseAlphaMSOne (
pra, inp_i, sub_i, exception);
pra->solnX += ix;
pra->solnY += iy;
pra->solnXf = pra->solnX;
pra->solnYf = pra->solnY;
if (pra->supSampFact > 1) {
if (!subRmseSuper (pra, inp_i, sub_i, exception)) {
fprintf (stderr, "rmsealpha: bad subRmseSuper\n");
return MagickFalse;
}
}
inp_i->page.x = ix;
inp_i->page.y = iy;
sub_i->page.x = sx;
sub_i->page.y = sy;
if (pra->saveInpScales) {
pra->inpScalesSaved = MagickTrue;
if (pra->do_verbose) WrList (pra->inp_list, stderr);
}
if (pra->saveSubScales) {
pra->subScalesSaved = MagickTrue;
if (pra->do_verbose) WrList (pra->sub_list, stderr);
}
return r;
}
#endif
/* Calculate the mean and standard deviation
of the three colour channels,
of an image or a VIEW_PIX_PTR.
Updated:
3-April-2018 for v7.0.7-28
*/
#ifndef CALCMNSD_INC
#define CALCMNSD_INC 1
typedef struct {
// Returns values in range 0.0 to 1.0.
double mnR;
double mnG;
double mnB;
double sdR;
double sdG;
double sdB;
MagickBooleanType isMeanSdDefined;
} MeanSdT;
static void WrMeanSd (MeanSdT * pms, FILE * fh)
{
fprintf (fh, "isMeanSdDefined: %i\n", pms->isMeanSdDefined);
if (pms->isMeanSdDefined) {
fprintf (fh, " mean: %g %g %g\n", pms->mnR, pms->mnG, pms->mnB);
fprintf (fh, " SD: %g %g %g\n", pms->sdR, pms->sdG, pms->sdB);
}
}
static double inline sqrtEps (double v)
{
return (v < 1e-10) ? 0 : sqrt(v);
}
static MagickBooleanType CalcMeanSdVP (
const Image *image,
const CacheView * in_view,
ssize_t columns,
ssize_t rows,
ssize_t offsX,
ssize_t offsY,
MeanSdT * pms,
ExceptionInfo *exception
)
{
double sigR=0, sigG=0, sigB=0, sigA=0;
double sigR2=0, sigG2=0, sigB2=0;
MagickBooleanType okay = MagickTrue;
ssize_t y;
#if defined(MAGICKCORE_OPENMP_SUPPORT)
#pragma omp parallel for schedule(static,4) \
MAGICK_THREADS(image,image,rows,1)
#endif
for (y=0; y < rows; y++) {
MagickBooleanType okayY = MagickTrue;
const VIEW_PIX_PTR *p=GetCacheViewVirtualPixels (
in_view,offsX,y+offsY,columns,1,exception);
if (p == (const VIEW_PIX_PTR *) NULL) {
okayY = MagickFalse;
continue;
}
double sigRY=0, sigGY=0, sigBY=0, sigAY=0;
double sigR2Y=0, sigG2Y=0, sigB2Y=0;
ssize_t x;
double r, g, b, a;
for (x=0; x < columns; x++) {
a = GET_PIXEL_ALPHA(image,p) / QuantumRange;
r = a * GET_PIXEL_RED(image,p) / QuantumRange;
g = a * GET_PIXEL_GREEN(image,p) / QuantumRange;
b = a * GET_PIXEL_BLUE(image,p) / QuantumRange;
sigRY += r;
sigGY += g;
sigBY += b;
sigAY += a;
sigR2Y += r*r;
sigG2Y += g*g;
sigB2Y += b*b;
p += Inc_ViewPixPtr (image);
}
#if defined(MAGICKCORE_OPENMP_SUPPORT)
#pragma omp atomic
sigR += sigRY;
#pragma omp atomic
sigG += sigGY;
#pragma omp atomic
sigB += sigBY;
#pragma omp atomic
sigA += sigAY;
#pragma omp atomic
sigR2 += sigR2Y;
#pragma omp atomic
sigG2 += sigG2Y;
#pragma omp atomic
sigB2 += sigG2Y;
#pragma omp atomic
okay &= okayY;
#else
sigR += sigRY;
sigG += sigGY;
sigB += sigBY;
sigA += sigAY;
sigR2 += sigR2Y;
sigG2 += sigG2Y;
sigB2 += sigG2Y;
okay &= okayY;
#endif
}
if (sigA == 0) {
pms->isMeanSdDefined = MagickFalse;
return MagickTrue;
}
pms->isMeanSdDefined = MagickTrue;
pms->mnR = sigR / sigA;
pms->mnG = sigG / sigA;
pms->mnB = sigB / sigA;
pms->sdR = sqrtEps (sigR2/sigA - (pms->mnR)*(pms->mnR));
pms->sdG = sqrtEps (sigG2/sigA - (pms->mnG)*(pms->mnG));
pms->sdB = sqrtEps (sigB2/sigA - (pms->mnB)*(pms->mnB));
return MagickTrue;
}
static MagickBooleanType CalcMeanSdImg (
const Image *image,
MeanSdT * pms,
ExceptionInfo *exception
)
// Calculate mean and standard deviation,
// accounting for alpha (eg ignoring pixels that are entirely transparent).
// Returns values in range 0.0 to 1.0.
// Returns false if major problem.
// Returns isMeanSdDefined = MagickFalse iff statistics are not defined
// (image is entirely transparent).
{
// mnR = sig(R) / sig(alpha)
// sdR = sqrt ( sig(R^2) / sig(alpha) - mnR^2 )
//
// Likewise for G and B.
CacheView *in_view = AcquireVirtualCacheView (image, exception);
MagickBooleanType r = CalcMeanSdVP (
image, in_view, image->columns, image->rows, 0, 0, pms, exception);
in_view = DestroyCacheView (in_view);
return r;
}
#endif
/*
Reference: http://im.snibgo.com/srchimg.htm
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <math.h>
#include <ctype.h>
#include "vsn_defines.h"
#include "rmsealpha.inc"
#define VERSION "srchimg v1.0 Copyright (c) 2017 Alan Gibson"
typedef struct {
int
precision;
MagickBooleanType
doCrop,
addCanvOffs, // whether to add canvas offset to result
verbose;
double
sizeDivider;
int
minDim;
int
supSampFact;
FILE *
fh_data;
} srchimgT;
static void usage (void)
{
printf ("Usage: -process 'srchimg [OPTION]...'\n");
printf ("Searches for second image in first.\n");
printf ("\n");
printf (" n, noCrop don't replace images with crop\n");
printf (" z, sizeDiv number size divider for each level\n");
printf (" md, minDim integer minimum dimension for ms\n");
printf (" ss, superSample number factor for supersampling\n");
printf (" cnv, canvasOffset add canvas offset to result\n");
printf (" f, file string write to file stream stdout or stderr\n");
printf (" v, verbose write text information to stdout\n");
printf (" version write version information to stdout\n");
printf ("\n");
}
/* TODO:
Action on input offsets:
- ignore (effectively "+repage" at start)
- regard ("+repage" but add in at the end)
*/
static MagickBooleanType IsArg (char * pa, char * ShtOpt, char * LongOpt)
{
// LocaleCompare is not case-sensitive,
// so we use strcmp for the short option.
if ((strcmp(pa, ShtOpt)==0) || (LocaleCompare(pa, LongOpt)==0))
return MagickTrue;
return MagickFalse;
}
#define NEXTARG \
if (i < argc-1) i++; \
else { \
fprintf (stderr, "srchimg: ERROR: [%s] needs argument\n", pa); \
status = MagickFalse; \
}
static MagickBooleanType menu(
const int argc,
const char **argv,
srchimgT * psi
)
// Returns MagickTrue if okay.
{
int
i;
MagickBooleanType
status = MagickTrue;
psi->verbose = MagickFalse;
psi->doCrop = MagickTrue;
psi->addCanvOffs = MagickFalse;
psi->sizeDivider = 2.0;
psi->minDim = 20;
psi->supSampFact = 1;
psi->fh_data = stderr;
for (i=0; i < argc; i++) {
char * pa = (char *)argv[i];
//printf ("Arg %i [%s]\n", i, pa);
if (IsArg (pa, "n", "noCrop")==MagickTrue) {
psi->doCrop = MagickFalse;
} else if (IsArg (pa, "z", "sizeDiv")==MagickTrue) {
NEXTARG;
psi->sizeDivider = atof(argv[i]);
} else if (IsArg (pa, "md", "minDim")==MagickTrue) {
NEXTARG;
psi->minDim = atoi(argv[i]);
} else if (IsArg (pa, "ss", "superSample")==MagickTrue) {
NEXTARG;
psi->supSampFact = atoi(argv[i]);
} else if (IsArg (pa, "cnv", "canvasOffset")==MagickTrue) {
psi->addCanvOffs = MagickTrue;
} else if (IsArg (pa, "f", "file")==MagickTrue) {
NEXTARG;
if (strcasecmp (argv[i], "stdout")==0) psi->fh_data = stdout;
else if (strcasecmp (argv[i], "stderr")==0) psi->fh_data = stderr;
else status = MagickFalse;
} else if (IsArg (pa, "v", "verbose")==MagickTrue) {
psi->verbose = MagickTrue;
} else if (IsArg (pa, "version", "version")==MagickTrue) {
fprintf (stdout, "%s\n", VERSION);
} else {
fprintf (stderr, "srchimg: ERROR: unknown option [%s]\n", pa);
status = MagickFalse;
}
}
if (psi->verbose) {
fprintf (stderr, "srchimg options:");
if (!psi->doCrop) fprintf (stderr, " noCrop");
fprintf (stderr, " sizeDiv %g", psi->sizeDivider);
fprintf (stderr, " minDim %i", psi->minDim);
if (psi->supSampFact > 1) fprintf (stderr, " superSample %i", psi->supSampFact);
if (psi->addCanvOffs) fprintf (stderr, " canvasOffset");
if (psi->fh_data == stdout) fprintf (stderr, " file stdout");
if (psi->verbose) fprintf (stderr, " verbose");
fprintf (stderr, "\n");
}
if (status == MagickFalse)
usage ();
return (status);
}
// The next function takes an image list,
// may modify it, and returns a status.
//
static MagickBooleanType srchimg (
Image **images,
srchimgT * psi,
ExceptionInfo *exception)
{
if ((*images)->debug != MagickFalse)
(void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",(*images)->filename);
int ListLen = (int)GetImageListLength(*images);
if (ListLen !=2) {
fprintf (stderr, "srchimg needs exactly 2 images\n");
return MagickFalse;
}
Image * inp_image = *images;
Image * sub_image = GetNextImageInList (inp_image);
if (psi->verbose) {
fprintf (stderr, "srchimg: Input image [%s] %ix%i depth is %i\n",
inp_image->filename,
(int)inp_image->columns, (int)inp_image->rows,
(int)inp_image->depth);
}
RmseAlphaT ra;
InitRmseAlpha (&ra);
ra.do_verbose = psi->verbose;
ra.multiScale = MagickTrue;
ra.sizeDivider = psi->sizeDivider;
ra.minDim = psi->minDim;
ra.supSampFact = psi->supSampFact;
if (!subRmseAlphaMS (&ra, inp_image, sub_image, exception)) {
fprintf (stderr, "subRmseAlphaMS failed\n");
return MagickFalse;
}
DeInitRmseAlpha (&ra);
ssize_t offsX=0, offsY=0;
if (psi->addCanvOffs) {
offsX = inp_image->page.x;
offsY = inp_image->page.y;
}
double dx = ra.solnXf + offsX;
double dy = ra.solnYf + offsY;
// fprintf (psi->fh_data, "%.*g @ %li,%li\n",
// psi->precision, ra.score,
// ra.solnX + offsX, ra.solnY + offsY);
fprintf (psi->fh_data, "%.*g @ %g,%g\n",
psi->precision, ra.score,
dx, dy);
if (psi->doCrop) {
Image * inp_crp;
if (psi->supSampFact == 1) {
RectangleInfo geom;
geom.x = ra.solnX;
geom.y = ra.solnY;
geom.width = sub_image->columns;
geom.height = sub_image->rows;
inp_crp = CropImage (inp_image, &geom, exception);
if (!inp_crp) return MagickFalse;
} else {
// fixme: also set viewport
char text[MaxTextExtent];
sprintf (text, "%lux%lu+0+0",
sub_image->columns, sub_image->rows);
#if IMV6OR7==6
SetImageProperty (inp_image, "viewport", text);
#else
SetImageProperty (inp_image, "viewport", text, exception);
#endif
MagickBooleanType bestfit = MagickTrue;
double srtArray[6];
srtArray[0] = dx;
srtArray[1] = dx;
srtArray[2] = 1;
srtArray[3] = 0;
srtArray[4] = 0;
srtArray[5] = 0;
inp_crp = DistortImage (inp_image, ScaleRotateTranslateDistortion,
6, srtArray, bestfit, exception);
if (!inp_crp) { fprintf (stderr, "DistortImage failed\n"); return MagickFalse; }
}
DeleteImageFromList (&sub_image);
ReplaceImageInList (&inp_image, inp_crp);
// Replace messes up the images pointer. Make it good:
*images = GetFirstImageInList (inp_image);
}
if (psi->verbose) {
fprintf (stderr, "Finished srchimg\n");
}
return MagickTrue;
}
ModuleExport size_t srchimgImage (
Image **images,
const int argc,
const char **argv,
ExceptionInfo *exception)
{
srchimgT si;
assert(images != (Image **) NULL);
assert(*images != (Image *) NULL);
assert((*images)->signature == MAGICK_CORE_SIG);
MagickBooleanType status = menu (argc, argv, &si);
if (!status) return -1;
si.precision = GetMagickPrecision();
status = srchimg (images, &si, exception);
if (!status) return (-1);
return(MagickImageFilterSignature);
}
/* Updated:
6-April-2018 Corrected bug in verbose.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include "vsn_defines.h"
#include "aggrsrch.inc"
#define VERSION "aggrsrch v1.0 Copyright (c) 2017 Alan Gibson"
typedef struct {
MagickBooleanType
verbose,
makeMask;
double
aggression, // 0.0 < aggression <= 1.0.
maskThreshold; // typically 0.0 to 1.0.
} SubSrchT;
static void InitSubSrch (SubSrchT * pss)
{
pss->makeMask = MagickFalse;
pss->verbose = MagickFalse;
pss->maskThreshold = 0.75;
pss->aggression = 1.0;
}
static void DeInitSubSrch (SubSrchT * pss)
{
; // Nothing
}
static void usage (void)
{
printf ("Usage: -process 'aggrsrch [OPTION]...'\n");
printf ("Search with aggressive resizing.\n");
printf ("\n");
printf (" a, aggression number 0.0 to 1.0\n");
printf (" m, makeMask make mask from result\n");
printf (" t, threshold number 0.0 to 1.0\n");
printf (" v, verbose write text information to stdout\n");
printf (" version write version information to stdout\n");
printf ("\n");
}
static MagickBooleanType IsArg (char * pa, char * ShtOpt, char * LongOpt)
{
if ((LocaleCompare(pa, ShtOpt)==0) || (LocaleCompare(pa, LongOpt)==0))
return MagickTrue;
return MagickFalse;
}
#define NEXTARG \
if (i < argc-1) i++; \
else { \
fprintf (stderr, "aggrsrch: ERROR: [%s] needs argument\n", pa); \
status = MagickFalse; \
}
static MagickBooleanType menu(
const int argc,
const char **argv,
SubSrchT * pss
)
// Returns MagickTrue if okay.
{
int
i;
MagickBooleanType
status;
status = MagickTrue;
for (i=0; i < argc; i++) {
char * pa = (char *)argv[i];
if (IsArg (pa, "m", "makeMask")==MagickTrue) {
pss->makeMask = MagickTrue;
} else if (IsArg (pa, "a", "aggression")==MagickTrue) {
NEXTARG;
pss->aggression = atof(argv[i]);
} else if (IsArg (pa, "t", "threshold")==MagickTrue) {
NEXTARG;
pss->maskThreshold = atof(argv[i]);
} else if (IsArg (pa, "v", "verbose")==MagickTrue) {
pss->verbose = MagickTrue;
} else if (IsArg (pa, "version", "version")==MagickTrue) {
fprintf (stdout, "%s\n", VERSION);
} else {
fprintf (stderr, "aggrsrch: ERROR: unknown option\n");
status = MagickFalse;
}
}
if (pss->verbose) {
fprintf (stderr, "aggrsrch options:");
fprintf (stderr, " aggression %g", pss->aggression);
if (pss->makeMask) {
fprintf (stderr, " makeMask");
fprintf (stderr, " threshold %g", pss->maskThreshold);
}
if (pss->verbose) fprintf (stderr, " verbose");
fprintf (stderr, "\n");
}
if (status == MagickFalse)
usage ();
return (status);
}
// The next function takes an image list,
// may modify it, and returns a status.
//
static MagickBooleanType aggrsrch (
Image **images,
SubSrchT * pss,
ExceptionInfo *exception)
{
Image * main_img = *images;
Image * sub_img = GetNextImageInList (main_img);
if (!sub_img) return MagickFalse;
if (sub_img->columns > main_img->columns ||
sub_img->rows > main_img->rows)
{
fprintf (stderr, "aggrsrch: subimage is larger than main\n");
return MagickFalse;
}
Image * r = aggrSrchDiff (
main_img, sub_img, pss->makeMask,
pss->aggression, pss->maskThreshold, pss->verbose, exception);
if (!r) return MagickFalse;
// FIXME: This makes a mask, which is useful.
// But how about finding the search coordinates?
// For each of the sinks, crop the appropriate rectangle from main,
// and search it for sub.
DeleteImageFromList (&sub_img);
DeleteImageFromList (&main_img);
*images = GetFirstImageInList (r);
return MagickTrue;
}
ModuleExport size_t aggrsrchImage(Image **images,const int argc,
const char **argv,ExceptionInfo *exception)
{
MagickBooleanType
status;
SubSrchT
ss;
assert(images != (Image **) NULL);
assert(*images != (Image *) NULL);
assert((*images)->signature == MAGICK_CORE_SIG);
InitSubSrch (&ss);
status = menu (argc, argv, &ss);
if (status == MagickFalse)
return (-1);
int ListLen = (int)GetImageListLength(*images);
if (ListLen != 2) {
fprintf (stderr, "aggrsrch: needs 2 images\n");
return (-1);
}
if (!aggrsrch (images, &ss, exception)) return (-1);
DeInitSubSrch (&ss);
return(MagickImageFilterSignature);
}
#ifndef AGGRSRCH_INC
#define AGGRSRCH_INC
#include "resetpage.inc"
#include "findsinks.inc"
#include "cropchk.inc"
static MagickBooleanType SetImgRMS (
Image *image,
MagickRealType *vLo,
ssize_t *fndX,
ssize_t *fndY,
ExceptionInfo *exception
)
// Sets R,G and B channels to the RMS of R, G and B.
// Sets alpha opaque.
// Return the darkest coordinate.
// Returns whether okay.
{
MagickBooleanType okay = MagickTrue;
ssize_t y;
CacheView * in_view = AcquireAuthenticCacheView (image, exception);
*vLo = QuantumRange;
ssize_t xLo = 0, yLo = 0;
for (y = 0; y < image->rows; y++) {
if (!okay) continue;
VIEW_PIX_PTR *p = GetCacheViewAuthenticPixels(
in_view,0,y,image->columns,1,exception);
if (!p) {okay = MagickFalse; continue; }
ssize_t x;
MagickRealType r, g, b, v;
for (x = 0; x < image->columns; x++) {
r = GET_PIXEL_RED (image, p);
g = GET_PIXEL_GREEN (image, p);
b = GET_PIXEL_BLUE (image, p);
v = sqrt ((r*r + g*g + b*b) / 3.0);
if (*vLo > v) {
*vLo = v;
xLo = x;
yLo = y;
}
SET_PIXEL_RED (image, v, p);
SET_PIXEL_GREEN (image, v, p);
SET_PIXEL_BLUE (image, v, p);
SET_PIXEL_ALPHA (image, QuantumRange, p);
p += Inc_ViewPixPtr (image);
}
if (SyncCacheViewAuthenticPixels(in_view,exception) == MagickFalse)
okay = MagickFalse;
}
in_view = DestroyCacheView (in_view);
*vLo /= QuantumRange;
*fndX = xLo;
*fndY = yLo;
return okay;
}
static Image * aggrSrchDiff (
Image * main_img,
Image * sub_img,
MagickBooleanType MakeMask,
double aggression, // 0.0 < aggression <= 1.0.
double maskThreshold, // 0.0 to 1.0. >= 1.0 for no threshold.
MagickBooleanType verbose,
ExceptionInfo *exception)
{
/* This should account for an expected minimum size.
Eg if the sub_img appears in main_img at half the size,
then we can't be so aggressive.
*/
if (aggression <= 0) {
fprintf (stderr, "aggrSrch Diff: aggression=%g\n", aggression);
return NULL;
}
ssize_t mainW = main_img->columns;
ssize_t mainH = main_img->rows;
ssize_t newW = floor (mainW / (aggression * sub_img->columns ) + 0.5);
ssize_t newH = floor (mainH / (aggression * sub_img->rows ) + 0.5);
if (verbose) fprintf (stderr,
"aggrSrch Diff: main %lix%li sub %lix%li newMain %lix%li threshold %g\n",
mainW, mainH,
sub_img->columns, sub_img->rows,
newW, newH,
maskThreshold);
if (newW < 1 || newH < 1) {
fprintf (stderr, "aggrSrchDiff: sub_img too small\n");
}
Image * main_res = ScaleImage (main_img, newW, newH, exception);
if (!main_res) return NULL;
Image * sub_res1 = ScaleImage (sub_img, 1, 1, exception);
if (!sub_res1) return NULL;
#if IMV6OR7==6
SetImageAlphaChannel (sub_res1, DeactivateAlphaChannel);
#else
SetImageAlphaChannel (sub_res1, DeactivateAlphaChannel, exception);
#endif
Image * sub_res2 = ScaleImage (sub_res1, newW, newH, exception);
if (!sub_res2) return NULL;
sub_res1 = DestroyImage (sub_res1);
if (!COMPOSITE(main_res, DifferenceCompositeOp, sub_res2, 0,0, exception))
return NULL;
sub_res2 = DestroyImage (sub_res2);
//#if IMV6OR7==6
// if (!GrayscaleImage (main_res, RMSPixelIntensityMethod)) return NULL;
//
// TransformImageColorspace (main_res, sRGBColorspace);
//
// SetImageAlphaChannel (main_res, DeactivateAlphaChannel);
//#else
// if (!GrayscaleImage (main_res, RMSPixelIntensityMethod, exception)) return NULL;
//
// TransformImageColorspace (main_res, sRGBColorspace, exception);
//
// SetImageAlphaChannel (main_res, DeactivateAlphaChannel, exception);
//#endif
MagickRealType vLo;
ssize_t fndX, fndY;
if (! SetImgRMS (main_res, &vLo, &fndX, &fndY, exception)) return NULL;
if (verbose) {
fprintf (stderr,
"aggrsrch Res: %g @ %li,%li\n",
vLo, (long int)fndX, (long int)fndY);
}
if (MakeMask) {
Image * main_lns = LightenNonSinks (main_res, verbose, exception);
if (!main_lns) return NULL;
DestroyImage (main_res);
main_res = main_lns;
}
Image * main_res2 = RESIZEIMG (main_res,
mainW, mainH, exception);
if (!main_res2) return NULL;
DestroyImage (main_res);
main_res = main_res2;
if (MakeMask) {
// Crop to size for the mask, offset by half subimage dimensions.
RectangleInfo fndRect;
fndRect.width = mainW - sub_img->columns + 1;
fndRect.height = mainH - sub_img->rows + 1;
if (fndRect.width < 1 || fndRect.height < 1) {
fprintf (stderr, "aggrSrch Diff: zero or neg mask\n");
return NULL;
}
fndRect.x = sub_img->columns / 2;
fndRect.y = sub_img->rows / 2;
if (verbose) fprintf (stderr, "aggrSrch Mask: crop to %lix%li+%li+%li\n",
fndRect.width, fndRect.height, fndRect.x, fndRect.y);
Image * main_res3 = CropCheck (main_res, &fndRect, exception);
if (!main_res3) return NULL;
ResetPage (main_res3);
DestroyImage (main_res);
main_res = main_res3;
// Set red and green channels to zero.
// Where blue is more than a threshold, set it to 100%.
double limitBlue = maskThreshold * QuantumRange;
int nRemoved = 0;
CacheView * img_view = AcquireAuthenticCacheView (main_res, exception);
MagickBooleanType okay = MagickTrue;
ssize_t y;
for (y = 0; y < main_res->rows; y++) {
if (!okay) continue;
VIEW_PIX_PTR *q = GetCacheViewAuthenticPixels(
img_view,0,y,main_res->columns,1,exception);
if (!q) {okay = MagickFalse; continue; }
ssize_t x;
for (x = 0; x < main_res->columns; x++) {
SET_PIXEL_RED (main_res, 0, q);
SET_PIXEL_GREEN (main_res, 0, q);
double b = GET_PIXEL_BLUE (main_res, q);
if (b > limitBlue) {
SET_PIXEL_BLUE (main_res, QuantumRange, q);
nRemoved++;
}
q += Inc_ViewPixPtr (main_res);
}
if (SyncCacheViewAuthenticPixels(img_view,exception) == MagickFalse)
okay = MagickFalse;
}
if (!okay) return NULL;
if (verbose) fprintf (stderr, "aggrSrch Mask: removed %g%%\n",
nRemoved * 100.0 / (double)(main_res->columns * main_res->rows));
img_view = DestroyCacheView (img_view);
}
return main_res;
}
#endif
/*
Created 30-Nov-2015
Updated:
3-April-2018 for v7.0.7-28
*/
/* Takes two images: reference and source, any size, possibly with transparency.
Returns image same size as source,
with red and green channels as relative or absolute displacement map,
and blue channel as RMSE score.
The map points to pixels in the reference that centre on windows that match source window.
Output pixels corresponding to fully-transparent source pixels have
null displacement and zero score.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <ctype.h>
#include "vsn_defines.h"
#include "chklist.h"
#include "compwind.h"
#include "match.h"
// If you want search windows clamped to the bounds of the reference image,
// as suggested in the PatchMatch paper, define CLAMP_SEARCH_WINDOW as 1.
//
// If you want my modification that avoids generating random offsets
// that would exceed the bounds,
// define CLAMP_SEARCH_WINDOW as 0.
//
#define CLAMP_SEARCH_WINDOW 0
typedef double pmValueT;
typedef enum {
psToMatch, // Find a match for this pixel.
psTrans, // Pixel is transparent; don't try to match it.
psMatched, // A good-enough match has been found.
} pixStateT;
// Valid state transitions:
typedef enum {
dtAbsolute,
dtRelative
} DispTypeT;
typedef struct {
pixStateT
State;
ssize_t
ref_x,
ref_y;
pmValueT
score; // MSE (_not_ RMSE)
} PixT;
typedef struct {
CompWindT
CompWind;
MatchT
Match;
MagickBooleanType
do_verbose,
AutoRepeat,
WindowRad_IsPc,
tanc,
do_search,
do_part,
do_sweep,
debug;
double
WindowRad;
int
WindowRadPix,
MaxPartIter;
DispTypeT
dispType;
char
write_filename[MaxTextExtent];
/* Calculated. */
int
sqDim;
ssize_t
refWidth,
refHeight,
srcWidth,
srcHeight;
PixT
** pix;
RandomInfo
*restrict random_info;
Image
*src_image;
// Also: relative or absolute
} PixMatchT;
#include "compwind.inc"
#include "match.inc"
#include "writeframe.inc"
static void usage (void)
{
printf ("Usage: -process 'pixmatch [OPTION]...'\n");
printf ("Matches pixels from source in reference.\n");
printf ("\n");
printf (" wr, window_radius N radius of search window, >= 0\n");
printf (" lsr, limit_search_radius N limit radius to search, >= 0\n");
printf (" default = 0 = no limit\n");
printf (" st, similarity_threshold N\n");
printf (" stop searching when RMSE <= N (eg 0.01)\n");
printf (" default 0\n");
printf (" hc, hom_chk X homogeneity check, X is off or a small number\n");
printf (" default 0.1\n");
printf (" e, search_to_edges X search for matches to image edges, on or off\n");
printf (" default on\n");
printf (" s, search X X=none or entire or random or skip\n");
printf (" default entire\n");
printf (" rs, rand_searches N number of random searches (eg 100)\n");
printf (" default 0\n");
printf (" sn, skip_num N number of searches to skip in each direction\n");
printf (" (eg 10)\n");
printf (" default 0\n");
printf (" ref, refine X whether to refine random and skip searches,\n");
printf (" on or off\n");
printf (" default on\n");
printf (" part, propagate_and_random_trial X\n");
printf (" on or off\n");
printf (" default on\n");
printf (" mpi, max_part_iter N maximum number of PART iterations, >= 1\n");
printf (" 0 = no maximum\n");
printf (" default 10\n");
printf (" tanc, terminate_after_no_change X\n");
printf (" on or off\n");
printf (" default on\n");
printf (" swp, sweep X on or off\n");
printf (" default off\n");
printf (" dpt, displacement_type X displacement type for input and output,\n");
printf (" absolute or relative\n");
printf (" default absolute\n");
printf (" ac, auto_correct N when RMSE score > N, attempt to correct (eg 0.02)\n");
printf (" default 0 = no auto correction\n");
//printf (" a, auto_repeat if pixels changed but any unfilled, repeat\n");
//printf (" w, write filename write frames to files\n");
printf (" v, verbose write text information to stdout\n");
printf ("\n");
}
static MagickBooleanType IsArg (char * pa, char * ShtOpt, char * LongOpt)
{
if ((LocaleCompare(pa, ShtOpt)==0) || (LocaleCompare(pa, LongOpt)==0))
return MagickTrue;
return MagickFalse;
}
static inline MagickBooleanType EndsPc (const char *s)
{
char c = *(s+strlen(s)-1);
if (c == '%' || c == 'c')
return MagickTrue;
return MagickFalse;
}
static MagickBooleanType menu(
const int argc,
const char **argv,
PixMatchT * pmh
)
// Returns MagickTrue if okay.
{
int
i;
MagickBooleanType
status;
status = MagickTrue;
pmh->do_verbose = MagickFalse;
pmh->WindowRad = 1.0;
pmh->WindowRad_IsPc = MagickFalse;
pmh->WindowRadPix = 1;
pmh->debug = MagickFalse;
pmh->AutoRepeat = MagickFalse;
pmh->MaxPartIter = 10;
pmh->do_search = pmh->do_part = pmh->tanc = MagickTrue;
pmh->do_sweep = MagickFalse;
pmh->dispType = dtAbsolute;
pmh->write_filename[0] = '\0';
pmh->CompWind.HomChkOn = MagickTrue;
pmh->CompWind.HomChk = 0.1;
pmh->CompWind.nCompares = 0;
SetDefaultMatch (&pmh->Match);
pmh->Match.DoSubTransTest = MagickTrue;
pmh->Match.AutoLs = MagickFalse;
for (i=0; i < argc; i++) {
char * pa = (char *)argv[i];
if (IsArg (pa, "wr", "window_radius")==MagickTrue) {
i++;
if (!isdigit ((int)*argv[i]))
{
fprintf (stderr, "Non-numeric argument to 'window_radius'.\n");
status = MagickFalse;
}
else {
pmh->WindowRad = atof(argv[i]);
pmh->WindowRad_IsPc = EndsPc (argv[i]);
if (!pmh->WindowRad_IsPc)
pmh->WindowRadPix = pmh->WindowRad + 0.5;
}
if (pmh->WindowRad < 0) {
fprintf (stderr, "Bad 'window_radius' value.\n");
status = MagickFalse;
}
} else if (IsArg (pa, "lsr", "limit_search_radius")==MagickTrue) {
// FIXME: also allow percentage of smaller image dimension.
i++;
if (!isdigit ((int)*argv[i])) {
fprintf (stderr, "Non-numeric argument to 'limit_search_radius'.\n");
status = MagickFalse;
} else {
pmh->Match.LimSrchRad = atof(argv[i]);
pmh->Match.LimSrchRad_IsPc = EndsPc (argv[i]);
if (!pmh->Match.LimSrchRad_IsPc)
pmh->Match.limSrchRadX = pmh->Match.limSrchRadY = pmh->Match.LimSrchRad + 0.5;
}
if (pmh->Match.LimSrchRad < 0) {
fprintf (stderr, "Bad 'limit_search_radius' value.\n");
status = MagickFalse;
}
} else if (IsArg (pa, "ref", "refine")==MagickTrue) {
i++;
if (LocaleCompare(argv[i], "on")==0)
pmh->Match.Refine = MagickTrue;
else if (LocaleCompare(argv[i], "off")==0)
pmh->Match.Refine = MagickFalse;
else {
fprintf (stderr, "Invalid 'refine' [%s]\n", argv[i]);
status = MagickFalse;
}
} else if (IsArg (pa, "dpt", "displacement_type")==MagickTrue) {
i++;
if (LocaleCompare(argv[i], "absolute")==0)
pmh->dispType = dtAbsolute;
else if (LocaleCompare(argv[i], "relative")==0)
pmh->dispType = dtRelative;
else {
fprintf (stderr, "Invalid 'displacement_type' [%s]\n", argv[i]);
status = MagickFalse;
}
} else if (IsArg (pa, "s", "search")==MagickTrue) {
i++;
if (LocaleCompare(argv[i], "entire")==0) {
pmh->Match.searchWhat = swEntire;
pmh->do_search = MagickTrue;
} else if (LocaleCompare(argv[i], "random")==0) {
pmh->Match.searchWhat = swRandom;
pmh->do_search = MagickTrue;
} else if (LocaleCompare(argv[i], "skip")==0) {
pmh->Match.searchWhat = swSkip;
pmh->do_search = MagickTrue;
} else if (LocaleCompare(argv[i], "none")==0) {
pmh->Match.searchWhat = swEntire;
pmh->do_search = MagickFalse;
} else {
fprintf (stderr, "Invalid 'search' [%s]\n", argv[i]);
status = MagickFalse;
}
} else if (IsArg (pa, "rs", "rand_searches")==MagickTrue) {
i++;
if (!isdigit ((int)*argv[i])) {
fprintf (stderr, "Non-numeric argument to 'rand_searches'.\n");
status = MagickFalse;
} else {
pmh->Match.RandSearchesF = atof (argv[i]);
pmh->Match.RandSearches_IsPc = EndsPc (argv[i]);
if (!pmh->Match.RandSearches_IsPc)
pmh->Match.RandSearchesI = pmh->Match.RandSearchesF + 0.5;
}
if (pmh->Match.RandSearchesF <= 0) {
fprintf (stderr, "Bad 'rand_searches' value.\n");
status = MagickFalse;
}
} else if (IsArg (pa, "sn", "skip_num")==MagickTrue) {
i++;
if (!isdigit ((int)*argv[i])) {
fprintf (stderr, "Non-numeric argument to 'skip_num'.\n");
status = MagickFalse;
} else {
pmh->Match.SkipNumF = atof (argv[i]);
pmh->Match.SkipNum_IsPc = EndsPc (argv[i]);
if (!pmh->Match.SkipNum_IsPc)
pmh->Match.SkipNumI = pmh->Match.SkipNumF + 0.5;
}
if (pmh->Match.SkipNumF <= 0) {
fprintf (stderr, "Bad 'skip_num' value.\n");
status = MagickFalse;
}
} else if (IsArg (pa, "hc", "hom_chk")==MagickTrue) {
i++;
if (LocaleCompare(argv[i], "off")==0) {
pmh->CompWind.HomChkOn = MagickFalse;
} else {
pmh->CompWind.HomChkOn = MagickTrue;
if (!isdigit ((int)*argv[i])) {
fprintf (stderr, "'hom_chk' argument must be number or 'off'.\n");
status = MagickFalse;
} else {
pmh->CompWind.HomChk = atof (argv[i]);
}
}
} else if (IsArg (pa, "part", "propagate_and_random_trial")==MagickTrue) {
i++;
if (LocaleCompare(argv[i], "off")==0) {
pmh->do_part = MagickFalse;
} else if (LocaleCompare(argv[i], "on")==0) {
pmh->do_part = MagickTrue;
} else {
fprintf (stderr, "'part' argument must be 'on' or 'off'.\n");
status = MagickFalse;
}
} else if (IsArg (pa, "swp", "sweep")==MagickTrue) {
i++;
if (LocaleCompare(argv[i], "off")==0) {
pmh->do_sweep = MagickFalse;
} else if (LocaleCompare(argv[i], "on")==0) {
pmh->do_sweep = MagickTrue;
} else {
fprintf (stderr, "'sweep' argument must be 'on' or 'off'.\n");
status = MagickFalse;
}
} else if (IsArg (pa, "tanc", "terminate_after_no_change")==MagickTrue) {
i++;
if (LocaleCompare(argv[i], "off")==0) {
pmh->tanc = MagickFalse;
} else if (LocaleCompare(argv[i], "on")==0) {
pmh->tanc = MagickTrue;
} else {
fprintf (stderr, "'tanc' argument must be 'on' or 'off'.\n");
status = MagickFalse;
}
} else if (IsArg (pa, "e", "search_to_edges")==MagickTrue) {
i++;
if (LocaleCompare(argv[i], "on")==0)
pmh->Match.SearchToEdges = MagickTrue;
else if (LocaleCompare(argv[i], "off")==0)
pmh->Match.SearchToEdges = MagickFalse;
else {
fprintf (stderr, "Invalid 'search_to_edges' [%s]\n", argv[i]);
status = MagickFalse;
}
} else if (IsArg (pa, "a", "auto_repeat")==MagickTrue) {
pmh->AutoRepeat = MagickTrue;
} else if (IsArg (pa, "st", "similarity_threshold")==MagickTrue) {
i++;
if (!isdigit ((int)*argv[i])) {
fprintf (stderr, "Non-numeric argument to 'similarity_threshold'.\n");
status = MagickFalse;
} else {
pmh->Match.simThreshold = atof (argv[i]);
}
} else if (IsArg (pa, "ac", "auto_correct")==MagickTrue) {
i++;
if (!isdigit ((int)*argv[i])) {
fprintf (stderr, "Non-numeric argument to 'auto_correct'.\n");
status = MagickFalse;
} else {
pmh->Match.autoCorrectThreshold = atof (argv[i]);
}
} else if (IsArg (pa, "mpi", "max_part_iter")==MagickTrue) {
i++;
if (!isdigit ((int)*argv[i])) {
fprintf (stderr, "Non-numeric argument to 'max_part_iter'.\n");
status = MagickFalse;
} else {
pmh->MaxPartIter = atoi (argv[i]);
}
} else if (IsArg (pa, "w", "write")==MagickTrue) {
i++;
CopyMagickString (pmh->write_filename, argv[i], MaxTextExtent);
if (!*pmh->write_filename) {
fprintf (stderr, "Invalid 'write' [%s]\n", argv[i]);
status = MagickFalse;
}
} else if (IsArg (pa, "v", "verbose")==MagickTrue) {
pmh->do_verbose = MagickTrue;
} else if (IsArg (pa, "d", "debug")==MagickTrue) {
pmh->debug = MagickTrue;
} else {
fprintf (stderr, "pixmatch: ERROR: unknown option [%s]\n", pa);
status = MagickFalse;
}
}
pmh->Match.simThresholdSq = pmh->Match.simThreshold * pmh->Match.simThreshold;
pmh->Match.autoCorrectThresholdSq = pmh->Match.autoCorrectThreshold * pmh->Match.autoCorrectThreshold;
if (pmh->do_verbose) {
fprintf (stderr, "pixmatch options:\n");
fprintf (stderr, " window_radius %g", pmh->WindowRad);
if (pmh->WindowRad_IsPc) fprintf (stderr, "%c", '%');
fprintf (stderr, " limit_search_radius %g", pmh->Match.LimSrchRad);
if (pmh->Match.LimSrchRad_IsPc) fprintf (stderr, "%c", '%');
fprintf (stderr, " similarity_threshold %g", pmh->Match.simThreshold);
fprintf (stderr, " hom_chk ");
if (pmh->CompWind.HomChkOn) {
fprintf (stderr, "%g", pmh->CompWind.HomChk);
} else {
fprintf (stderr, "off");
}
if (!pmh->Match.SearchToEdges) fprintf (stderr, " search_to_edges off");
fprintf (stderr, "\n search ");
if (pmh->do_search == MagickFalse) {
fprintf (stderr, "none");
} else {
switch (pmh->Match.searchWhat) {
case swEntire: fprintf (stderr, "entire"); break;
case swSkip:
fprintf (stderr, "skip skip_num %g", pmh->Match.SkipNumF);
if (pmh->Match.SkipNum_IsPc) fprintf (stderr, "%c", '%');
break;
case swRandom:
fprintf (stderr, "random rand_searches %g", pmh->Match.RandSearchesF);
if (pmh->Match.RandSearches_IsPc) fprintf (stderr, "%c", '%');
break;
default: fprintf (stderr, "??");
}
}
if (pmh->do_search == MagickTrue) {
fprintf (stderr, " refine %s", pmh->Match.Refine ? "on" : "off");
if (pmh->Match.autoCorrectThreshold > 0)
fprintf (stderr, " auto_correct %g", pmh->Match.autoCorrectThreshold);
}
fprintf (stderr, "\n propagate_and_random_trial %s", pmh->do_part ? "on" : "off");
if (pmh->do_part) {
fprintf (stderr, " max_part_iter %i", pmh->MaxPartIter);
fprintf (stderr, " tanc %s", pmh->tanc ? "on" : "off");
}
fprintf (stderr, "\n sweep %s", pmh->do_sweep ? "on" : "off");
fprintf (stderr, "\n displacement_type ");
switch (pmh->dispType) {
case dtAbsolute: fprintf (stderr, "absolute"); break;
case dtRelative: fprintf (stderr, "relative"); break;
default: fprintf (stderr, " ??");
}
if (pmh->AutoRepeat) fprintf (stderr, " auto_repeat");
if (pmh->debug) fprintf (stderr, " debug");
if (pmh->do_verbose) fprintf (stderr, " verbose");
fprintf (stderr, "\n");
}
if (status == MagickFalse)
usage ();
return (status);
}
static void InitRand (PixMatchT * pmh)
{
pmh->random_info=AcquireRandomInfo();
// There seems to be a problem: the first few values show coherency,
// so skip over them.
int i;
for (i=0; i < 20; i++) {
GetPseudoRandomValue(pmh->random_info);
}
}
static void DeInitRand (PixMatchT * pmh)
{
pmh->random_info=DestroyRandomInfo(pmh->random_info);
}
static MagickBooleanType Initialise (const Image *image,
PixMatchT * pmh,
ExceptionInfo *exception
)
{
ssize_t
x, y;
MagickBooleanType
status = MagickTrue;
//CacheView
// *image_view;
// Allocate memory
pmh->pix = (PixT **) AcquireQuantumMemory(pmh->srcHeight, sizeof(*pmh->pix));
if (pmh->pix == (PixT **) NULL) {
return MagickFalse;
}
for (y = 0; y < pmh->srcHeight; y++) {
pmh->pix[y] = (PixT *) AcquireQuantumMemory(pmh->srcWidth, sizeof(**pmh->pix));
if (pmh->pix[y] == (PixT *) NULL) break;
}
if (y < pmh->srcHeight) {
for (y--; y >= 0; y--) {
if (pmh->pix[y] != (PixT *) NULL)
pmh->pix[y] = (PixT *) RelinquishMagickMemory(pmh->pix[y]);
}
pmh->pix = (PixT **) RelinquishMagickMemory(pmh->pix);
return MagickFalse;
}
// Populate values
//#if defined(MAGICKCORE_OPENMP_SUPPORT)
// #pragma omp parallel for schedule(static,4) shared(status)
// MAGICK_THREADS(image,image,image->rows,1)
//#endif
for (y = 0; y < pmh->srcHeight; y++) {
VIEW_PIX_PTR const
*p;
if (status == MagickFalse)
continue;
p=GetCacheViewVirtualPixels(pmh->CompWind.sub_view,0,y,image->columns,1,exception);
if (p == (const VIEW_PIX_PTR *) NULL)
status=MagickFalse;
for (x = 0; x < pmh->srcWidth; x++) {
PixT * pn = &(pmh->pix[y][x]);
pn->State = (GET_PIXEL_ALPHA (image, p) <= 0) ? psTrans: psToMatch;
pn->ref_x = GetPseudoRandomValue(pmh->random_info) * pmh->refWidth;
pn->ref_y = GetPseudoRandomValue(pmh->random_info) * pmh->refHeight;
// fprintf (stderr, "%li,%li ", pn->ref_x, pn->ref_y);
pn->score = 123;
assert (pn->ref_x >= 0 && pn->ref_x < pmh->refWidth && pn->ref_y >= 0 && pn->ref_y < pmh->refHeight);
p += Inc_ViewPixPtr (image);
}
}
return (status);
}
static void deInitialise (PixMatchT * pmh)
{
ssize_t
y;
for (y = 0; y < pmh->srcHeight; y++) {
pmh->pix[y] = (PixT *) RelinquishMagickMemory(pmh->pix[y]);
}
pmh->pix = (PixT **) RelinquishMagickMemory(pmh->pix);
}
static void CalcScores (PixMatchT * pmh,
ExceptionInfo *exception)
{
ssize_t
y;
if (pmh->debug) fprintf (stderr, "CalcScores\n");
for (y = 0; y < pmh->srcHeight; y++) {
ssize_t
x;
for (x = 0; x < pmh->srcWidth; x++) {
PixT * pn = &(pmh->pix[y][x]);
if (pn->State == psToMatch) {
assert (pn->ref_x >= 0 && pn->ref_x < pmh->refWidth && pn->ref_y >= 0 && pn->ref_y < pmh->refHeight);
pmh->CompWind.refX = pn->ref_x - pmh->WindowRadPix;
pmh->CompWind.refY = pn->ref_y - pmh->WindowRadPix;
pmh->CompWind.subX = x - pmh->WindowRadPix;
pmh->CompWind.subY = y - pmh->WindowRadPix;
pmValueT v = CompareWindow (&pmh->CompWind, exception);
if (pmh->debug) fprintf (stderr, "%li,%li v=%g ", x, y, v);
pn->score = v;
if (v < pmh->Match.simThresholdSq) pn->State = psMatched;
}
}
if (pmh->debug) fprintf (stderr, "\n");
}
}
static MagickBooleanType inline WithinRefX (PixMatchT * pmh, ssize_t x)
{
return (x >= 0 && x < pmh->refWidth);
}
static MagickBooleanType inline WithinRefY (PixMatchT * pmh, ssize_t y)
{
return (y >= 0 && y < pmh->refHeight);
}
static MagickBooleanType inline WithinSrcX (PixMatchT * pmh, ssize_t x)
{
return (x >= 0 && x < pmh->srcWidth);
}
static MagickBooleanType inline WithinSrcY (PixMatchT * pmh, ssize_t y)
{
return (y >= 0 && y < pmh->srcHeight);
}
static MagickBooleanType WhichProp (PixMatchT * pmh,
ssize_t x,
ssize_t y,
int dxy,
int * dx,
int * dy)
// Enter with dxy=-1 for upper/left,
// or dxy=+1 for lower/right.
// If either has score lower than this,
// returns MagickTrue and sets dx, dy.
// Otherwise returns MagickFalse.
// (Could also do diagonal.))
{
PixT *pn = &(pmh->pix[y][x]);
pmValueT v = pn->score;
assert (pn->ref_x >= 0 && pn->ref_x < pmh->refWidth && pn->ref_y >= 0 && pn->ref_y < pmh->refHeight);
PixT *pno;
*dx = *dy = 0;
if (pmh->debug) fprintf (stderr, "WhichProp %li,%li, %i\n", x, y, dxy);
if (WithinSrcY(pmh, y+dxy)) {
pno = &(pmh->pix[y+dxy][x]);
if (v > pno->score && WithinRefY (pmh, pno->ref_y - dxy) )
{
*dy = dxy;
v = pno->score;
}
}
if (WithinSrcX(pmh, x+dxy)) {
pno = &(pmh->pix[y][x+dxy]);
if (v > pno->score && WithinRefX (pmh, pno->ref_x - dxy) )
{
*dy = 0;
*dx = dxy;
v = pno->score;
}
}
if (pmh->debug) fprintf (stderr, " dx,dy=%i,%i, v=%g\n", *dx, *dy, v);
return (*dx != 0 || *dy != 0);
}
static int Propagate (PixMatchT * pmh,
ssize_t x,
ssize_t y,
int dxy,
ExceptionInfo *exception)
// Returns 1 if result changed; otherwise 0.
{
int dx, dy;
PixT * pn = &(pmh->pix[y][x]);
assert (pn->ref_x >= 0 && pn->ref_x < pmh->refWidth && pn->ref_y >= 0 && pn->ref_y < pmh->refHeight);
int nChanges = 0;
if (pmh->debug) fprintf (stderr, "prop %li,%li: v=%g ", x, y, pn->score);
if (WhichProp (pmh, x, y, dxy, &dx, &dy)) {
PixT * pno = &(pmh->pix[y+dy][x+dx]);
ssize_t candX = pno->ref_x - dx;
ssize_t candY = pno->ref_y - dy;
if (pmh->debug) fprintf (stderr, "Cand %li,%li ", candX, candY);
// Now try match.
pmh->CompWind.subX = x - pmh->WindowRadPix;
pmh->CompWind.subY = y - pmh->WindowRadPix;
ssize_t limX = pmh->Match.limSrchRadX;
ssize_t limY = pmh->Match.limSrchRadY;
if (limX == 0) limX = 3;
if (limY == 0) limY = 3;
RangeT rng;
CalcMatchRange (candX, candY, limX, limY, &pmh->Match, &rng);
for (pmh->CompWind.refY = rng.i0; pmh->CompWind.refY < rng.i1; pmh->CompWind.refY++) {
for (pmh->CompWind.refX = rng.j0; pmh->CompWind.refX < rng.j1; pmh->CompWind.refX++) {
pmValueT v = CompareWindow (&pmh->CompWind, exception);
if (pmh->debug) fprintf (stderr, "v=%g ", v);
if (pn->score > v) {
if (pmh->debug) fprintf (stderr, "better ");
pn->score = v;
pn->ref_x = pmh->CompWind.refX + pmh->WindowRadPix;
pn->ref_y = pmh->CompWind.refY + pmh->WindowRadPix;
assert (pn->ref_x >= 0 && pn->ref_x < pmh->refWidth && pn->ref_y >= 0 && pn->ref_y < pmh->refHeight);
if (v < pmh->Match.simThresholdSq) {
pn->State = psMatched;
break;
}
nChanges = 1;
}
if (pn->State == psMatched) break;
}
}
}
if (pmh->debug) fprintf (stderr, "\n");
return nChanges;
}
static int RandTrial (PixMatchT * pmh,
ssize_t x,
ssize_t y,
ExceptionInfo *exception)
// Returns 1 if result changed; otherwise 0.
{
#define ALPHA (0.5)
pmValueT fact = 1.0;
ssize_t maxRad = pmh->refWidth;
if (maxRad < pmh->refHeight) maxRad = pmh->refHeight;
pmValueT rad = maxRad * fact;
pmh->CompWind.subX = x - pmh->WindowRadPix;
pmh->CompWind.subY = y - pmh->WindowRadPix;
PixT * pn = &(pmh->pix[y][x]);
int changes = 0;
do {
ssize_t tx, ty;
#if CLAMP_SEARCH_WINDOW==1
tx = pn->ref_x + rad * (GetPseudoRandomValue(pmh->random_info)*2-1);
ty = pn->ref_y + rad * (GetPseudoRandomValue(pmh->random_info)*2-1);
if (tx < 0) tx = 0;
if (ty < 0) ty = 0;
if (tx >= pmh->refWidth) tx = pmh->refWidth-1;
if (ty >= pmh->refHeight) ty = pmh->refHeight-1;
#else
do {
tx = pn->ref_x + rad * (GetPseudoRandomValue(pmh->random_info)*2-1);
} while (tx < 0 || tx >= pmh->refWidth);
do {
ty = pn->ref_y + rad * (GetPseudoRandomValue(pmh->random_info)*2-1);
} while (ty < 0 || ty >= pmh->refHeight);
#endif
if (pmh->debug) fprintf (stderr, "trial %li,%li ", tx, ty);
pmh->CompWind.refX = tx - pmh->WindowRadPix;
pmh->CompWind.refY = ty - pmh->WindowRadPix;
pmValueT v = CompareWindow (&pmh->CompWind, exception);
//if (pmh->debug) fprintf (stderr, "v=%g ", v);
if (pn->score > v) {
if (pmh->debug) fprintf (stderr, "better %li,%li,%g=>%li,%li,%g ", x, y, pn->score, tx, ty, v);
pn->score = v;
pn->ref_x = tx;
pn->ref_y = ty;
assert (pn->ref_x >= 0 && pn->ref_x < pmh->refWidth && pn->ref_y >= 0 && pn->ref_y < pmh->refHeight);
if (v < pmh->Match.simThresholdSq) pn->State = psMatched;
changes = 1;
}
fact *= ALPHA;
rad = maxRad * fact;
} while (rad > 1.0 && pn->State != psMatched);
return (changes);
}
static int OnePart (PixMatchT * pmh,
ExceptionInfo *exception)
// Returns the number of changes made.
{
ssize_t
y;
int nChanges = 0;
int nToMatch = 0;
for (y = 0; y < pmh->srcHeight; y++) {
ssize_t
x;
for (x = 0; x < pmh->srcWidth; x++) {
PixT * pn = &(pmh->pix[y][x]);
if (pn->State == psToMatch)
nChanges += Propagate (pmh, x, y, -1, exception);
if (pn->State == psToMatch)
nChanges += RandTrial (pmh, x, y, exception);
}
}
for (y = pmh->srcHeight-1; y >= 0; y--) {
ssize_t
x;
for (x = pmh->srcWidth-1; x >= 0; x--) {
PixT * pn = &(pmh->pix[y][x]);
if (pn->State == psToMatch)
nChanges += Propagate (pmh, x, y, +1, exception);
if (pn->State == psToMatch)
nChanges += RandTrial (pmh, x, y, exception);
if (pn->State == psToMatch) nToMatch++;
}
}
if (pmh->do_verbose) fprintf (stderr, "OnePart: nChanges=%i nToMatch=%i\n", nChanges, nToMatch);
return nChanges;
}
static int Sweep (PixMatchT * pmh,
ExceptionInfo *exception)
// Returns the number of changes made.
{
ssize_t
y;
RangeT
rng;
CalcMatchRange (0, 0, 0, 0, &pmh->Match, &rng);
int nChanges = 0;
ssize_t
besti=0, bestj=0;
double v;
CompWindT * cmpwin = &pmh->CompWind;
for (y = 0; y < pmh->srcHeight; y++) {
ssize_t
x;
for (x = 0; x < pmh->srcWidth; x++) {
PixT * pn = &(pmh->pix[y][x]);
if (pn->State == psToMatch) {
double bestScore = pn->score;
cmpwin->subX = x - pmh->Match.matchRadX;
cmpwin->subY = y - pmh->Match.matchRadY;
for (cmpwin->refY = rng.i0; cmpwin->refY < rng.i1; cmpwin->refY++) {
for (cmpwin->refX = rng.j0; cmpwin->refX < rng.j1; cmpwin->refX++) {
v = CompareWindow (cmpwin, exception);
if (bestScore > v) {
bestScore = v;
besti = cmpwin->refY;
bestj = cmpwin->refX;
if (bestScore <= pmh->Match.simThresholdSq) break;
}
}
if (bestScore <= pmh->Match.simThresholdSq) break;
}
if (pn->score > bestScore) {
pn->ref_x = bestj + pmh->Match.matchRadX;
pn->ref_y = besti + pmh->Match.matchRadY;
pn->score = bestScore;
nChanges++;
}
}
} // for x
} // for y
if (pmh->do_verbose) fprintf (stderr, "Sweep: nChanges=%i\n", nChanges);
return nChanges;
}
static Image *MkDispImage (
PixMatchT * pmh,
ExceptionInfo *exception)
// Make an image from the pixel data.
{
Image
*new_image;
MagickBooleanType
status = MagickTrue;
CacheView
*new_view;
ssize_t
y;
const long double
factX = QuantumRange / (long double)(pmh->refWidth-1),
factY = QuantumRange / (long double)(pmh->refHeight-1);
if (pmh->debug) fprintf (stderr, "MkDispImage\n");
// Clone the image, same size, copied pixels:
//
new_image=CloneImage(pmh->src_image, 0, 0, MagickTrue, exception);
if (new_image == (Image *) NULL)
return(new_image);
if (SetNoPalette (new_image, exception) == MagickFalse)
return (Image *)NULL;
new_view = AcquireAuthenticCacheView (new_image, exception);
chkentry("mdi",&new_image);
#if defined(MAGICKCORE_OPENMP_SUPPORT)
#pragma omp parallel for schedule(static,4) shared(status) \
MAGICK_THREADS(new_image,new_image,new_image->rows,1)
#endif
for (y = 0; y < pmh->srcHeight; y++) {
VIEW_PIX_PTR
*p;
ssize_t
x;
p = GetCacheViewAuthenticPixels(new_view,0,y,new_image->columns,1,exception);
if (p == (const VIEW_PIX_PTR *) NULL) status = MagickFalse;
if (status == MagickFalse)
continue;
for (x = 0; x < pmh->srcWidth; x++) {
// FIXME: plus half if not HDRI.
PixT * pn = &(pmh->pix[y][x]);
assert (pn->ref_x >= 0 && pn->ref_x < pmh->refWidth && pn->ref_y >= 0 && pn->ref_y < pmh->refHeight);
if (pmh->dispType == dtAbsolute) {
SET_PIXEL_RED (new_image, pn->ref_x * factX, p);
SET_PIXEL_GREEN (new_image, pn->ref_y * factY, p);
} else {
SET_PIXEL_RED (new_image, ((pn->ref_x - x) / (pmValueT)(2*pmh->refWidth) + 0.5) * QuantumRange, p);
SET_PIXEL_GREEN (new_image, ((pn->ref_y - y) / (pmValueT)(2*pmh->refHeight) + 0.5) * QuantumRange, p);
}
SET_PIXEL_BLUE (new_image, sqrt(pn->score) * QuantumRange, p);
SET_PIXEL_ALPHA (new_image, (pn->score > 4) ? 0 : QuantumRange, p);
p += Inc_ViewPixPtr (new_image);
}
}
if (SyncCacheViewAuthenticPixels (new_view,exception) == MagickFalse)
return (Image *)NULL;
new_view=DestroyCacheView (new_view);
chkentry("mdi2",&new_image);
return (new_image);
}
static MagickBooleanType ReadPrevResult (
PixMatchT * pmh,
Image * prevResult,
ExceptionInfo *exception)
{
CacheView
*view;
MagickBooleanType
status = MagickTrue;
ssize_t
y;
const long double
factX = QuantumRange / (long double)(pmh->refWidth-1),
factY = QuantumRange / (long double)(pmh->refHeight-1);
if (pmh->do_verbose) fprintf (stderr, "ReadPrevResult\n");
if (SetNoPalette (prevResult, exception) == MagickFalse)
return MagickFalse;
if (prevResult->rows != pmh->srcHeight || prevResult->columns != pmh->srcWidth) {
fprintf (stderr, "Sizes of source and previous result do not match.\n");
return MagickFalse;
}
view = AcquireVirtualCacheView (prevResult, exception);
#if defined(MAGICKCORE_OPENMP_SUPPORT)
#pragma omp parallel for schedule(static,4) shared(status) \
MAGICK_THREADS(prevResult,prevResult,prevResult->rows,1)
#endif
for (y = 0; y < pmh->srcHeight; y++) {
const VIEW_PIX_PTR
*p;
ssize_t
x;
p = GetCacheViewVirtualPixels(view,0,y,prevResult->columns,1,exception);
if (p == (const VIEW_PIX_PTR *) NULL) status = MagickFalse;
if (status == MagickFalse)
continue;
for (x = 0; x < pmh->srcWidth; x++) {
PixT * pn = &(pmh->pix[y][x]);
if (pmh->dispType == dtAbsolute) {
pn->ref_x = GET_PIXEL_RED (prevResult, p) / factX;
pn->ref_y = GET_PIXEL_GREEN (prevResult, p) / factY;
} else {
pn->ref_x = (GET_PIXEL_RED (prevResult, p) / QuantumRange - 0.5)
* 2*pmh->refWidth;
pn->ref_y = (GET_PIXEL_GREEN (prevResult, p) / QuantumRange - 0.5)
* 2*pmh->refHeight;
}
// The read image may have been resized, maybe with ringing,
// possibly causing values to go out of range,
// so we need to clamp.
if (pn->ref_x < 0) pn->ref_x = 0;
else if (pn->ref_x >= pmh->refWidth) pn->ref_x = pmh->refWidth-1;
if (pn->ref_y < 0) pn->ref_y = 0;
else if (pn->ref_y >= pmh->refHeight) pn->ref_y = pmh->refHeight-1;
assert (pn->ref_x >= 0 && pn->ref_x < pmh->refWidth && pn->ref_y >= 0 && pn->ref_y < pmh->refHeight);
// The score may have been upscaled, so might not be accurate.
pn->score = GET_PIXEL_BLUE (prevResult, p) / QuantumRange;
pn->score *= pn->score;
pn->State = psToMatch; // Until we know better.
p += Inc_ViewPixPtr (prevResult);
}
}
view=DestroyCacheView (view);
return MagickTrue;
}
static void IterParts (PixMatchT * pmh,
ExceptionInfo *exception)
// Returns the number of changes made.
{
int nChanges = 0;
int nIter = 0;
for (;;) {
nChanges = OnePart (pmh, exception);
if (*pmh->write_filename) {
Image *tmp_image = MkDispImage (pmh, exception);
WriteFrame (pmh->write_filename, tmp_image, nIter, exception);
DestroyImage (tmp_image);
}
if (nChanges == 0 && pmh->tanc) break;
nIter++;
if (pmh->MaxPartIter > 0 && nIter >= pmh->MaxPartIter) break;
}
if (pmh->do_verbose) fprintf (stderr, "IterParts: nIter=%i\n", nIter);
// FIXME: Also terminate if all pixels are state=matched
}
#if 0
static void DumpPixels (PixMatchT * pmh)
{
ssize_t
x, y;
fprintf (stderr, "DumpPixels\n");
for (y = 0; y < pmh->srcHeight; y++) {
for (x = 0; x < pmh->srcWidth; x++) {
PixT * pn = &(pmh->pix[y][x]);
char cState;
switch (pn->State) {
case psToMatch: cState='m'; break;
case psTrans: cState='t'; break;
case psMatched: cState='d'; break;
default: cState = '?';
}
fprintf (stderr, "%li,%li %c %li,%li %g\n",
x, y, cState,pn->ref_x, pn->ref_y, pn->score );
}
}
}
#endif
static void SearchPixels (
PixMatchT * pmh,
MagickBooleanType UsePrev,
ExceptionInfo *exception)
{
ssize_t
y;
//printf ("SearchPixels %i\n", (int)UsePrev);
ssize_t
nChanges = 0;
if (UsePrev) {
for (y = 0; y < pmh->srcHeight; y++) {
ssize_t
x;
for (x = 0; x < pmh->srcWidth; x++) {
PixT * pn = &(pmh->pix[y][x]);
if (pn->State != psMatched) {
// We could attempt propagation here.
Match (x, y, pn->ref_x, pn->ref_y,
&pmh->Match, &pmh->CompWind, pmh->random_info, exception);
if (pn->score > pmh->Match.bestScore) {
pn->ref_x = pmh->Match.bestX;
pn->ref_y = pmh->Match.bestY;
assert (pn->ref_x >= 0 && pn->ref_x < pmh->refWidth && pn->ref_y >= 0 && pn->ref_y < pmh->refHeight);
pn->score = pmh->Match.bestScore;
nChanges++;
if (pn->score < pmh->Match.simThresholdSq) pn->State = psMatched;
}
}
}
}
} else {
for (y = 0; y < pmh->srcHeight; y++) {
ssize_t
x;
for (x = 0; x < pmh->srcWidth; x++) {
PixT * pn = &(pmh->pix[y][x]);
if (pn->State != psMatched) {
Match (x, y, x, y,
&pmh->Match, &pmh->CompWind, pmh->random_info, exception);
pn->ref_x = pmh->Match.bestX;
pn->ref_y = pmh->Match.bestY;
assert (pn->ref_x >= 0 && pn->ref_x < pmh->refWidth && pn->ref_y >= 0 && pn->ref_y < pmh->refHeight);
pn->score = pmh->Match.bestScore;
nChanges++;
if (pn->score < pmh->Match.simThresholdSq) pn->State = psMatched;
}
}
}
}
if (pmh->do_verbose) fprintf (stderr, "Search: nChanges=%li\n", nChanges);
}
// The next function takes two or three images, and returns one image.
//
static Image *pixmatch (
PixMatchT * pmh,
Image *ref_image,
Image *src_image,
Image *prev_image,
ExceptionInfo *exception)
{
Image
*new_image;
MagickBooleanType
UsePrev = MagickFalse;
if (ref_image->debug != MagickFalse)
(void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",ref_image->filename);
pmh->refWidth = ref_image->columns;
pmh->refHeight = ref_image->rows;
pmh->srcWidth = src_image->columns;
pmh->srcHeight = src_image->rows;
if (pmh->do_verbose) {
fprintf (stderr, "---------------------------------------\npixmatch: ref %lix%li src %lix%li\n",
pmh->refWidth, pmh->refHeight, pmh->srcWidth, pmh->srcHeight);
}
pmh->CompWind.ref_image = ref_image;
pmh->CompWind.sub_image = src_image;
pmh->CompWind.ref_view = AcquireVirtualCacheView (ref_image,exception);
pmh->CompWind.sub_view = AcquireVirtualCacheView (src_image,exception);
pmh->src_image = src_image;
if (Initialise (src_image, pmh, exception) == MagickFalse)
return (Image *) NULL;
if (prev_image != (Image *) NULL) {
MagickBooleanType r = ReadPrevResult (pmh, prev_image, exception);
if (r == MagickFalse) return (Image *) NULL;
UsePrev = MagickTrue;
}
pmh->CompWind.win_wi = pmh->WindowRadPix*2+1;
pmh->CompWind.win_ht = pmh->WindowRadPix*2+1;
pmh->CompWind.WorstCompare = 9e11;
pmh->CompWind.AllowEquCoord = MagickTrue;
pmh->Match.ref_rows = ref_image->rows;
pmh->Match.ref_columns = ref_image->columns;
pmh->Match.matchRadX = pmh->WindowRadPix;
pmh->Match.matchRadY = pmh->WindowRadPix;
pmh->Match.AutoLs = MagickFalse;
pmh->Match.DissimOn = MagickFalse;
ssize_t nTotalCompares = 0;
pmh->CompWind.nCompares = 0;
CalcScores (pmh, exception);
if (pmh->do_verbose) fprintf (stderr, "CalcScores: nCompares=%li\n", pmh->CompWind.nCompares);
nTotalCompares += pmh->CompWind.nCompares;
if (pmh->do_search) {
pmh->CompWind.nCompares = 0;
SearchPixels (pmh, UsePrev, exception);
if (pmh->do_verbose) fprintf (stderr, "Search: nCompares=%li\n", pmh->CompWind.nCompares);
nTotalCompares += pmh->CompWind.nCompares;
}
//if (pmh->debug) DumpPixels (pmh);
if (pmh->do_part) {
pmh->CompWind.nCompares = 0;
IterParts (pmh, exception);
if (pmh->do_verbose) fprintf (stderr, "IterParts: nCompares=%li\n", pmh->CompWind.nCompares);
nTotalCompares += pmh->CompWind.nCompares;
}
if (pmh->do_sweep) {
pmh->CompWind.nCompares = 0;
Sweep (pmh, exception);
if (pmh->do_verbose) fprintf (stderr, "Sweep: nCompares=%li\n", pmh->CompWind.nCompares);
nTotalCompares += pmh->CompWind.nCompares;
}
if (pmh->do_verbose) fprintf (stderr, "nTotalCompares=%li\n", nTotalCompares);
pmh->CompWind.sub_view=DestroyCacheView (pmh->CompWind.sub_view);
pmh->CompWind.ref_view=DestroyCacheView (pmh->CompWind.ref_view);
new_image = MkDispImage (pmh, exception);
if (new_image == (Image *) NULL) {
fprintf (stderr, "MkDispImage failed\n");
return (new_image);
}
if (pmh->debug) fprintf (stderr, "deInitialise\n");
deInitialise (pmh);
chkentry("be",&new_image);
return (new_image);
}
ModuleExport size_t pixmatchImage (
Image **images,
const int argc,
const char **argv,
ExceptionInfo *exception)
{
Image
*image,
*ref_image,
*src_image,
*prev_image,
*new_image;
MagickBooleanType
status;
PixMatchT
pm;
int
ListLen;
(void) argc;
(void) argv;
assert(images != (Image **) NULL);
assert(*images != (Image *) NULL);
assert((*images)->signature == MAGICK_CORE_SIG);
status = menu (argc, argv, &pm);
if (status == MagickFalse)
return (-1);
InitRand (&pm);
chklist("chkL A", images);
ListLen = (int)GetImageListLength(*images);
if (ListLen < 2 || ListLen > 3) {
fprintf (stderr, "pixmatch needs 2 or 3 images\n");
return (-1);
}
image=(*images);
ref_image = image;
image=GetNextImageInList(image);
src_image = image;
if (ListLen == 3) {
image=GetNextImageInList(image);
prev_image = image;
} else {
prev_image = (Image *)NULL;
}
new_image = pixmatch(&pm, ref_image, src_image, prev_image, exception);
if (new_image == (Image *) NULL) {
fprintf (stderr, "pixmatch failed.\n");
status = MagickFalse;
}
DeleteImageFromList (&src_image);
if (ListLen == 3) {
DeleteImageFromList (&prev_image);
}
image = ref_image;
if (status == MagickTrue) {
ReplaceImageInList(&image,new_image);
}
chklist("after rem", &image);
*images=GetFirstImageInList(image);
chklist("after rem list", images);
DeInitRand (&pm);
if (status == MagickFalse) return -1;
return(MagickImageFilterSignature);
}
#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);
}
#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);
}
/* Updated:
27-October-2017 added version; introduced QoverLev2m1.
3-April-2018 for v7.0.7-28
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <ctype.h>
#include "vsn_defines.h"
#define DEBUG 0
#define VERSION "sphaldcl v1.0 Copyright (c) 2017 Alan Gibson"
typedef enum {
mIdentity,
mShepards,
mVoronoi
} methodT;
typedef struct {
int index;
double distance;
} distanceT;
typedef struct {
MagickBooleanType
do_verbose;
methodT
method;
int
haldLevel,
nearest;
double
powParam;
distanceT
*distances;
} sphaldclT;
static void usage (void)
{
printf ("Usage: -process 'sphaldcl [OPTION]...'\n");
printf ("Replace last image (Nx2) with hald clut.\n");
printf ("\n");
printf (" h, haldLevel N hald level [8]\n");
printf (" m, method String one of: identity, shepards, voronoi [identity]\n");
printf (" p, power N power [2]\n");
printf (" n, nearest N use just nearest (0=all) [0]\n");
printf (" v, verbose write text information to stdout\n");
printf (" version write version information to stdout\n");
printf ("\n");
}
static MagickBooleanType IsArg (char * pa, char * ShtOpt, char * LongOpt)
{
if ((LocaleCompare(pa, ShtOpt)==0) || (LocaleCompare(pa, LongOpt)==0))
return MagickTrue;
return MagickFalse;
}
#define NEXTARG \
if (i < argc-1) i++; \
else { \
fprintf (stderr, "sphaldcl: ERROR: [%s] needs argument\n", pa); \
status = MagickFalse; \
}
static MagickBooleanType menu(
const int argc,
const char **argv,
sphaldclT * pat
)
// Returns MagickTrue if okay.
{
int
i;
MagickBooleanType
status;
status = MagickTrue;
pat->method = mIdentity;
pat->haldLevel = 8;
pat->powParam = 2;
pat->nearest = 0;
pat->do_verbose = MagickFalse;
pat->distances = NULL;
for (i=0; i < argc; i++) {
char * pa = (char *)argv[i];
if (IsArg (pa, "h", "haldlevel")==MagickTrue) {
NEXTARG;
pat->haldLevel = atoi(argv[i]);
} else if (IsArg (pa, "p", "power")==MagickTrue) {
NEXTARG;
pat->powParam = atof(argv[i]);
} else if (IsArg (pa, "m", "method")==MagickTrue) {
NEXTARG;
if (LocaleCompare(argv[i], "identity")==0) pat->method = mIdentity;
else if (LocaleCompare(argv[i], "shepards")==0) pat->method = mShepards;
else if (LocaleCompare(argv[i], "voronoi")==0) pat->method = mVoronoi;
else {
fprintf (stderr, "sphaldcl: ERROR: unknown method [%s]\n", argv[i]);
status = MagickFalse;
}
} else if (IsArg (pa, "n", "nearest")==MagickTrue) {
NEXTARG;
pat->nearest = atoi(argv[i]);
} else if (IsArg (pa, "v", "verbose")==MagickTrue) {
pat->do_verbose = MagickTrue;
} else if (IsArg (pa, "version", "version")==MagickTrue) {
fprintf (stdout, "%s\n", VERSION);
} else {
fprintf (stderr, "sphaldcl: ERROR: unknown option [%s]\n", pa);
status = MagickFalse;
}
}
if (pat->do_verbose) {
fprintf (stderr, "sphaldcl options:");
if (pat->do_verbose) fprintf (stderr, " verbose");
fprintf (stderr, " haldLevel %i", pat->haldLevel);
fprintf (stderr, " method ");
switch (pat->method) {
case mIdentity:
fprintf (stderr, "identity");
break;
case mShepards:
fprintf (stderr, "shepards");
fprintf (stderr, " power %g", pat->powParam);
break;
case mVoronoi:
fprintf (stderr, "voronoi");
break;
}
fprintf (stderr, " nearest %i", pat->nearest);
fprintf (stderr, "\n");
}
if (status == MagickFalse)
usage ();
return (status);
}
static double inline colDist (
Image *image,
const VIEW_PIX_PTR *p0,
const VIEW_PIX_PTR *p1
)
// Returns distance between colours, scale 0.0 to 1.0.
{
double r = GET_PIXEL_RED(image,p0) - GET_PIXEL_RED(image,p1);
double g = GET_PIXEL_GREEN(image,p0) - GET_PIXEL_GREEN(image,p1);
double b = GET_PIXEL_BLUE(image,p0) - GET_PIXEL_BLUE(image,p1);
return sqrt ((r*r + g*g + b*b) / 3.0) / QuantumRange;
}
static double inline colDistRel (
Image *image,
const VIEW_PIX_PTR *p0,
const VIEW_PIX_PTR *p1
)
// Returns relative distance between colours.
{
double r = GET_PIXEL_RED(image,p0) - GET_PIXEL_RED(image,p1);
double g = GET_PIXEL_GREEN(image,p0) - GET_PIXEL_GREEN(image,p1);
double b = GET_PIXEL_BLUE(image,p0) - GET_PIXEL_BLUE(image,p1);
return (r*r + g*g + b*b);
}
static void inline calcWeight (
sphaldclT * pat,
Image *image,
const VIEW_PIX_PTR *p0,
const VIEW_PIX_PTR *p1,
double * weight,
MagickBooleanType *DistIsZero
)
{
double dist = colDist (image, p0, p1);
//printf ("dist=%g\n", dist);
*DistIsZero = (dist < 1e-8);
if (*DistIsZero) *weight = 0;
else *weight = 1.0 / pow (dist, pat->powParam);
}
static void inline calcShepard (
sphaldclT * pat,
Image *image,
const VIEW_PIX_PTR *p0, // row of input RGB
const VIEW_PIX_PTR *p1, // row of differences
ssize_t inRed, // 0 to QuantumRange
ssize_t inGreen,
ssize_t inBlue,
// Ouputs are differences, to be added to identity.
MagickRealType * outRed,
MagickRealType * outGreen,
MagickRealType * outBlue
)
// Voronoi: return the colour of the sample nearest input coords.
{
VIEW_PIX_PTR pIn;
const VIEW_PIX_PTR *ppOut;
SET_PIXEL_RED (image, inRed, &pIn);
SET_PIXEL_GREEN (image, inGreen, &pIn);
SET_PIXEL_BLUE (image, inBlue, &pIn);
double weight;
MagickBooleanType DistIsZero;
int i;
double sumWeight = 0.0, sumR = 0.0, sumG = 0.0, sumB = 0.0;
for (i=0; i < (ssize_t) image->columns; i++) {
calcWeight (
pat, image,
&pIn, p0 + i * Inc_ViewPixPtr (image),
&weight, &DistIsZero);
ppOut = p1 + i * Inc_ViewPixPtr (image);
if (DistIsZero) {
*outRed = GET_PIXEL_RED (image, ppOut);
*outGreen = GET_PIXEL_GREEN (image, ppOut);
*outBlue = GET_PIXEL_BLUE (image, ppOut);
sumWeight = 0;
break;
}
sumWeight += weight;
sumR += weight * GET_PIXEL_RED (image, ppOut);
sumG += weight * GET_PIXEL_GREEN (image, ppOut);
sumB += weight * GET_PIXEL_BLUE (image, ppOut);
}
if (sumWeight > 0) {
*outRed = sumR / sumWeight;
*outGreen = sumG / sumWeight;
*outBlue = sumB / sumWeight;
}
//printf ("outRGB = %g %g %g\n", *outRed, *outGreen, *outBlue);
}
static void inline calcVoronoi (
sphaldclT * pat,
Image *image,
const VIEW_PIX_PTR *p0, // row of input RGB
const VIEW_PIX_PTR *p1, // row of differences
ssize_t inRed, // 0 to QuantumRange
ssize_t inGreen,
ssize_t inBlue,
// Ouputs are differences, to be added to identity.
MagickRealType * outRed,
MagickRealType * outGreen,
MagickRealType * outBlue
)
{
VIEW_PIX_PTR pIn;
SET_PIXEL_RED (image, inRed, &pIn);
SET_PIXEL_GREEN (image, inGreen, &pIn);
SET_PIXEL_BLUE (image, inBlue, &pIn);
double minDist = 99e99;
int i, iAtMin = 0;
for (i=0; i < (ssize_t) image->columns; i++) {
double dist = colDistRel (image, &pIn, p0 + i * Inc_ViewPixPtr (image));
if (minDist > dist) {
minDist = dist;
iAtMin = i;
if (minDist == 0) break;
}
}
const VIEW_PIX_PTR *ppOut = p1 + iAtMin * Inc_ViewPixPtr (image);
*outRed = GET_PIXEL_RED (image, ppOut);
*outGreen = GET_PIXEL_GREEN (image, ppOut);
*outBlue = GET_PIXEL_BLUE (image, ppOut);
//printf ("outRGB = %g %g %g\n", *outRed, *outGreen, *outBlue);
}
// The next function corresponds in style to functions in transform.c
// It takes one image, and returns an image.
//
static Image *sphaldcl (
Image *image,
sphaldclT * pat,
ExceptionInfo *exception)
{
Image
*new_image;
CacheView
*in_view,
*out_view;
ssize_t
y;
MagickBooleanType
status;
assert(image != (Image *) NULL);
assert(image->signature == MAGICK_CORE_SIG);
if (image->debug != MagickFalse)
(void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
if (pat->do_verbose) {
fprintf (stderr,
"sphaldcl: Input image [%s] depth %i size %ix%i\n",
image->filename, (int)image->depth,
(int)image->columns, (int)image->rows);
}
if (image->rows != 2 ) {
fprintf (stderr, "sphaldcl: image needs height 2\n");
return (Image *)NULL;
}
if (SetNoPalette (image, exception) == MagickFalse)
return (Image *)NULL;
// Make a clone of this image, undefined pixel values:
//
int sqSize = pat->haldLevel * pat->haldLevel * pat->haldLevel;
new_image = CloneImage (image, sqSize, sqSize, MagickTrue, exception);
if (new_image == (Image *) NULL) {
fprintf (stderr, "sphaldcl clone failed %i\n", sqSize);
return(new_image);
}
if (SetNoPalette (new_image, exception) == MagickFalse)
return (Image *)NULL;
in_view = AcquireVirtualCacheView (image,exception);
out_view = AcquireAuthenticCacheView (new_image,exception);
status = MagickTrue;
const VIEW_PIX_PTR *p0=GetCacheViewVirtualPixels(in_view,0,0,image->columns,1,exception);
if (p0 == (const VIEW_PIX_PTR *) NULL) {
return (Image *)NULL;
}
const VIEW_PIX_PTR *p1=GetCacheViewVirtualPixels(in_view,0,1,image->columns,1,exception);
if (p1 == (const VIEW_PIX_PTR *) NULL) {
return (Image *)NULL;
}
pat->distances = (distanceT *) AcquireQuantumMemory (image->columns, sizeof(distanceT));
if (!pat->distances) {
fprintf (stderr, "sphaldcl: oom\n");
return (Image *)NULL;
}
const int lev = pat->haldLevel;
const int lev2 = pat->haldLevel * pat->haldLevel;
const int lev2m1 = lev2 - 1;
const double QoverLev2m1 = QuantumRange / (double)lev2m1;
#if defined(MAGICKCORE_OPENMP_SUPPORT)
#pragma omp parallel for schedule(static,4) shared(status) \
MAGICK_THREADS(new_image,new_image,new_image->rows,1)
#endif
for (y=0; y < (ssize_t) new_image->rows; y++)
{
VIEW_PIX_PTR
*q;
ssize_t
x;
MagickRealType
fr=0, fg=0, fb=0;
if (status == MagickFalse)
continue;
q=GetCacheViewAuthenticPixels(out_view,0,y,new_image->columns,1,exception);
if (q == (const VIEW_PIX_PTR *) NULL)
{
status=MagickFalse;
continue;
}
ssize_t
inRed, inGreen, inBlue;
for (x=0; x < (ssize_t) new_image->columns; x++)
{
inRed = x % lev2;
inGreen = x / lev2 + (y % lev) * lev;
inBlue = y / lev;
switch (pat->method) {
case mIdentity: {
SET_PIXEL_RED (new_image, QoverLev2m1 * inRed , q);
SET_PIXEL_GREEN (new_image, QoverLev2m1 * inGreen, q);
SET_PIXEL_BLUE (new_image, QoverLev2m1 * inBlue , q);
#if DEBUG==1
ssize_t x2 = inRed % lev2 + (inGreen % lev) * lev2;
ssize_t y2 = inBlue * lev + inGreen / lev;
if ((x != x2) || (y != y2))
printf ("xy=%li,%li xy2=%li,%li\n", x, y, x2, y2);
#endif
break;
}
case mShepards:
default: {
calcShepard (pat, image, p0, p1,
inRed * QoverLev2m1,
inGreen * QoverLev2m1,
inBlue * QoverLev2m1,
&fr, &fg, &fb);
SET_PIXEL_RED (new_image, fr + QoverLev2m1 * inRed , q);
SET_PIXEL_GREEN (new_image, fg + QoverLev2m1 * inGreen, q);
SET_PIXEL_BLUE (new_image, fb + QoverLev2m1 * inBlue , q);
break;
}
case mVoronoi: {
calcVoronoi (pat, image, p0, p1,
inRed * QoverLev2m1,
inGreen * QoverLev2m1,
inBlue * QoverLev2m1,
&fr, &fg, &fb);
SET_PIXEL_RED (new_image, fr + QoverLev2m1 * inRed , q);
SET_PIXEL_GREEN (new_image, fg + QoverLev2m1 * inGreen, q);
SET_PIXEL_BLUE (new_image, fb + QoverLev2m1 * inBlue , q);
break;
}
}
q += Inc_ViewPixPtr (new_image);
}
if (SyncCacheViewAuthenticPixels(out_view,exception) == MagickFalse)
status=MagickFalse;
}
out_view = DestroyCacheView (out_view);
in_view = DestroyCacheView (in_view);
if (pat->distances)
pat->distances = RelinquishMagickMemory (pat->distances);
if (status == MagickFalse) return NULL;
return (new_image);
}
ModuleExport size_t sphaldclImage(Image **images,const int argc,
const char **argv,ExceptionInfo *exception)
{
Image
*image1,
*new_image;
MagickBooleanType
status;
sphaldclT
at;
assert(images != (Image **) NULL);
assert(*images != (Image *) NULL);
assert((*images)->signature == MAGICK_CORE_SIG);
status = menu (argc, argv, &at);
if (status == MagickFalse)
return (-1);
image1=GetLastImageInList (*images);
new_image = sphaldcl (image1, &at, exception);
if (new_image == (Image *) NULL)
return(-1);
ReplaceImageInList (images,new_image);
// Replace messes up the images pointer. Make it good:
*images = GetFirstImageInList (*images);
return(MagickImageFilterSignature);
}
/* Last updated:
5-September-2017 use NEXTARG.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include "vsn_defines.h"
// CAUTION: When an image is a constant colour,
// IM's GetImageMean() returns a standard_deviation of NaN,
// "not a number" instead of zero.
typedef enum {
fullframe,
circular
} FormatT;
typedef enum {
dIncOnly,
dDecOnly,
dBoth
} directionT;
typedef struct {
double
mid_pt,
goal_sd,
goal_mn,
tolerance,
initCon0,
initCon2;
int
precision;
directionT
direction;
MagickBooleanType
has_sd_goal,
has_mn_goal,
pin_sd,
pin_mn,
mid_is_mean;
int
do_verbose;
FILE *
fh_data;
MagickBooleanType
sharpen;
double
upper_goal_sd,
lower_goal_sd,
mid_pt_q,
fnd_mean,
fnd_sd,
error,
pow;
int
nIter;
} setmnsdT;
static void usage (void)
{
printf ("Usage: -process 'setmnsd [OPTION]...'\n");
printf ("Adjusts mean and standard deviation.\n");
printf ("\n");
printf (" sd, sdGoal N goal for standard deviation [none]\n");
printf (" mn, meanGoal N goal for mean [none]\n");
printf (" t, tolerance N tolerance for results [0.00001]\n");
printf (" m, mid N mid-point (percentage of quantum) [mean]\n");
printf (" i0, initCon0 N lower guess for contrast [0]\n");
printf (" i2, initCon2 N upper guess for contrast [1000]\n");
printf (" d, direction string one of 'incOnly', 'decOnly', 'both' [both]\n");
printf (" f, file string write to file stream stdout or stderr\n");
printf (" v, verbose write text information\n");
printf (" v2, verbose2 write more text information\n");
printf ("\n");
}
static MagickBooleanType IsArg (char * pa, char * ShtOpt, char * LongOpt)
{
if ((LocaleCompare(pa, ShtOpt)==0) || (LocaleCompare(pa, LongOpt)==0))
return MagickTrue;
return MagickFalse;
}
static inline MagickBooleanType EndsPc (const char *s)
{
char c = *(s+strlen(s)-1);
if (c == '%' || c == 'c')
return MagickTrue;
return MagickFalse;
}
#define NEXTARG \
if (i < argc-1) i++; \
else { \
fprintf (stderr, "srchimg: ERROR: [%s] needs argument\n", pa); \
status = MagickFalse; \
}
static MagickBooleanType menu(
const int argc,
const char **argv,
setmnsdT * psss
)
// Returns MagickTrue if okay.
{
int
i;
MagickBooleanType
status;
status = MagickTrue;
psss->do_verbose = 0;
psss->goal_sd = 0.166667;
psss->goal_mn = 0.5;
psss->has_sd_goal = MagickFalse;
psss->has_mn_goal = MagickFalse;
psss->pin_sd = MagickFalse;
psss->pin_mn = MagickFalse;
psss->tolerance = 0.00001;
psss->error = psss->tolerance;
psss->mid_pt = 50;
psss->mid_is_mean = MagickTrue;
psss->direction = dBoth;
psss->fh_data = stderr;
psss->initCon0 = 0.0000001;
psss->initCon2 = 30.0;
psss->pow = 1.0;
for (i=0; i < argc; i++) {
char * pa = (char *)argv[i];
if (IsArg (pa, "sd", "sdGoal")==MagickTrue) {
NEXTARG;
if (LocaleCompare(argv[i], "pin")==0) {
psss->pin_sd = MagickTrue;
} else {
psss->goal_sd = atof(argv[i]);
if (EndsPc (argv[i])) psss->goal_sd /= 100.0;
}
psss->has_sd_goal = MagickTrue;
} else if (IsArg (pa, "mn", "meanGoal")==MagickTrue) {
NEXTARG;
if (LocaleCompare(argv[i], "pin")==0) {
psss->pin_mn = MagickTrue;
} else {
psss->goal_mn = atof(argv[i]);
if (EndsPc (argv[i])) psss->goal_mn /= 100.0;
}
psss->has_mn_goal = MagickTrue;
} else if (IsArg (pa, "t", "tolerance")==MagickTrue) {
NEXTARG;
psss->tolerance = atof(argv[i]);
if (EndsPc (argv[i])) psss->tolerance /= 100.0;
} else if (IsArg (pa, "m", "mid")==MagickTrue) {
NEXTARG;
if (LocaleCompare(argv[i], "mean")==0) {
psss->mid_is_mean = MagickTrue;
} else {
psss->mid_is_mean = MagickFalse;
psss->mid_pt = atof(argv[i]);
}
} else if (IsArg (pa, "i0", "initCon0")==MagickTrue) {
NEXTARG;
psss->initCon0 = atof(argv[i]);
} else if (IsArg (pa, "i2", "initCon2")==MagickTrue) {
NEXTARG;
psss->initCon2 = atof(argv[i]);
} else if (IsArg (pa, "d", "direction")==MagickTrue) {
NEXTARG;
if (LocaleCompare(argv[i], "incOnly")==0) {
psss->direction = dIncOnly;
} else if (LocaleCompare(argv[i], "decOnly")==0) {
psss->direction = dDecOnly;
} else if (LocaleCompare(argv[i], "both")==0) {
psss->direction = dBoth;
} else {
fprintf (stderr, "setmnsd: ERROR: direction: invalid option [%s]\n", argv[i]);
status = MagickFalse;
}
} else if (IsArg (pa, "f", "file")==MagickTrue) {
NEXTARG;
if (strcasecmp (argv[i], "stdout")==0) psss->fh_data = stdout;
else if (strcasecmp (argv[i], "stderr")==0) psss->fh_data = stderr;
else status = MagickFalse;
} else if (IsArg (pa, "v", "verbose")==MagickTrue) {
psss->do_verbose = 1;
} else if (IsArg (pa, "v2", "verbose2")==MagickTrue) {
psss->do_verbose = 2;
} else {
fprintf (stderr, "setmnsd: ERROR: unknown option [%s]\n", pa);
status = MagickFalse;
}
}
if (psss->do_verbose) {
fprintf (stderr, "setmnsd options:");
if (psss->do_verbose > 1) fprintf (stderr, " verbose2");
else fprintf (stderr, " verbose");
if (psss->has_mn_goal) {
if (psss->pin_mn) fprintf (stderr, " meanGoal pin");
else fprintf (stderr, " meanGoal %.*g", psss->precision, psss->goal_mn);
}
if (psss->has_sd_goal) {
if (psss->pin_sd) fprintf (stderr, " sdGoal pin");
else fprintf (stderr, " sdGoal %.*g", psss->precision, psss->goal_sd);
}
fprintf (stderr, " tolerance %.*g", psss->precision, psss->tolerance);
if (psss->mid_is_mean) {
fprintf (stderr, " mid mean");
} else {
fprintf (stderr, " mid %.*g", psss->precision, psss->mid_pt);
}
fprintf (stderr, " initCon0 %.*g", psss->precision, psss->initCon0);
fprintf (stderr, " initCon2 %.*g", psss->precision, psss->initCon2);
fprintf (stderr, " direction ");
switch (psss->direction) {
case dIncOnly: fprintf (stderr, "incOnly");
case dDecOnly: fprintf (stderr, "decOnly");
case dBoth: fprintf (stderr, "both");
}
if (psss->fh_data == stdout) fprintf (stderr, " file stdout");
fprintf (stderr, "\n");
}
if (status == MagickFalse)
usage ();
return (status);
}
static MagickBooleanType ApplySigmoid (
Image *image,
MagickBooleanType sharpen,
double contrast,
double mid,
ExceptionInfo *exception)
{
#if IMV6OR7==6
return SigmoidalContrastImageChannel (
image, DefaultChannels, sharpen, contrast, mid);
#else
return SigmoidalContrastImage (
image, sharpen, contrast, mid, exception);
#endif
}
static Image *TryContrast (
Image *image,
setmnsdT * psss,
double contrast,
ExceptionInfo *exception)
{
// Make a clone of this image, copied pixel values:
//
Image *temp_image = CloneImage(image, 0, 0, MagickTrue, exception);
if (temp_image == (Image *) NULL)
return(temp_image);
// if (SetNoPalette (temp_image, exception) == MagickFalse)
// return (Image *)NULL;
psss->nIter++;
if (!ApplySigmoid (temp_image, psss->sharpen, contrast, psss->mid_pt_q, exception))
return (Image *)NULL;
// FIXME: Beware: next doesn't treat alpha as I would like.
if (!GetImageMean (temp_image, &psss->fnd_mean, &psss->fnd_sd, exception))
return (Image *)NULL;
if (isnan(psss->fnd_sd)) psss->fnd_sd = 0;
psss->pow = 1.0;
if (psss->has_mn_goal) {
if (psss->do_verbose > 1) fprintf (psss->fh_data, "goal_mn=%.*g ",
psss->precision, psss->goal_mn);
// We special-case 0.0 and 1.0 to avoid not-a-number, infinity etc.
if (psss->goal_mn == 0.0) {
psss->pow = 9e99;
SetAllBlack (temp_image, exception);
} else if (psss->goal_mn == 1.0) {
psss->pow = 0.0;
SetAllOneCol (image, "white", exception);
} else {
// We have a "real" goal for mean.
// Iterate until fabs(onePow-1) < tolerance.
int i;
for (i=0; i < 10; i++) {
double / 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: pow=%.*g: mean=%.*g sd=%.*g ",
psss->precision, onePow,
psss->precision, psss->pow,
psss->precision, psss->fnd_mean / (double)QuantumRange,
psss->precision, psss->fnd_sd / (double)QuantumRange);
psss->nIter++;
if (!EvaluateImage (temp_image, PowEvaluateOperator, onePow, exception))
return (Image *)NULL;
if (!GetImageMean (temp_image, &psss->fnd_mean, &psss->fnd_sd, exception))
return (Image *)NULL;
if (isnan(psss->fnd_sd)) psss->fnd_sd = 0;
}
}
}
if (psss->do_verbose > 1) fprintf (psss->fh_data, "contrast=%.*g: mean=%.*g sd=%.*g ",
psss->precision, contrast,
psss->precision, psss->fnd_mean / (double)QuantumRange,
psss->precision, psss->fnd_sd / (double)QuantumRange);
double err = fabs (psss->fnd_sd/(double)QuantumRange - psss->goal_sd);
psss->error = (err > psss->tolerance) ? err : psss->tolerance;
return (temp_image);
}
static void AnnounceResult (
Image *image,
setmnsdT * psss,
double contrast,
ExceptionInfo *exception)
{
if (psss->do_verbose) {
fprintf (psss->fh_data, "nIter=%i\n", psss->nIter);
fprintf (psss->fh_data, "result: mean=%.*g sd=%.*g\n",
psss->precision, psss->fnd_mean / (double)QuantumRange,
psss->precision, psss->fnd_sd / (double)QuantumRange);
}
char msg[200] = "";
if (contrast != 0) {
sprintf (msg, "%csigmoidal-contrast %.*g,%.*g%%",
psss->sharpen ? '-':'+',
psss->precision, contrast,
psss->precision, psss->mid_pt_q * 100.0 / (double)QuantumRange);
}
if (psss->has_mn_goal) {
char msg2[100];
sprintf (msg2, " -evaluate pow %.*g",
psss->precision, psss->pow);
strcat (msg, msg2);
}
// If no change, make a "no-op" operation.
if (!*msg) strcpy (msg, "-evaluate Add 0");
if (psss->do_verbose) {
fprintf (psss->fh_data, "setmnsd command: %s\n", msg);
}
#if IMV6OR7==6
SetImageProperty (image, "filter:setmnsd", msg);
#else
SetImageProperty (image, "filter:setmnsd", msg, exception);
#endif
}
// The next function corresponds in style to functions in transform.c
// It takes one image, and returns an image (or the input image, unchanged).
//
static Image *setmnsd (
Image *image,
setmnsdT * psss,
ExceptionInfo *exception)
{
assert(image != (Image *) NULL);
assert(image->signature == MAGICK_CORE_SIG);
if (image->debug != MagickFalse)
(void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
// if (SetNoPalette (image, exception) == MagickFalse)
// return (Image *)NULL;
psss->nIter = 0;
double tol_q = psss->tolerance * QuantumRange;
psss->mid_pt_q = psss->mid_pt/100.0 * QuantumRange;
{
double goal_sd_q = psss->goal_sd * QuantumRange;
psss->upper_goal_sd = goal_sd_q + tol_q;
psss->lower_goal_sd = goal_sd_q - tol_q;
}
double contrast0 = psss->initCon0;
double contrast1;
double contrast2 = psss->initCon2;
if (psss->do_verbose > 1) fprintf (psss->fh_data, "setmnsd: contrasts %.*g %.*g\n",
psss->precision, contrast0,
psss->precision, contrast2);
double mean, stddev;
if (!GetImageMean (image, &mean, &stddev, exception))
return (Image *)NULL;
if (isnan(stddev)) stddev = 0;
if (psss->do_verbose) fprintf (psss->fh_data, "input: mean=%.*g sd=%.*g\n",
psss->precision, mean / (double)QuantumRange,
psss->precision, stddev / (double)QuantumRange);
if (stddev==0 && psss->has_sd_goal) {
if (psss->do_verbose) fprintf (psss->fh_data, "setmnsd Warning: Input SD is zero, so ignoring SD goal\n");
psss->has_sd_goal = MagickFalse;
psss->pin_sd = MagickFalse;
}
if ((mean<=0 || mean==QuantumRange) && psss->has_mn_goal) {
if (psss->do_verbose) fprintf (psss->fh_data, "setmnsd Warning: Input mean is <=zero or 100%%, so ignoring mean goal\n");
psss->has_mn_goal = MagickFalse;
psss->pin_mn = MagickFalse;
}
if (psss->pin_sd) {
psss->goal_sd = stddev / (double)QuantumRange;
double goal_sd_q = psss->goal_sd * QuantumRange;
psss->upper_goal_sd = goal_sd_q + tol_q;
psss->lower_goal_sd = goal_sd_q - tol_q;
}
if (psss->pin_mn) psss->goal_mn = mean / (double)QuantumRange;
if (psss->mid_is_mean) {
psss->mid_pt_q = mean;
}
Image * iInit = TryContrast (image, psss, 0, exception);
if (iInit == (Image *)NULL) return (iInit);
if (!psss->has_sd_goal) {
// That's all we have to do.
AnnounceResult (iInit, psss, 0, exception);
return (iInit);
}
iInit = DestroyImage (iInit);
mean = psss->fnd_mean;
stddev = psss->fnd_sd;
psss->sharpen = MagickTrue; // whether to increase contrast in middle
if (stddev > psss->upper_goal_sd) {
if (psss->do_verbose > 1) fprintf (psss->fh_data, "above upper\n");
psss->sharpen = MagickFalse;
if (psss->direction == dIncOnly) return (image);
double t = contrast0;
contrast0 = contrast2;
contrast2 = t;
} else if (stddev < psss->lower_goal_sd) {
if (psss->do_verbose > 1) fprintf (psss->fh_data, "below lower\n");
if (psss->direction == dDecOnly) return (image);
psss->sharpen = MagickTrue;
} else {
if (psss->do_verbose > 1) fprintf (psss->fh_data, "within tolerance\n");
AnnounceResult (image, psss, 0, exception);
return (image);
}
Image * i0 = TryContrast (image, psss, contrast0, exception);
if (i0 == (Image *)NULL) return (i0);
if (psss->fnd_sd > psss->upper_goal_sd) {
if (psss->do_verbose) fprintf (psss->fh_data, "i0 above upper -- fail\n");
fprintf (stderr, "i0 C=%.*g stddev too high: %.*g\n",
psss->precision, contrast0,
psss->precision, psss->fnd_sd / (double)QuantumRange);
i0 = DestroyImage (i0);
return (i0);
} else if (psss->fnd_sd < psss->lower_goal_sd) {
if (psss->do_verbose > 1) fprintf (psss->fh_data, "i0 below lower -- good\n");
} else {
if (psss->do_verbose > 1) fprintf (psss->fh_data, "i0 within tolerance\n");
AnnounceResult (i0, psss, contrast0, exception);
return (i0);
}
i0 = DestroyImage (i0);
Image * i2 = TryContrast (image, psss, contrast2, exception);
if (i2 == (Image *)NULL) return (i2);
if (psss->fnd_sd > psss->upper_goal_sd) {
if (psss->do_verbose > 1) fprintf (psss->fh_data, "i2 above upper -- good\n");
} else if (psss->fnd_sd < psss->lower_goal_sd) {
if (psss->do_verbose) fprintf (psss->fh_data, "i2 below lower -- fail\n");
fprintf (stderr, "i2 C=%.*g stddev too low: %.*g\n",
psss->precision, contrast2,
psss->precision, psss->fnd_sd / (double)QuantumRange);
i2 = DestroyImage (i2);
return (i2);
} else {
if (psss->do_verbose > 1) fprintf (psss->fh_data, "i2 within tolerance\n");
AnnounceResult (i2, psss, contrast2, exception);
return (i2);
}
i2 = DestroyImage (i2);
// Limit the number of iterations, just in case...
int i;
for (i=0; i < 100; i++) {
double prod = contrast0 * contrast2;
if (prod > 0) contrast1 = sqrt (contrast0 * contrast2);
else contrast1 = (contrast0 + contrast2) / 2.0;
Image * i1 = TryContrast (image, psss, contrast1, exception);
if (i1 == (Image *)NULL) return (i1);
if (psss->fnd_sd > psss->upper_goal_sd) {
if (psss->do_verbose > 1) fprintf (psss->fh_data, "i1 above upper\n");
contrast2 = contrast1;
} else if (psss->fnd_sd < psss->lower_goal_sd) {
if (psss->do_verbose > 1) fprintf (psss->fh_data, "i1 below lower\n");
contrast0 = contrast1;
} else {
if (psss->do_verbose > 1) fprintf (psss->fh_data, "i1 within tolerance\n");
// If mean is a goal, do it again with tighter tolerance on mean.
if (psss->has_mn_goal) {
i1 = DestroyImage (i1);
i1 = TryContrast (image, psss, contrast1, exception);
if (i1 == (Image *)NULL) return (i1);
}
AnnounceResult (i1, psss, contrast1, exception);
return (i1);
}
i1 = DestroyImage (i1);
}
if (psss->do_verbose) fprintf (psss->fh_data, "Bust. Using contrast1 = %.*g\n", psss->precision, contrast1);
Image * i1 = TryContrast (image, psss, contrast1, exception);
if (i1 == (Image *)NULL) return (i1);
AnnounceResult (i1, psss, contrast1, exception);
return (i1);
}
ModuleExport size_t setmnsdImage(Image **images,const int argc,
const char **argv,ExceptionInfo *exception)
{
Image
*image,
*new_image;
MagickBooleanType
status;
setmnsdT
sss;
assert(images != (Image **) NULL);
assert(*images != (Image *) NULL);
assert((*images)->signature == MAGICK_CORE_SIG);
sss.precision = GetMagickPrecision();
status = menu (argc, argv, &sss);
if (status == MagickFalse)
return (-1);
for (image=(*images); image != (Image *) NULL; image=GetNextImageInList(image))
{
new_image = setmnsd (image, &sss, exception);
if (new_image == (Image *) NULL)
return(-1);
if (new_image != image) ReplaceImageInList(&image,new_image);
*images=GetFirstImageInList(image);
}
return(MagickImageFilterSignature);
}
/*
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"
typedef enum {
aaAuto,
aaDisregard,
aaRegard
} alphaActionT;
typedef struct {
FILE *
fh_data;
MagickBooleanType
do_regardalpha,
do_verbose;
alphaActionT
alphaAction;
} integimT;
static void usage (void)
{
printf ("Usage: -process 'integim [OPTION]...'\n");
printf ("Create an HDRI integral image (summed area table).\n");
printf ("\n");
printf (" pm, premult 'yes' or 'no' or 'auto'\n");
printf (" f, file string Write verbose text to stderr or stdout\n");
printf (" v, verbose write text information to stdout\n");
printf ("\n");
}
static MagickBooleanType IsArg (char * pa, char * ShtOpt, char * LongOpt)
{
if ((LocaleCompare(pa, ShtOpt)==0) || (LocaleCompare(pa, LongOpt)==0))
return MagickTrue;
return MagickFalse;
}
static MagickBooleanType menu(
const int argc,
const char **argv,
integimT * pch
)
// Returns MagickTrue if okay.
{
int
i;
MagickBooleanType
status;
status = MagickTrue;
pch->fh_data = stderr;
pch->do_regardalpha =
pch->do_verbose = MagickFalse;
pch->alphaAction = aaRegard;
for (i=0; i < argc; i++) {
char * pa = (char *)argv[i];
if (IsArg (pa, "pm", "premult")==MagickTrue) {
i++;
if (strcasecmp (argv[i], "auto")==0) pch->alphaAction = aaAuto;
else if (strcasecmp (argv[i], "yes")==0) pch->alphaAction = aaRegard;
else if (strcasecmp (argv[i], "no")==0) pch->alphaAction = aaDisregard;
else status = MagickFalse;
} else if (IsArg (pa, "f", "file")==MagickTrue) {
i++;
if (strcasecmp (argv[i], "stdout")==0) pch->fh_data = stdout;
else if (strcasecmp (argv[i], "stderr")==0) pch->fh_data = stderr;
else status = MagickFalse;
} else if (IsArg (pa, "v", "verbose")==MagickTrue) {
pch->do_verbose = MagickTrue;
} else {
fprintf (stderr, "integim: ERROR: unknown option [%s]\n", pa);
status = MagickFalse;
}
}
if (pch->do_verbose) {
fprintf (stderr, "integim options:");
// if (pch->do_regardalpha) fprintf (stderr, " regardalpha");
if (pch->alphaAction != aaAuto) {
fprintf (stderr, " premult ");
if (pch->alphaAction==aaRegard) fprintf (stderr, "yes");
else if (pch->alphaAction==aaDisregard) fprintf (stderr, "no");
else fprintf (stderr, "??");
}
if (pch->fh_data == stdout) fprintf (stderr, " file stdout");
if (pch->do_verbose) fprintf (stderr, " verbose");
fprintf (stderr, "\n");
}
if (status == MagickFalse)
usage ();
return (status);
}
#if defined(MAGICKCORE_HDRI_SUPPORT)
#define ADD_HALF
#else
#define ADD_HALF +0.5
#endif
// The next function corresponds in style to functions in transform.c
// It takes one image, and returns an image.
//
static Image * integim (
const Image *image,
integimT * pch,
ExceptionInfo *exception)
{
Image
*new_image;
CacheView
*image_view,
*out_view;
ssize_t
y,
inXmult,
outXmult;
MagickBooleanType
IsAlphaOn = IS_ALPHA_CH(image),
status;
if (image->debug != MagickFalse)
(void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
if (pch->do_verbose) {
fprintf (pch->fh_data,
"integim: Input image [%s] depth is %i IsAlpha %s\n",
image->filename,
(int)image->depth,
IsAlphaOn ? "ON":"OFF"
);
}
switch (pch->alphaAction) {
case aaAuto:
pch->do_regardalpha = IsAlphaOn;
if (pch->do_verbose)
fprintf (pch->fh_data, "integim: alphaaction auto: regardalpha %s\n",
pch->do_regardalpha ? "TRUE" : "FALSE");
break;
case aaRegard:
pch->do_regardalpha = MagickTrue;
break;
case aaDisregard:
pch->do_regardalpha = MagickFalse;
break;
}
MagickBooleanType UseAlpha = (IsAlphaOn && pch->do_regardalpha);
// FIXME: Can we do this in-place?
new_image=CloneImage(image, image->columns, image->rows, MagickTrue, exception);
if (new_image == (Image *) NULL)
return(new_image);
// Just in case image had a palette.
if (SetNoPalette (new_image, exception) == MagickFalse)
return (Image *)NULL;
inXmult = Inc_ViewPixPtr (image);
outXmult = Inc_ViewPixPtr (new_image);
status=MagickTrue;
out_view=AcquireAuthenticCacheView(new_image,exception);
image_view=AcquireVirtualCacheView(image,exception);
// We can't easily parallelise
// because each output row needs the previous row.
for (y=0; y < (ssize_t) image->rows; y++)
{
const VIEW_PIX_PTR
*p,
*q_prev = NULL;
VIEW_PIX_PTR
*q_out;
ssize_t
x;
long double
alpha;
if (status==MagickFalse) continue;
p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
if (p == (const VIEW_PIX_PTR *) NULL)
{
fprintf (stderr, "bad GetCacheViewVirtualPixels image_view\n");
status=MagickFalse;
}
q_out=GetCacheViewAuthenticPixels(out_view,0,y,new_image->columns,1,exception);
if (q_out == (const VIEW_PIX_PTR *) NULL) {
fprintf (stderr, "integim: bad GetCacheViewAuthenticPixels out_view\n");
status=MagickFalse;
}
if (y > 0) {
q_prev=GetCacheViewVirtualPixels(out_view,0,y-1,new_image->columns,1,exception);
if (q_prev == (const VIEW_PIX_PTR *) NULL) {
fprintf (stderr, "integim: bad GetCacheViewAuthenticPixels q_prev\n");
status=MagickFalse;
}
}
alpha = 1.0;
if (status==MagickFalse) continue;
long double
left_r = 0, up_r = 0, upleft_r = 0,
left_g = 0, up_g = 0, upleft_g = 0,
left_b = 0, up_b = 0, upleft_b = 0,
left_a = 0, up_a = 0, upleft_a = 0;
for (x=0; x < (ssize_t) image->columns; x++)
{
if (x > 0) {
left_r = GET_PIXEL_RED(new_image, q_out-outXmult);
left_g = GET_PIXEL_GREEN(new_image, q_out-outXmult);
left_b = GET_PIXEL_BLUE(new_image, q_out-outXmult);
left_a = GET_PIXEL_ALPHA(new_image, q_out-outXmult);
}
if (y > 0) {
up_r = GET_PIXEL_RED(new_image, q_prev);
up_g = GET_PIXEL_GREEN(new_image, q_prev);
up_b = GET_PIXEL_BLUE(new_image, q_prev);
up_a = GET_PIXEL_ALPHA(new_image, q_prev);
if (x > 0) {
upleft_r = GET_PIXEL_RED(new_image, q_prev-outXmult);
upleft_g = GET_PIXEL_GREEN(new_image, q_prev-outXmult);
upleft_b = GET_PIXEL_BLUE(new_image, q_prev-outXmult);
upleft_a = GET_PIXEL_ALPHA(new_image, q_prev-outXmult);
}
}
if (UseAlpha) {
alpha = GET_PIXEL_ALPHA(image,p) / (long double)QuantumRange;
}
SET_PIXEL_RED (new_image,
alpha * GET_PIXEL_RED(image,p) + left_r + up_r - upleft_r ADD_HALF,
q_out);
SET_PIXEL_GREEN (new_image,
alpha * GET_PIXEL_GREEN(image,p) + left_g + up_g - upleft_g ADD_HALF,
q_out);
SET_PIXEL_BLUE (new_image,
alpha * GET_PIXEL_BLUE(image,p) + left_b + up_b - upleft_b ADD_HALF,
q_out);
if (IsAlphaOn) {
SET_PIXEL_ALPHA (new_image,
GET_PIXEL_ALPHA(image,p) + left_a + up_a - upleft_a ADD_HALF,
q_out);
}
p += inXmult;
q_out += outXmult;
q_prev += outXmult;
}
if (SyncCacheViewAuthenticPixels(out_view,exception) == MagickFalse) {
fprintf (stderr, "bad sync\n");
status=MagickFalse;
}
}
image_view=DestroyCacheView(image_view);
out_view=DestroyCacheView(out_view);
return (new_image);
}
ModuleExport size_t integimImage(Image **images,const int argc,
const char **argv,ExceptionInfo *exception)
{
Image
*image,
*new_image;
MagickBooleanType
status;
integimT
cumul_histo;
assert(images != (Image **) NULL);
assert(*images != (Image *) NULL);
assert((*images)->signature == MAGICK_CORE_SIG);
status = menu (argc, argv, &cumul_histo);
if (status == MagickFalse)
return (-1);
for (image=(*images); image != (Image *) NULL; image=GetNextImageInList(image))
{
new_image = integim (image, &cumul_histo, exception);
if (!new_image) return(-1);
ReplaceImageInList(&image,new_image);
*images=GetFirstImageInList(image);
}
return(MagickImageFilterSignature);
}
/*
Reference:
http://im.snibgo.com/customim.htm
http://im.snibgo.com/integim.htm
Latest update:
24-July-2017 Fixed bug on top line and left column.
(x1 and y1 can now be -1.)
3-April-2018 for v7.0.7-28
16-November-2022 Added "adjEdges" option.
*/
#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,
adjEdges,
dontDivide;
alphaActionT
alphaAction;
ssize_t
offsetx,
offsety,
winWidth,
winHeight;
} deintegimT;
static void usage (void)
{
printf ("Usage: -process 'deintegim [OPTION]...'\n");
printf ("De-integrate an HDRI integral image (summed area table).\n");
printf ("\n");
printf (" w, window string window size, WxH\n");
printf (" pd, postdiv 'yes' or 'no' or 'auto'\n");
printf (" ae, adjedges adjust divisor near image edges\n");
printf (" dd, dontdivide don't divide colour channels\n");
printf (" ox, offsetX int offset for window centre in X-direction\n");
printf (" oy, offsetY int offset for window centre in Y-direction\n");
printf (" f, file string Write verbose text to stderr or stdout\n");
printf (" v, verbose write text information to stdout\n");
printf ("\n");
}
static MagickBooleanType IsArg (char * pa, char * ShtOpt, char * LongOpt)
{
if ((LocaleCompare(pa, ShtOpt)==0) || (LocaleCompare(pa, LongOpt)==0))
return MagickTrue;
return MagickFalse;
}
static MagickBooleanType menu(
const int argc,
const char **argv,
deintegimT * pch
)
// Returns MagickTrue if okay.
{
int
i;
MagickBooleanType
status;
status = MagickTrue;
pch->fh_data = stderr;
pch->sWindow = NULL;
pch->alphaAction = aaRegard;
pch->do_regardalpha =
pch->do_verbose = MagickFalse;
pch->adjEdges = MagickFalse;
pch->dontDivide = MagickFalse;
pch->offsetx = 0;
pch->offsety = 0;
for (i=0; i < argc; i++) {
char * pa = (char *)argv[i];
if (IsArg (pa, "w", "window")==MagickTrue) {
i++;
pch->sWindow = (char *)argv[i];
} else if (IsArg (pa, "pd", "postdiv")==MagickTrue) {
i++;
if (strcasecmp (argv[i], "auto")==0) pch->alphaAction = aaAuto;
else if (strcasecmp (argv[i], "yes")==0) pch->alphaAction = aaRegard;
else if (strcasecmp (argv[i], "no")==0) pch->alphaAction = aaDisregard;
else status = MagickFalse;
} else if (IsArg (pa, "f", "file")==MagickTrue) {
i++;
if (strcasecmp (argv[i], "stdout")==0) pch->fh_data = stdout;
else if (strcasecmp (argv[i], "stderr")==0) pch->fh_data = stderr;
else status = MagickFalse;
} else if (IsArg (pa, "ox", "offsetX")==MagickTrue) {
i++;
pch->offsetx = atoi(argv[i]);
} else if (IsArg (pa, "oy", "offsetY")==MagickTrue) {
i++;
pch->offsety = atoi(argv[i]);
} else if (IsArg (pa, "ae", "adjedges")==MagickTrue) {
pch->adjEdges = MagickTrue;
} 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->adjEdges && pch->dontDivide) {
fprintf (stderr, "Can't have both 'adjedges' and 'don'tdivide'.\n");
status = MagickFalse;
}
if (pch->do_verbose) {
fprintf (stderr, "deintegim options:");
if (pch->sWindow) fprintf (stderr, " window %s", pch->sWindow);
if (pch->offsetx) fprintf (stderr, " offsetX %li", pch->offsetx);
if (pch->offsety) fprintf (stderr, " offsetY %li", pch->offsety);
if (pch->alphaAction != aaAuto) {
fprintf (stderr, " postdiv ");
if (pch->alphaAction==aaRegard) fprintf (stderr, "yes");
else if (pch->alphaAction==aaDisregard) fprintf (stderr, "no");
else fprintf (stderr, "??");
}
if (pch->fh_data == stdout) fprintf (stderr, " file stdout");
if (pch->adjEdges) fprintf (stderr, " adjedges");
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, each 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;
long double
reqArea;
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);
reqArea = pch->winWidth * pch->winHeight;
ssize_t winSemiHt = (pch->winHeight+1) / 2;
ssize_t winSemiWi = (pch->winWidth+1) / 2;
switch (pch->alphaAction) {
case aaAuto:
pch->do_regardalpha = IsAlphaOn;
if (pch->do_verbose)
fprintf (pch->fh_data, "deintegim: alphaaction auto: regardalpha %s\n",
pch->do_regardalpha ? "TRUE" : "FALSE");
break;
case aaRegard:
pch->do_regardalpha = MagickTrue;
break;
case aaDisregard:
pch->do_regardalpha = MagickFalse;
break;
}
MagickBooleanType UseAlpha = (IsAlphaOn && pch->do_regardalpha);
// Note: We can't do this in-place,
// because an output pixel generally depends on a _later_ input pixel.
new_image=CloneImage(image, image->columns, image->rows, MagickTrue, exception);
if (new_image == (Image *) NULL)
return(new_image);
// Just in case image had a palette.
if (SetNoPalette (new_image, exception) == MagickFalse)
return (Image *)NULL;
inXmult = Inc_ViewPixPtr (image);
outXmult = Inc_ViewPixPtr (new_image);
status=MagickTrue;
image_view=AcquireVirtualCacheView(image,exception);
out_view=AcquireAuthenticCacheView(new_image,exception);
#if defined(MAGICKCORE_OPENMP_SUPPORT)
#pragma omp parallel for schedule(static,4) shared(status) \
MAGICK_THREADS(new_image,new_image,new_image->rows,1)
#endif
for (y=0; y < (ssize_t) new_image->rows; y++)
{
const VIEW_PIX_PTR
*p,
*p_prev = NULL;
VIEW_PIX_PTR
*q_out;
ssize_t
y1, y2,
x;
long double
alpha;
y1 = y - winSemiHt + pch->offsety;
y2 = y1 + pch->winHeight;
if (y1 < 0) y1 = -1;
if (y2 > image->rows-1) y2 = image->rows-1;
ssize_t winHt = y2 - y1;
if (winHt==0) winHt = 1;
if (status==MagickFalse) continue;
p=GetCacheViewVirtualPixels(image_view,0,y2,image->columns,1,exception);
if (p == (const VIEW_PIX_PTR *) NULL) {
fprintf (stderr, "bad GetCacheViewVirtualPixels image_view\n");
status=MagickFalse;
}
if (y1 >= 0) {
p_prev=GetCacheViewVirtualPixels(image_view,0,y1,image->columns,1,exception);
if (p_prev == (const VIEW_PIX_PTR *) NULL) {
fprintf (stderr, "deintegim: bad GetCacheViewAuthenticPixels p_prev\n");
status=MagickFalse;
}
} else {
p_prev = NULL;
}
q_out=GetCacheViewAuthenticPixels(out_view,0,y,new_image->columns,1,exception);
if (q_out == (const VIEW_PIX_PTR *) NULL) {
fprintf (stderr, "deintegim: bad GetCacheViewAuthenticPixels out_view\n");
status=MagickFalse;
}
alpha = 1.0;
if (status==MagickFalse) continue;
long double
left_r = 0, up_r = 0, upleft_r = 0,
left_g = 0, up_g = 0, upleft_g = 0,
left_b = 0, up_b = 0, upleft_b = 0,
left_a = 0, up_a = 0, upleft_a = 0;
ssize_t x1, x2;
for (x=0; x < (ssize_t) new_image->columns; x++)
{
x1 = x - winSemiWi + pch->offsetx;
x2 = x1 + pch->winWidth;
if (x1 < 0) x1 = -1;
if (x2 > image->columns-1) x2 = image->columns-1;
ssize_t winWi = x2 - x1;
if (winWi==0) winWi = 1;
int winArea = winHt * winWi;
if (x1 >= 0) {
const VIEW_PIX_PTR * pv = p+x1*inXmult;
left_r = GET_PIXEL_RED (image, pv);
left_g = GET_PIXEL_GREEN (image, pv);
left_b = GET_PIXEL_BLUE (image, pv);
left_a = GET_PIXEL_ALPHA (image, pv);
}
if (p_prev) {
const VIEW_PIX_PTR * pv = p_prev+x2*inXmult;
up_r = GET_PIXEL_RED (image, pv);
up_g = GET_PIXEL_GREEN (image, pv);
up_b = GET_PIXEL_BLUE (image, pv);
up_a = GET_PIXEL_ALPHA (image, pv);
if (x1 >= 0) {
const VIEW_PIX_PTR * pv = p_prev + x1*inXmult;
upleft_r = GET_PIXEL_RED (image, pv);
upleft_g = GET_PIXEL_GREEN (image, pv);
upleft_b = GET_PIXEL_BLUE (image, pv);
upleft_a = GET_PIXEL_ALPHA (image, pv);
}
}
// point to bottom-right of window
const VIEW_PIX_PTR * pbr = p + x2*inXmult;
alpha = 1.0;
if (IsAlphaOn) {
// If alpha isn't on, image alpha values are 100%.
alpha = GET_PIXEL_ALPHA (image,pbr) - left_a - up_a + upleft_a;
// Next can be wrong.
SET_PIXEL_ALPHA (new_image, alpha / winArea ADD_HALF, q_out);
}
if (UseAlpha && alpha != 0) {
alpha /= (long double)QuantumRange;
} else {
alpha = winArea;
}
// Next probably wrong when we have transparency.
if (pch->dontDivide) alpha = 1.0; // temp hack
else if (pch->adjEdges) alpha /= reqArea;
if (alpha == 0) {
SET_PIXEL_RED (new_image, 0, q_out);
SET_PIXEL_GREEN (new_image, 0, q_out);
SET_PIXEL_BLUE (new_image, 0, q_out);
} else {
SET_PIXEL_RED (new_image,
(GET_PIXEL_RED(image,pbr) - left_r - up_r + upleft_r)/alpha ADD_HALF,
q_out);
SET_PIXEL_GREEN (new_image,
(GET_PIXEL_GREEN(image,pbr) - left_g - up_g + upleft_g)/alpha ADD_HALF,
q_out);
SET_PIXEL_BLUE (new_image,
(GET_PIXEL_BLUE(image,pbr) - left_b - up_b + upleft_b)/alpha ADD_HALF,
q_out);
}
q_out += outXmult;
}
if (SyncCacheViewAuthenticPixels(out_view,exception) == MagickFalse) {
fprintf (stderr, "bad sync\n");
status=MagickFalse;
}
}
image_view=DestroyCacheView(image_view);
out_view=DestroyCacheView(out_view);
return (new_image);
}
ModuleExport size_t deintegimImage(Image **images,const int argc,
const char **argv,ExceptionInfo *exception)
{
Image
*image,
*new_image;
MagickBooleanType
status;
deintegimT
di;
assert(images != (Image **) NULL);
assert(*images != (Image *) NULL);
assert((*images)->signature == MAGICK_CORE_SIG);
status = menu (argc, argv, &di);
if (status == MagickFalse)
return (-1);
for (image=(*images); image != (Image *) NULL; image=GetNextImageInList(image))
{
new_image = deintegim (image, &di, exception);
if (!new_image) return(-1);
ReplaceImageInList(&image,new_image);
*images=GetFirstImageInList(image);
}
return(MagickImageFilterSignature);
}
/*
Reference:
http://im.snibgo.com/customim.htm
http://im.snibgo.com/integim.htm
Latest update:
24-July-2017 Fixed bug on top line and left column.
(x1 and y1 can now be -1.)
3-April-2018 for v7.0.7-28
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include "vsn_defines.h"
typedef enum {
aaAuto,
aaDisregard,
aaRegard
} alphaActionT;
typedef struct {
FILE *
fh_data;
char
*sWindow;
MagickBooleanType
do_regardalpha,
do_verbose,
dontDivide;
alphaActionT
alphaAction;
ssize_t
offsetx,
offsety,
winWidth,
winHeight;
} deintegimT;
static void usage (void)
{
printf ("Usage: -process 'deintegim [OPTION]...'\n");
printf ("De-integrate an HDRI integral image (summed area table).\n");
printf ("\n");
printf (" w, window string window size, WxH\n");
printf (" pd, postdiv 'yes' or 'no' or 'auto'\n");
printf (" dd, dontdivide don't divide colour channels\n");
printf (" ox, offsetX int offset for window centre in X-direction\n");
printf (" oy, offsetY int offset for window centre in Y-direction\n");
printf (" f, file string Write verbose text to stderr or stdout\n");
printf (" v, verbose write text information to stdout\n");
printf ("\n");
}
static MagickBooleanType IsArg (char * pa, char * ShtOpt, char * LongOpt)
{
if ((LocaleCompare(pa, ShtOpt)==0) || (LocaleCompare(pa, LongOpt)==0))
return MagickTrue;
return MagickFalse;
}
static MagickBooleanType menu(
const int argc,
const char **argv,
deintegimT * pch
)
// Returns MagickTrue if okay.
{
int
i;
MagickBooleanType
status;
status = MagickTrue;
pch->fh_data = stderr;
pch->sWindow = NULL;
pch->alphaAction = aaRegard;
pch->do_regardalpha =
pch->do_verbose = MagickFalse;
pch->dontDivide = MagickFalse;
pch->offsetx = 0;
pch->offsety = 0;
for (i=0; i < argc; i++) {
char * pa = (char *)argv[i];
if (IsArg (pa, "w", "window")==MagickTrue) {
i++;
pch->sWindow = (char *)argv[i];
} else if (IsArg (pa, "pd", "postdiv")==MagickTrue) {
i++;
if (strcasecmp (argv[i], "auto")==0) pch->alphaAction = aaAuto;
else if (strcasecmp (argv[i], "yes")==0) pch->alphaAction = aaRegard;
else if (strcasecmp (argv[i], "no")==0) pch->alphaAction = aaDisregard;
else status = MagickFalse;
} else if (IsArg (pa, "f", "file")==MagickTrue) {
i++;
if (strcasecmp (argv[i], "stdout")==0) pch->fh_data = stdout;
else if (strcasecmp (argv[i], "stderr")==0) pch->fh_data = stderr;
else status = MagickFalse;
} else if (IsArg (pa, "ox", "offsetX")==MagickTrue) {
i++;
pch->offsetx = atoi(argv[i]);
} else if (IsArg (pa, "oy", "offsetY")==MagickTrue) {
i++;
pch->offsety = atoi(argv[i]);
} else if (IsArg (pa, "dd", "dontdivide")==MagickTrue) {
pch->dontDivide = MagickTrue;
} else if (IsArg (pa, "v", "verbose")==MagickTrue) {
pch->do_verbose = MagickTrue;
} else {
fprintf (stderr, "deintegim: ERROR: unknown option [%s]\n", pa);
status = MagickFalse;
}
}
if (pch->do_verbose) {
fprintf (stderr, "deintegim options:");
if (pch->sWindow) fprintf (stderr, " window %s", pch->sWindow);
if (pch->offsetx) fprintf (stderr, " offsetX %li", pch->offsetx);
if (pch->offsety) fprintf (stderr, " offsetY %li", pch->offsety);
if (pch->alphaAction != aaAuto) {
fprintf (stderr, " postdiv ");
if (pch->alphaAction==aaRegard) fprintf (stderr, "yes");
else if (pch->alphaAction==aaDisregard) fprintf (stderr, "no");
else fprintf (stderr, "??");
}
if (pch->fh_data == stdout) fprintf (stderr, " file stdout");
if (pch->dontDivide) fprintf (stderr, " dontdivide");
if (pch->do_verbose) fprintf (stderr, " verbose");
fprintf (stderr, "\n");
}
if (status == MagickFalse)
usage ();
return (status);
}
static MagickBooleanType GetNumber (
char ** p,
double *pValue,
ssize_t dim)
// Returns whether valid.
{
int len;
if (sscanf (*p, "%lf%n", pValue, &len) != 1) return MagickFalse;
*p += len;
char c = **p;
switch (c) {
case 'c':
case 'C':
case '%':
*pValue = floor (*pValue/100.0 * dim + 0.5);
if (*pValue < 1) *pValue = 1;
(*p)++;
break;
case 'p':
case 'P':
*pValue = floor (*pValue * dim + 0.5);
if (*pValue < 1) *pValue = 1;
(*p)++;
break;
case '\0':
case 'x':
case 'X':
/* nothing */ ;
break;
default:
printf ("Bad terminator? [%c]\n", c);
}
return MagickTrue;
}
static MagickBooleanType ParseWindow (
deintegimT * pch,
ssize_t imgWidth,
ssize_t imgHeight
)
// Expected format: WxH
// where W and H are numbers optionally followed by '%' or 'c' or 'p'.
// Returns whether okay.
{
double w=0, h=0;
char * p = pch->sWindow;
if (!*p) return MagickFalse;
if (!GetNumber (&p, &w, imgWidth)) {
fprintf (stderr, "deintegim error: Window needs two dimensions.\n");
return MagickFalse;
}
if (*p!='x') {
fprintf (stderr, "deintegim error: Window needs 'x' betweem two dimensions.\n");
return MagickFalse;
}
p++;
if (!GetNumber (&p, &h, imgHeight)) {
fprintf (stderr, "deintegim error: Window needs two dimensions.\n");
return MagickFalse;
}
if (w < 1 || h < 1) {
fprintf (stderr, "deintegim error: Window dimensions must be >=1.\n");
return MagickFalse;
}
if (*p) {
fprintf (stderr, "deintegim error: Window dimensions have extra characters [%s].\n", p);
return MagickFalse;
}
pch->winWidth = w;
pch->winHeight = h;
return MagickTrue;
}
#if defined(MAGICKCORE_HDRI_SUPPORT)
#define ADD_HALF
#else
#define ADD_HALF +0.5
#endif
// The next function corresponds in style to functions in transform.c
// It takes one image, and returns an image.
//
static Image * deintegim2 (
Image **images,
deintegimT * pch,
ExceptionInfo *exception)
{
Image
*new_image;
CacheView
*imageA_view,
*imageB_view,
*out_view;
ssize_t
y,
inBmult,
outXmult;
Image * imageA = *images;
MagickBooleanType
IsAlphaOn = IS_ALPHA_CH(imageA),
status;
if (imageA->debug != MagickFalse)
(void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",imageA->filename);
assert(imageA != (Image *) NULL);
assert(imageA->signature == MAGICK_CORE_SIG);
if (imageA->debug != MagickFalse)
(void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",imageA->filename);
Image * imageB = GetNextImageInList (imageA);
if (!imageB) {
fprintf (stderr, "deintegim2: needs two images, the same size\n");
return MagickFalse;
}
if (imageA->columns != imageB->columns ||
imageA->rows != imageB->rows)
{
fprintf (stderr, "deintegim2: the two images must be the same size\n");
return MagickFalse;
}
if (SetNoPalette (imageA, exception) == MagickFalse)
return MagickFalse;
if (SetNoPalette (imageB, exception) == MagickFalse)
return MagickFalse;
//const ssize_t incA = Inc_ViewPixPtr (imageA);
//const ssize_t incB = Inc_ViewPixPtr (imageB);
if (pch->do_verbose) {
fprintf (pch->fh_data,
"deintegim2: Input image [%s] depth is %i IsAlpha %s\n",
imageA->filename,
(int)imageA->depth,
IsAlphaOn ? "ON":"OFF"
);
}
pch->winWidth = pch->winHeight = 1;
if (pch->sWindow) {
if (! ParseWindow (pch, imageA->columns, imageA->rows))
return (Image *)NULL;
}
if (pch->do_verbose)
fprintf (pch->fh_data, "deintegim Window: %lix%li\n",
pch->winWidth, pch->winHeight);
ssize_t winSemiHt = (pch->winHeight+1) / 2;
ssize_t winSemiWi = (pch->winWidth+1) / 2;
switch (pch->alphaAction) {
case aaAuto:
pch->do_regardalpha = IsAlphaOn;
if (pch->do_verbose)
fprintf (pch->fh_data, "deintegim2: alphaaction auto: regardalpha %s\n",
pch->do_regardalpha ? "TRUE" : "FALSE");
break;
case aaRegard:
pch->do_regardalpha = MagickTrue;
break;
case aaDisregard:
pch->do_regardalpha = MagickFalse;
break;
}
MagickBooleanType UseAlpha = (IsAlphaOn && pch->do_regardalpha);
// Note: We can't do this in-place,
// because an output pixel generally depends on a _later_ input pixel.
new_image=CloneImage(imageA, imageA->columns, imageA->rows, MagickTrue, exception);
if (new_image == (Image *) NULL)
return(new_image);
// Just in case image had a palette.
if (SetNoPalette (new_image, exception) == MagickFalse)
return (Image *)NULL;
//inXmult = Inc_ViewPixPtr (imageA);
inBmult = Inc_ViewPixPtr (imageB);
outXmult = Inc_ViewPixPtr (new_image);
status=MagickTrue;
imageA_view=AcquireVirtualCacheView(imageA,exception);
imageB_view=AcquireVirtualCacheView(imageB,exception);
out_view=AcquireAuthenticCacheView(new_image,exception);
#if defined(MAGICKCORE_OPENMP_SUPPORT)
#pragma omp parallel for schedule(static,4) shared(status) \
MAGICK_THREADS(new_image,new_image,new_image->rows,1)
#endif
for (y=0; y < (ssize_t) new_image->rows; y++)
{
const VIEW_PIX_PTR
*pB;
VIEW_PIX_PTR
*q_out;
ssize_t
y1, y2,
x;
long double
alpha;
if (status==MagickFalse) continue;
/*===
p=GetCacheViewVirtualPixels(imageA_view,0,y2,imageA->columns,1,exception);
if (!p) {
fprintf (stderr, "deintegim2: bad GetCacheViewVirtualPixels imageA_view\n");
status=MagickFalse;
}
if (y1 >= 0) {
p_prev=GetCacheViewVirtualPixels(imageA_view,0,y1,imageA->columns,1,exception);
if (!p_prev) {
fprintf (stderr, "deintegim2: bad GetCacheViewAuthenticPixels p_prev\n");
status=MagickFalse;
}
} else {
p_prev = NULL;
}
===*/
pB=GetCacheViewVirtualPixels(imageB_view,0,y,imageB->columns,1,exception);
if (!pB) {
fprintf (stderr, "deintegim2: bad GetCacheViewVirtualPixels imageB_view\n");
status=MagickFalse;
}
q_out=GetCacheViewAuthenticPixels(out_view,0,y,new_image->columns,1,exception);
if (!q_out) {
fprintf (stderr, "deintegim2: bad GetCacheViewAuthenticPixels out_view\n");
status=MagickFalse;
}
alpha = 1.0;
if (status==MagickFalse) continue;
long double
left_r = 0, up_r = 0, upleft_r = 0,
left_g = 0, up_g = 0, upleft_g = 0,
left_b = 0, up_b = 0, upleft_b = 0,
left_a = 0, up_a = 0, upleft_a = 0;
for (x=0; x < (ssize_t) new_image->columns; x++)
{
double factX = GET_PIXEL_RED(imageB, pB) / (long double)QuantumRange;
double factY = GET_PIXEL_GREEN(imageB, pB) / (long double)QuantumRange;
ssize_t winW = floor ((pch->winWidth-1) * factX + 1.5);
winSemiWi = floor (winW/2.0 + 0.5);
ssize_t x1 = x - winSemiWi + pch->offsetx;
ssize_t x2 = x1 + winW;
if (x1 < 0) x1 = -1;
if (x2 > imageA->columns-1) x2 = imageA->columns-1;
ssize_t winWi = x2 - x1;
if (winWi==0) winWi = 1;
ssize_t winH = floor ((pch->winHeight-1) * factY + 1.5);
winSemiHt = floor (winH/2.0 + 0.5);
y1 = y - winSemiHt + pch->offsety;
y2 = y1 + winH;
if (y1 < 0) y1 = -1;
if (y2 > imageA->rows-1) y2 = imageA->rows-1;
ssize_t winHt = y2 - y1;
if (winHt==0) winHt = 1;
int winArea = winHt * winWi;
if (x1 >= 0) {
//const VIEW_PIX_PTR * pv = p+x1*inXmult;
const VIEW_PIX_PTR * pv = GetCacheViewVirtualPixels(imageA_view,x1,y2,1,1,exception);
if (!pv) {
fprintf (stderr, "deintegim2: bad GetCacheViewVirtualPixels imageA_view\n");
status=MagickFalse;
}
left_r = GET_PIXEL_RED(imageA, pv);
left_g = GET_PIXEL_GREEN(imageA, pv);
left_b = GET_PIXEL_BLUE(imageA, pv);
left_a = GET_PIXEL_ALPHA(imageA, pv);
}
if (y1 >= 0) {
//const VIEW_PIX_PTR * pv = p_prev+x2*inXmult;
const VIEW_PIX_PTR * pv = GetCacheViewVirtualPixels(imageA_view,x2,y1,1,1,exception);
if (!pv) {
fprintf (stderr, "deintegim2: bad GetCacheViewVirtualPixels imageA_view\n");
status=MagickFalse;
}
up_r = GET_PIXEL_RED(imageA, pv);
up_g = GET_PIXEL_GREEN(imageA, pv);
up_b = GET_PIXEL_BLUE(imageA, pv);
up_a = GET_PIXEL_ALPHA(imageA, pv);
if (x1 >= 0) {
//const VIEW_PIX_PTR * pv = p_prev + x1*inXmult;
const VIEW_PIX_PTR * pv = GetCacheViewVirtualPixels(imageA_view,x1,y1,1,1,exception);
if (!pv) {
fprintf (stderr, "deintegim2: bad GetCacheViewVirtualPixels imageA_view\n");
status=MagickFalse;
}
upleft_r = GET_PIXEL_RED(imageA, pv);
upleft_g = GET_PIXEL_GREEN(imageA, pv);
upleft_b = GET_PIXEL_BLUE(imageA, pv);
upleft_a = GET_PIXEL_ALPHA(imageA, pv);
}
}
// point to bottom-right of window
//const VIEW_PIX_PTR * pbr = p + x2*inXmult;
const VIEW_PIX_PTR * pbr = GetCacheViewVirtualPixels(imageA_view,x2,y2,1,1,exception);
if (!pbr) {
fprintf (stderr, "deintegim2: bad GetCacheViewVirtualPixels imageA_view\n");
status=MagickFalse;
}
if (IsAlphaOn) {
// If alpha isn't on, image alpha values are 100%.
alpha = GET_PIXEL_ALPHA(imageA,pbr) - left_a - up_a + upleft_a;
SET_PIXEL_ALPHA (new_image, alpha / winArea ADD_HALF, q_out);
}
if (UseAlpha && alpha != 0) {
alpha /= (long double)QuantumRange;
} else {
alpha = winArea;
}
if (pch->dontDivide) alpha = 1.0; // temp hack
if (alpha == 0) {
SET_PIXEL_RED (new_image, 0, q_out);
SET_PIXEL_GREEN (new_image, 0, q_out);
SET_PIXEL_BLUE (new_image, 0, q_out);
} else {
SET_PIXEL_RED (new_image,
(GET_PIXEL_RED(imageA,pbr) - left_r - up_r + upleft_r)/alpha ADD_HALF,
q_out);
SET_PIXEL_GREEN (new_image,
(GET_PIXEL_GREEN(imageA,pbr) - left_g - up_g + upleft_g)/alpha ADD_HALF,
q_out);
SET_PIXEL_BLUE (new_image,
(GET_PIXEL_BLUE(imageA,pbr) - left_b - up_b + upleft_b)/alpha ADD_HALF,
q_out);
}
pB += inBmult;
q_out += outXmult;
}
if (SyncCacheViewAuthenticPixels(out_view,exception) == MagickFalse) {
fprintf (stderr, "bad sync\n");
status=MagickFalse;
}
}
imageA_view=DestroyCacheView(imageA_view);
imageB_view=DestroyCacheView(imageB_view);
out_view=DestroyCacheView(out_view);
DeleteImageFromList (&imageB);
return (new_image);
}
ModuleExport size_t deintegim2Image(Image **images,const int argc,
const char **argv,ExceptionInfo *exception)
{
MagickBooleanType
status;
deintegimT
di;
assert(images != (Image **) NULL);
assert(*images != (Image *) NULL);
assert((*images)->signature == MAGICK_CORE_SIG);
status = menu (argc, argv, &di);
if (status == MagickFalse)
return (-1);
int ListLen = (int)GetImageListLength(*images);
if (ListLen != 2) {
fprintf (stderr, "deintegim2: needs 2 images\n");
return (-1);
}
Image * new_image = deintegim2 (images, &di, exception);
if (!new_image) return(-1);
ReplaceImageInList (images, new_image);
*images=GetFirstImageInList(new_image);
return(MagickImageFilterSignature);
}
/*
Reference: http://im.snibgo.com/kcluster.htm
Last update: 9-August-2017.
18-August-2017 added SetNoPalette ()
21-August-2017 removed 'j' option; 'k' can now be range.
7-September-2017 for v7.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <math.h>
#include "vsn_defines.h"
#define VERSION "kcluster v1.0 Copyright (c) 2017 Alan Gibson"
typedef enum {
mNull,
mMeans,
mFuzzy,
mHarmonic
} methodT;
typedef enum {
iColors,
iForgy,
iRandPart,
iSdLine,
iFromList
} initT;
typedef struct {
double r;
double g;
double b;
} normaliseT;
typedef struct {
Quantum red;
Quantum green;
Quantum blue;
Quantum alpha;
double count;
} clusterT;
typedef struct {
int
verbose;
methodT
method;
initT
init;
int
precision,
jump0,
jump1,
kCols,
maxIter;
MagickBooleanType
hasJump,
hasSetY,
regardAlpha,
sdNorm,
dither,
debug;
normaliseT
normalise;
double
fuzzyR,
harmonicP,
tolerance,
jumpY;
double
powParam1,
powParam2,
powParam3;
FILE *
fh_data;
RandomInfo
*random_info;
ssize_t
*nearClust;
clusterT
*clusters;
char
*frameName;
int
frameNum;
double
*membDenoms;
} kclusterT;
static void usage (void)
{
printf ("Usage: -process 'kcluster [OPTION]...'\n");
printf ("Apply a k-clustering method.\n");
printf ("\n");
printf (" m, method string 'Null', 'Means', 'Fuzzy' or 'Harmonic'\n");
printf (" k, kCols int[-int] number [range] of clusters to find\n");
// printf (" j, jump integer,integer find k\n");
printf (" s, sdNorm normalise channel SD\n");
printf (" o, dither dither output\n");
printf (" a, regardAlpha process opacity\n");
printf (" i, initialize string 'Colors', 'Forgy', 'RandPart', 'SdLine' or 'FromList'\n");
printf (" x, maxIter integer maximum number of iterations\n");
printf (" y, jumpY number y for jump power\n");
printf (" r, fuzzyR number r for fuzzy method\n");
printf (" p, harmonicP number p for harmonic method\n");
printf (" t, tolerance number tolerance\n");
printf (" w, write filename write iterations eg frame_%%06d.png\n");
printf (" d, debug write debugging information to stderr\n");
printf (" f, file string write verbose to file stream stdout or stderr\n");
printf (" v, verbose write text information\n");
printf (" v2, verbose2 write more text information\n");
printf (" version write version information to stdout\n");
printf ("\n");
}
static MagickBooleanType IsArg (char * pa, char * ShtOpt, char * LongOpt)
{
// LocaleCompare is not case-sensitive,
// so we use strcmp for the short option.
if ((strcmp(pa, ShtOpt)==0) || (LocaleCompare(pa, LongOpt)==0))
return MagickTrue;
return MagickFalse;
}
static void InitNormalise (
kclusterT * pkc
)
{
pkc->normalise.r = 1.0;
pkc->normalise.g = 1.0;
pkc->normalise.b = 1.0;
}
static MagickBooleanType GetInteger (
char ** p,
int *pValue)
// Returns whether valid.
{
int len;
if (sscanf (*p, "%i%n", pValue, &len) != 1) return MagickFalse;
*p += len;
char c = **p;
switch (c) {
case '\0':
case ',':
/* nothing */ ;
break;
default:
printf ("Bad terminator? [%c]\n", c);
}
return MagickTrue;
}
static MagickBooleanType ParseTwoJumps (
kclusterT * pkc,
char * p
)
// Expected format: A,B
// where A and B are numbers.
// Returns whether okay.
{
int v0=0, v1=0;
//char * p = pch->sWindow;
if (!*p) return MagickFalse;
if (!GetInteger (&p, &v0)) {
fprintf (stderr, "kcluster error: needs two integers.\n");
return MagickFalse;
}
if (*p!=',') {
fprintf (stderr, "kcluster error: needs 'x' betweem two integers.\n");
return MagickFalse;
}
p++;
if (!GetInteger (&p, &v1)) {
fprintf (stderr, "kcluster error: needs two integers.\n");
return MagickFalse;
}
if (v0 < 1 || v1 < v0) {
fprintf (stderr, "kcluster error: integers must be >=1, ascending.\n");
return MagickFalse;
}
if (*p) {
fprintf (stderr, "kcluster error: extra characters [%s].\n", p);
return MagickFalse;
}
pkc->jump0 = v0;
pkc->jump1 = v1;
return MagickTrue;
}
static MagickBooleanType ParseRange (
kclusterT * pkc,
char * p,
int * valA,
int * valB,
int * numVals
)
// Expected format: A or A-B where A and B are integers.
// Returns whether okay.
{
*numVals = 0;
int len;
if (sscanf (p, "%i%n", valA, &len) != 1) return MagickFalse;
p += len;
*numVals = 1;
if (*p == '-') {
p++;
if (sscanf (p, "%i%n", valB, &len) != 1) return MagickFalse;
p += len;
*numVals = 2;
}
if (*p) {
fprintf (stderr, "kcluster ParseRange error: extra characters [%s].\n", p);
return MagickFalse;
}
return MagickTrue;
}
#define NEXTARG \
if (i < argc-1) i++; \
else { \
fprintf (stderr, "kcluster: ERROR: [%s] needs argument\n", pa); \
status = MagickFalse; \
}
static MagickBooleanType menu(
const int argc,
const char **argv,
kclusterT * pkc
)
// Returns MagickTrue if okay.
{
int
i;
MagickBooleanType
status = MagickTrue;
char ** pargv = (char **)argv;
pkc->verbose = 0;
pkc->init = iColors;
pkc->regardAlpha = MagickFalse;
pkc->debug = MagickFalse;
pkc->sdNorm = MagickFalse;
pkc->dither = MagickFalse;
pkc->method = mMeans;
pkc->hasJump = MagickFalse;
pkc->hasSetY = MagickFalse;
pkc->jumpY = 0;
pkc->jump0 = 1;
pkc->jump1 = 10;
pkc->kCols = 10;
pkc->maxIter = 100;
pkc->fuzzyR = 1.5;
pkc->harmonicP = 3.5;
pkc->tolerance = 0.01;
pkc->fh_data = stderr;
pkc->random_info = NULL;
pkc->frameName = NULL;
pkc->frameNum = 0;
pkc->clusters = NULL;
pkc->membDenoms = NULL;
InitNormalise (pkc);
for (i=0; i < argc; i++) {
char * pa = (char *)argv[i];
if (IsArg (pa, "m", "method")==MagickTrue) {
NEXTARG;
if (LocaleCompare(argv[i], "means")==0) pkc->method = mMeans;
else if (LocaleCompare(argv[i], "fuzzy")==0) pkc->method = mFuzzy;
else if (LocaleCompare(argv[i], "harmonic")==0) pkc->method = mHarmonic;
else if (LocaleCompare(argv[i], "null")==0) pkc->method = mNull;
else {
fprintf (stderr, "kcluster: ERROR: unknown method [%s]\n", argv[i]);
status = MagickFalse;
}
} else if (IsArg (pa, "k", "kCols")==MagickTrue) {
NEXTARG;
pkc->hasJump = MagickFalse;
pkc->kCols = atoi(pargv[i]);
int valA, valB, numVals;
if (ParseRange (pkc, pargv[i], &valA, &valB, &numVals)) {
if (numVals==1) {
pkc->hasJump = MagickFalse;
pkc->kCols = valA;
} else if (numVals==2) {
pkc->hasJump = MagickTrue;
pkc->jump0 = valA;
pkc->jump1 = valB;
} else {
fprintf (stderr, "kcluster: ERROR: bad kCols [%s]\n", argv[i]);
status = MagickFalse;
}
} else {
fprintf (stderr, "kcluster: ERROR: bad kCols [%s]\n", argv[i]);
status = MagickFalse;
}
} else if (IsArg (pa, "j", "jump")==MagickTrue) {
NEXTARG;
pkc->hasJump = MagickTrue;
if (!ParseTwoJumps (pkc, pargv[i])) status = MagickFalse;
} else if (IsArg (pa, "i", "initialize")==MagickTrue) {
NEXTARG;
if (LocaleCompare(argv[i], "colors")==0) pkc->init = iColors;
else if (LocaleCompare(argv[i], "colours")==0) pkc->init = iColors;
else if (LocaleCompare(argv[i], "forgy")==0) pkc->init = iForgy;
else if (LocaleCompare(argv[i], "randPart")==0) pkc->init = iRandPart;
else if (LocaleCompare(argv[i], "sdLine")==0) pkc->init = iSdLine;
else if (LocaleCompare(argv[i], "fromList")==0) pkc->init = iFromList;
else {
fprintf (stderr, "kcluster: ERROR: unknown initialize [%s]\n", argv[i]);
status = MagickFalse;
}
} else if (IsArg (pa, "x", "maxIter")==MagickTrue) {
NEXTARG;
pkc->maxIter = atoi(pargv[i]);
} else if (IsArg (pa, "y", "jumpY")==MagickTrue) {
NEXTARG;
pkc->hasSetY = MagickTrue;
pkc->jumpY = atof(pargv[i]);
} else if (IsArg (pa, "r", "fuzzyR")==MagickTrue) {
NEXTARG;
pkc->fuzzyR = atof(pargv[i]);
} else if (IsArg (pa, "p", "harmonicP")==MagickTrue) {
NEXTARG;
pkc->harmonicP = atof(pargv[i]);
} else if (IsArg (pa, "t", "tolerance")==MagickTrue) {
NEXTARG;
pkc->tolerance = atof(pargv[i]);
} else if (IsArg (pa, "s", "sdNorm")==MagickTrue) {
pkc->sdNorm = MagickTrue;
} else if (IsArg (pa, "o", "dither")==MagickTrue) {
pkc->dither = MagickTrue;
} else if (IsArg (pa, "a", "regardAlpha")==MagickTrue) {
pkc->regardAlpha = MagickTrue;
} else if (IsArg (pa, "w", "write")==MagickTrue) {
NEXTARG;
pkc->frameName = pargv[i];
} else if (IsArg (pa, "d", "debug")==MagickTrue) {
pkc->debug = MagickTrue;
} else if (IsArg (pa, "f", "file")==MagickTrue) {
NEXTARG;
if (strcasecmp (argv[i], "stdout")==0) pkc->fh_data = stdout;
else if (strcasecmp (argv[i], "stderr")==0) pkc->fh_data = stderr;
else status = MagickFalse;
} else if (IsArg (pa, "v", "verbose")==MagickTrue) {
pkc->verbose = 1;
} else if (IsArg (pa, "v2", "verbose2")==MagickTrue) {
pkc->verbose = 2;
} else if (IsArg (pa, "version", "version")==MagickTrue) {
fprintf (stdout, "%s\n", VERSION);
} else {
fprintf (stderr, "kcluster: ERROR: unknown option [%s]\n", pa);
status = MagickFalse;
}
}
if (pkc->dither && pkc->method != mFuzzy) {
fprintf (stderr, "kcluster: WARNING dither is available only for fuzzy.");
pkc->dither = MagickFalse;
}
if (pkc->verbose) {
fprintf (stderr, "kcluster options: ");
fprintf (stderr, " method ");
switch (pkc->method) {
case mNull:
fprintf (stderr, "Null");
break;
case mMeans:
fprintf (stderr, "Means");
break;
case mFuzzy:
fprintf (stderr, "Fuzzy");
fprintf (stderr, " fuzzyR %.*g", pkc->precision, pkc->fuzzyR);
break;
case mHarmonic:
fprintf (stderr, "Harmonic");
fprintf (stderr, " harmonicP %.*g", pkc->precision, pkc->harmonicP);
break;
}
if (pkc->hasJump) {
fprintf (stderr, " kCols %i-%i", pkc->jump0, pkc->jump1);
if (pkc->hasSetY) fprintf (stderr, " jumpY %.*g", pkc->precision, pkc->jumpY);
}
if (pkc->init != iFromList && !pkc->hasJump)
fprintf (stderr, " kCols %i", pkc->kCols);
fprintf (stderr, " initialize ");
switch (pkc->init) {
case iColors:
fprintf (stderr, "Colors");
break;
case iForgy:
fprintf (stderr, "Forgy");
break;
case iRandPart:
fprintf (stderr, "RandPart");
break;
case iSdLine:
fprintf (stderr, "SdLine");
break;
case iFromList:
fprintf (stderr, "FromList");
break;
}
fprintf (stderr, " tolerance %.*g", pkc->precision, pkc->tolerance);
fprintf (stderr, " maxIter %i", pkc->maxIter);
if (pkc->sdNorm) fprintf (stderr, " sdNorm");
if (pkc->dither) fprintf (stderr, " dither");
if (pkc->regardAlpha) fprintf (stderr, " regardAlpha");
if (pkc->frameName) fprintf (stderr, " write %s", pkc->frameName);
if (pkc->debug) fprintf (stderr, " debug");
if (pkc->fh_data == stdout) fprintf (stderr, " file stdout");
if (pkc->verbose==1) fprintf (stderr, " verbose");
else if (pkc->verbose==2) fprintf (stderr, " verbose2");
fprintf (stderr, "\n");
}
if (status == MagickFalse)
usage ();
return (status);
}
static void InitRand (kclusterT * pkc)
{
pkc->random_info=AcquireRandomInfo();
// There seems to be a problem: the first few values show coherency,
// so skip over them.
int i;
for (i=0; i < 20; i++) {
GetPseudoRandomValue(pkc->random_info);
}
}
static void DeInitRand (kclusterT * pkc)
{
if (pkc->random_info)
pkc->random_info=DestroyRandomInfo(pkc->random_info);
}
static void DumpImage (
kclusterT * pkc,
Image *image,
ExceptionInfo *exception)
{
CacheView *in_view = AcquireVirtualCacheView (image, exception);
ssize_t x, y;
for (y=0; y < (ssize_t) image->rows; y++) {
const VIEW_PIX_PTR *p=GetCacheViewVirtualPixels(in_view,0,y,image->columns,1,exception);
if (p == (const VIEW_PIX_PTR *) NULL) return;
for (x=0; x < (ssize_t) image->columns; x++) {
fprintf (pkc->fh_data, "%li,%li: %g %g %g %g\n",
x, y,
GET_PIXEL_RED(image,p),
GET_PIXEL_GREEN(image,p),
GET_PIXEL_BLUE(image,p),
GET_PIXEL_ALPHA(image,p));
p += Inc_ViewPixPtr (image);
}
}
in_view = DestroyCacheView (in_view);
}
static void getRandomPixel (
kclusterT * pkc,
Image *image,
Image *forgy_img,
CacheView *in_view,
VIEW_PIX_PTR * pf, // points to pixel in forgy_img
ExceptionInfo *exception
)
{
// If we are regarding alpha,
// try to get non-transparent colour.
int i;
for (i=0; i < 10; i++) {
ssize_t x = image->columns * GetPseudoRandomValue(pkc->random_info);
ssize_t y = image->rows * GetPseudoRandomValue(pkc->random_info);
const VIEW_PIX_PTR *pi = GetCacheViewVirtualPixels(in_view,x,y,1,1,exception);
SET_PIXEL_RED (forgy_img, GET_PIXEL_RED(image,pi), pf);
SET_PIXEL_GREEN (forgy_img, GET_PIXEL_GREEN(image,pi), pf);
SET_PIXEL_BLUE (forgy_img, GET_PIXEL_BLUE(image,pi), pf);
double a = GET_PIXEL_ALPHA(image,pi);
if (a < 0) a = 0;
SET_PIXEL_ALPHA (forgy_img, a, pf);
if (!pkc->regardAlpha) break;
if (a > 0) break;
}
}
static Image * InitialiseForgy (
kclusterT * pkc,
Image *image,
ExceptionInfo *exception
)
{
if (!pkc->random_info) InitRand (pkc);
Image *forgy_img = CloneImage (image, pkc->kCols,1, MagickFalse, exception);
if (!forgy_img) return NULL;
CacheView *in_view = AcquireVirtualCacheView (image, exception);
CacheView *forgy_view = AcquireAuthenticCacheView (forgy_img, exception);
VIEW_PIX_PTR * pf = GetCacheViewAuthenticPixels (
forgy_view,0,0,forgy_img->columns,1,exception);
int i;
for (i=0; i < pkc->kCols; i++) {
getRandomPixel (pkc, image, forgy_img, in_view, pf, exception);
pf += Inc_ViewPixPtr (forgy_img);
}
if (!SyncCacheViewAuthenticPixels(forgy_view,exception))
return NULL;
forgy_view = DestroyCacheView (forgy_view);
in_view = DestroyCacheView (in_view);
// FIXME: then ensure they are unique.
return forgy_img;
}
static Image * MakeUnique (
kclusterT * pkc,
Image *image,
Image *sml_image,
ExceptionInfo *exception
)
// Returns image of unique colours, or NULL if failure.
{
Image *uniq_img = UniqueImageColors (sml_image, exception);
if (!uniq_img) return NULL;
if (pkc->debug) {
fprintf (stderr, "AfterUnique:\n");
DumpImage (pkc, uniq_img, exception);
}
if (uniq_img->columns < pkc->kCols) {
if (pkc->verbose) {
fprintf (pkc->fh_data, "kcluster: wanted %i but found %li unique\n",
pkc->kCols, uniq_img->columns);
}
ssize_t skipOver = uniq_img->columns;
uniq_img->gravity = WestGravity;
// Background?
#if IMV6OR7==6
SetImageExtent (uniq_img, pkc->kCols, 1);
#else
SetImageExtent (uniq_img, pkc->kCols, 1, exception);
#endif
if (pkc->verbose) {
fprintf (pkc->fh_data, "kcluster: -- now have %li\n", uniq_img->columns);
}
if (!pkc->random_info) InitRand (pkc);
CacheView *in_view = AcquireVirtualCacheView (image, exception);
CacheView *uniq_view = AcquireAuthenticCacheView (uniq_img, exception);
VIEW_PIX_PTR * pu = GetCacheViewAuthenticPixels (
uniq_view, 0, 0, uniq_img->columns, 1, exception);
pu += skipOver * Inc_ViewPixPtr (uniq_img);
int i;
for (i = skipOver; i < uniq_img->columns; i++) {
getRandomPixel (pkc, image, uniq_img, in_view, pu, exception);
pu += Inc_ViewPixPtr (uniq_img);
}
if (!SyncCacheViewAuthenticPixels(uniq_view,exception))
return NULL;
uniq_view = DestroyCacheView (uniq_view);
in_view = DestroyCacheView (in_view);
}
if (pkc->debug) {
fprintf (stderr, "After padding:\n");
DumpImage (pkc, uniq_img, exception);
}
if (pkc->regardAlpha) {
// If we have caught any fully-transparent colours,
// try for better ones.
if (!pkc->random_info) InitRand (pkc);
CacheView *in_view = AcquireVirtualCacheView (image, exception);
CacheView *uniq_view = AcquireAuthenticCacheView (uniq_img, exception);
VIEW_PIX_PTR * pu = GetCacheViewAuthenticPixels(uniq_view,0,0,uniq_img->columns,1,exception);
int i;
for (i=0; i < uniq_img->columns; i++) {
if (GET_PIXEL_ALPHA(uniq_img,pu) <= 0) {
getRandomPixel (pkc, image, uniq_img, in_view, pu, exception);
}
pu += Inc_ViewPixPtr (uniq_img);
}
if (!SyncCacheViewAuthenticPixels(uniq_view,exception))
return NULL;
uniq_view = DestroyCacheView (uniq_view);
in_view = DestroyCacheView (in_view);
}
return uniq_img;
}
static Image * InitialiseColors (
kclusterT * pkc,
Image *image,
ExceptionInfo *exception
)
// Returns image of unique colours, or NULL if failure.
{
Image *quant_img = CloneImage (image, 0,0, MagickFalse, exception);
if (!quant_img) return NULL;
QuantizeInfo *qi = AcquireQuantizeInfo (NULL);
qi->number_colors = pkc->kCols;
qi->dither_method = NoDitherMethod;
#if IMV6OR7==6
qi->dither = MagickFalse;
if (!QuantizeImage (qi, quant_img)) return NULL;
#else
if (!QuantizeImage (qi, quant_img, exception)) return NULL;
#endif
qi = DestroyQuantizeInfo (qi);
Image * uniq_img = MakeUnique (pkc, image, quant_img, exception);
Image * uniq_img2 = MakeUnique (pkc, image, uniq_img, exception);
// FIXME: free images
return uniq_img2;
}
static Image * InitialiseRandomPartition (
kclusterT * pkc,
Image *image,
ExceptionInfo *exception
)
// Returns image of unique colours, or NULL if failure.
{
// Assign each image pixel to a random partition.
// Set each partition to the average of the pixels (each weighted by alpha).
if (!pkc->random_info) InitRand (pkc);
Image *rp_img = CloneImage (image, pkc->kCols,1, MagickFalse, exception);
if (!rp_img) return NULL;
if (!pkc->clusters) {
fprintf (stderr, "kcluster: BUG: irp no clusters");
return NULL;
}
CacheView *in_view = AcquireVirtualCacheView (image, exception);
CacheView *rp_view = AcquireAuthenticCacheView (rp_img, exception);
ssize_t x, y, qx;
for (qx = 0; qx < rp_img->columns; qx++) {
clusterT * pClust = &pkc->clusters[qx];
pClust->red =
pClust->green =
pClust->blue =
pClust->alpha =
pClust->count = 0;
}
double r, g, b, a;
for (y=0; y < (ssize_t) image->rows; y++) {
const VIEW_PIX_PTR *p=GetCacheViewVirtualPixels(in_view,0,y,image->columns,1,exception);
if (p == (const VIEW_PIX_PTR *) NULL) return MagickFalse;
for (x=0; x < (ssize_t) image->columns; x++) {
a = GET_PIXEL_ALPHA(image,p) / (double)QuantumRange;
r = a * GET_PIXEL_RED(image,p) / (double)QuantumRange;
g = a * GET_PIXEL_GREEN(image,p) / (double)QuantumRange;
b = a * GET_PIXEL_BLUE(image,p) / (double)QuantumRange;
qx = rp_img->columns * GetPseudoRandomValue(pkc->random_info);
clusterT * pClust = &pkc->clusters[qx];
pClust->red += r;
pClust->green += g;
pClust->blue += b;
pClust->alpha += a;
pClust->count++;
p += Inc_ViewPixPtr (image);
}
}
// We may have clusters with count=0,
// no image pixels have been assigned to it.
VIEW_PIX_PTR *rp=GetCacheViewAuthenticPixels(rp_view,0,0,rp_img->columns,1,exception);
for (qx = 0; qx < rp_img->columns; qx++) {
clusterT * pClust = &pkc->clusters[qx];
if (pClust->count==0) {
SET_PIXEL_ALPHA (rp_img, QuantumRange, rp);
SET_PIXEL_RED (rp_img, 0, rp);
SET_PIXEL_GREEN (rp_img, 0, rp);
SET_PIXEL_BLUE (rp_img, 0, rp);
} else {
SET_PIXEL_ALPHA (rp_img, QuantumRange * pClust->alpha / pClust->count, rp);
if (pClust->alpha == 0) pClust->alpha = 1.0;
SET_PIXEL_RED (rp_img, QuantumRange * pClust->red / pClust->alpha, rp);
SET_PIXEL_GREEN (rp_img, QuantumRange * pClust->green / pClust->alpha, rp);
SET_PIXEL_BLUE (rp_img, QuantumRange * pClust->blue / pClust->alpha, rp);
}
rp += Inc_ViewPixPtr (rp_img);
}
if (!SyncCacheViewAuthenticPixels(rp_view,exception))
return NULL;
rp_view = DestroyCacheView (rp_view);
in_view = DestroyCacheView (in_view);
Image * uniq_img = MakeUnique (pkc, image, rp_img, exception);
Image * uniq_img2 = MakeUnique (pkc, image, uniq_img, exception);
// FIXME: free images
return uniq_img2;
}
static MagickBooleanType CalcMeanSd (
kclusterT * pkc,
Image *image,
double *mnR,
double *mnG,
double *mnB,
double *sdR,
double *sdG,
double *sdB,
MagickBooleanType *isMeanSdDefined,
ExceptionInfo *exception
)
// Calculate mean and standard deviation,
// accounting for alpha (eg ignoring pixels that are entirely transparent).
// Returns values in range 0.0 to 1.0.
// Returns false if major problem.
// Returns isMeanSdDefined = MagickFalse iff statistics are not defined
// (image is entirely transparent).
{
// mnR = sig(R) / sig(alpha)
// sdR = sqrt ( sig(R^2) / sig(alpha) - mnR^2 )
//
// Likewise for G and B.
CacheView *in_view = AcquireVirtualCacheView (image, exception);
double r, g, b, a;
double sigR=0, sigG=0, sigB=0, sigA=0;
double sigR2=0, sigG2=0, sigB2=0;
ssize_t x, y;
for (y=0; y < (ssize_t) image->rows; y++) {
const VIEW_PIX_PTR *p=GetCacheViewVirtualPixels(in_view,0,y,image->columns,1,exception);
if (p == (const VIEW_PIX_PTR *) NULL) return MagickFalse;
for (x=0; x < (ssize_t) image->columns; x++) {
a = GET_PIXEL_ALPHA(image,p) / QuantumRange;
r = a * GET_PIXEL_RED(image,p) / QuantumRange;
g = a * GET_PIXEL_GREEN(image,p) / QuantumRange;
b = a * GET_PIXEL_BLUE(image,p) / QuantumRange;
sigR += r;
sigG += g;
sigB += b;
sigA += a;
sigR2 += r*r;
sigG2 += g*g;
sigB2 += b*b;
p += Inc_ViewPixPtr (image);
}
}
in_view = DestroyCacheView (in_view);
if (sigA == 0) {
*isMeanSdDefined = MagickFalse;
return MagickTrue;
}
*isMeanSdDefined = MagickTrue;
*mnR = sigR / sigA;
*mnG = sigG / sigA;
*mnB = sigB / sigA;
*sdR = sqrt (sigR2/sigA - (*mnR)*(*mnR));
*sdG = sqrt (sigG2/sigA - (*mnG)*(*mnG));
*sdB = sqrt (sigB2/sigA - (*mnB)*(*mnB));
return MagickTrue;
}
static Image * InitialiseSdLine (
kclusterT * pkc,
Image *image,
ExceptionInfo *exception
)
// Returns image of unique colours, or NULL if failure.
{
double mnR=0, mnG=0, mnB=0;
double sdR=0, sdG=0, sdB=0;
MagickBooleanType isMeanSdDefined;
if (!CalcMeanSd (pkc, image,
&mnR, &mnG, &mnB, &sdR, &sdG, &sdB,
&isMeanSdDefined, exception))
return MagickFalse;
if (sdR==0 && sdG==0 && sdB==0) {
fprintf (stderr, "kcluster: initialize SdLine with flat-colour image: only one cluster.");
pkc->kCols = 1;
}
double loR = mnR - sdR;
double loG = mnG - sdG;
double loB = mnB - sdB;
double semiR = sdR / (double)pkc->kCols;
double semiG = sdG / (double)pkc->kCols;
double semiB = sdB / (double)pkc->kCols;
if (pkc->debug) fprintf (stderr,
"InitialiseSdLine: mnR=%g sdR=%g loR=%g semiR=%g\n",
mnR, sdR, loR, semiR);
Image *sd_img = CloneImage (image, pkc->kCols,1, MagickFalse, exception);
if (!sd_img) return NULL;
CacheView *sd_view = AcquireAuthenticCacheView (sd_img, exception);
VIEW_PIX_PTR *q=GetCacheViewAuthenticPixels(sd_view,0,0,sd_img->columns,1,exception);
int qx;
for (qx = 0; qx < sd_img->columns; qx++) {
SET_PIXEL_ALPHA (sd_img, QuantumRange, q);
if (pkc->debug) fprintf (stderr,
"InitialiseSdLine: qx=%i R=%g\n",
qx, loR + semiR*(2*qx+1));
SET_PIXEL_RED (sd_img, QuantumRange * (loR + semiR*(2*qx+1)), q);
SET_PIXEL_GREEN (sd_img, QuantumRange * (loG + semiG*(2*qx+1)), q);
SET_PIXEL_BLUE (sd_img, QuantumRange * (loB + semiB*(2*qx+1)), q);
q += Inc_ViewPixPtr (sd_img);
}
if (!SyncCacheViewAuthenticPixels(sd_view,exception))
return NULL;
sd_view = DestroyCacheView (sd_view);
return sd_img;
}
static Image * InitialiseFromList (
kclusterT * pkc,
Image *image,
ExceptionInfo *exception
)
// Returns image of unique colours, or NULL if failure.
{
Image *last_img = GetLastImageInList(image);
if (last_img == NULL || last_img == image) {
fprintf (stderr, "kcluster: FromList: can't find last\n");
return NULL;
}
if (pkc->verbose) {
fprintf (pkc->fh_data, "kcluster: FromList [%s] %ix%i depth %i\n",
last_img->filename,
(int)last_img->columns, (int)last_img->rows,
(int)last_img->depth);
}
Image *uniq_img = UniqueImageColors (last_img, exception);
if (!uniq_img) return NULL;
// FIXME: transparency?
pkc->kCols = uniq_img->columns;
if (pkc->verbose) fprintf (pkc->fh_data, "kcluster: FromList k=%i\n", pkc->kCols);
return uniq_img;
}
static MagickBooleanType CalcNormalise (
kclusterT * pkc,
Image *image,
ExceptionInfo *exception
)
{
InitNormalise (pkc);
double mnR=0, mnG=0, mnB=0;
double sdR=0, sdG=0, sdB=0;
MagickBooleanType isMeanSdDefined;
if (!CalcMeanSd (pkc, image,
&mnR, &mnG, &mnB, &sdR, &sdG, &sdB,
&isMeanSdDefined, exception))
return MagickFalse;
if (pkc->debug) {
fprintf (pkc->fh_data, "kcluster: mnsd defined: %i\n", isMeanSdDefined);
fprintf (pkc->fh_data, "kcluster: mn: %.*g,%.*g,%.*g sd: %.*g,%.*g,%.*g\n",
pkc->precision, mnR,
pkc->precision, mnG,
pkc->precision, mnB,
pkc->precision, sdR,
pkc->precision, sdG,
pkc->precision, sdB);
}
if (!isMeanSdDefined) return MagickFalse;
// FIXME: Can this also set Y for jump???
double sdMin = sdR;
if (sdMin > sdG) sdMin = sdG;
if (sdMin > sdB) sdMin = sdB;
double sdMax = sdR;
if (sdMax < sdG) sdMax = sdG;
if (sdMax < sdB) sdMax = sdB;
if (sdMin==sdMax) return MagickTrue;
if (sdR+sdG+sdB==sdMax) {
if (pkc->verbose) fprintf (pkc->fh_data, "kcluster: only one channel\n");
return MagickTrue;
}
if (sdMin==0) {
if (pkc->verbose) fprintf (pkc->fh_data, "kcluster: only two channels\n");
if (sdMax==sdR) {
pkc->normalise.r = ((sdG==0) ? sdB : sdG) / sdR;
} else if (sdMax==sdG) {
pkc->normalise.g = ((sdR==0) ? sdB : sdR) / sdG;
} else if (sdMax==sdB) {
pkc->normalise.b = ((sdR==0) ? sdG : sdR) / sdB;
}
} else {
if (pkc->verbose) fprintf (pkc->fh_data, "kcluster: three channels\n");
if (sdMin==sdR) {
pkc->normalise.g = sdR / sdG;
pkc->normalise.b = sdR / sdB;
} else if (sdMin==sdG) {
pkc->normalise.r = sdG / sdR;
pkc->normalise.b = sdG / sdB;
} else if (sdMin==sdB) {
pkc->normalise.r = sdB / sdR;
pkc->normalise.g = sdB / sdG;
}
}
if (pkc->normalise.r == 0) pkc->normalise.r = 1.0;
if (pkc->normalise.g == 0) pkc->normalise.g = 1.0;
if (pkc->normalise.b == 0) pkc->normalise.b = 1.0;
if (pkc->verbose)
fprintf (pkc->fh_data, "kcluster: normalise %.*g,%.*g,%.*g\n",
pkc->precision, pkc->normalise.r,
pkc->precision, pkc->normalise.g,
pkc->precision, pkc->normalise.b);
return MagickTrue;
}
static MagickStatusType WriteFrame (
kclusterT * pkc,
Image * img,
ExceptionInfo *exception)
{
ImageInfo
*ii;
MagickStatusType
status;
Image
*copy_img;
ii = AcquireImageInfo ();
copy_img = CloneImage(img, 0, 0, MagickTrue, exception);
if (copy_img == (Image *) NULL)
return MagickFalse;
copy_img->scene = pkc->frameNum++;
CopyMagickString (copy_img->filename, pkc->frameName, MaxTextExtent);
#if IMV6OR7==6
status = WriteImage (ii, copy_img);
#else
status = WriteImage (ii, copy_img, exception);
#endif
DestroyImageList(copy_img);
ii = DestroyImageInfo (ii);
return status;
}
static MagickBooleanType ApplyNormalise (
kclusterT * pkc,
Image *image,
ExceptionInfo *exception,
MagickBooleanType IsForwards
)
{
double mr, mg, mb;
if (IsForwards) {
mr = pkc->normalise.r;
mg = pkc->normalise.g;
mb = pkc->normalise.b;
} else {
mr = 1.0 / pkc->normalise.r;
mg = 1.0 / pkc->normalise.g;
mb = 1.0 / pkc->normalise.b;
}
CacheView *in_view = AcquireAuthenticCacheView (image, exception);
ssize_t x, y;
for (y=0; y < (ssize_t) image->rows; y++) {
VIEW_PIX_PTR *p=GetCacheViewAuthenticPixels(in_view,0,y,image->columns,1,exception);
if (p == (const VIEW_PIX_PTR *) NULL) return MagickFalse;
for (x=0; x < (ssize_t) image->columns; x++) {
SET_PIXEL_RED (image, mr * GET_PIXEL_RED(image,p), p);
SET_PIXEL_GREEN (image, mg * GET_PIXEL_GREEN(image,p), p);
SET_PIXEL_BLUE (image, mb * GET_PIXEL_BLUE(image,p), p);
p += Inc_ViewPixPtr (image);
}
if (!SyncCacheViewAuthenticPixels(in_view,exception))
return MagickFalse;
}
in_view = DestroyCacheView (in_view);
return MagickTrue;
}
static double inline colDistRel (
Image *image0,
Image *image1,
const VIEW_PIX_PTR *p0,
const VIEW_PIX_PTR *p1
)
// Returns relative distance between colours.
{
double r = GET_PIXEL_RED(image0,p0) - GET_PIXEL_RED(image1,p1);
double g = GET_PIXEL_GREEN(image0,p0) - GET_PIXEL_GREEN(image1,p1);
double b = GET_PIXEL_BLUE(image0,p0) - GET_PIXEL_BLUE(image1,p1);
return (r*r + g*g + b*b);
}
static double inline colDist (
Image *image0,
Image *image1,
const VIEW_PIX_PTR *p0,
const VIEW_PIX_PTR *p1
)
// Returns distance between colours, scale 0.0 to 1.0.
{
return sqrt (colDistRel(image0, image1, p0, p1) / 3.0) / QuantumRange;
}
static double inline powDist (
Image *image0,
Image *image1,
const VIEW_PIX_PTR *p0,
const VIEW_PIX_PTR *p1,
double powParam
)
{
double r = colDist (image0, image1, p0, p1);
if (r==0) return 0;
return pow (r, powParam);
}
static double inline sumPowDist (
Image *image,
Image *uniq_img,
const VIEW_PIX_PTR *pImg, // an image pixel
const VIEW_PIX_PTR *pUniq, // first entry in uniq_img
double powParam
)
{
double r = 0.0, c;
ssize_t qx;
const VIEW_PIX_PTR *qu = pUniq;
for (qx=0; qx < uniq_img->columns; qx++) {
c = colDist (image, uniq_img, pImg, qu);
if (c > 0) r += pow (c, powParam);
qu += Inc_ViewPixPtr (uniq_img);
}
return r;
}
static double inline fuzzyMembership (
kclusterT * pkc,
Image *image,
Image *uniq_img,
const VIEW_PIX_PTR *pImg, // an image pixel
const VIEW_PIX_PTR *pUniq, // first entry in uniq_img
ssize_t qx, // index into uniq_img
double denom
)
{
double numer = powDist (image, uniq_img,
pImg, pUniq + qx * Inc_ViewPixPtr (uniq_img),
pkc->powParam1);
if (numer == denom) return 1.0;
if (pkc->debug) {
if (isnan(numer)) fprintf (stderr, "numer is nan ");
if (isnan(denom)) fprintf (stderr, "denom is nan ");
if (isnan (numer / denom)) {
fprintf (stderr, "fuzm %g,%g qx=%li ", numer, denom, qx);
}
if (numer > denom) fprintf (stderr, "fm %g/%g ", numer, denom);
}
return numer / denom;
}
static double inline harmonicMembership (
kclusterT * pkc,
Image *image,
Image *uniq_img,
const VIEW_PIX_PTR *pImg, // an image pixel
const VIEW_PIX_PTR *pUniq, // first entry in uniq_img
ssize_t qx, // index into uniq_img
double denom
)
{
double numer = powDist (image, uniq_img,
pImg, pUniq + qx * Inc_ViewPixPtr (uniq_img),
pkc->powParam2);
//double denom = sumPowDist (image, uniq_img, pImg, pUniq, pkc->powParam2);
if (numer == denom) return 1.0;
return numer / denom;
}
static double inline harmonicWeight (
kclusterT * pkc,
Image *image,
Image *uniq_img,
const VIEW_PIX_PTR *pImg, // an image pixel
const VIEW_PIX_PTR *pUniq // first entry in uniq_img
)
{
double numer = sumPowDist (image, uniq_img, pImg, pUniq, pkc->powParam2);
double denom = sumPowDist (image, uniq_img, pImg, pUniq, pkc->powParam3);
denom = denom * denom;
if (numer == denom) return 1.0;
if (pkc->debug) {
if (isnan(numer)) fprintf (stderr, "numer is nan ");
if (isnan(denom)) fprintf (stderr, "denom is nan ");
if (isnan (numer / denom)) {
fprintf (stderr, "harm w %g,%g ", numer, denom);
}
}
return numer / denom;
}
static void fuzzyDither (
kclusterT * pkc,
Image *image,
Image *uniq_img,
ssize_t x,
ssize_t y,
VIEW_PIX_PTR *pImg, // an image pixel
const VIEW_PIX_PTR *pUniq // first entry in uniq_img
)
{
double rand = GetPseudoRandomValue(pkc->random_info);
double cumMemb = 0;
if (!pkc->membDenoms)
fprintf (stderr, "null membDenoms\n");
double denom = pkc->membDenoms[y*image->columns+x];
ssize_t qx;
for (qx=0; qx < uniq_img->columns; qx++) {
// stop when cumulative membership >= rand
cumMemb += fuzzyMembership (pkc, image, uniq_img, pImg, pUniq, qx, denom);
if (cumMemb >= rand) {
const VIEW_PIX_PTR *qu = pUniq + qx * Inc_ViewPixPtr (uniq_img);
SET_PIXEL_RED (image, GET_PIXEL_RED (uniq_img, qu), pImg);
SET_PIXEL_GREEN (image, GET_PIXEL_GREEN (uniq_img, qu), pImg);
SET_PIXEL_BLUE (image, GET_PIXEL_BLUE (uniq_img, qu), pImg);
break;
}
}
}
static MagickBooleanType setMembDenoms (
kclusterT * pkc,
Image *image,
Image *uniq_img,
VIEW_PIX_PTR *pUniq,
CacheView *in_view,
double powParam, // pkc->powParam1
ExceptionInfo *exception)
{
if (!pkc->membDenoms) return MagickFalse;
ssize_t x, y;
for (y=0; y < (ssize_t) image->rows; y++) {
const VIEW_PIX_PTR *p=GetCacheViewVirtualPixels(in_view,0,y,image->columns,1,exception);
if (p == (const VIEW_PIX_PTR *) NULL) return MagickFalse;
for (x=0; x < (ssize_t) image->columns; x++) {
pkc->membDenoms[y*image->columns+x] = sumPowDist (image, uniq_img, p, pUniq, powParam);
p += Inc_ViewPixPtr (image);
}
}
return MagickTrue;
}
static MagickBooleanType updateImage (
kclusterT * pkc,
Image *image,
Image *uniq_img,
VIEW_PIX_PTR *pUniq,
ExceptionInfo *exception
)
// Returns whether okay.
{
// Populate image with the updated cluster colours.
assert(image != (Image *) NULL);
assert(image->signature == MAGICK_CORE_SIG);
assert (pkc->clusters);
CacheView *upd_view = AcquireAuthenticCacheView (image, exception);
if (pkc->dither) {
if (!setMembDenoms (
pkc, image, uniq_img, pUniq, upd_view, pkc->powParam1,
exception)) return MagickFalse;
}
ssize_t x, y, qx;
for (y=0; y < (ssize_t) image->rows; y++) {
VIEW_PIX_PTR *p=GetCacheViewAuthenticPixels(upd_view,0,y,image->columns,1,exception);
if (p == (const VIEW_PIX_PTR *) NULL)
return MagickFalse;
for (x=0; x < (ssize_t) image->columns; x++) {
if (pkc->dither) {
fuzzyDither (pkc, image, uniq_img, x, y, p, pUniq);
} else {
qx = pkc->nearClust[y*image->columns+x];
clusterT * pClust = &pkc->clusters[qx];
SET_PIXEL_RED (image, pClust->red, p);
SET_PIXEL_GREEN (image, pClust->green, p);
SET_PIXEL_BLUE (image, pClust->blue, p);
}
p += Inc_ViewPixPtr (image);
}
if (!SyncCacheViewAuthenticPixels(upd_view,exception))
return MagickFalse;
}
upd_view = DestroyCacheView (upd_view);
return MagickTrue;
}
// The next function corresponds in style to functions in transform.c
// It takes one image, and returns an image.
//
static Image *kclusterOneK (
Image *image,
kclusterT * pkc,
ExceptionInfo *exception)
//
// This may adjust pkc->kCols downwards.
//
{
if (image->debug != MagickFalse)
(void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
ssize_t nPixels = image->columns * image->rows;
// Beware: if FromList, we don't know kCol yet.
if (pkc->kCols < 1) pkc->kCols = 1;
if (pkc->kCols > nPixels) pkc->kCols = nPixels;
if (pkc->verbose) {
fprintf (pkc->fh_data, "kcluster: Input image [%s] %ix%i depth %i; nPixels %li k=%i\n",
image->filename,
(int)image->columns, (int)image->rows,
(int)image->depth,
(long int)nPixels,
pkc->kCols);
}
pkc->nearClust = (ssize_t *) AcquireQuantumMemory (nPixels, sizeof(ssize_t));
if (!pkc->nearClust) {
fprintf (stderr, "kcluster: oom2\n");
return (Image *)NULL;
}
// If FromList, we don't know kCol yet.
// But RandPart needs clusters.
pkc->clusters = NULL;
Image *uniq_img = NULL;
switch (pkc->init) {
case iColors:
default:
uniq_img = InitialiseColors (pkc, image, exception);
break;
case iForgy:
uniq_img = InitialiseForgy (pkc, image, exception);
break;
case iRandPart:
pkc->clusters = (clusterT *) AcquireQuantumMemory (pkc->kCols, sizeof(clusterT));
if (!pkc->clusters) {
fprintf (stderr, "kcluster: oom2\n");
return (Image *)NULL;
}
uniq_img = InitialiseRandomPartition (pkc, image, exception);
break;
case iSdLine:
uniq_img = InitialiseSdLine (pkc, image, exception);
break;
case iFromList:
uniq_img = InitialiseFromList (pkc, image, exception);
if (pkc->kCols > nPixels) pkc->kCols = nPixels;
break;
}
if (!uniq_img) {
fprintf (stderr, "kcluster: Initialise failed\n");
return NULL;
}
if (!pkc->clusters) {
pkc->clusters = (clusterT *) AcquireQuantumMemory (pkc->kCols, sizeof(clusterT));
if (!pkc->clusters) {
fprintf (stderr, "kcluster: oom2\n");
return (Image *)NULL;
}
}
if (pkc->debug) DumpImage (pkc, uniq_img, exception);
if (pkc->verbose)
fprintf (pkc->fh_data, "kcluster: %li uniq_img\n", uniq_img->columns);
CacheView *uniq_view = AcquireAuthenticCacheView (uniq_img,exception);
VIEW_PIX_PTR *q_uniq = GetCacheViewAuthenticPixels(uniq_view,0,0,uniq_img->columns,1,exception);
if (!q_uniq) return NULL;
CacheView *in_view = AcquireVirtualCacheView (image, exception);
pkc->powParam1 = -2 / (pkc->fuzzyR - 1);
pkc->powParam2 = -pkc->harmonicP - 2;
pkc->powParam3 = -pkc->harmonicP;
pkc->membDenoms = (double *) AcquireQuantumMemory (nPixels, sizeof(double));
if (!pkc->membDenoms) {
fprintf (stderr, "kcluster: oom membDen\n");
return (Image *)NULL;
}
double opacity = 1.0;
// Note that an iteration is NOT guaranteed to be better than the previous one.
int iter;
for (iter=0; iter < 9999; iter++) {
ssize_t x, y;
ssize_t qx;
// Loop through x, y, uniq, to populate nearClust.
for (y=0; y < (ssize_t) image->rows; y++) {
const VIEW_PIX_PTR *p=GetCacheViewVirtualPixels(in_view,0,y,image->columns,1,exception);
if (p == (const VIEW_PIX_PTR *) NULL) return (Image *)NULL;
for (x=0; x < (ssize_t) image->columns; x++) {
double minDist=0;
VIEW_PIX_PTR *qu = q_uniq;
for (qx=0; qx < uniq_img->columns; qx++) {
double dist = colDistRel (image, uniq_img, p, qu);
if (qx==0 || minDist > dist) {
minDist = dist;
pkc->nearClust[y*image->columns+x] = qx;
}
qu += Inc_ViewPixPtr (uniq_img);
}
p += Inc_ViewPixPtr (image);
}
}
if (pkc->method == mMeans) {
// Zero cluster accumulators.
for (qx=0; qx < uniq_img->columns; qx++) {
clusterT * pClust = &pkc->clusters[qx];
pClust->red = pClust->green = pClust->blue = 0.0;
pClust->count = 0;
}
// Loop though y,x; add colour to appropriate cluster accumulator.
for (y=0; y < (ssize_t) image->rows; y++) {
const VIEW_PIX_PTR *p=GetCacheViewVirtualPixels(in_view,0,y,image->columns,1,exception);
if (p == (const VIEW_PIX_PTR *) NULL) return (Image *)NULL;
for (x=0; x < (ssize_t) image->columns; x++) {
ssize_t qx = pkc->nearClust[y*image->columns+x];
clusterT * pClust = &pkc->clusters[qx];
if (pkc->regardAlpha)
opacity = GET_PIXEL_ALPHA (image, p) / QuantumRange;
pClust->red += opacity * GET_PIXEL_RED (image, p);
pClust->green += opacity * GET_PIXEL_GREEN (image, p);
pClust->blue += opacity * GET_PIXEL_BLUE (image, p);
pClust->count += opacity;
p += Inc_ViewPixPtr (image);
}
}
// Calc the mean colour per cluster.
for (qx=0; qx < uniq_img->columns; qx++) {
clusterT * pClust = &pkc->clusters[qx];
if (pClust->count) {
pClust->red /= pClust->count;
pClust->green /= pClust->count;
pClust->blue /= pClust->count;
}
}
} else if (pkc->method == mFuzzy) {
if (!setMembDenoms (
pkc, image, uniq_img, q_uniq, in_view, pkc->powParam1,
exception)) return NULL;
for (qx=0; qx < uniq_img->columns; qx++) {
double sumM = 0.0, sumMr = 0.0, sumMg = 0.0, sumMb = 0.0;
for (y=0; y < (ssize_t) image->rows; y++) {
const VIEW_PIX_PTR *p=GetCacheViewVirtualPixels(in_view,0,y,image->columns,1,exception);
if (p == (const VIEW_PIX_PTR *) NULL) return (Image *)NULL;
for (x=0; x < (ssize_t) image->columns; x++) {
double m = fuzzyMembership (pkc,
image, uniq_img, p, q_uniq, qx,
pkc->membDenoms[y*image->columns+x]);
if (isnan(m)) fprintf (stderr, "m is nan xy=%li,%li ", x, y);
if (pkc->regardAlpha) {
opacity = GET_PIXEL_ALPHA (image, p) / QuantumRange;
m *= opacity;
}
sumMr += m * GET_PIXEL_RED (image, p);
sumMg += m * GET_PIXEL_GREEN (image, p);
sumMb += m * GET_PIXEL_BLUE (image, p);
sumM += m;
p += Inc_ViewPixPtr (image);
}
}
if (pkc->debug) fprintf (pkc->fh_data, "fuzzy %li %g, %g,%g,%g\n",
qx,
sumM, sumMr, sumMg, sumMb);
clusterT * pClust = &pkc->clusters[qx];
if (sumM==0) {
pClust->red = pClust->green = pClust->blue = 0;
} else {
pClust->red = sumMr / sumM;
pClust->green = sumMg / sumM;
pClust->blue = sumMb / sumM;
}
}
} else if (pkc->method == mHarmonic) {
double *weights = (double *) AcquireQuantumMemory (nPixels, sizeof(double));
if (!weights) {
fprintf (stderr, "kcluster: oomwt\n");
return (Image *)NULL;
}
for (y=0; y < (ssize_t) image->rows; y++) {
const VIEW_PIX_PTR *p=GetCacheViewVirtualPixels(in_view,0,y,image->columns,1,exception);
if (p == (const VIEW_PIX_PTR *) NULL) return (Image *)NULL;
for (x=0; x < (ssize_t) image->columns; x++) {
weights[y*image->columns+x] = harmonicWeight (pkc, image, uniq_img, p, q_uniq);
pkc->membDenoms[y*image->columns+x] = sumPowDist (image, uniq_img, p, q_uniq, pkc->powParam2);
p += Inc_ViewPixPtr (image);
}
}
for (qx=0; qx < uniq_img->columns; qx++) {
double sumM = 0.0, sumMr = 0.0, sumMg = 0.0, sumMb = 0.0;
for (y=0; y < (ssize_t) image->rows; y++) {
const VIEW_PIX_PTR *p=GetCacheViewVirtualPixels(in_view,0,y,image->columns,1,exception);
if (p == (const VIEW_PIX_PTR *) NULL) return (Image *)NULL;
for (x=0; x < (ssize_t) image->columns; x++) {
double m = harmonicMembership (
pkc, image, uniq_img, p, q_uniq, qx,
pkc->membDenoms[y*image->columns+x]);
if (isnan(m)) fprintf (stderr, "m is nan ");
double w = weights[y*image->columns+x];
if (isnan(w)) fprintf (stderr, "w is nan ");
if (pkc->regardAlpha) {
opacity = GET_PIXEL_ALPHA (image, p) / QuantumRange;
m *= opacity;
}
double mw = m * w;
sumMr += mw * GET_PIXEL_RED (image, p);
sumMg += mw * GET_PIXEL_GREEN (image, p);
sumMb += mw * GET_PIXEL_BLUE (image, p);
sumM += mw;
p += Inc_ViewPixPtr (image);
}
}
clusterT * pClust = &pkc->clusters[qx];
if (sumM==0) {
pClust->red = pClust->green = pClust->blue = 0;
} else {
pClust->red = sumMr / sumM;
pClust->green = sumMg / sumM;
pClust->blue = sumMb / sumM;
}
}
weights = RelinquishMagickMemory (weights);
} else if (pkc->method == mNull) {
VIEW_PIX_PTR *qu = q_uniq;
for (qx=0; qx < uniq_img->columns; qx++) {
clusterT * pClust = &pkc->clusters[qx];
pClust->red = GET_PIXEL_RED (uniq_img, qu);
pClust->green = GET_PIXEL_GREEN (uniq_img, qu);
pClust->blue = GET_PIXEL_BLUE (uniq_img, qu);
qu += Inc_ViewPixPtr (uniq_img);
}
} // mNull
// Calc how much we have changed;
// update the cluster colours.
VIEW_PIX_PTR *qu = q_uniq;
double diff, maxDiff = 0;
for (qx=0; qx < uniq_img->columns; qx++) {
clusterT * pClust = &pkc->clusters[qx];
diff = fabs (pClust->red - GET_PIXEL_RED (uniq_img, qu));
if (maxDiff < diff) maxDiff = diff;
diff = fabs (pClust->green - GET_PIXEL_GREEN (uniq_img, qu));
if (maxDiff < diff) maxDiff = diff;
diff = fabs (pClust->blue - GET_PIXEL_BLUE (uniq_img, qu));
if (maxDiff < diff) maxDiff = diff;
SET_PIXEL_RED (uniq_img, pClust->red, qu);
SET_PIXEL_GREEN (uniq_img, pClust->green, qu);
SET_PIXEL_BLUE (uniq_img, pClust->blue, qu);
qu += Inc_ViewPixPtr (uniq_img);
}
if (!SyncCacheViewAuthenticPixels(uniq_view,exception))
return NULL;
maxDiff /= QuantumRange;
if (pkc->verbose > 1) {
fprintf (pkc->fh_data,
"kcluster: iteration %i maxDiff %.*g\n",
iter, pkc->precision, maxDiff);
}
if (pkc->debug) DumpImage (pkc, uniq_img, exception);
if (pkc->frameName) {
// For performance: don't need to create and destroy every time.
Image * frame_img = CloneImage(image, 0, 0, MagickTrue, exception);
if (frame_img == (Image *) NULL)
return MagickFalse;
if (!updateImage (pkc, frame_img, uniq_img, q_uniq, exception)) return NULL;
WriteFrame (pkc, frame_img, exception);
DestroyImage (frame_img);
}
if (maxDiff <= pkc->tolerance && (iter > 2 || pkc->init != iRandPart))
break;
if (pkc->maxIter && iter >= pkc->maxIter-1) break;
} // end iteration loop
// Populate image with the updated cluster colours.
if (!updateImage (pkc, image, uniq_img, q_uniq, exception)) return NULL;
if (pkc->frameName) WriteFrame (pkc, image, exception);
if (pkc->membDenoms)
pkc->membDenoms = RelinquishMagickMemory (pkc->membDenoms);
in_view = DestroyCacheView (in_view);
uniq_view = DestroyCacheView (uniq_view);
if (pkc->verbose) {
fprintf (pkc->fh_data, "kcluster: %i iterations\n", iter+1);
}
DestroyImage (uniq_img);
pkc->clusters = RelinquishMagickMemory (pkc->clusters);
pkc->nearClust = RelinquishMagickMemory (pkc->nearClust);
return (image);
}
static MagickBooleanType CompareRmseAlpha (
Image *image1,
Image *image2,
double * score,
ExceptionInfo *exception)
// Returns RMSE score (aka "distortion") of two equal-size images,
// taking alpha into account.
{
CacheView *in1_view = AcquireVirtualCacheView (image1, exception);
CacheView *in2_view = AcquireVirtualCacheView (image2, exception);
if ((image1->columns != image2->columns) || (image1->rows != image2->rows)) {
fprintf (stderr, "CompareRmseAlpha: images sizes should match");
return MagickFalse;
}
double sigScore=0, sigmAlpha=0;
ssize_t y;
for (y=0; y < (ssize_t) image1->rows; y++) {
const VIEW_PIX_PTR *p1=GetCacheViewVirtualPixels(in1_view,0,y,image1->columns,1,exception);
if (p1 == (const VIEW_PIX_PTR *) NULL) return MagickFalse;
const VIEW_PIX_PTR *p2=GetCacheViewVirtualPixels(in2_view,0,y,image2->columns,1,exception);
if (p2 == (const VIEW_PIX_PTR *) NULL) return MagickFalse;
ssize_t x;
double a1, a2;
for (x=0; x < (ssize_t) image1->columns; x++) {
a1 = GET_PIXEL_ALPHA(image1, p1) / QuantumRange;
a2 = GET_PIXEL_ALPHA(image2, p2) / QuantumRange;
double mAlpha = a1 * a2;
double dRed =
(GET_PIXEL_RED(image1,p1)
- GET_PIXEL_RED(image2,p2)) / QuantumRange;
double dGreen =
(GET_PIXEL_GREEN(image1,p1)
- GET_PIXEL_GREEN(image2,p2)) / QuantumRange;
double dBlue =
(GET_PIXEL_BLUE(image1,p1)
- GET_PIXEL_BLUE(image2,p2)) / QuantumRange;
if (mAlpha > 0) {
sigScore += mAlpha * (dRed*dRed + dGreen*dGreen + dBlue*dBlue);
sigmAlpha += mAlpha;
}
p1 += Inc_ViewPixPtr (image1);
p2 += Inc_ViewPixPtr (image2);
}
}
in2_view = DestroyCacheView (in2_view);
in1_view = DestroyCacheView (in1_view);
if (sigmAlpha==0) {
// Every location has one or other pixel fully transparent.
*score = 0.0;
} else {
*score = sqrt (sigScore / sigmAlpha / 3.0);
}
return MagickTrue;
}
static Image *kcluster (
Image *image,
kclusterT * pkc,
ExceptionInfo *exception)
{
if (image->debug != MagickFalse)
(void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
if (! SetNoPalette (image, exception))
return NULL;
if (pkc->sdNorm) {
if (!CalcNormalise (pkc, image, exception)) {
fprintf (stderr, "kcluster: CalcNormalise failed\n");
return (Image *)NULL;
}
if (!ApplyNormalise (pkc, image, exception, MagickTrue)) {
fprintf (stderr, "kcluster: ApplyNormalise failed\n");
return (Image *)NULL;
}
}
if (!pkc->hasJump) {
image = kclusterOneK (image, pkc, exception);
} else {
if (pkc->verbose) {
fprintf (pkc->fh_data, "kcluster: jump %i to %i\n", pkc->jump0, pkc->jump1);
}
double jumpY; // The transform power
if (pkc->hasSetY) {
jumpY = pkc->jumpY;
} else {
double jumpP;
#if IMV6OR7==6
jumpP = IsGrayImage (image, exception) ? 1 : 3; // Number of dimensions.
#else
jumpP = IsImageGray (image) ? 1 : 3; // Number of dimensions.
#endif
jumpY = jumpP / 2.0;
}
if (pkc->verbose) {
fprintf (pkc->fh_data, "kcluster: jumpY = %.*g\n",
pkc->precision, jumpY);
}
double trans, J, maxJ = 0;
int kAtMax = 0;
double transPrev = 0.0;
// if jump0 > 1,
// start at jump0-1 but don't count first result for maxJ.
MagickBooleanType ignoreThis = MagickFalse;
int initK = pkc->jump0;
if (initK > 1) {
ignoreThis = MagickTrue;
initK--;
}
int k;
for (k = initK; k <= pkc->jump1; k++) {
// clone it, cluster it, compare it
pkc->kCols = k;
Image * jump_img = CloneImage(image, 0, 0, MagickTrue, exception);
if (jump_img == (Image *) NULL)
return MagickFalse;
jump_img = kclusterOneK (jump_img, pkc, exception);
double dist, distArg;
// FIXME: following ignores alpha.
if (!GetImageDistortion (
image, jump_img, RootMeanSquaredErrorMetric, &dist, exception))
{
return MagickFalse;
}
if (!CompareRmseAlpha (
image, jump_img, &distArg, exception))
{
return MagickFalse;
}
trans = (dist > 0) ? pow (dist, -jumpY) : 0;
J = trans - transPrev;
if (!ignoreThis) {
if (maxJ < J) {
maxJ = J;
kAtMax = pkc->kCols;
}
if (pkc->verbose) {
fprintf (pkc->fh_data, "kcluster: k=%i d=%.*g dArg=%.*g trans=%.*g J=%.*g\n",
pkc->kCols,
pkc->precision, dist,
pkc->precision, distArg,
pkc->precision, trans,
pkc->precision, J);
}
}
transPrev = trans;
ignoreThis = MagickFalse;
DestroyImage (jump_img);
}
if (pkc->verbose) {
fprintf (pkc->fh_data, "kcluster: maxJ=%.*g at k=%i\n",
pkc->precision, maxJ,
kAtMax);
}
if (kAtMax == 0) kAtMax = 1;
// FIXME: write this to attribute?
pkc->kCols = kAtMax;
image = kclusterOneK (image, pkc, exception);
}
if (pkc->sdNorm) {
if (!ApplyNormalise (pkc, image, exception, MagickFalse)) {
fprintf (stderr, "kcluster: ApplyNormalise failed\n");
return MagickFalse;
}
}
return image;
}
ModuleExport size_t kclusterImage (
Image **images,
const int argc,
const char **argv,
ExceptionInfo *exception)
{
Image
*image,
*new_image;
MagickBooleanType
status;
kclusterT pkc;
assert(images != (Image **) NULL);
assert(*images != (Image *) NULL);
assert((*images)->signature == MAGICK_CORE_SIG);
pkc.precision = GetMagickPrecision();
status = menu (argc, argv, &pkc);
if (status == MagickFalse)
return (-1);
if (!pkc.random_info) InitRand (&pkc);
if (pkc.init == iFromList) {
int ListLen = (int)GetImageListLength(*images);
if (ListLen < 2) {
fprintf (stderr, "kcluster: initialize FromList needs at least 2 images\n");
return (-1);
}
}
for (image=(*images); image != (Image *) NULL; image=GetNextImageInList(image))
{
Image * next = GetNextImageInList(image);
if (next == NULL && pkc.init == iFromList) {
//fprintf (stderr, "discarding map image\n");
DeleteImageFromList (&image);
} else {
new_image = kcluster (image, &pkc, exception);
if (!new_image) return (-1);
if (new_image != image) {
ReplaceImageInList(&image,new_image);
}
*images=GetFirstImageInList(image);
}
}
DeInitRand (&pkc);
return(MagickImageFilterSignature);
}
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <ctype.h>
#include "vsn_defines.h"
#include "centsmcrop.inc"
typedef struct {
MagickBooleanType
verbose;
} cscT;
static void usage (void)
{
printf ("Usage: -process 'centsmcrop [OPTION]...'\n");
printf ("Crops both images to dimensions of the smallest.\n");
printf ("\n");
printf (" v, verbose write text information to stdout\n");
printf ("\n");
}
static MagickBooleanType IsArg (char * pa, char * ShtOpt, char * LongOpt)
{
if ((LocaleCompare(pa, ShtOpt)==0) || (LocaleCompare(pa, LongOpt)==0))
return MagickTrue;
return MagickFalse;
}
static MagickBooleanType menu(
const int argc,
const char **argv,
cscT * pCsc
)
// Returns MagickTrue if okay.
{
int
i;
MagickBooleanType
status;
status = MagickTrue;
pCsc->verbose = MagickFalse;
for (i=0; i < argc; i++) {
char * pa = (char *)argv[i];
if (IsArg (pa, "v", "verbose")==MagickTrue) {
pCsc->verbose = MagickTrue;
} else {
fprintf (stderr, "centsmcrop: ERROR: unknown option [%s]\n", pa);
status = MagickFalse;
}
}
if (pCsc->verbose) {
fprintf (stderr, "centsmcrop options:");
if (pCsc->verbose) fprintf (stderr, " verbose");
fprintf (stderr, "\n");
}
if (status == MagickFalse)
usage ();
return (status);
}
ModuleExport size_t centsmcropImage(Image **images,const int argc,
const char **argv,ExceptionInfo *exception)
{
Image
*image1,
*image2;
cscT
csc;
assert(images != (Image **) NULL);
assert(*images != (Image *) NULL);
assert((*images)->signature == MAGICK_CORE_SIG);
// Replace each pair of images with atan2(u,v).
// The images must be the same size.
int ListLen = (int)GetImageListLength(*images);
if (ListLen !=2) {
fprintf (stderr, "centsmcrop needs 2 images\n");
return (-1);
}
MagickBooleanType status = menu (argc, argv, &csc);
if (status == MagickFalse)
return (-1);
image1 = (*images);
image2 = GetNextImageInList (image1);
if (!CentralSmallestCrop (&image1, &image2, csc.verbose, exception))
return -1;
// Replace messes up the images pointer. Make it good:
*images = GetFirstImageInList (image1);
return (MagickImageFilterSignature);
}
#ifndef CENTSMCROP_INC
#define CENTSMCROP_INC
#include "resetpage.inc"
#include "cropchk.inc"
static MagickBooleanType CentralSmallestCrop (
Image ** iA,
Image ** iB,
MagickBooleanType verbose,
ExceptionInfo *exception)
// Returns both images cropped, if required, to same dimensions,
// being the smallest of the inputs in each direction.
{
ssize_t smX = (*iA)->columns;
if (smX > (*iB)->columns) smX = (*iB)->columns;
ssize_t smY = (*iA)->rows;
if (smY > (*iB)->rows) smY = (*iB)->rows;
if (verbose) fprintf (stderr,
"CentralSmallestCrop: %lix%li %lix%li %lix%li\n",
(*iA)->columns, (*iA)->rows,
(*iB)->columns, (*iB)->rows,
smX, smY);
if ((*iA)->columns > smX || (*iA)->rows > smY) {
RectangleInfo crpRect;
crpRect.width = smX;
crpRect.height = smY;
crpRect.x = ((*iA)->columns - smX) / 2;
crpRect.y = ((*iA)->rows - smY) / 2;
ResetPage (*iA);
Image * smA = CropCheck (*iA, &crpRect, exception);
if (!smA) return MagickFalse;
ResetPage (smA);
ReplaceImageInList (iA, smA);
}
if ((*iB)->columns > smX || (*iB)->rows > smY) {
RectangleInfo crpRect;
crpRect.width = smX;
crpRect.height = smY;
crpRect.x = ((*iB)->columns - smX) / 2;
crpRect.y = ((*iB)->rows - smY) / 2;
ResetPage (*iB);
Image * smB = CropCheck (*iB, &crpRect, exception);
if (!smB) return MagickFalse;
ResetPage (smB);
ReplaceImageInList (iB, smB);
}
return MagickTrue;
}
#endif
/* Updated:
5-April-2018 for v7.0.7-28, and correct intensity bug.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <math.h>
#include <ctype.h>
#include "vsn_defines.h"
#include "findsinks.inc"
typedef struct {
MagickBooleanType
verbose,
findPeaks;
} findsinksT;
#define VERSION "findsinks v1.0 Copyright (c) 2017 Alan Gibson"
static void usage (void)
{
printf ("Usage: -process 'findsinks [OPTION]...'\n");
printf ("Find sinks or peaks.\n");
printf ("\n");
printf (" p, findPeaks find peaks instead of sinks\n");
printf (" v, verbose write text information to stdout\n");
printf (" version write version information to stdout\n");
printf ("\n");
}
static MagickBooleanType IsArg (char * pa, char * ShtOpt, char * LongOpt)
{
// LocaleCompare is not case-sensitive,
// so we use strcmp for the short option.
if ((strcmp(pa, ShtOpt)==0) || (LocaleCompare(pa, LongOpt)==0))
return MagickTrue;
return MagickFalse;
}
#define NEXTARG \
if (i < argc-1) i++; \
else { \
fprintf (stderr, "findsinks: ERROR: [%s] needs argument\n", pa); \
status = MagickFalse; \
}
static MagickBooleanType menu(
const int argc,
const char **argv,
findsinksT * pfs
)
// Returns MagickTrue if okay.
{
int
i;
MagickBooleanType
status = MagickTrue;
pfs->findPeaks = MagickFalse;
pfs->verbose = MagickFalse;
for (i=0; i < argc; i++) {
char * pa = (char *)argv[i];
if (IsArg (pa, "p", "findPeaks")==MagickTrue) {
pfs->findPeaks = MagickTrue;
} else if (IsArg (pa, "v", "verbose")==MagickTrue) {
pfs->verbose = MagickTrue;
} else if (IsArg (pa, "version", "version")==MagickTrue) {
fprintf (stdout, "%s\n", VERSION);
} else {
fprintf (stderr, "findsinks: ERROR: unknown option [%s]\n", pa);
status = MagickFalse;
}
}
if (pfs->verbose) {
fprintf (stderr, "findsinks options: ");
if (pfs->findPeaks) fprintf (stderr, " findPeaks");
if (pfs->verbose) fprintf (stderr, " verbose");
fprintf (stderr, "\n");
}
if (status == MagickFalse)
usage ();
return (status);
}
static MagickBooleanType SetImgGray (
Image *image,
ExceptionInfo *exception)
// Sets R,G and B channels to the pixel intensity.
// Does not change alpha.
// Returns whether okay.
{
MagickBooleanType okay = MagickTrue;
ssize_t y;
CacheView * in_view = AcquireAuthenticCacheView (image, exception);
for (y = 0; y < image->rows; y++) {
if (!okay) continue;
VIEW_PIX_PTR *p = GetCacheViewAuthenticPixels(
in_view,0,y,image->columns,1,exception);
if (!p) {okay = MagickFalse; continue; }
ssize_t x;
for (x = 0; x < image->columns; x++) {
MagickRealType v = GetPixelIntensity (image, p);
SET_PIXEL_RED (image, v, p);
SET_PIXEL_GREEN (image, v, p);
SET_PIXEL_BLUE (image, v, p);
p += Inc_ViewPixPtr (image);
}
if (SyncCacheViewAuthenticPixels(in_view,exception) == MagickFalse)
okay = MagickFalse;
}
in_view = DestroyCacheView (in_view);
return okay;
}
// The next function corresponds in style to functions in transform.c
// It takes one image, and returns an image.
//
static Image *findsinks (
Image *image,
findsinksT * pfs,
ExceptionInfo *exception)
{
if (image->debug != MagickFalse)
(void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
if (pfs->verbose) {
fprintf (stderr, "findsinks: Input image [%s] %ix%i depth is %i\n",
image->filename,
(int)image->columns, (int)image->rows,
(int)image->depth);
}
// Clone, make grayscale. Perhaps negate.
Image * new_img = CloneImage (image, 0, 0, MagickTrue, exception);
if (!new_img) return NULL;
//#if IMV6OR7==6
// if (!GrayscaleImage (new_img, MSPixelIntensityMethod)) return NULL;
//#else
// if (!GrayscaleImage (new_img, MSPixelIntensityMethod, exception)) return NULL;
//#endif
SetImgGray (new_img, exception);
Quantum outNotFnd;
if (pfs->findPeaks) {
#if IMV6OR7==6
if (!NegateImage (new_img, MagickFalse)) return NULL;
#else
ChannelType channel_mask = SetImageChannelMask (
new_img,
RedChannel | GreenChannel | BlueChannel);
if (!NegateImage (new_img, MagickFalse, exception)) return NULL;
SetImageChannelMask (new_img, channel_mask);
#endif
outNotFnd = 0;
} else {
outNotFnd = QuantumRange;
}
Image * new_img2 = LightenNonSinks (new_img, pfs->verbose, exception);
if (!new_img2) return NULL;
DestroyImage (new_img);
// Where new_img2 has blue at QuantumRange (or zero),
// make image pixel white (or black).
// Put another way:
// where new_img2 has blue at not QuantumRange (or not zero),
// copy colours from image.
CacheView * in_view = AcquireVirtualCacheView (image, exception);
CacheView * out_view = AcquireAuthenticCacheView (new_img2, exception);
MagickBooleanType okay = MagickTrue;
ssize_t y;
for (y = 0; y < image->rows; y++) {
if (!okay) continue;
VIEW_PIX_PTR *q = GetCacheViewAuthenticPixels(
out_view,0,y,image->columns,1,exception);
if (!q) {okay = MagickFalse; continue; }
const VIEW_PIX_PTR *pIn = GetCacheViewVirtualPixels(
in_view,0,y,image->columns,1,exception);
if (!pIn) {okay = MagickFalse; continue; }
ssize_t x;
for (x = 0; x < image->columns; x++) {
if ( GET_PIXEL_BLUE (new_img2, q) == QuantumRange) {
SET_PIXEL_RED (new_img2, outNotFnd, q);
SET_PIXEL_GREEN (new_img2, outNotFnd, q);
SET_PIXEL_BLUE (new_img2, outNotFnd, q);
} else {
SET_PIXEL_RED (new_img2, GET_PIXEL_RED(image,pIn), q);
SET_PIXEL_GREEN (new_img2, GET_PIXEL_GREEN(image,pIn), q);
SET_PIXEL_BLUE (new_img2, GET_PIXEL_BLUE(image,pIn), q);
}
SET_PIXEL_ALPHA (new_img2, GET_PIXEL_ALPHA(image,pIn), q);
q += Inc_ViewPixPtr (new_img2);
pIn += Inc_ViewPixPtr (image);
}
if (SyncCacheViewAuthenticPixels(out_view,exception) == MagickFalse)
okay = MagickFalse;
}
out_view = DestroyCacheView (out_view);
in_view = DestroyCacheView (in_view);
return (new_img2);
}
ModuleExport size_t findsinksImage (
Image **images,
const int argc,
const char **argv,
ExceptionInfo *exception)
{
Image
*image,
*new_image;
MagickBooleanType
status;
findsinksT s3d;
assert(images != (Image **) NULL);
assert(*images != (Image *) NULL);
assert((*images)->signature == MAGICK_CORE_SIG);
status = menu (argc, argv, &s3d);
if (status == MagickFalse)
return (-1);
for (image=(*images); image != (Image *) NULL; image=GetNextImageInList(image))
{
new_image = findsinks (image, &s3d, exception);
ReplaceImageInList(&image,new_image);
*images=GetFirstImageInList(image);
}
return(MagickImageFilterSignature);
}
/* Updated:
3-April-2018 for v7.0.7-28
5-April-2018 bug: using ip when should be oq
*/
#ifndef FINDSINKS_INC
#define FINDSINKS_INC
static Image * LightenNonSinks (
Image *image,
MagickBooleanType verbose,
ExceptionInfo *exception)
{
// In blue channel, makes non-sinks QuantumRange.
// A "sink" is a pixel,
// or connected group of pixels with the same value,
// with all 8 neighbours lighter than it.
// Thus, every pixel (or connected group) with a darker neighbour
// is made white.
#if IMV6OR7==6
//SetPixelCacheVirtualMethod (image, EdgeVirtualPixelMethod);
SetImageVirtualPixelMethod (image, EdgeVirtualPixelMethod);
#else
//SetPixelCacheVirtualMethod (image,
// EdgeVirtualPixelMethod, exception);
SetImageVirtualPixelMethod (image, EdgeVirtualPixelMethod, exception);
#endif
Image * new_img = CloneImage (image, 0, 0, MagickTrue, exception);
if (!new_img) return NULL;
CacheView * in_view = AcquireVirtualCacheView (image, exception);
CacheView * out_view = AcquireAuthenticCacheView (new_img, exception);
MagickBooleanType okay = MagickTrue;
const ssize_t dy1 = image->columns+2;
const ssize_t dy2 = 2 * (image->columns+2);
ssize_t y;
const int ip = Inc_ViewPixPtr (image);
const int oq = Inc_ViewPixPtr (new_img);
for (y = 0; y < image->rows; y++) {
if (!okay) continue;
VIEW_PIX_PTR *q = GetCacheViewAuthenticPixels(
out_view,0,y,image->columns,1,exception);
if (!q) {okay = MagickFalse; continue; }
const VIEW_PIX_PTR *pIn = GetCacheViewVirtualPixels(
in_view,-1,y-1,image->columns+2,3,exception);
if (!pIn) {okay = MagickFalse; continue; }
ssize_t x;
for (x = 0; x < image->columns; x++) {
double score = GET_PIXEL_BLUE (new_img, q);
if (score < QuantumRange) {
if (
score > GET_PIXEL_BLUE (image, pIn + (x+0)*ip)
|| score > GET_PIXEL_BLUE (image, pIn + (x+1)*ip)
|| score > GET_PIXEL_BLUE (image, pIn + (x+2)*ip)
|| score > GET_PIXEL_BLUE (image, pIn + (dy1+x+0)*ip)
|| score > GET_PIXEL_BLUE (image, pIn + (dy1+x+1)*ip)
|| score > GET_PIXEL_BLUE (image, pIn + (dy1+x+2)*ip)
|| score > GET_PIXEL_BLUE (image, pIn + (dy2+x+0)*ip)
|| score > GET_PIXEL_BLUE (image, pIn + (dy2+x+1)*ip)
|| score > GET_PIXEL_BLUE (image, pIn + (dy2+x+2)*ip)
)
{
SET_PIXEL_BLUE (new_img, QuantumRange, q);
// If any pixels to the right have the same score,
// set them to QuantumRange.
if (x < image->columns-1) {
ssize_t x2;
VIEW_PIX_PTR *q2 = q + ip;
for (x2=x+1; x2 < image->columns; x2++) {
double score2 = GET_PIXEL_BLUE (new_img, q2);
if (score2 != score) break;
SET_PIXEL_BLUE (new_img, QuantumRange, q2);
q2 += ip;
}
}
}
}
// (pIn is _not_ incremented)
q += oq;
} // for x
if (SyncCacheViewAuthenticPixels(out_view,exception) == MagickFalse)
okay = MagickFalse;
}
if (!okay) return NULL;
in_view = DestroyCacheView (in_view);
// To get all the pixels on a terrace, we iterate:
// every pixel that is next to a white pixel,
// where that white pixel was the same colour as this pixel,
// is made white.
MagickBooleanType anyChanged = MagickTrue;
int nIter = 0;
while (anyChanged) {
anyChanged = MagickFalse;
CacheView * out_view_rdm = AcquireVirtualCacheView (new_img, exception);
CacheView * out_view_rdp = AcquireVirtualCacheView (new_img, exception);
CacheView * in_view0 = AcquireVirtualCacheView (image, exception);
CacheView * in_view1 = AcquireVirtualCacheView (image, exception);
CacheView * in_view2 = AcquireVirtualCacheView (image, exception);
for (y = 0; y < image->rows; y++) {
if (!okay) continue;
VIEW_PIX_PTR *q = GetCacheViewAuthenticPixels(
out_view,0,y,image->columns,1,exception);
if (!q) {okay = MagickFalse; continue; }
const VIEW_PIX_PTR *qm1 = GetCacheViewVirtualPixels(
out_view_rdm,-1,y-1,image->columns+2,1,exception);
if (!qm1) {okay = MagickFalse; continue; }
const VIEW_PIX_PTR *qp1 = GetCacheViewVirtualPixels(
out_view_rdp,-1,y+1,image->columns+2,1,exception);
if (!qp1) {okay = MagickFalse; continue; }
qm1 += oq;
qp1 += oq;
const VIEW_PIX_PTR *p0 = GetCacheViewVirtualPixels(
in_view0,-1,y-1,image->columns+2,1,exception);
if (!p0) {okay = MagickFalse; continue; }
const VIEW_PIX_PTR *p1 = GetCacheViewVirtualPixels(
in_view1,-1,y,image->columns+2,1,exception);
if (!p1) {okay = MagickFalse; continue; }
const VIEW_PIX_PTR *p2 = GetCacheViewVirtualPixels(
in_view2,-1,y+1,image->columns+2,1,exception);
if (!p2) {okay = MagickFalse; continue; }
p0 += ip;
p1 += ip;
p2 += ip;
ssize_t x;
for (x = 0; x < image->columns; x++) {
double score = GET_PIXEL_BLUE (new_img, q);
if (score < QuantumRange) {
// FIXME: for now,
// just the pixels directly above, left, right and down.
if (
( score == GET_PIXEL_BLUE (image, p0)
&& GET_PIXEL_BLUE (new_img, qm1)==QuantumRange)
|| ( x > 0
&& score == GET_PIXEL_BLUE (image, p1-ip)
&& GET_PIXEL_BLUE (new_img, q-ip)==QuantumRange)
|| ( x < image->columns-1
&& score == GET_PIXEL_BLUE (image, p1+ip)
&& GET_PIXEL_BLUE (new_img, q+ip)==QuantumRange)
|| ( score == GET_PIXEL_BLUE (image, p2)
&& GET_PIXEL_BLUE (new_img, qp1)==QuantumRange)
)
{
SET_PIXEL_BLUE (new_img, QuantumRange, q);
anyChanged = MagickTrue;
}
}
q += oq;
qm1 += oq;
qp1 += oq;
p0 += ip;
p1 += ip;
p2 += ip;
}
if (SyncCacheViewAuthenticPixels(out_view,exception) == MagickFalse)
okay = MagickFalse;
}
if (!okay) break;
in_view2 = DestroyCacheView (in_view2);
in_view1 = DestroyCacheView (in_view1);
in_view0 = DestroyCacheView (in_view0);
out_view_rdp = DestroyCacheView (out_view_rdp);
out_view_rdm = DestroyCacheView (out_view_rdm);
nIter++;
} // while
if (!okay) return NULL;
out_view = DestroyCacheView (out_view);
if (verbose) fprintf (stderr, "LightenNonSinks: nIter %i\n", nIter);
return new_img;
}
#endif
/* Updated:
3-April-2018 for v7.0.7-28
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include "vsn_defines.h"
#include "avgconcrings.inc"
static void usage (void)
{
printf ("Usage: -process 'avgconcrings [OPTION]...'\n");
printf ("Finds mean of concentric rings for a range of scales.\n");
printf ("\n");
printf (" s0, minScale number minimum scale\n");
printf (" s1, maxScale number maximum scale\n");
printf (" n, nScales integer number of scales\n");
printf (" m, method string 'slow' or 'quick'\n");
printf (" v, verbose write text information to stdout\n");
printf (" v2, verbose2 more text information to stdout\n");
printf ("\nOutput has radii on x-axis, with centre at left, scales on y-axis, lowest at top.\n");
printf ("\n");
}
static MagickBooleanType IsArg (char * pa, char * ShtOpt, char * LongOpt)
{
if ((LocaleCompare(pa, ShtOpt)==0) || (LocaleCompare(pa, LongOpt)==0))
return MagickTrue;
return MagickFalse;
}
#define NEXTARG \
if (i < argc-1) i++; \
else { \
fprintf (stderr, "paintpatches: ERROR: [%s] needs argument\n", pa); \
status = MagickFalse; \
}
static MagickBooleanType menu(
const int argc,
const char **argv,
ConcRingsT * pcr
)
// Returns MagickTrue if okay.
{
int
i;
MagickBooleanType
status;
status = MagickTrue;
for (i=0; i < argc; i++) {
char * pa = (char *)argv[i];
if (IsArg (pa, "s0", "minScale")==MagickTrue) {
NEXTARG;
pcr->minScale = atof(argv[i]);
} else if (IsArg (pa, "s1", "maxScale")==MagickTrue) {
NEXTARG;
pcr->maxScale = atof(argv[i]);
} else if (IsArg (pa, "n", "nScales")==MagickTrue) {
NEXTARG;
pcr->nScales = atoi(argv[i]);
} else if (IsArg (pa, "m", "method")==MagickTrue) {
NEXTARG;
if (LocaleCompare(argv[i], "slow")==0) pcr->ConcRingsMethod = crmSlow;
else if (LocaleCompare(argv[i], "quick")==0) pcr->ConcRingsMethod = crmQuick;
else {
fprintf (stderr, "avgconcrings: ERROR: unknown method [%s]\n", argv[i]);
status = MagickFalse;
}
} else if (IsArg (pa, "v", "verbose")==MagickTrue) {
pcr->verbose = 1;
} else if (IsArg (pa, "v2", "verbose2")==MagickTrue) {
pcr->verbose = 2;
} else {
fprintf (stderr, "avgconcrings: ERROR: unknown option\n");
status = MagickFalse;
}
}
if (pcr->verbose) {
fprintf (stderr, "avgconcrings options:");
fprintf (stderr, " maxScale %g", pcr->maxScale);
fprintf (stderr, " minScale %g", pcr->minScale);
fprintf (stderr, " nScales %i", pcr->nScales);
fprintf (stderr, " method ");
switch (pcr->ConcRingsMethod) {
case crmSlow: fprintf (stderr, "slow"); break;
case crmQuick: fprintf (stderr, "quick"); break;
}
if (pcr->verbose > 1) fprintf (stderr, " verbose%i ", pcr->verbose);
else if (pcr->verbose) fprintf (stderr, " verbose ");
fprintf (stderr, "\n");
}
if (status == MagickFalse)
usage ();
return (status);
}
ModuleExport size_t avgconcringsImage(Image **images,const int argc,
const char **argv,ExceptionInfo *exception)
{
Image
*image,
*new_image;
MagickBooleanType
status;
ConcRingsT
cr;
assert(images != (Image **) NULL);
assert(*images != (Image *) NULL);
assert((*images)->signature == MAGICK_CORE_SIG);
InitConcRings (&cr);
status = menu (argc, argv, &cr);
if (status == MagickFalse)
return (-1);
for (image=(*images); image != (Image *) NULL; image=GetNextImageInList(image))
{
new_image = avgconcrings (image, &cr, exception);
if (new_image == (Image *) NULL) return (-1);
ReplaceImageInList(&image,new_image);
*images=GetFirstImageInList(image);
ReInitConcRings (&cr);
}
DeInitConcRings (&cr);
return(MagickImageFilterSignature);
}
/* Updated:
3-April-2018 for v7.0.7-28
5-April-2018 improved error-reporting;
explicit channel copy from scal_img to new_img.
*/
#ifndef AVGCONCRINGS_INC
#define AVGCONCRINGS_INC
#include "rmsealpha.inc"
#include "resetpage.inc"
#include "trimone.inc"
typedef enum {
crmSlow,
crmQuick
} ConcRingsMethodT;
typedef struct {
int
verbose;
double
minScale,
maxScale;
int
nScales;
ConcRingsMethodT
ConcRingsMethod;
// Used internally:
RmseAlphaT
ra;
// Calculated:
double
scaleFact; // A measure of error.
double
nScalesM1;
} ConcRingsT;
static void InitConcRings (ConcRingsT * p)
{
p->verbose = 0;
p->minScale = 0.5;
p->maxScale = 2.0;
p->nScales = 20;
p->ConcRingsMethod = crmQuick;
p->scaleFact = 0;
p->nScalesM1 = (double)(p->nScales-1);
InitRmseAlpha (&p->ra);
}
static void ReInitConcRings (ConcRingsT * p)
{
ReInitRmseAlpha (&p->ra);
}
static void DeInitConcRings (ConcRingsT * p)
{
DeInitRmseAlpha (&p->ra);
}
static double inline ConcRingsScale (
ConcRingsT * pcr,
ssize_t y
)
// Returns the scale at a given y-coordinate.
{
if (pcr->nScalesM1==0) return pcr->maxScale;
return pcr->minScale *
pow (pcr->maxScale/pcr->minScale, y / pcr->nScalesM1);
}
// The next function corresponds in style to functions in transform.c
// It takes one image, and returns an image.
//
static Image *avgconcrings (
Image *image,
ConcRingsT * pcr,
ExceptionInfo *exception)
{
Image * cln = CloneImage (image, 0, 0, MagickTrue, exception);
if (!cln) return NULL;
//#if IMV6OR7==6
// SetPixelCacheVirtualMethod (cln, TransparentVirtualPixelMethod);
// SetImageAlphaChannel (cln, ActivateAlphaChannel);
//#else
// SetPixelCacheVirtualMethod (cln,
// TransparentVirtualPixelMethod, exception);
// SetImageAlphaChannel (cln, ActivateAlphaChannel, exception);
//#endif
VIRT_NONE(cln, exception);
MagickBooleanType bestfit = MagickTrue;
double minusOne=-1.0;
Image *tmp_img = DistortImage (cln, DePolarDistortion,
1, &minusOne, bestfit, exception);
if (!tmp_img) return NULL;
if (pcr->verbose) fprintf (stderr,
"avgconcrings: tmp_img %lix%li\n",
tmp_img->columns, tmp_img->rows);
ssize_t unscaledHeight = tmp_img->rows;
tmp_img = DestroyImage (tmp_img);
if (pcr->verbose) fprintf (stderr,
"avgconcrings: %lix%li unscaled %li\n",
cln->columns, cln->rows, unscaledHeight);
pcr->nScalesM1 = (double)(pcr->nScales-1);
if (pcr->nScalesM1 == 0) {
pcr->scaleFact = 0;
} else {
pcr->scaleFact = pow (pcr->maxScale/pcr->minScale, 1.0 / pcr->nScalesM1);
}
// Make an image for output: unscaledHeight x nScales, transparent black.
Image * new_img = CloneImage(cln, unscaledHeight, pcr->nScales,
MagickTrue, exception);
if (!new_img) return NULL;
SetAllBlack (new_img, exception);
#if IMV6OR7==6
SetImageAlphaChannel (new_img, TransparentAlphaChannel);
#else
SetImageAlphaChannel (new_img, TransparentAlphaChannel, exception);
#endif
double scaleArray[2];
scaleArray[0] = 1.0; // Scale factor.
scaleArray[1] = 0.0; // Rotation angle.
Image *depol_max2_img = NULL;
if (pcr->ConcRingsMethod == crmQuick) {
// For the quick method:
// Make a single-column image at the maximum scale,
// and resize it down for the others.
scaleArray[0] = pcr->maxScale;
Image *max_img = DistortImage (cln, ScaleRotateTranslateDistortion,
2, scaleArray, bestfit, exception);
if (!max_img) {
fprintf (stderr, "avgconcrings: DistortImage SRT max_img failed\n");
return NULL;
}
ResetPage (max_img);
if (!TrimOne (&max_img, exception)) return NULL;
if (pcr->verbose) fprintf (stderr,
"avgconcrings: max_img %lix%li\n",
max_img->columns, max_img->rows);
Image *depol_max_img = DistortImage (max_img, DePolarDistortion,
1, &minusOne, bestfit, exception);
if (!depol_max_img) return NULL;
DestroyImage (max_img);
// Scale to 1 column.
depol_max2_img = ScaleImage (depol_max_img, 1, depol_max_img->rows, exception);
if (!depol_max2_img) return NULL;
if (pcr->verbose) fprintf (stderr,
"avgconcrings: depol_max2_img %lix%li\n",
depol_max2_img->columns, depol_max2_img->rows);
DestroyImage (depol_max_img);
}
MagickBooleanType okay = MagickTrue;
int i;
for (i=0; i < pcr->nScales; i++) {
if (!okay) continue;
scaleArray[0] = ConcRingsScale (pcr, i);
if (pcr->verbose > 1) fprintf (stderr,
"avgconcrings: %i %g\n", i, scaleArray[0]);
Image *scal_img = NULL;
if (pcr->ConcRingsMethod == crmSlow) {
Image *tmp_img = DistortImage (cln, ScaleRotateTranslateDistortion,
2, scaleArray, bestfit, exception);
if (!tmp_img) {okay = MagickFalse; continue;}
ResetPage (tmp_img);
if (!TrimOne (&tmp_img, exception))
{okay = MagickFalse; continue;};
if (pcr->verbose > 1) fprintf (stderr,
"avgconcrings: tmp_img %lix%li\n",
tmp_img->columns, tmp_img->rows);
Image *depol_img = DistortImage (tmp_img, DePolarDistortion,
1, &minusOne, bestfit, exception);
if (!depol_img) {okay = MagickFalse; continue;}
tmp_img = DestroyImage (tmp_img);
if (pcr->verbose > 1) fprintf (stderr,
"avgconcrings: depol_img %lix%li\n",
depol_img->columns, depol_img->rows);
scal_img = ScaleImage (depol_img, 1, depol_img->rows, exception);
if (!scal_img) {okay = MagickFalse; continue;}
depol_img = DestroyImage (depol_img);
} else if (pcr->ConcRingsMethod == crmQuick) {
// Alternative: make scal_img by scaling depol_max2_img down.
scaleArray[0] /= pcr->maxScale;
#if IMV6OR7==6
SetImageVirtualPixelMethod (depol_max2_img, EdgeVirtualPixelMethod);\
//SetPixelCacheVirtualMethod (depol_max2_img, EdgeVirtualPixelMethod);
#else
SetImageVirtualPixelMethod (depol_max2_img, EdgeVirtualPixelMethod, exception);\
//SetPixelCacheVirtualMethod (depol_max2_img,
// EdgeVirtualPixelMethod, exception);
#endif
scal_img = DistortImage (depol_max2_img, ScaleRotateTranslateDistortion,
2, scaleArray, bestfit, exception);
if (!scal_img) {
fprintf (stderr, "avgconcrings DistortImage scal_img failed\n");
okay = MagickFalse;
continue;
}
ResetPage (scal_img);
if (!TrimOne (&scal_img, exception)) {
fprintf (stderr, "avgconcrings: TrimOne scal_img failed\n");
okay = MagickFalse;
continue;
}
}
if (pcr->verbose > 1) fprintf (stderr,
"avgconcrings: scal_img %lix%li\n",
scal_img->columns, scal_img->rows);
// Copy pixels from scal_img to new_img.
CacheView * scal_view = AcquireVirtualCacheView (scal_img, exception);
CacheView * new_view = AcquireAuthenticCacheView (new_img, exception);
const VIEW_PIX_PTR *sp = GetCacheViewVirtualPixels (
scal_view,0,0,1,scal_img->rows,exception);
if (!sp) {okay = MagickFalse; continue;}
VIEW_PIX_PTR *np = GetCacheViewAuthenticPixels (
new_view,0,i,new_img->columns,1,exception);
if (!np) {okay = MagickFalse; continue;}
ssize_t maxN = new_img->columns;
if (maxN > scal_img->rows) maxN = scal_img->rows;
ssize_t n;
for (n=0; n < maxN; n++) {
//*np = *sp;
SET_PIXEL_RED (new_img, GET_PIXEL_RED(scal_img, sp), np);
SET_PIXEL_GREEN (new_img, GET_PIXEL_GREEN(scal_img, sp), np);
SET_PIXEL_BLUE (new_img, GET_PIXEL_BLUE(scal_img, sp), np);
SET_PIXEL_ALPHA (new_img, GET_PIXEL_ALPHA(scal_img, sp), np);
sp += Inc_ViewPixPtr (scal_img);
np += Inc_ViewPixPtr (new_img);
}
new_view = DestroyCacheView (new_view);
scal_view = DestroyCacheView (scal_view);
scal_img = DestroyImage (scal_img);
}
if (!okay) {
fprintf (stderr, "avgconcrings not okay\n");
return NULL;
}
if (depol_max2_img) DestroyImage (depol_max2_img);
cln = DestroyImage (cln);
return new_img;
}
#endif // not AVGCONCRINGS_INC
#ifndef WRITEFRAME_INC
#define WRITEFRAME_INC
static MagickBooleanType WriteFrame (
char *FileName,
Image * img,
int frame_num,
ExceptionInfo *exception)
{
ImageInfo
*ii;
MagickBooleanType
okay;
Image
*copy_img;
ii = AcquireImageInfo ();
copy_img = CloneImage(img, 0, 0, MagickTrue, exception);
if (!copy_img) return(MagickFalse); // FIXME: raise error
copy_img->scene = frame_num;
CopyMagickString (copy_img->filename, FileName, MaxTextExtent);
okay = WRITEIMAGE(ii, copy_img, exception);
DestroyImageList(copy_img);
ii = DestroyImageInfo (ii);
return okay;
}
#endif
/* Updated:
3-April-2018 for v7.0.7-28
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include "vsn_defines.h"
#include "rmsealpha.inc"
#include "absdispmap.inc"
#include "whatrot.inc"
#define VERSION "whatrot v1.0 Copyright (c) 2017 Alan Gibson"
typedef struct {
WhatRotT
wr;
int
verbose;
Image
*DispMap;
MagickBooleanType
doRotate,
UseDispMap,
approxOnly,
debug;
int
precision;
double
tolerance;
FILE *
fh_data;
} SubSrchT;
static void InitSubSrch (SubSrchT * pss)
{
InitWhatRot (&pss->wr);
pss->verbose = 0;
pss->DispMap = NULL;
pss->doRotate = MagickTrue;
pss->UseDispMap = MagickTrue;
pss->approxOnly = MagickFalse;
pss->precision = 6;
pss->tolerance = 0.01;
pss->fh_data = stderr;
pss->debug = MagickFalse;
}
static void DeInitSubSrch (SubSrchT * pss)
{
if (pss->DispMap) pss->DispMap = DestroyImage (pss->DispMap);
DeInitWhatRot (&pss->wr);
}
static void usage (void)
{
printf ("Usage: -process 'whatrot [OPTION]...'\n");
printf ("Find rotation by unrolling and golden section methods.\n");
printf ("\n");
printf (" a, approxOnly use only the first (approximate) method\n");
printf (" t, tolerance number for golden section method\n");
printf (" x, noRotate don't replace images with rotation\n");
printf (" f, file string write data to file stream stdout or stderr\n");
printf (" d, debug create debugging images\n");
printf (" v, verbose write text information to stdout\n");
printf (" v2, verbose2 write more information to stdout\n");
printf (" v3, verbose3 yet more information to stdout\n");
printf (" version write version information to stdout\n");
printf ("\n");
}
// FIXME: also option to normalise mean and SD.
static MagickBooleanType IsArg (char * pa, char * ShtOpt, char * LongOpt)
{
if ((LocaleCompare(pa, ShtOpt)==0) || (LocaleCompare(pa, LongOpt)==0))
return MagickTrue;
return MagickFalse;
}
#define NEXTARG \
if (i < argc-1) i++; \
else { \
fprintf (stderr, "paintpatches: ERROR: [%s] needs argument\n", pa); \
status = MagickFalse; \
}
static MagickBooleanType menu(
const int argc,
const char **argv,
SubSrchT * pss
)
// Returns MagickTrue if okay.
{
int
i;
MagickBooleanType
status;
status = MagickTrue;
for (i=0; i < argc; i++) {
char * pa = (char *)argv[i];
if (IsArg (pa, "a", "approxOnly")==MagickTrue) {
pss->approxOnly = MagickTrue;
} else if (IsArg (pa, "x", "noRotate")==MagickTrue) {
pss->doRotate = MagickFalse;
} else if (IsArg (pa, "t", "tolerance")==MagickTrue) {
NEXTARG;
pss->tolerance = atof (argv[i]);
} else if (IsArg (pa, "f", "file")==MagickTrue) {
NEXTARG;
if (strcasecmp (argv[i], "stdout")==0) pss->fh_data = stdout;
else if (strcasecmp (argv[i], "stderr")==0) pss->fh_data = stderr;
else status = MagickFalse;
} else if (IsArg (pa, "d", "debug")==MagickTrue) {
pss->debug = MagickTrue;
} else if (IsArg (pa, "v", "verbose")==MagickTrue) {
pss->verbose = 1;
} else if (IsArg (pa, "v2", "verbose2")==MagickTrue) {
pss->verbose = 2;
} else if (IsArg (pa, "v3", "verbose3")==MagickTrue) {
pss->verbose = 3;
} else if (IsArg (pa, "version", "version")==MagickTrue) {
fprintf (stdout, "%s\n", VERSION);
} else {
fprintf (stderr, "whatrot: ERROR: unknown option\n");
status = MagickFalse;
}
}
if (pss->verbose) {
fprintf (stderr, "whatrot options:");
if (pss->approxOnly) fprintf (stderr, " approxOnly");
fprintf (stderr, " tolerance %g", pss->tolerance);
if (!pss->doRotate) fprintf (stderr, " noRotate");
if (pss->fh_data == stdout) fprintf (stderr, " file stdout");
if (pss->debug) fprintf (stderr, " debug");
if (pss->verbose > 1) fprintf (stderr, " verbose%i", pss->verbose);
else if (pss->verbose) fprintf (stderr, " verbose");
fprintf (stderr, "\n");
}
if (status == MagickFalse)
usage ();
return (status);
}
// The next function takes an image list,
// may modify it, and returns a status.
//
static MagickBooleanType whatrot (
Image **images,
SubSrchT * pss,
ExceptionInfo *exception)
{
Image * main_img = *images;
Image * sub_img = GetNextImageInList (main_img);
if (!sub_img) return MagickFalse;
/*==
if (sub_img->columns > main_img->columns ||
sub_img->rows > main_img->rows)
{
fprintf (stderr, "whatrot: subimage is larger than main\n");
return MagickFalse;
}
==*/
#if IMV6OR7==6
//SetPixelCacheVirtualMethod (main_img, TransparentVirtualPixelMethod);
SetImageVirtualPixelMethod (main_img, TransparentVirtualPixelMethod);
#else
//SetPixelCacheVirtualMethod (main_img,
// TransparentVirtualPixelMethod, exception);
SetImageVirtualPixelMethod (main_img, TransparentVirtualPixelMethod, exception);
#endif
// Make displacement map, and unroll it.
double minusOne=-1.0;
MagickBooleanType bestfit = MagickTrue;
Image * id_map = mIdentAbsDispMap (sub_img, exception);
#if IMV6OR7==6
SetImageAlphaChannel (id_map, OpaqueAlphaChannel);
#else
SetImageAlphaChannel (id_map, OpaqueAlphaChannel, exception);
#endif
pss->DispMap = DistortImage (id_map, DePolarDistortion,
1, &minusOne, bestfit, exception);
if (!pss->DispMap) return MagickFalse;
if (pss->verbose) fprintf (stderr, "whatrot: DispMap %lix%li\n",
pss->DispMap->columns, pss->DispMap->rows);
pss->wr.verbose = (pss->verbose <= 1) ? 0 : pss->verbose-1;
if (pss->debug) pss->wr.debug = "wr_dbg";
if (!CalcApproxRot (&pss->wr, main_img, sub_img, NULL, NULL, exception)) {
fprintf (stderr, "CalcApproxRot failed\n");
return MagickFalse;
}
if (pss->verbose) {
fprintf (stderr, "Approx ang=%.*g +/-%.*g score=%.*g\n",
pss->precision, pss->wr.angle,
pss->precision, pss->wr.plusOrMinus,
pss->precision, pss->wr.score);
}
if (!pss->approxOnly) {
// Save, in case GoldSect fails.
double saveAng = pss->wr.angle;
double savePorm = pss->wr.plusOrMinus;
double saveScore = pss->wr.score;
pss->wr.tolerance = pss->tolerance;
if (GoldSectRot (&pss->wr, main_img, sub_img, exception)) {
if (pss->verbose) fprintf (stderr,
"From gss: ang=%.*g +/-%.*g score=%.*g\n",
pss->precision, pss->wr.angle,
pss->precision, pss->wr.plusOrMinus,
pss->precision, pss->wr.score);
} else {
fprintf (stderr, "whatrot: GoldSectRot failed\n");
pss->wr.angle = saveAng;
pss->wr.plusOrMinus = savePorm;
pss->wr.score = saveScore;
}
}
fprintf (pss->fh_data,
"whatrot: ang=%.*g angPorm=%.*g angScore=%.*g\n",
pss->precision, pss->wr.angle,
pss->precision, pss->wr.plusOrMinus,
pss->precision, pss->wr.score);
if (pss->doRotate) {
MagickBooleanType bestfit = MagickTrue;
double scaleArray[2];
scaleArray[0] = 1.0; // Scale factor.
scaleArray[1] = pss->wr.angle; // Rotation angle.
//#if IMV6OR7==6
// SetPixelCacheVirtualMethod (sub_img, TransparentVirtualPixelMethod);
//#else
// SetPixelCacheVirtualMethod (sub_img,
// TransparentVirtualPixelMethod, exception);
//#endif
VIRT_NONE(sub_img, exception);
Image *rot_img = DistortImage (sub_img, ScaleRotateTranslateDistortion,
2, scaleArray, bestfit, exception);
if (!rot_img) return MagickFalse;
// We don't ResetPage (rot_img);
if (pss->verbose) fprintf (stderr,
"WhatRot: created image %lix%li %lix%li%+li%+li\n",
rot_img->columns, rot_img->rows,
rot_img->page.width, rot_img->page.height,
rot_img->page.x, rot_img->page.y);
DeleteImageFromList (&sub_img);
ReplaceImageInList (&main_img, rot_img);
// Replace messes up the images pointer. Make it good:
*images = GetFirstImageInList (main_img);
}
return MagickTrue;
}
ModuleExport size_t whatrotImage(Image **images,const int argc,
const char **argv,ExceptionInfo *exception)
{
MagickBooleanType
status;
SubSrchT
ss;
assert(images != (Image **) NULL);
assert(*images != (Image *) NULL);
assert((*images)->signature == MAGICK_CORE_SIG);
InitSubSrch (&ss);
ss.precision = GetMagickPrecision();
status = menu (argc, argv, &ss);
if (status == MagickFalse)
return (-1);
int ListLen = (int)GetImageListLength(*images);
if (ListLen != 2) {
fprintf (stderr, "whatrot: needs 2 images\n");
return (-1);
}
if (!whatrot (images, &ss, exception)) return (-1);
DeInitSubSrch (&ss);
return(MagickImageFilterSignature);
}
/* Updated:
3-April-2018 for v7.0.7-28
*/
#ifndef WHATROT_INC
#define WHATROT_INC
// FIXME: caller might normalize mean and SD.
#include "rmsealpha.inc"
#include "goldsectsrch.inc"
#include "resetpage.inc"
#include "trimone.inc"
#include "centsmcrop.inc"
//#include "virtnone.inc"
#include "writeframe.inc"
typedef struct {
int
verbose;
MagickBooleanType
recordA,
recordB;
char
*debug;
double
tolerance;
// Used internally:
Image
*scaledA,
*scaledB2;
RmseAlphaT
ra;
// Returned results:
double
angle, // degrees clockwise, 0.0 <= angle <= 360.0.
plusOrMinus,
score;
} WhatRotT;
static void InitWhatRot (WhatRotT * pwr)
{
pwr->verbose = 0;
pwr->recordA = pwr->recordB = MagickFalse;
pwr->scaledA = pwr->scaledB2 = NULL;
pwr->tolerance = 0.01;
pwr->score = pwr->angle = pwr->plusOrMinus = 0.0;
pwr->debug = NULL;
InitRmseAlpha (&pwr->ra);
}
static void ReInitWhatRot (WhatRotT * pwr)
{
if (pwr->scaledA) pwr->scaledA = DestroyImage (pwr->scaledA);
if (pwr->scaledB2) pwr->scaledB2 = DestroyImage (pwr->scaledB2);
ReInitRmseAlpha (&pwr->ra);
}
static void DeInitWhatRot (WhatRotT * pwr)
{
ReInitWhatRot (pwr);
DeInitRmseAlpha (&pwr->ra);
}
static Image * AppendRowSelf (
Image * img,
ExceptionInfo *exception)
// Returns image twice as wide, just one row,
// made from top row of img appended to itself.
{
Image * new_img = CloneImage (img, 2 * img->columns, 1,
MagickTrue, exception);
if (!new_img) return NULL;
CacheView * img_view = AcquireVirtualCacheView (img, exception);
CacheView * new_view = AcquireAuthenticCacheView (new_img, exception);
const VIEW_PIX_PTR *p = GetCacheViewVirtualPixels(
img_view,0,0,img->columns,1,exception);
if (!p) return NULL;
VIEW_PIX_PTR *q = GetCacheViewAuthenticPixels(
new_view,0,0,new_img->columns,1,exception);
if (!q) return NULL;
ssize_t dq = img->columns * Inc_ViewPixPtr (new_img);
ssize_t x;
for (x=0; x < img->columns; x++) {
SET_PIXEL_RED (new_img, GET_PIXEL_RED(img,p), q);
SET_PIXEL_GREEN (new_img, GET_PIXEL_GREEN(img,p), q);
SET_PIXEL_BLUE (new_img, GET_PIXEL_BLUE(img,p), q);
SET_PIXEL_ALPHA (new_img, GET_PIXEL_ALPHA(img,p), q);
SET_PIXEL_RED (new_img, GET_PIXEL_RED(img,p), q+dq);
SET_PIXEL_GREEN (new_img, GET_PIXEL_GREEN(img,p), q+dq);
SET_PIXEL_BLUE (new_img, GET_PIXEL_BLUE(img,p), q+dq);
SET_PIXEL_ALPHA (new_img, GET_PIXEL_ALPHA(img,p), q+dq);
p += Inc_ViewPixPtr (img);
q += Inc_ViewPixPtr (new_img);
}
new_view = DestroyCacheView (new_view);
img_view = DestroyCacheView (img_view);
return new_img;
}
static MagickBooleanType CalcApproxRot (
WhatRotT * pwr,
Image * imageA,
Image * imageB,
Image * DispMapA, // May be null.
Image * DispMapB, // May be null.
ExceptionInfo *exception)
//
// Find the rotation (degrees clockwise) that applied to imageB
// makes it best match imageA.
//
// Finds the approximate angle by radial filter,
// ie unroll and scale to one row.
{
MagickBooleanType bestfit = MagickTrue;
double minusOne=-1.0;
if (!pwr->scaledA) {
Image * clnA = CloneImage (imageA, 0, 0, MagickTrue, exception);
if (!clnA) return MagickFalse;
//#if IMV6OR7==6
// SetPixelCacheVirtualMethod (clnA, TransparentVirtualPixelMethod);
// SetImageAlphaChannel (clnA, ActivateAlphaChannel);
//#else
// SetPixelCacheVirtualMethod (clnA,
// TransparentVirtualPixelMethod, exception);
// SetImageAlphaChannel (clnA, ActivateAlphaChannel, exception);
//#endif
VIRT_NONE(clnA, exception);
Image *unrlA = DistortImage (clnA, DePolarDistortion,
1, &minusOne, bestfit, exception);
if (!unrlA) return MagickFalse;
clnA = DestroyImage (clnA);
pwr->scaledA = ScaleImage (unrlA, unrlA->columns, 1,
exception);
if (!pwr->scaledA) return MagickFalse;
unrlA = DestroyImage (unrlA);
}
if (!pwr->scaledB2) {
Image * clnB = CloneImage (imageB, 0, 0, MagickTrue, exception);
if (!clnB) return MagickFalse;
//#if IMV6OR7==6
// SetPixelCacheVirtualMethod (clnB, TransparentVirtualPixelMethod);
// SetImageAlphaChannel (clnB, ActivateAlphaChannel);
//#else
// SetPixelCacheVirtualMethod (clnB,
// TransparentVirtualPixelMethod, exception);
// SetImageAlphaChannel (clnB, ActivateAlphaChannel, exception);
//#endif
VIRT_NONE(clnB, exception);
Image *unrlB = DistortImage (clnB, DePolarDistortion,
1, &minusOne, bestfit, exception);
if (!unrlB) return MagickFalse;
clnB = DestroyImage (clnB);
// Scale to one row, and width to match A.
Image * scaledB = ScaleImage (unrlB, pwr->scaledA->columns, 1,
exception);
if (!scaledB) return MagickFalse;
unrlB = DestroyImage (unrlB);
pwr->scaledB2 = AppendRowSelf (scaledB, exception);
if (!pwr->scaledB2) return MagickFalse;
scaledB = DestroyImage (scaledB);
}
ssize_t unrWidth = pwr->scaledA->columns;
pwr->ra.do_verbose = (pwr->verbose > 0);
//WriteFrame ("wr_scaledB2.png", pwr->scaledB2, 0, exception);
//WriteFrame ("wr_scaledA.png", pwr->scaledA, 0, exception);
// FIXME: Should MS be an option?
// It wouldn't currently improve performance.
if (!subRmseAlpha (&pwr->ra, pwr->scaledB2, pwr->scaledA, exception))
return MagickFalse;
pwr->score = pwr->ra.score;
pwr->angle = pwr->ra.solnX * 360.0 / (double)unrWidth;
pwr->plusOrMinus = 180.0 / (double)unrWidth;
if (pwr->verbose) fprintf (stderr,
"CalcApproxRot: solnX=%li degrees=%g score=%g\n",
pwr->ra.solnX, pwr->angle, pwr->score);
if (!pwr->recordB) pwr->scaledB2 = DestroyImage (pwr->scaledB2);
if (!pwr->recordA) pwr->scaledA = DestroyImage (pwr->scaledA);
return MagickTrue;
}
static MagickBooleanType InCircOnly (
Image * img,
ExceptionInfo *exception)
// Makes pixels outside an inscribed circle transparent.
{
CacheView * img_view = AcquireAuthenticCacheView (img, exception);
// FIXME: enable alpha for the image?
// FIXME: No anti-aliasing.
//if (pat_img->columns <= 2 || pat_img->rows <= 2) return MagickTrue;
// FIXME: following isn't symmetrical.
double r = img->columns;
if (r > img->rows) r = img->rows;
r /= 2.0;
double a = r;
double b = r;
double a2 = a*a;
double b2 = b*b;
double a2b2 = a2*b2;
MagickBooleanType okay = MagickTrue;
ssize_t y;
#if defined(MAGICKCORE_OPENMP_SUPPORT)
#pragma omp parallel for schedule(static,4) \
shared(okay) \
MAGICK_THREADS(img,img,img->rows,1)
#endif
for (y = 0; y < img->rows; y++) {
ssize_t x;
VIEW_PIX_PTR *sp = GetCacheViewAuthenticPixels(
img_view,0,y,img->columns,1,exception);
if (!sp) okay = MagickFalse;
if (!okay) continue;
double a2y2 = a2 * (y-b) * (y-b);
double a2y2m1 = a2 * (y-b-0.5) * (y-b-0.5);
double a2y2p1 = a2 * (y-b+0.5) * (y-b+0.5);
for (x = 0; x < img->columns; x++) {
double b2x2 = b2 * (x-a) * (x-a);
double b2x2m1 = b2 * (x-a-0.5) * (x-a-0.5);
double b2x2p1 = b2 * (x-a+0.5) * (x-a+0.5);
int nIn = 0;
if (b2x2m1 + a2y2m1 < a2b2) nIn++;
if (b2x2 + a2y2m1 < a2b2) nIn++;
if (b2x2p1 + a2y2m1 < a2b2) nIn++;
if (b2x2m1 + a2y2 < a2b2) nIn++;
if (b2x2 + a2y2 < a2b2) nIn++;
if (b2x2p1 + a2y2 < a2b2) nIn++;
if (b2x2m1 + a2y2p1 < a2b2) nIn++;
if (b2x2 + a2y2p1 < a2b2) nIn++;
if (b2x2p1 + a2y2p1 < a2b2) nIn++;
if (nIn < 9) {
SET_PIXEL_ALPHA (img,
nIn / 9.0 * GET_PIXEL_ALPHA(img,sp),
sp);
}
sp += Inc_ViewPixPtr (img);
}
if (SyncCacheViewAuthenticPixels(img_view,exception) == MagickFalse)
okay = MagickFalse;
}
if (!okay) return MagickFalse;
img_view = DestroyCacheView (img_view);
return MagickTrue;
}
typedef struct {
Image * imageA;
Image * imageB;
int verbose;
ExceptionInfo *exception;
char * debug;
} GoldSectRotT;
static double cbGetRot (double ang, void * pData, int * okay)
{
GoldSectRotT * pGsr = (GoldSectRotT *)pData;
/* For now, a simple method:
-distort SRT 1,angle the second image
Make transparent all pixels outside an inscribed circle.
(See PaintPatches EllipseOnly().)
Return the alpha-adjusted rmse with the first image.
As this loses data where some rotated imageB pixels fall on
the main image but outside imageA, this can give false positives.
A better method would lose no data, which needs a larger imageA.
We would make transparent imageA pixels that are outside rotated imageB.
(Then possibly normalise mean and SD.)
*/
MagickBooleanType bestfit = MagickTrue;
double scaleArray[2];
scaleArray[0] = 1.0; // Scale factor.
scaleArray[1] = ang; // Rotation angle.
Image *rot_img = DistortImage (pGsr->imageB, ScaleRotateTranslateDistortion,
2, scaleArray, bestfit, pGsr->exception);
if (!rot_img) { *okay=0; return 0; }
ResetPage (rot_img);
TrimOne (&rot_img, pGsr->exception);
Image * clnA = CloneImage (pGsr->imageA, 0, 0, MagickTrue, pGsr->exception);
if (!clnA) { *okay=0; return 0; }
if (!CentralSmallestCrop (&clnA, &rot_img, (pGsr->verbose > 1), pGsr->exception))
{ *okay=0; return 0; }
//printf ("clnA: %lix%li ", clnA->columns, clnA->rows);
//printf ("rot_img: %lix%li\n", rot_img->columns, rot_img->rows);
// FIXME: Do we need to restrict to inscribed circle?
// if (!InCircOnly (rot_img, pGsr->exception)) { *okay=0; return 0; }
RmseAlphaT ra;
InitRmseAlpha (&ra); // FIXME: Can we do this outside the loop?
ra.do_verbose = (pGsr->verbose <= 1) ? 0 : pGsr->verbose-1;
if (pGsr->debug) {
WriteFrame ("wrcb_rot.png", rot_img, 0, pGsr->exception);
WriteFrame ("wrcb_clna.png", clnA, 0, pGsr->exception);
}
if (!subRmseAlpha (&ra, rot_img, clnA, pGsr->exception))
{ *okay=0; fprintf (stderr, "cbGetRot sra failed\n"); return 0; }
DeInitRmseAlpha (&ra);
DestroyImage (rot_img);
DestroyImage (clnA);
if (ra.score < 0) {
fprintf (stderr, "cbGetRot sra %g\n", ra.score);
*okay = 0;
}
if (pGsr->verbose) fprintf (stderr,
"cbGetRot: ang=%g, score=%g\n", ang, ra.score);
return ra.score;
}
static MagickBooleanType GoldSectRot (
WhatRotT * pwr,
Image * imageA,
Image * imageB,
ExceptionInfo *exception)
// Finds rotation by golden section search.
{
if (pwr->verbose) fprintf (stderr, "GoldSectRot %g +/- %g\n",
pwr->angle, pwr->plusOrMinus);
Image * clnB = CloneImage (imageB, 0, 0, MagickTrue, exception);
if (!clnB) return MagickFalse;
//#if IMV6OR7==6
// SetPixelCacheVirtualMethod (clnB, TransparentVirtualPixelMethod);
//#else
// SetPixelCacheVirtualMethod (clnB,
// TransparentVirtualPixelMethod, exception);
//#endif
VIRT_NONE(clnB, exception);
goldSectSrchT gss;
InitGoldSectSrch (&gss);
double porm = pwr->plusOrMinus * 2;
if (porm < 5) porm = 5;
gss.xLo = pwr->angle - porm;
gss.xHi = pwr->angle + porm;
gss.epsX = pwr->tolerance * 2;
gss.getYcb = cbGetRot;
gss.verbose = (pwr->verbose <= 1) ? 0 : pwr->verbose-1 ;
GoldSectRotT gsr;
gsr.imageA = imageA;
gsr.imageB = clnB;
gsr.verbose = gss.verbose;
gsr.exception = exception;
gsr.debug = pwr->debug;
if (! GoldSectSrch (&gss, &gsr)) {
clnB = DestroyImage (clnB);
return MagickFalse;
}
pwr->angle = gss.fndX;
pwr->plusOrMinus = gss.xPlusOrMinus;
pwr->score = gss.calcY;
clnB = DestroyImage (clnB);
return MagickTrue;
}
#endif
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include "vsn_defines.h"
#include "rmsealpha.inc"
//#include "chklist.h"
#include "avgconcrings.inc"
#include "writeframe.inc"
#include "absdispmap.inc"
#include "whatscale.inc"
#define VERSION "whatrot v1.0 Copyright (c) 2017 Alan Gibson"
typedef struct {
ConcRingsT
cr;
Image
*AvgConcRings,
*DispMap;
MagickBooleanType
doScale,
UseDispMap,
JustSinks,
FindRotation,
approxOnly,
refineOnly,
phApprox,
phRefine,
debug;
int
verbose,
precision,
scaleRadMax;
double
inPorm,
tolerance;
FILE *
fh_data;
} SubSrchT;
static void InitSubSrch (SubSrchT * pss)
{
InitConcRings (&pss->cr);
pss->AvgConcRings = NULL;
pss->DispMap = NULL;
pss->doScale = MagickTrue;
pss->UseDispMap = MagickTrue;
pss->JustSinks = MagickTrue;
pss->FindRotation = MagickFalse;
pss->approxOnly = pss->refineOnly = MagickFalse;
pss->phApprox = pss->phRefine = MagickTrue;
pss->debug = MagickFalse;
pss->verbose = 0;
pss->precision = 6;
pss->scaleRadMax = 0;
pss->inPorm = 1.3;
// pss->porm = 0.3;
pss->tolerance = 0.01;
pss->fh_data = stderr;
}
static void DeInitSubSrch (SubSrchT * pss)
{
if (pss->AvgConcRings) pss->AvgConcRings = DestroyImage (pss->AvgConcRings);
if (pss->DispMap) pss->DispMap = DestroyImage (pss->DispMap);
DeInitConcRings (&pss->cr);
}
static void usage (void)
{
printf ("Usage: -process 'whatscale [OPTION]...'\n");
printf ("Find scale by unrolling and golden section methods.\n");
printf ("\n");
printf (" a, approxOnly use only the first (approximate) method\n");
printf (" r, refineOnly use only the second (refining) method\n");
printf (" x, noScale don't replace images with rescaled\n");
printf (" s0, minScale number minimum scale\n");
printf (" s1, maxScale number maximum scale\n");
printf (" ns, nScales integer number of scales\n");
printf (" p, porm number plus or minus for scale gss\n");
printf (" t, tolerance number error tolerance for scale gss\n");
printf (" r, scaleRadMax int scale so radius is this, at max\n");
printf (" m, method string for concentric rings, 'slow' or 'quick'\n");
printf (" f, file string write data to file stream stdout or stderr\n");
printf (" d, debug create debugging images\n");
printf (" v, verbose write text information to stdout\n");
printf (" v2, verbose2 write more information to stdout\n");
printf (" version write version information to stdout\n");
printf ("\n");
}
// FIXME: also option to not update image list.
// FIXME: also option to normalise mean and SD.
static MagickBooleanType IsArg (char * pa, char * ShtOpt, char * LongOpt)
{
if ((LocaleCompare(pa, ShtOpt)==0) || (LocaleCompare(pa, LongOpt)==0))
return MagickTrue;
return MagickFalse;
}
#define NEXTARG \
if (i < argc-1) i++; \
else { \
fprintf (stderr, "paintpatches: ERROR: [%s] needs argument\n", pa); \
status = MagickFalse; \
}
static MagickBooleanType menu(
const int argc,
const char **argv,
SubSrchT * pss
)
// Returns MagickTrue if okay.
{
int
i;
MagickBooleanType
status;
status = MagickTrue;
for (i=0; i < argc; i++) {
char * pa = (char *)argv[i];
if (IsArg (pa, "a", "approxOnly")==MagickTrue) {
pss->approxOnly = MagickTrue;
pss->phRefine = MagickFalse;
} else if (IsArg (pa, "r", "refineOnly")==MagickTrue) {
pss->refineOnly = MagickTrue;
pss->phApprox = MagickFalse;
} else if (IsArg (pa, "x", "noScale")==MagickTrue) {
pss->doScale = MagickFalse;
} else if (IsArg (pa, "s0", "minScale")==MagickTrue) {
NEXTARG;
pss->cr.minScale = atof(argv[i]);
} else if (IsArg (pa, "s1", "maxScale")==MagickTrue) {
NEXTARG;
pss->cr.maxScale = atof(argv[i]);
} else if (IsArg (pa, "ns", "nScales")==MagickTrue) {
NEXTARG;
pss->cr.nScales = atoi(argv[i]);
} else if (IsArg (pa, "p", "porm")==MagickTrue) {
NEXTARG;
pss->inPorm = atof(argv[i]);
} else if (IsArg (pa, "t", "tolerance")==MagickTrue) {
NEXTARG;
pss->tolerance = atof(argv[i]);
} else if (IsArg (pa, "r", "scaleRadMax")==MagickTrue) {
NEXTARG;
pss->scaleRadMax = atoi(argv[i]);
} else if (IsArg (pa, "m", "method")==MagickTrue) {
NEXTARG;
if (LocaleCompare(argv[i], "slow")==0) pss->cr.ConcRingsMethod = crmSlow;
else if (LocaleCompare(argv[i], "quick")==0) pss->cr.ConcRingsMethod = crmQuick;
else {
fprintf (stderr, "whatscale: ERROR: unknown method [%s]\n", argv[i]);
status = MagickFalse;
}
} else if (IsArg (pa, "d", "debug")==MagickTrue) {
pss->debug = MagickTrue;
} else if (IsArg (pa, "f", "file")==MagickTrue) {
NEXTARG;
if (strcasecmp (argv[i], "stdout")==0) pss->fh_data = stdout;
else if (strcasecmp (argv[i], "stderr")==0) pss->fh_data = stderr;
else status = MagickFalse;
} else if (IsArg (pa, "v", "verbose")==MagickTrue) {
pss->verbose = 1;
} else if (IsArg (pa, "v2", "verbose2")==MagickTrue) {
pss->verbose = 2;
} else if (IsArg (pa, "version", "version")==MagickTrue) {
fprintf (stdout, "%s\n", VERSION);
} else {
fprintf (stderr, "whatscale: ERROR: unknown option\n");
status = MagickFalse;
}
}
if (pss->verbose) {
fprintf (stderr, "whatscale options:");
if (pss->approxOnly) fprintf (stderr, " approxOnly");
if (pss->refineOnly) fprintf (stderr, " refineOnly");
fprintf (stderr, " minScale %g", pss->cr.minScale);
fprintf (stderr, " maxScale %g", pss->cr.maxScale);
fprintf (stderr, " nScales %i", pss->cr.nScales);
fprintf (stderr, " porm %g", pss->inPorm);
fprintf (stderr, " tolerance %g", pss->tolerance);
fprintf (stderr, " method ");
switch (pss->cr.ConcRingsMethod) {
case crmSlow: fprintf (stderr, "slow"); break;
case crmQuick: fprintf (stderr, "quick"); break;
}
if (!pss->doScale) fprintf (stderr, " noScale");
if (pss->debug) fprintf (stderr, " debug");
if (pss->fh_data == stdout) fprintf (stderr, " file stdout");
if (pss->verbose==2) fprintf (stderr, " verbose2");
else if (pss->verbose) fprintf (stderr, " verbose");
fprintf (stderr, "\n");
}
if (status == MagickFalse)
usage ();
return (status);
}
// The next function takes an image list,
// may modify it, and returns a status.
//
static MagickBooleanType whatscale (
Image **images,
SubSrchT * pss,
ExceptionInfo *exception)
// Takes two images: can be same size, or either can be larger.
{
Image * main_img = *images;
Image * sub_img = GetNextImageInList (main_img);
if (!sub_img) return MagickFalse;
pss->cr.verbose = (pss->verbose > 1);
/*--
// Make the concentric rings version of the subimage.
pss->AvgConcRings = avgconcrings (sub_img, &pss->cr, exception);
if (!pss->AvgConcRings) {
fprintf (stderr, "whatscale: avgconcrings failed\n");
return MagickFalse;
}
// Make displacement map, and unroll it.
double minusOne=-1.0;
MagickBooleanType bestfit = MagickTrue;
Image * id_map = mIdentAbsDispMap (sub_img, exception);
#if IMV6OR7==6
SetImageAlphaChannel (id_map, OpaqueAlphaChannel);
#else
SetImageAlphaChannel (id_map, OpaqueAlphaChannel, exception);
#endif
pss->DispMap = DistortImage (id_map, DePolarDistortion,
1, &minusOne, bestfit, exception);
if (!pss->DispMap) return MagickFalse;
if (pss->verbose) fprintf (stderr, "whatscale: DispMap %lix%li\n",
pss->DispMap->columns, pss->DispMap->rows);
//if (!WriteFrameImage (pss->DispMap, "dispmap.png", NULL, exception))
// return MagickFalse;
// Check: width of AvgConcRings should be height of DispMap.
if (pss->AvgConcRings->columns != pss->DispMap->rows) {
fprintf (stderr, "pss->AvgConcRings->columns != pss->DispMap->rows\n");
}
// Possibly shrink pss->AvgConcRings to X columns,
// and pss->DispMap in same proportion.
if (pss->scaleRadMax && pss->AvgConcRings->columns > pss->scaleRadMax) {
Image * tmp_img1 = RESIZEIMG (pss->AvgConcRings, pss->scaleRadMax, pss->AvgConcRings->rows,
exception);
Image * tmp_img2 = RESIZEIMG (pss->DispMap, pss->DispMap->rows, pss->scaleRadMax,
exception);
if (!tmp_img1) return MagickFalse;
ReplaceImageInList (&pss->AvgConcRings, tmp_img1);
if (!tmp_img2) return MagickFalse;
ReplaceImageInList (&pss->DispMap, tmp_img2);
}
pss->cr.ra.do_verbose = pss->cr.verbose;
--*/
WhatScaleT ws;
InitWhatScale (&ws);
ws.scale = 1.0;
ws.inPorm = pss->inPorm;
ws.tolerance = pss->tolerance;
ws.verbose = pss->verbose;
ws.score = 1.0;
if (pss->debug) ws.debug = "ws_dbg";
// FIXME: next shld be a parameter, so user can prevent the loop.
#define MAX_ITER 10
int i;
for (i = 0; i < MAX_ITER; i++) {
if (! PrepCalcApproxScale (
&pss->cr, sub_img, &pss->AvgConcRings,
NULL, exception)) return MagickFalse;
// pss->DispMap = DestroyImage (pss->DispMap); // FIXME: temp
if (pss->debug) {
WriteFrame ("wsx_main.png", main_img, 0, exception);
WriteFrame ("wsx_sub.png", sub_img, 0, exception);
WriteFrame ("wsx_acr.png", pss->AvgConcRings, 0, exception);
}
if (pss->phApprox) {
if (!CalcApproxScale (&ws, &pss->cr, main_img, pss->AvgConcRings,
pss->DispMap, exception))
{
fprintf (stderr, "CalcApproxScale failed\n");
}
if (pss->verbose) {
fprintf (stderr,
"whatscale: approx scale=%.*g +/-=%.*g score=%.*g\n",
pss->precision, ws.scale,
pss->precision, ws.porm,
pss->precision, ws.score);
}
if (ws.possSmaller) {
double rangeFact = pss->cr.maxScale / pss->cr.minScale;
pss->cr.maxScale = sqrt (pss->cr.minScale * pss->cr.maxScale);
pss->cr.minScale = pss->cr.maxScale / rangeFact;
if (pss->verbose) fprintf (stderr,
"Try smaller scale, range %g to %g\n",
pss->cr.minScale, pss->cr.maxScale);
} else if (ws.possLarger) {
double rangeFact = pss->cr.maxScale / pss->cr.minScale;
pss->cr.minScale = sqrt (pss->cr.minScale * pss->cr.maxScale);
pss->cr.maxScale = pss->cr.minScale * rangeFact;
if (pss->verbose) fprintf (stderr,
"Try larger scale, range %g to %g\n",
pss->cr.minScale, pss->cr.maxScale);
} else {
break;
}
}
}
if (i==MAX_ITER) fprintf (stderr,
"whatscale: scale probably wrong, between %g and %g, scale=%g?\n",
pss->cr.minScale, pss->cr.maxScale,
ws.scale);
if (pss->phRefine) {
// Save, in case GoldSect fails.
double saveScale = ws.scale;
double savePorm = ws.porm;
double saveScore = ws.score;
ws.porm = pss->tolerance;
ws.tolerance = pss->tolerance;
if (GoldSectScale (&ws, main_img, sub_img, exception)) {
if (pss->verbose) {
fprintf (stderr,
"whatscale: gss scale=%.*g +/-=%.*g score=%.*g\n",
pss->precision, ws.scale,
pss->precision, ws.porm,
pss->precision, ws.score);
}
} else {
fprintf (stderr, "whatscale: GoldSectScale failed\n");
ws.scale = saveScale;
ws.porm = savePorm;
ws.score = saveScore;
}
}
fprintf (pss->fh_data,
"whatscale: scale=%.*g scalePorm=%.*g scaleScore=%.*g\n",
pss->precision, ws.scale,
pss->precision, ws.porm,
pss->precision, ws.score);
DeInitWhatScale (&ws);
if (pss->doScale) {
MagickBooleanType bestfit = MagickTrue;
double scaleArray[2];
scaleArray[0] = ws.scale; // Scale factor.
scaleArray[1] = 0.0; // Rotation angle.
#if IMV6OR7==6
//SetPixelCacheVirtualMethod (sub_img, TransparentVirtualPixelMethod);
SetImageVirtualPixelMethod (sub_img, TransparentVirtualPixelMethod);
#else
//SetPixelCacheVirtualMethod (sub_img,
// TransparentVirtualPixelMethod, exception);
SetImageVirtualPixelMethod (sub_img, TransparentVirtualPixelMethod, exception);
#endif
Image *resB = DistortImage (sub_img, ScaleRotateTranslateDistortion,
2, scaleArray, bestfit, exception);
if (!resB) return MagickFalse;
// We don't ResetPage (resB);
if (pss->verbose) fprintf (stderr,
"WhatScale: created image %lix%li %lix%li%+li%+li\n",
resB->columns, resB->rows,
resB->page.width, resB->page.height,
resB->page.x, resB->page.y);
DeleteImageFromList (&sub_img);
ReplaceImageInList (&main_img, resB);
// Replace messes up the images pointer. Make it good:
*images = GetFirstImageInList (main_img);
}
return MagickTrue;
}
ModuleExport size_t whatscaleImage(Image **images,const int argc,
const char **argv,ExceptionInfo *exception)
{
MagickBooleanType
status;
SubSrchT
ss;
assert(images != (Image **) NULL);
assert(*images != (Image *) NULL);
assert((*images)->signature == MAGICK_CORE_SIG);
InitSubSrch (&ss);
ss.precision = GetMagickPrecision();
status = menu (argc, argv, &ss);
if (status == MagickFalse)
return (-1);
int ListLen = (int)GetImageListLength(*images);
if (ListLen != 2) {
fprintf (stderr, "whatscale: needs 2 images\n");
return (-1);
}
if (!whatscale (images, &ss, exception)) return -1;
DeInitSubSrch (&ss);
return(MagickImageFilterSignature);
}
#ifndef WHATSCALE_INC
#define WHATSCALE_INC
// Returns the angle and score.
// FIXME: caller might normalize mean and SD.
/* Updated:
5-April-2018 consider smaller or larger only if non-zero score.
*/
#include "rmsealpha.inc"
#include "goldsectsrch.inc"
#include "avgconcrings.inc"
#include "resetpage.inc"
#include "trimone.inc"
#include "centsmcrop.inc"
#include "writeframe.inc"
typedef struct {
int
verbose;
char
*debug;
double
inPorm,
tolerance;
// Results:
double
scale,
porm, // error margin, plus or minus
scaleFact, // error margin
score;
MagickBooleanType
possSmaller,
possLarger;
} WhatScaleT;
static void InitWhatScale (WhatScaleT * pws)
{
pws->verbose = 0;
pws->score = pws->scale = pws->scaleFact = 0;
pws->inPorm = 1.2; // >= 1.0
pws->debug = NULL;
pws->tolerance = 0.01;
pws->scale = pws->porm = pws->scaleFact = pws->score = 0.0;
}
static void ReInitWhatScale (WhatScaleT * pws)
{
; // Nothing.
}
static void DeInitWhatScale (WhatScaleT * pws)
{
; // Nothing.
}
static MagickBooleanType PrepCalcApproxScale (
ConcRingsT * pcr,
Image * sub_img,
Image ** pAvgConcRings, // Creates this.
Image ** pDispMap, // If not null, creates this.
ExceptionInfo *exception)
{
// Make the concentric rings version of the subimage.
*pAvgConcRings = avgconcrings (sub_img, pcr, exception);
if (!*pAvgConcRings) {
fprintf (stderr, "whatscale: avgconcrings failed\n");
return MagickFalse;
}
if (pDispMap) {
// Make displacement map, and unroll it.
double minusOne=-1.0;
MagickBooleanType bestfit = MagickTrue;
Image * id_map = mIdentAbsDispMap (sub_img, exception);
#if IMV6OR7==6
SetImageAlphaChannel (id_map, OpaqueAlphaChannel);
#else
SetImageAlphaChannel (id_map, OpaqueAlphaChannel, exception);
#endif
*pDispMap = DistortImage (id_map, DePolarDistortion,
1, &minusOne, bestfit, exception);
if (!*pDispMap) return MagickFalse;
if (pcr->verbose) fprintf (stderr, "whatscale: DispMap %lix%li\n",
(*pDispMap)->columns, (*pDispMap)->rows);
//if (!WriteFrameImage (pss->DispMap, "dispmap.png", NULL, exception))
// return MagickFalse;
// Check: width of AvgConcRings should be height of DispMap.
if ((*pAvgConcRings)->columns != (*pDispMap)->rows) {
fprintf (stderr, "AvgConcRings->columns != DispMap->rows\n");
}
}
// Possibly shrink pss->AvgConcRings to X columns,
// and pss->DispMap in same proportion.
/*==
if (pss->scaleRadMax && pss->AvgConcRings->columns > pss->scaleRadMax) {
Image * tmp_img1 = RESIZEIMG (pss->AvgConcRings, pss->scaleRadMax, pss->AvgConcRings->rows,
exception);
Image * tmp_img2 = RESIZEIMG (pss->DispMap, pss->DispMap->rows, pss->scaleRadMax,
exception);
if (!tmp_img1) return MagickFalse;
ReplaceImageInList (&pss->AvgConcRings, tmp_img1);
if (!tmp_img2) return MagickFalse;
ReplaceImageInList (&pss->DispMap, tmp_img2);
}
==*/
pcr->ra.do_verbose = pcr->verbose;
return MagickTrue;
}
static MagickBooleanType CalcApproxScale (
WhatScaleT * pws,
ConcRingsT * pcr,
Image * cropped, // Must not be null.
Image * AvgConcRings, // Must not be null.
Image * DispMap, // May be null.
ExceptionInfo *exception)
{
pws->possSmaller = pws->possLarger = MagickFalse;
MagickBooleanType bestfit = MagickTrue;
double minusOne=-1.0;
Image *unrl_img;
if (DispMap) {
// For DePolar, faster alternative is to use pre-built displacement map.
// Scale image to size of the disp map, and displace ("distort") it.
// Scale instead of resize. Scale is quicker, and no practical difference.
unrl_img = ScaleImage (cropped, DispMap->columns, DispMap->rows,
exception);
if (!unrl_img) return MagickFalse;
if (!COMPOSITE(unrl_img, DistortCompositeOp, DispMap, 0,0, exception))
{
fprintf (stderr, "CalcApproxScale: Composite failed\n");
return MagickFalse;
}
if (!COMPOSITE(unrl_img, COPY_OPACITY, DispMap, 0,0, exception))
{
fprintf (stderr, "CalcApproxScale: Composite2 failed\n");
return MagickFalse;
}
if (pws->debug)
WriteFrame ("wrx_unrl_img.png", unrl_img, 0, exception);
//if (pcr->do_verbose) fprintf (stderr, "CalcApproxScale: unrl 2 %lix%li\n",
// unrl_img->columns, unrl_img->rows);
} else {
unrl_img = DistortImage (cropped, DePolarDistortion,
1, &minusOne, bestfit, exception);
}
if (!unrl_img) return MagickFalse;
// Scale to 1 column, with height of AvgConcRings width
// (instead of unrl_img->rows).
// FIXME: put it back.
Image * one_col_img = ScaleImage (unrl_img, 1, unrl_img->rows, exception);
if (!one_col_img) return MagickFalse;
unrl_img = DestroyImage (unrl_img);
// Rotate by -90 degrees.
Image * one_row_img = IntegralRotateImage (one_col_img, 3, exception);
if (!one_row_img) return MagickFalse;
one_col_img = DestroyImage (one_col_img);
ResetPage (one_row_img);
if (pws->debug)
WriteFrame ("wsx_one_row_img.png", one_row_img, 0, exception);
//if (pcr->do_verbose) fprintf (stderr, "CalcApproxScale: %lix%li\n",
// one_row_img->columns, one_row_img->rows);
// Search for one_row_img in AvgConcRings.
// Return score and scale.
pcr->ra.doSlideX = MagickFalse;
if (!subRmseAlpha (&pcr->ra, AvgConcRings, one_row_img, exception)) {
fprintf (stderr, "CalcApproxScale: subRmseAlpha failed\n");
return MagickFalse;
}
one_row_img = DestroyImage (one_row_img);
if (pcr->ra.score != 0) {
if (pcr->ra.solnY==0) pws->possSmaller = MagickTrue;
else if (pcr->ra.solnY==AvgConcRings->rows-1) pws->possLarger = MagickTrue;
}
pws->scale = ConcRingsScale (pcr, pcr->ra.solnY);
pws->porm = ConcRingsScale (pcr, pcr->ra.solnY + 1) - pws->scale;
pws->score = pcr->ra.score;
if (pws->verbose) {
fprintf (stderr,
"CalcApproxScale: solnY=%li scale=%g score=%g",
pcr->ra.solnY, pws->scale, pws->score);
if (pws->possSmaller) fprintf (stderr, " (possibly smaller)");
if (pws->possLarger) fprintf (stderr, " (possibly larger)");
fprintf (stderr, "\n");
}
return MagickTrue;
}
typedef struct {
Image * imageA;
Image * imageB;
int verbose;
ExceptionInfo *exception;
char * debug;
} GoldSectScaleT;
static double cbGetScale (double scale, void * pData, int * okay)
{
GoldSectScaleT * pGsr = (GoldSectScaleT *)pData;
/* Resize imageB.
Crop the larger to the smaller.
Return the alpha-adjusted rmse.
*/
MagickBooleanType bestfit = MagickTrue;
double scaleArray[2];
scaleArray[0] = scale; // Scale factor.
scaleArray[1] = 0.0; // Rotation angle.
Image *resB = DistortImage (pGsr->imageB, ScaleRotateTranslateDistortion,
2, scaleArray, bestfit, pGsr->exception);
if (!resB) { fprintf (stderr, "DistortImage failed\n"); *okay=0; return 0; }
ResetPage (resB);
TrimOne (&resB, pGsr->exception);
Image * clnA = CloneImage (pGsr->imageA, 0, 0, MagickTrue, pGsr->exception);
if (!clnA) {
fprintf (stderr, "cbGetScale clone failed\n");
*okay=0;
return 0;
}
if (!CentralSmallestCrop (&clnA, &resB, (pGsr->verbose > 1), pGsr->exception)) {
fprintf (stderr, "cbGetScale csc failed\n");
*okay=0;
return 0;
}
if (pGsr->debug) {
WriteFrame ("wscb_clna.png", clnA, 0, pGsr->exception);
WriteFrame ("wscb_resb.png", resB, 0, pGsr->exception);
}
/*===
MagickBooleanType CropIt = MagickTrue;
Image *toCrop, *othImg, *crpd_img;
if (resB->columns > pGsr->imageA->columns) {
toCrop = resB;
othImg = pGsr->imageA;
} else if (resB->columns < pGsr->imageA->columns) {
toCrop = pGsr->imageA;
othImg = resB;
} else {
CropIt = MagickFalse;
crpd_img = resB;
othImg = pGsr->imageA;
}
if (CropIt) {
RectangleInfo crpRect;
crpRect.width = othImg->columns;
crpRect.height = othImg->rows;
crpRect.x = (toCrop->columns - othImg->columns) / 2;
crpRect.y = (toCrop->rows - othImg->rows) / 2;
crpd_img = CropCheck (toCrop, &crpRect, pGsr->exception);
if (!crpd_img) { fprintf (stderr, "CropImage failed\n"); *okay=0; return 0; }
ResetPage (crpd_img);
}
===*/
RmseAlphaT ra;
InitRmseAlpha (&ra); // FIXME: Can we do this outside the loop?
ra.do_verbose = (pGsr->verbose <= 1) ? 0 : pGsr->verbose-1;
// if (!subRmseAlpha (&ra, crpd_img, othImg, pGsr->exception))
if (!subRmseAlpha (&ra, clnA, resB, pGsr->exception))
{
fprintf (stderr, "cbGetScale: subRmseAlpha failed\n");
*okay=0;
return 0;
}
DeInitRmseAlpha (&ra);
// if (CropIt) DestroyImage (crpd_img);
DestroyImage (clnA);
DestroyImage (resB);
if (ra.score < 0) {
fprintf (stderr, "cbGetScale: subRmseAlpha %g\n", ra.score);
*okay = 0;
}
if (pGsr->verbose) fprintf (stderr,
"cbGetScale: scale=%g, score=%g\n", scale, ra.score);
return ra.score;
}
static MagickBooleanType GoldSectScale (
WhatScaleT * pws,
Image * imageA,
Image * imageB,
ExceptionInfo *exception)
// Finds scale by golden section search.
{
if (pws->verbose) fprintf (stderr,
"GoldSectScale inScale=%g inPorm=%g tol=%g\n",
pws->scale, pws->inPorm, pws->tolerance);
goldSectSrchT gss;
InitGoldSectSrch (&gss);
gss.xLo = pws->scale / pws->inPorm; // FIXME: get error margin from pws.
gss.xHi = pws->scale * pws->inPorm;
gss.epsX = pws->tolerance;
gss.getYcb = cbGetScale;
gss.verbose = (pws->verbose <= 1) ? 0 : pws->verbose-1 ;
GoldSectScaleT gsr;
gsr.imageA = imageA;
gsr.imageB = imageB;
gsr.verbose = gss.verbose;
gsr.exception = exception;
gsr.debug = pws->debug;
if (! GoldSectSrch (&gss, &gsr)) return MagickFalse;
pws->scale = gss.fndX;
pws->scaleFact = gss.xPlusOrMinus * 2;
pws->score = gss.calcY;
pws->porm = gss.xPlusOrMinus;
return MagickTrue;
}
#endif
/* Updated:
3-April-2018 for v7.0.7-28
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include "vsn_defines.h"
#include "rmsealpha.inc"
#include "absdispmap.inc"
#include "whatrot.inc"
#include "whatscale.inc"
#include "whatrotscale.inc"
#define VERSION "whatrotscale v1.0 Copyright (c) 2017 Alan Gibson"
typedef struct {
WhatRotScaleT
wrs;
int
verbose;
Image
*DispMap;
MagickBooleanType
doRotate,
UseDispMap,
approxOnly,
debug;
int
precision;
double
tolerance;
FILE *
fh_data;
} SubSrchT;
static void InitSubSrch (SubSrchT * pss)
{
InitWhatRotScale (&pss->wrs);
pss->verbose = 0;
pss->DispMap = NULL;
pss->doRotate = MagickTrue;
pss->UseDispMap = MagickTrue;
pss->approxOnly = MagickFalse;
pss->precision = 6;
pss->tolerance = 0.01;
pss->fh_data = stderr;
pss->debug = MagickFalse;
}
static void DeInitSubSrch (SubSrchT * pss)
{
if (pss->DispMap) pss->DispMap = DestroyImage (pss->DispMap);
DeInitWhatRotScale (&pss->wrs);
}
static void usage (void)
{
printf ("Usage: -process 'whatrot [OPTION]...'\n");
printf ("Find scale and rotation by unrolling and golden section methods.\n");
printf ("\n");
printf (" a, approxOnly use only the first (approximate) method\n");
printf (" t, tolerance number for golden section method\n");
printf (" x, noChange don't replace images\n");
printf (" f, file string write data to file stream stdout or stderr\n");
printf (" d, debug create debugging images\n");
printf (" v, verbose write text information to stdout\n");
printf (" v2, verbose2 write more information to stdout\n");
printf (" v3, verbose3 yet more information to stdout\n");
printf (" version write version information to stdout\n");
printf ("\n");
}
// FIXME: also option to normalise mean and SD.
static MagickBooleanType IsArg (char * pa, char * ShtOpt, char * LongOpt)
{
if ((LocaleCompare(pa, ShtOpt)==0) || (LocaleCompare(pa, LongOpt)==0))
return MagickTrue;
return MagickFalse;
}
#define NEXTARG \
if (i < argc-1) i++; \
else { \
fprintf (stderr, "paintpatches: ERROR: [%s] needs argument\n", pa); \
status = MagickFalse; \
}
static MagickBooleanType menu(
const int argc,
const char **argv,
SubSrchT * pss
)
// Returns MagickTrue if okay.
{
int
i;
MagickBooleanType
status;
status = MagickTrue;
for (i=0; i < argc; i++) {
char * pa = (char *)argv[i];
if (IsArg (pa, "a", "approxOnly")==MagickTrue) {
pss->approxOnly = MagickTrue;
} else if (IsArg (pa, "x", "noChange")==MagickTrue) {
pss->doRotate = MagickFalse;
} else if (IsArg (pa, "t", "tolerance")==MagickTrue) {
NEXTARG;
pss->tolerance = atof (argv[i]);
} else if (IsArg (pa, "f", "file")==MagickTrue) {
NEXTARG;
if (strcasecmp (argv[i], "stdout")==0) pss->fh_data = stdout;
else if (strcasecmp (argv[i], "stderr")==0) pss->fh_data = stderr;
else status = MagickFalse;
} else if (IsArg (pa, "d", "debug")==MagickTrue) {
pss->debug = MagickTrue;
} else if (IsArg (pa, "v", "verbose")==MagickTrue) {
pss->verbose = 1;
} else if (IsArg (pa, "v2", "verbose2")==MagickTrue) {
pss->verbose = 2;
} else if (IsArg (pa, "v3", "verbose3")==MagickTrue) {
pss->verbose = 3;
} else if (IsArg (pa, "version", "version")==MagickTrue) {
fprintf (stdout, "%s\n", VERSION);
} else {
fprintf (stderr, "whatrotscale: ERROR: unknown option\n");
status = MagickFalse;
}
}
if (pss->verbose) {
fprintf (stderr, "whatrotscale options:");
if (pss->approxOnly) fprintf (stderr, " approxOnly");
fprintf (stderr, " tolerance %g", pss->tolerance);
if (!pss->doRotate) fprintf (stderr, " noChange");
if (pss->fh_data == stdout) fprintf (stderr, " file stdout");
if (pss->debug) fprintf (stderr, " debug");
if (pss->verbose > 1) fprintf (stderr, " verbose%i", pss->verbose);
else if (pss->verbose) fprintf (stderr, " verbose");
fprintf (stderr, "\n");
}
if (status == MagickFalse)
usage ();
return (status);
}
#if 0
// The next function takes an image list,
// may modify it, and returns a status.
//
static MagickBooleanType doWhatrotscale (
Image **images,
SubSrchT * pss,
ExceptionInfo *exception)
{
Image * main_img = *images;
Image * sub_img = GetNextImageInList (main_img);
if (!sub_img) return MagickFalse;
/*==
if (sub_img->columns > main_img->columns ||
sub_img->rows > main_img->rows)
{
fprintf (stderr, "whatrotscale: subimage is larger than main\n");
return MagickFalse;
}
==*/
//#if IMV6OR7==6
// SetPixelCacheVirtualMethod (main_img, TransparentVirtualPixelMethod);
//#else
// SetPixelCacheVirtualMethod (main_img,
// TransparentVirtualPixelMethod, exception);
//#endif
VIRT_NONE(main_ing, exception);
// Make displacement map, and unroll it.
double minusOne=-1.0;
MagickBooleanType bestfit = MagickTrue;
Image * id_map = mIdentAbsDispMap (sub_img, exception);
#if IMV6OR7==6
SetImageAlphaChannel (id_map, OpaqueAlphaChannel);
#else
SetImageAlphaChannel (id_map, OpaqueAlphaChannel, exception);
#endif
pss->DispMap = DistortImage (id_map, DePolarDistortion,
1, &minusOne, bestfit, exception);
if (!pss->DispMap) return MagickFalse;
if (pss->verbose) fprintf (stderr, "srchconcr: DispMap %lix%li\n",
pss->DispMap->columns, pss->DispMap->rows);
pss->wrs.wr.verbose = pss->verbose;
pss->wrs.ws.verbose = pss->verbose;
if (pss->debug) {
pss->wrs.debug = "wrs_dbg";
pss->wr.debug = "wrs_dbg";
pss->ws.debug = "wrs_dbg";
}
if (!CalcApproxRot (&pss->wrs.wr, main_img, sub_img, NULL, NULL, exception)) {
fprintf (stderr, "CalcApproxRot failed\n");
return MagickFalse;
}
if (pss->verbose) {
fprintf (stderr, "Approx ang=%.*g +/-%.*g score=%.*g\n",
pss->precision, pss->wrs.wr.angle,
pss->precision, pss->wrs.wr.plusOrMinus,
pss->precision, pss->wrs.wr.score);
}
if (!pss->approxOnly) {
// Save, in case GoldSect fails.
double saveAng = pss->wrs.wr.angle;
double savePorm = pss->wrs.wr.plusOrMinus;
double saveScore = pss->wrs.wr.score;
pss->wr.tolerance = pss->tolerance;
if (GoldSectRot (&pss->wr, main_img, sub_img, exception)) {
if (pss->verbose) fprintf (stderr,
"From gss: ang=%.*g +/-%.*g score=%.*g\n",
pss->precision, pss->wr.angle,
pss->precision, pss->wr.plusOrMinus,
pss->precision, pss->wr.score);
} else {
fprintf (stderr, "whatrot: GoldSectScale failed\n");
pss->wr.angle = saveAng;
pss->wr.plusOrMinus = savePorm;
pss->wr.score = saveScore;
}
}
fprintf (pss->fh_data, "whatrotscale: ang=%.*g angPorm=%.*g angScore=%.*g\n",
pss->precision, pss->wr.angle,
pss->precision, pss->wr.plusOrMinus,
pss->precision, pss->wr.score);
if (pss->doRotate) {
MagickBooleanType bestfit = MagickTrue;
double scaleArray[2];
scaleArray[0] = 1.0; // Scale factor.
scaleArray[1] = pss->wr.angle; // Rotation angle.
//#if IMV6OR7==6
// SetPixelCacheVirtualMethod (sub_img, TransparentVirtualPixelMethod);
//#else
// SetPixelCacheVirtualMethod (sub_img,
// TransparentVirtualPixelMethod, exception);
//#endif
VIRT_NONE(sub_img, exception);
Image *rot_img = DistortImage (sub_img, ScaleRotateTranslateDistortion,
2, scaleArray, bestfit, exception);
if (!rot_img) return MagickFalse;
// We don't ResetPage (rot_img);
DeleteImageFromList (&sub_img);
ReplaceImageInList (&main_img, rot_img);
// Replace messes up the images pointer. Make it good:
*images = GetFirstImageInList (main_img);
}
return MagickTrue;
}
#endif
static MagickBooleanType doWhatrotscale (
Image **images,
SubSrchT * pss,
ExceptionInfo *exception)
{
int ListLen = (int)GetImageListLength(*images);
if (ListLen != 2) {
fprintf (stderr, "whatrotscale: needs 2 images\n");
return (-1);
}
Image * main_img = *images;
Image * sub_img = GetNextImageInList (main_img);
pss->wrs.verbose = pss->verbose;
if (pss->debug) {
pss->wrs.debug = "wrs_dbg";
}
if (!ApproxWhatRotScale (&pss->wrs, main_img, sub_img, exception))
return MagickFalse;
if (!RefineWhatRotScale (&pss->wrs, main_img, sub_img, exception))
return MagickFalse;
fprintf (pss->fh_data, "WhatRotScale scale=%.*g angle=%.*g score=%.*g\n",
pss->precision, pss->wrs.scale,
pss->precision, pss->wrs.angle,
pss->precision, pss->wrs.score);
if (pss->doRotate) {
MagickBooleanType bestfit = MagickTrue;
double scaleArray[2];
scaleArray[0] = pss->wrs.scale; // Scale factor.
scaleArray[1] = pss->wrs.angle; // Rotation angle.
VIRT_NONE(sub_img, exception);
Image *dist_img = DistortImage (sub_img, ScaleRotateTranslateDistortion,
2, scaleArray, bestfit, exception);
if (!dist_img) return MagickFalse;
// We don't ResetPage (dist_img);
if (pss->verbose) fprintf (stderr,
"WhatRotScale: created image %lix%li %lix%li%+li%+li\n",
dist_img->columns, dist_img->rows,
dist_img->page.width, dist_img->page.height,
dist_img->page.x, dist_img->page.y);
DeleteImageFromList (&sub_img);
ReplaceImageInList (&main_img, dist_img);
// Replace messes up the images pointer. Make it good:
*images = GetFirstImageInList (main_img);
}
return MagickTrue;
}
ModuleExport size_t whatrotscaleImage(Image **images,const int argc,
const char **argv,ExceptionInfo *exception)
{
MagickBooleanType
status;
SubSrchT
ss;
assert(images != (Image **) NULL);
assert(*images != (Image *) NULL);
assert((*images)->signature == MAGICK_CORE_SIG);
InitSubSrch (&ss);
ss.precision = GetMagickPrecision();
status = menu (argc, argv, &ss);
if (status == MagickFalse)
return (-1);
if (!doWhatrotscale (images, &ss, exception))
fprintf (stderr, "doWhatrotscale failed\n");
DeInitSubSrch (&ss);
return(MagickImageFilterSignature);
}
/* Updated:
3-April-2018 for v7.0.7-28
*/
#ifndef WHATROTSCALE_INC
#define WHATROTSCALE_INC
#define wrsDEBUG 1
//#include "virtnone.inc"
typedef struct {
WhatRotT wr;
WhatScaleT ws;
MagickBooleanType
verbose;
char
*debug;
double
ApproxScale,
ApproxAngle;
// Returned results:
double
scale,
angle,
score;
} WhatRotScaleT;
static void InitWhatRotScale (WhatRotScaleT *wrs)
{
InitWhatRot (&wrs->wr);
InitWhatScale (&wrs->ws);
wrs->verbose = MagickFalse;
wrs->ApproxScale = 1;
wrs->ApproxAngle = 0;
wrs->debug = NULL;
wrs->scale = wrs->angle = wrs->score = 0;
}
static void DeInitWhatRotScale (WhatRotScaleT *wrs)
{
DeInitWhatScale (&wrs->ws);
DeInitWhatRot (&wrs->wr);
}
static MagickBooleanType ApproxWhatRotScale (
WhatRotScaleT *wrs,
Image * main_img,
Image * sub_img,
ExceptionInfo *exception)
// Finds approximate scale and rotation.
{
ConcRingsT cr;
InitConcRings (&cr);
cr.verbose = (wrs->verbose > 1);
wrs->ws.verbose = wrs->verbose;
wrs->wr.verbose = wrs->verbose;
wrs->ws.debug = wrs->debug;
wrs->wr.debug = wrs->debug;
// Make the concentric rings version of the subimage.
Image * AvgConcRings = avgconcrings (sub_img, &cr, exception);
if (!AvgConcRings) {
fprintf (stderr, "ApproxWhatRotScale: avgconcrings failed\n");
return MagickFalse;
}
if (! CalcApproxScale (&wrs->ws, &cr,
main_img, AvgConcRings, NULL, exception))
return MagickFalse;
if (! CalcApproxRot (&wrs->wr, main_img, sub_img, NULL, NULL, exception))
return MagickFalse;
wrs->scale = wrs->ApproxScale = wrs->ws.scale;
wrs->angle = wrs->ApproxAngle = wrs->wr.angle;
wrs->score = (wrs->wr.score + wrs->ws.score) / 2.0;
if (wrs->verbose) fprintf (stderr,
"WhatRotScale approx scale=%g porm=%g ang=%g porm=%g score=%g\n",
wrs->scale, wrs->ws.porm,
wrs->angle, wrs->wr.plusOrMinus, wrs->score);
DestroyImage (AvgConcRings);
DeInitConcRings (&cr);
return MagickTrue;
}
static MagickBooleanType RefineWhatRotScale (
WhatRotScaleT *wrs,
Image * main_img,
Image * sub_img,
ExceptionInfo *exception)
// Given approximate scale and rotation,
// refine the scale and rotation by golden section searches.
// FIXME: Can the inputs be different sizes??
{
wrs->wr.verbose = (wrs->verbose <= 1) ? 0 : wrs->verbose-1;
wrs->ws.verbose = wrs->wr.verbose;
wrs->ws.debug = wrs->debug;
wrs->wr.debug = wrs->debug;
wrs->scale = wrs->ApproxScale;
wrs->angle = wrs->ApproxAngle;
if (wrs->verbose) fprintf (stderr,
"RefineWhatRotScale ApproxScale=%g ApproxAng=%g\n",
wrs->scale, wrs->angle);
Image * clnSub = CloneImage (sub_img, 0, 0, MagickTrue, exception);
if (!clnSub) return MagickFalse;
//#if IMV6OR7==6
// SetPixelCacheVirtualMethod (clnSub, TransparentVirtualPixelMethod);
// SetImageAlphaChannel (clnSub, ActivateAlphaChannel);
//#else
// SetPixelCacheVirtualMethod (clnSub,
// TransparentVirtualPixelMethod, exception);
// SetImageAlphaChannel (clnSub, ActivateAlphaChannel, exception);
//#endif
VIRT_NONE(clnSub, exception);
MagickBooleanType bestfit = MagickTrue;
MagickBooleanType okay = MagickTrue;
double srtArray[2];
int i;
double prevScore = 9.9;
for (i=0; i < 10; i++) {
srtArray[0] = wrs->scale;
srtArray[1] = wrs->angle;
#if wrsDEBUG==1
if (wrs->verbose > 1) {
fprintf (stderr, "RefineWhatRotScale sc=%g rot=%g\n", wrs->scale, wrs->angle);
WriteFrame ("wrsx_main.png", main_img, 0, exception);
WriteFrame ("wrsx_sub.png", sub_img, 0, exception);
}
#endif
Image *sub_trans = DistortImage (clnSub, ScaleRotateTranslateDistortion,
2, srtArray, bestfit, exception);
if (!sub_trans) {
fprintf (stderr, "WhatRotScale: distort sc and ang failed\n");
return MagickFalse;
}
ResetPage (sub_trans);
TrimOne (&sub_trans, exception);
#if wrsDEBUG==1
if (wrs->debug) {
WriteFrame ("wrsx_trans0.png", sub_trans, 0, exception);
}
#endif
int nFailed = 0;
wrs->wr.angle = 0;
wrs->wr.plusOrMinus = 5;
wrs->wr.debug = wrs->debug;
wrs->ws.debug = wrs->debug;
okay = GoldSectRot (&wrs->wr, main_img, sub_trans, exception);
DestroyImage (sub_trans);
if (!okay) {
if (wrs->verbose > 1) fprintf (stderr, "WhatRotScale: gss angle failed\n");
nFailed = 1;
wrs->wr.angle = 0.0;
// return MagickFalse;
}
wrs->angle += wrs->wr.angle;
if (wrs->verbose > 1) fprintf (stderr,
"RefineWhatRotScale ang=%g porm=%g score=%g\n",
wrs->angle, wrs->wr.plusOrMinus, wrs->wr.score);
srtArray[1] = wrs->angle;
sub_trans = DistortImage (clnSub, ScaleRotateTranslateDistortion,
2, srtArray, bestfit, exception);
if (!sub_trans) {
fprintf (stderr, "WhatRotScale: distort sc and new ang failed\n");
return MagickFalse;
}
ResetPage (sub_trans);
TrimOne (&sub_trans, exception);
#if wrsDEBUG==1
if (wrs->debug) {
WriteFrame ("wrsx_trans1.png", sub_trans, 0, exception);
}
#endif
wrs->ws.scale = 1.0;
okay = GoldSectScale (&wrs->ws, main_img, sub_trans, exception);
DestroyImage (sub_trans);
if (!okay) {
if (wrs->verbose > 1) fprintf (stderr, "WhatRotScale: gss scale failed\n");
nFailed++;
wrs->ws.scale = 1.0;
// return MagickFalse;
}
if (nFailed == 2) return MagickFalse;
wrs->scale *= wrs->ws.scale;
if (wrs->verbose > 1) fprintf (stderr,
"WhatRotScale scale=%g porm=%g score=%g\n",
wrs->scale, wrs->ws.porm, wrs->ws.score);
wrs->score = wrs->ws.score;
if (wrs->score < 0.01) break;
if (fabs(prevScore - wrs->score) < 0.001) break;
prevScore = wrs->score;
} // end for
if (wrs->verbose) fprintf (stderr,
"WhatRotScale refined nIter=%i scale=%g porm=%g ang=%g porm=%g score=%g\n",
i+1,
wrs->scale, wrs->ws.porm,
wrs->angle, wrs->wr.plusOrMinus, wrs->score);
return (wrs->score > 0);
}
#endif
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include "vsn_defines.h"
#include "srchmask.inc"
#define VERSION "srchmask v1.0 Copyright (c) 2017 Alan Gibson"
typedef struct {
int
verbose;
MagickBooleanType
updateMask,
makeComposite,
elimClose;
int
precision;
FILE *
fh_data;
} SubSrchT;
/*
Optionally dumps text.
Optionally applies transformation to subimage, merging with main image.
*/
static void InitSubSrch (SubSrchT * pss)
{
pss->verbose = 0;
pss->updateMask = MagickFalse;
pss->makeComposite = MagickFalse;
pss->elimClose = MagickFalse;
pss->precision = 6;
pss->fh_data = stderr;
}
static void DeInitSubSrch (SubSrchT * pss)
{
; // Nothing.
}
static void usage (void)
{
printf ("Usage: -process 'srchmask [OPTION]...'\n");
printf ("Find rotation by unrolling and golden section methods.\n");
printf ("\n");
printf (" u, updateMask update with only the 'good' positions\n");
printf (" c, makeComposite make composite output image\n");
printf (" e, elimClose eliminate close solutions\n");
printf (" f, file string write data to file stream stdout or stderr\n");
printf (" v, verbose write text information to stdout\n");
printf (" v2, verbose2 write more information to stdout\n");
printf (" version write version information to stdout\n");
printf ("\n");
}
// FIXME: also option to normalise mean and SD.
static MagickBooleanType IsArg (char * pa, char * ShtOpt, char * LongOpt)
{
if ((LocaleCompare(pa, ShtOpt)==0) || (LocaleCompare(pa, LongOpt)==0))
return MagickTrue;
return MagickFalse;
}
#define NEXTARG \
if (i < argc-1) i++; \
else { \
fprintf (stderr, "paintpatches: ERROR: [%s] needs argument\n", pa); \
status = MagickFalse; \
}
static MagickBooleanType menu(
const int argc,
const char **argv,
SubSrchT * pss
)
// Returns MagickTrue if okay.
{
int
i;
MagickBooleanType
status;
status = MagickTrue;
for (i=0; i < argc; i++) {
char * pa = (char *)argv[i];
if (IsArg (pa, "c", "makeComposite")==MagickTrue) {
pss->makeComposite = MagickTrue;
} else if (IsArg (pa, "e", "elimClose")==MagickTrue) {
pss->elimClose = MagickTrue;
} else if (IsArg (pa, "u", "updateMask")==MagickTrue) {
pss->updateMask = MagickTrue;
} else if (IsArg (pa, "f", "file")==MagickTrue) {
NEXTARG;
if (strcasecmp (argv[i], "stdout")==0) pss->fh_data = stdout;
else if (strcasecmp (argv[i], "stderr")==0) pss->fh_data = stderr;
else status = MagickFalse;
} else if (IsArg (pa, "v", "verbose")==MagickTrue) {
pss->verbose = 1;
} else if (IsArg (pa, "v2", "verbose2")==MagickTrue) {
pss->verbose = 2;
} else if (IsArg (pa, "version", "version")==MagickTrue) {
fprintf (stdout, "%s\n", VERSION);
} else {
fprintf (stderr, "srchmask: ERROR: unknown option\n");
status = MagickFalse;
}
}
if (pss->verbose) {
fprintf (stderr, "srchmask options:");
if (pss->updateMask) fprintf (stderr, " updateMask");
if (pss->makeComposite) fprintf (stderr, " makeComposite");
if (pss->fh_data == stdout) fprintf (stderr, " file stdout");
if (pss->verbose==2) fprintf (stderr, " verbose2");
else if (pss->verbose) fprintf (stderr, " verbose");
fprintf (stderr, "\n");
}
if (status == MagickFalse)
usage ();
return (status);
}
// The next function takes an image list,
// may modify it, and returns a status.
//
static MagickBooleanType srchmask (
Image **images,
SubSrchT * pss,
ExceptionInfo *exception)
{
int ListLen = (int)GetImageListLength(*images);
Image * img1 = *images;
Image * img2 = GetNextImageInList (img1);
Image * img3 = NULL;
if (img2) img3 = GetNextImageInList (img2);
Image *main_img=NULL, *sub_img=NULL, *mask_img=NULL;
switch (ListLen) {
case 1:
mask_img = img1;
break;
case 2:
sub_img = img1;
mask_img = img2;
break;
case 3:
main_img = img1;
sub_img = img2;
mask_img = img3;
break;
}
if (main_img) fprintf (stderr,
"main_img %lix%li ", main_img->columns, main_img->rows);
if (sub_img) fprintf (stderr,
"sub_img %lix%li ", sub_img->columns, sub_img->rows);
if (mask_img) fprintf (stderr,
"mask_img %lix%li", mask_img->columns, mask_img->rows);
fprintf (stderr, "\n");
/*==
if (sub_img->columns > main_img->columns ||
sub_img->rows > main_img->rows)
{
fprintf (stderr, "srchconcr: subimage is larger than main\n");
return MagickFalse;
}
==*/
//#if IMV6OR7==6
// SetPixelCacheVirtualMethod (main_img, TransparentVirtualPixelMethod);
//#else
// SetPixelCacheVirtualMethod (main_img,
// TransparentVirtualPixelMethod, exception);
//#endif
srchMaskT sm;
InitSrchMask (&sm);
sm.precision = GetMagickPrecision();
sm.verbose = pss->verbose;
if (main_img) {
// sm.main_width = main_img->columns;
// sm.main_height = main_img->rows;
}
if (sub_img) {
sm.sub_width = sub_img->columns;
sm.sub_height = sub_img->rows;
}
ReadSrchMask (mask_img, &sm, exception);
DumpSrchMask (stderr, &sm);
SortSrchMask (&sm);
DumpSrchMask (stderr, &sm);
FindGoodSrchMask (&sm);
WrSrchMaskSrt (stderr, &sm);
SrchMaskExclHidden (&sm);
WrSrchMaskSrt (stderr, &sm);
if (pss->updateMask) {
Image * new_msk = WrGoodSrchMask (mask_img, &sm, exception);
if (!new_msk) return MagickFalse;
ReplaceImageInList (&mask_img, new_msk);
}
if (pss->elimClose) {
SrchMaskExclHidden (&sm);
}
if (pss->makeComposite) {
Image * comp_img = SrchMaskMakeComposite (main_img, sub_img, &sm, exception);
if (!comp_img) {
fprintf (stderr, "SrchMaskMakeComposite failed\n");
return MagickFalse;
}
ReplaceImageInList (&mask_img, comp_img);
}
DeInitSrchMask (&sm);
if (sub_img) DeleteImageFromList (&sub_img);
if (main_img) DeleteImageFromList (&main_img);
*images = GetFirstImageInList (mask_img);
return MagickTrue;
}
ModuleExport size_t srchmaskImage(Image **images,const int argc,
const char **argv,ExceptionInfo *exception)
{
MagickBooleanType
status;
SubSrchT
ss;
assert(images != (Image **) NULL);
assert(*images != (Image *) NULL);
assert((*images)->signature == MAGICK_CORE_SIG);
InitSubSrch (&ss);
ss.precision = GetMagickPrecision();
status = menu (argc, argv, &ss);
if (status == MagickFalse)
return (-1);
int ListLen = (int)GetImageListLength(*images);
if (ListLen < 1 || ListLen > 3) {
fprintf (stderr, "srchconcr: needs 1, 2 or 3 images\n");
return (-1);
}
if (!srchmask (images, &ss, exception)) return (-1);
DeInitSubSrch (&ss);
return(MagickImageFilterSignature);
}
#ifndef WRITEFRAME_INC
#define WRITEFRAME_INC
static MagickBooleanType WriteFrame (
char *FileName,
Image * img,
int frame_num,
ExceptionInfo *exception)
{
ImageInfo
*ii;
MagickBooleanType
okay;
Image
*copy_img;
ii = AcquireImageInfo ();
copy_img = CloneImage(img, 0, 0, MagickTrue, exception);
if (!copy_img) return(MagickFalse); // FIXME: raise error
copy_img->scene = frame_num;
CopyMagickString (copy_img->filename, FileName, MaxTextExtent);
okay = WRITEIMAGE(ii, copy_img, exception);
DestroyImageList(copy_img);
ii = DestroyImageInfo (ii);
return okay;
}
#endif
/* Updated:
5-April-2018 for v7.0.7-28.
*/
// From an image, make a same-size absolute displacement map.
static void SetArgs (
double * args, int nArg,
ssize_t x, ssize_t y,
double r, double g, double b)
{
int i = nArg*5;
args[i++] = x;
args[i++] = y;
args[i++] = r;
args[i++] = g;
args[i++] = b;
}
static Image * mIdentAbsDispMap (
Image * image,
ExceptionInfo *exception)
{
#define NUM_SPARSE_ARGS 20
double args[NUM_SPARSE_ARGS];
SetArgs (args, 0, 0, 0, 0, 0, 0);
SetArgs (args, 1, image->columns-1, 0, 1, 0, 0);
SetArgs (args, 2, 0, image->rows-1, 0, 1, 0);
SetArgs (args, 3, image->columns-1, image->rows-1, 1, 1, 0);
// V7 SparseColorImage is different.
// Set the traits of image to RGB only.
#if IMV6OR7==6
Image * new_img = SparseColorImage (
image,
RedChannel | GreenChannel | BlueChannel,
BilinearColorInterpolate,
NUM_SPARSE_ARGS, args, exception);
#else
ChannelType channel_mask = SetImageChannelMask (
image,
RedChannel | GreenChannel | BlueChannel);
Image * new_img = SparseColorImage (
image,
BilinearColorInterpolate,
NUM_SPARSE_ARGS, args, exception);
SetImageChannelMask (image, channel_mask);
#endif
if (!new_img) {
fprintf (stderr, "mIdentAbsDispMap: SparseColorImage failed\n");
return NULL;
}
return (new_img);
}
#ifndef TRIMONE_INC
#define TRIMONE_INC
#include "resetpage.inc"
#include "cropchk.inc"
static MagickBooleanType TrimOne (
Image ** img,
ExceptionInfo *exception)
{
RectangleInfo crpRect;
crpRect.width = (*img)->columns - 2;
crpRect.height = (*img)->rows - 2;
crpRect.x = 1;
crpRect.y = 1;
Image * r = CropCheck (*img, &crpRect, exception);
if (!r) return MagickFalse;
ResetPage (r);
ReplaceImageInList (img, r);
return MagickTrue;
}
#endif
#ifndef CROPCHK_INC
#define CROPCHK_INC
static Image * CropCheck (
Image * img,
RectangleInfo *rect,
ExceptionInfo *exception)
{
Image * crp_img = CropImage (img, rect, exception);
if (!crp_img) return NULL;
// Check the crop really worked.
if (crp_img->columns != rect->width || crp_img->rows != rect->height) {
crp_img = DestroyImage (crp_img);
}
return crp_img;
}
#endif
#ifndef RESETPAGE_INC
#define RESETPAGE_INC
static void inline ResetPage (Image * img)
// Like "+repage".
{
img->page.width = img->page.height = img->page.x = img->page.y = 0;
}
#endif
#ifndef VIRTNONE_INC
#define VIRTNONE_INC
// This is temporary. Should be in vsn_defines.h.
#if IMV6OR7==6
#define VIRT_NONE(img) \
SetPixelCacheVirtualMethod (img, TransparentVirtualPixelMethod);\
SetImageAlphaChannel (img, ActivateAlphaChannel);
#else
#define VIRT_NONE(img) \
SetPixelCacheVirtualMethod (img,\
TransparentVirtualPixelMethod, exception);\
SetImageAlphaChannel (img, ActivateAlphaChannel, exception);
#endif
#endif
#ifndef GOLDSECTSRCH_INC
#define GOLDSECTSRCH_INC
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
/*
Ref: https://en.wikipedia.org/wiki/Golden_section_search
+------+------+-----+
x0 x1 x2 x3
<--a--><--b--><--c-->
Required:
b+c a a+b+c
--- = - = -----
a b b+c
Which implies:
b^2 + bc = a^2
c = (a^2-b^2) / b
ab + ac = ab + b^2 + bc
ac = b^2 + bc
c(a-b) = b^2
c = b^2 / (a-b)
So:
(a^2-b^2) / b = b^2 / (a-b)
(a^2-b^2)*(a-b) = b^3
a^3-a.b^2 -a^2.b + b^3 = b^3
a^3-a.b^2 -a^2.b = 0
a^2-b^2 -a.b = 0
a^2 -a.b -b^2 = 0
+b +-sqrt (b^2 + 4.1.b^2)
a = -------------------------
2
a = b * (1 +- sqrt(5)) / 2
a/b = phi
a = b.phi
But
c = b^2 / (a-b)
so:
c = b^2 / (b(phi-1))
c = b / (phi-1)
but phi-1 = 1/phi so
c = b.phi
*/
typedef double getYcbT (double x, void * pg, int * okay);
typedef struct {
double xLo;
double xHi;
double epsX;
double epsY;
getYcbT *getYcb; // This function will be called to calculate y.
int verbose;
// Returns results:
double fndX;
double xPlusOrMinus;
double calcY;
int dodgy;
int nIter;
} goldSectSrchT;
// FIXME: perhaps also return if less than xLo or greater than xHi.
/* This is a framework for searching for a minimum
within a 1D inverse-unimodal function,
such as finding the resize that makes an image best match another image.
*/
static void InitGoldSectSrch (goldSectSrchT *goldSectSrch)
{
goldSectSrch->xLo = 0.5;
goldSectSrch->xHi = 1.5;
goldSectSrch->epsX = 1e-2;
goldSectSrch->epsY = 0;
goldSectSrch->dodgy = 0;
goldSectSrch->nIter = 0;
goldSectSrch->getYcb = NULL;
goldSectSrch->verbose = 0;
}
static int GoldSectSrch (
goldSectSrchT *goldSectSrch,
void * pData)
// This finds x, xLo <= x <= xHi, such that getY(x) is a minimum.
// Returns 1 if okay, otherwise 0.
{
int okay = 1;
goldSectSrch->dodgy = 0;
double x0 = goldSectSrch->xLo;
double x3 = goldSectSrch->xHi;
double x1, x2;
double origX0 = x0;
double origX3 = x3;
double phi = (1.0 + sqrt(5.0))/2.0;
double y0 = goldSectSrch->getYcb (x0, pData, &okay);
if (!okay) return 0;
double y3 = goldSectSrch->getYcb (x3, pData, &okay);
if (!okay) return 0;
x1 = x3 - (x3-x0) / phi;
x2 = x0 + (x3-x0) / phi;
if (goldSectSrch->verbose > 1) {
fprintf (stderr, "x1=%g x2=%g phi=%g phi=%g\n",
x1, x2,
(x3-x1)/(x1-x0),
(x3-x0)/(x3-x1));
}
goldSectSrch->dodgy = 1;
double y1 = goldSectSrch->getYcb (x1, pData, &okay);
if (!okay) return 0;
double y2 = goldSectSrch->getYcb (x2, pData, &okay);
if (!okay) return 0;
if (y0 < y1 && y1 < y2 && y2 < y3) {
if (goldSectSrch->verbose) {
fprintf (stderr, "x: %g %g %g %g\n", x0, x1, x2, x3);
fprintf (stderr, "y: %g %g %g %g\n", y0, y1, y2, y3);
fprintf (stderr, "gss: Solution possibly < %g\n", x0);
}
return 0;
}
if (y0 > y1 && y1 > y2 && y2 > y3) {
if (goldSectSrch->verbose) {
fprintf (stderr, "x: %g %g %g %g\n", x0, x1, x2, x3);
fprintf (stderr, "y: %g %g %g %g\n", y0, y1, y2, y3);
fprintf (stderr, "gss: Solution possibly > %g\n", x3);
}
return 0;
}
if (y1 > y0 || y2 > y3) {
if (goldSectSrch->verbose) {
fprintf (stderr, "x: %g %g %g %g\n", x0, x1, x2, x3);
fprintf (stderr, "y: %g %g %g %g\n", y0, y1, y2, y3);
fprintf (stderr, "gss: dodgy initial y1 or y2\n");
}
return 0;
}
int cnt=0;
goldSectSrch->dodgy = 0;
for (;;) {
if (fabs (x3-x0) <= goldSectSrch->epsX) break;
if (fabs (y3-y0) <= goldSectSrch->epsY) break;
if (y1 > y0 && y1 > y3) {
if (goldSectSrch->verbose) {
fprintf (stderr, "x: %g %g %g %g\n", x0, x1, x2, x3);
fprintf (stderr, "y: %g %g %g %g\n", y0, y1, y2, y3);
fprintf (stderr, "gss: dodgy y1\n");
}
goldSectSrch->dodgy = 1;
break;
}
if (y2 > y0 && y2 > y3) {
if (goldSectSrch->verbose) {
fprintf (stderr, "x: %g %g %g %g\n", x0, x1, x2, x3);
fprintf (stderr, "y: %g %g %g %g\n", y0, y1, y2, y3);
fprintf (stderr, "gss: dodgy y2\n");
}
goldSectSrch->dodgy = 1;
break;
}
if (cnt++ > 100) break;
if (goldSectSrch->verbose > 1) {
fprintf (stderr, "%i x: %g %g %g %g\n", cnt, x0, x1, x2, x3);
fprintf (stderr, "y: %g %g %g %g\n", y0, y1, y2, y3);
}
if (y1 < y2) {
x3 = x2;
y3 = y2;
x2 = x1;
y2 = y1;
x1 = x3 - (x3-x0) / phi;
y1 = goldSectSrch->getYcb (x1, pData, &okay);
} else {
x0 = x1;
y0 = y1;
x1 = x2;
y1 = y2;
x2 = x0 + (x3-x0) / phi;
y2 = goldSectSrch->getYcb (x2, pData, &okay);
}
if (!okay) return 0;
}
if (goldSectSrch->verbose) {
fprintf (stderr, "\ngss: cnt=%i\nx: %g %g %g %g\n",
cnt, x0, x1, x2, x3);
fprintf (stderr, "y: %g %g %g %g\n", y0, y1, y2, y3);
}
if (origX0 == x0) {
fprintf (stderr, "gss: dodgy bottom\n");
goldSectSrch->dodgy = 1;
}
if (origX3 == x3) {
fprintf (stderr, "gss: dodgy top\n");
goldSectSrch->dodgy = 1;
}
goldSectSrch->fndX = (x0+x3) / 2.0;
goldSectSrch->xPlusOrMinus = (x3-x0) / 2.0;
goldSectSrch->calcY = goldSectSrch->getYcb (goldSectSrch->fndX, pData, &okay);
goldSectSrch->nIter = cnt;
return 1;
}
#endif
/* Updated:
1-September-2017 ParseXy now allows ',' or 'x' as separator.
*/
//---------------------------------------------------------------------------
// User-supplied coordinates: data structures and functions.
typedef enum {
csPix,
csPc,
csProp
} CoordSpecT;
typedef struct {
double
Num;
CoordSpecT
cs;
ssize_t
Pix;
} UserCoordT;
typedef struct {
UserCoordT
x,
y;
} UserCoordXyT;
static int ParseXy (const char * s, UserCoordXyT * uc)
// Returns 1 if okay; 0 if failure.
{
double d;
int n;
char * p = (char *)s;
sscanf (p, "%lg%n", &d, &n);
uc->x.Num = d;
uc->x.Pix = (int)(d+0.5);
p += n;
switch (*p) {
case '%':
case 'c':
uc->x.cs = csPc;
p++;
break;
case 'p':
uc->x.cs = csProp;
p++;
break;
}
if (*p != ',' && *p != 'x') return 0;
p++;
sscanf (p, "%lg%n", &d, &n);
uc->y.Num = d;
uc->y.Pix = (int)(d+0.5);
p += n;
switch (*p) {
case '%':
case 'c':
uc->y.cs = csPc;
p++;
break;
case 'p':
uc->y.cs = csProp;
p++;
break;
}
if (*p != '\0') return 0;
return 1;
}
static int ResolveUserCoords (
UserCoordXyT * uc,
ssize_t width,
ssize_t height,
double maxFact // Eg 1.0 or 2.0.
)
// Returns 1 if okay; 0 if failure.
{
switch (uc->x.cs) {
case csPc:
uc->x.Pix = (ssize_t)(uc->x.Num * (width-1) / 100.0 + 0.5);
break;
case csProp:
uc->x.Pix = (ssize_t)(uc->x.Num * (width-1) + 0.5);
break;
default:
uc->x.Pix = (ssize_t)(uc->x.Num + 0.5);
}
switch (uc->y.cs) {
case csPc:
uc->y.Pix = (ssize_t)(uc->y.Num * (height-1) / 100.0 + 0.5);
break;
case csProp:
uc->y.Pix = (ssize_t)(uc->y.Num * (height-1) + 0.5);
break;
default:
uc->y.Pix = (ssize_t)(uc->y.Num + 0.5);
}
int status = 1;
if (uc->x.Pix < 0 || uc->y.Pix < 0) {
fprintf (stderr, "Coordinates must be positive.\n");
status = 0;
}
if (uc->x.Pix >= width * maxFact || uc->y.Pix >= height * maxFact)
{
fprintf (stderr,
"Coordinates (%li,%li) must be less than width and height (%li,%li)",
uc->x.Pix, uc->y.Pix, width, height);
if (maxFact != 1.0) {
fprintf (stderr, " multipled by %g", maxFact);
}
fprintf (stderr, ".\n");
status = 0;
}
return status;
}
static int ResolveUserDims (
UserCoordXyT * uc,
ssize_t width,
ssize_t height,
double maxFact // Eg 1.0 or 2.0. If 0.0, no check.
)
// Returns 1 if okay; 0 if failure.
{
switch (uc->x.cs) {
case csPc:
uc->x.Pix = (ssize_t)(uc->x.Num * (width) / 100.0 + 0.5);
break;
case csProp:
uc->x.Pix = (ssize_t)(uc->x.Num * (width) + 0.5);
break;
default:
uc->x.Pix = (ssize_t)(uc->x.Num + 0.5);
}
switch (uc->y.cs) {
case csPc:
uc->y.Pix = (ssize_t)(uc->y.Num * (height) / 100.0 + 0.5);
break;
case csProp:
uc->y.Pix = (ssize_t)(uc->y.Num * (height) + 0.5);
break;
default:
uc->y.Pix = (ssize_t)(uc->y.Num + 0.5);
}
int status = 1;
if (uc->x.Pix < 0 || uc->y.Pix < 0) {
fprintf (stderr, "Dimensions must be positive.\n");
status = 0;
}
if (maxFact > 0) {
if (uc->x.Pix > width * maxFact || uc->y.Pix > height * maxFact)
{
fprintf (stderr,
"Dimensions (%li,%li) must be less than width and height (%li,%li)",
uc->x.Pix, uc->y.Pix, width, height);
if (maxFact != 1.0) {
fprintf (stderr, " multipled by %g", maxFact);
}
fprintf (stderr, ".\n");
status = 0;
}
}
return status;
}
static void WrUserCoord (UserCoordXyT * uc)
{
switch (uc->x.cs) {
case csPc:
fprintf (stderr, "%gc", uc->x.Num);
break;
case csProp:
fprintf (stderr, "%gp", uc->x.Num);
break;
default:
fprintf (stderr, "%li", uc->x.Pix);
}
fprintf (stderr, ",");
switch (uc->y.cs) {
case csPc:
fprintf (stderr, "%gc", uc->y.Num);
break;
case csProp:
fprintf (stderr, "%gp", uc->y.Num);
break;
default:
fprintf (stderr, "%li", uc->y.Pix);
}
}
/*
Reference: http://im.snibgo.com/cols2mat.htm
Updated:
3-April-2018 for v7.0.7-28
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include "vsn_defines.h"
//#include "chklist.h"
#if IMV6OR7==7
#include <MagickCore/matrix-private.h> // for GaussJordanElimination()
#endif
#include "cols2mat.inc"
#define VERSION "cols2mat v1.0 Copyright (c) 2017 Alan Gibson"
typedef struct {
int
precision;
MagickBooleanType
doTrans,
weightAlpha,
verbose;
c2mTypeT
c2mType;
int
polyDegree;
double
BottomLineWeight;
FILE *
fh_data;
} cols2matT;
static void usage (void)
{
printf ("Usage: -process 'cols2mat [OPTION]...'\n");
printf ("Finds the colour matrix (or polynomials) that transforms first image to second.\n");
printf ("\n");
printf (" m, method string 'Cross', 'NoCross', 'NoCrossPoly' or 'GainOnly'\n");
printf (" d, degreePoly integer degree for NoCrossPoly\n");
printf (" w, weightLast number weight for bottom line of image\n");
printf (" wa, weightAlpha multiply weight by product of alphas\n");
printf (" x, noTrans don't replace images with transformation\n");
printf (" f, file string write to file stream stdout or stderr\n");
printf (" v, verbose write text information to stdout\n");
printf (" version write version information to stdout\n");
printf ("\n");
}
static MagickBooleanType IsArg (char * pa, char * ShtOpt, char * LongOpt)
{
// LocaleCompare is not case-sensitive,
// so we use strcmp for the short option.
if ((strcmp(pa, ShtOpt)==0) || (LocaleCompare(pa, LongOpt)==0))
return MagickTrue;
return MagickFalse;
}
#define NEXTARG \
if (i < argc-1) i++; \
else { \
fprintf (stderr, "cols2mat: ERROR: [%s] needs argument\n", pa); \
status = MagickFalse; \
}
static MagickBooleanType menu(
const int argc,
const char **argv,
cols2matT * psi
)
// Returns MagickTrue if okay.
{
int
i;
MagickBooleanType
status = MagickTrue;
psi->verbose = MagickFalse;
psi->weightAlpha = MagickFalse;
psi->doTrans = MagickTrue;
psi->fh_data = stderr;
psi->c2mType = ctCross;
psi->polyDegree = 2;
psi->BottomLineWeight = 1.0;
for (i=0; i < argc; i++) {
char * pa = (char *)argv[i];
//printf ("Arg %i [%s]\n", i, pa);
if (IsArg (pa, "x", "noTrans")==MagickTrue) {
psi->doTrans = MagickFalse;
} else if (IsArg (pa, "m", "method")==MagickTrue) {
NEXTARG;
if (strcasecmp (argv[i], "Cross")==0) psi->c2mType = ctCross;
else if (strcasecmp (argv[i], "NoCross")==0) psi->c2mType = ctNoCross;
else if (strcasecmp (argv[i], "NoCrossPoly")==0) psi->c2mType = ctNoCrossPoly;
else if (strcasecmp (argv[i], "GainOnly")==0) psi->c2mType = ctGainOnly;
else status = MagickFalse;
} else if (IsArg (pa, "d", "degreePoly")==MagickTrue) {
NEXTARG;
psi->polyDegree = atoi(argv[i]);
} else if (IsArg (pa, "w", "weightLast")==MagickTrue) {
NEXTARG;
psi->BottomLineWeight = atof(argv[i]);
} else if (IsArg (pa, "wa", "weightAlpha")==MagickTrue) {
psi->weightAlpha = MagickTrue;
} else if (IsArg (pa, "f", "file")==MagickTrue) {
NEXTARG;
if (strcasecmp (argv[i], "stdout")==0) psi->fh_data = stdout;
else if (strcasecmp (argv[i], "stderr")==0) psi->fh_data = stderr;
else status = MagickFalse;
} else if (IsArg (pa, "v", "verbose")==MagickTrue) {
psi->verbose = MagickTrue;
} else if (IsArg (pa, "version", "version")==MagickTrue) {
fprintf (stdout, "%s\n", VERSION);
} else {
fprintf (stderr, "cols2mat: ERROR: unknown option [%s]\n", pa);
status = MagickFalse;
}
}
if (psi->polyDegree < 0) {
fprintf (stdout, "polyDegree is less than zero.\n");
status = MagickFalse;
}
if (psi->verbose) {
fprintf (stderr, "cols2mat options: ");
if (!psi->doTrans) fprintf (stderr, " noTrans");
fprintf (stderr, " method ");
switch (psi->c2mType) {
case ctCross:
fprintf (stderr, "Cross");
break;
case ctNoCross:
fprintf (stderr, "NoCross");
break;
case ctNoCrossPoly:
fprintf (stderr, "NoCrossPoly degreePoly %i", psi->polyDegree);
break;
case ctGainOnly:
fprintf (stderr, "GainOnly");
break;
}
fprintf (stderr, " weightLast %g", psi->BottomLineWeight);
if (psi->weightAlpha) fprintf (stderr, " weightAlpha");
if (psi->fh_data == stdout) fprintf (stderr, " file stdout");
if (psi->verbose) fprintf (stderr, " verbose");
fprintf (stderr, "\n");
}
if (status == MagickFalse)
usage ();
return (status);
}
// The next function takes an image list,
// may modify it, and returns a status.
//
static MagickBooleanType cols2mat (
Image **images,
cols2matT * psi,
ExceptionInfo *exception)
{
if ((*images)->debug != MagickFalse)
(void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",(*images)->filename);
int ListLen = (int)GetImageListLength(*images);
if (ListLen < 2 || ListLen > 3) {
fprintf (stderr, "cols2mat needs 2 or 3 images\n");
return MagickFalse;
}
Image * imgA = *images;
Image * imgB = GetNextImageInList (imgA);
assert(imgA->signature == MAGICK_CORE_SIG);
assert(imgB->signature == MAGICK_CORE_SIG);
Image * src_img = imgA;
if (ListLen == 3) {
src_img = GetNextImageInList (imgB);
assert(src_img->signature == MAGICK_CORE_SIG);
}
if (psi->verbose) {
fprintf (stderr, "cols2mat: imgA [%s] %ix%i depth is %i\n",
imgA->filename,
(int)imgA->columns, (int)imgA->rows,
(int)imgA->depth);
fprintf (stderr, "cols2mat: ListLen=%i\n", ListLen);
}
cols2matsT cols2mats;
InitCols2mat (&cols2mats);
cols2mats.verbose = psi->verbose;
cols2mats.weightAlpha = psi->weightAlpha;
cols2mats.c2mType = psi->c2mType;
cols2mats.polyDegree = psi->polyDegree;
cols2mats.BottomLineWeight = psi->BottomLineWeight;
cols2mats.kernel = NULL;
if (!cols2matGen (imgA, imgB, &cols2mats, psi->fh_data, exception))
return MagickFalse;
if (!cols2mats.solutionFound) {
fprintf (stderr, "cols2mat: No solution\n");
return MagickFalse;
}
if (psi->doTrans) {
Image * new_img = NULL;
switch (cols2mats.c2mType) {
case ctNoCrossPoly:
new_img = CloneImage (src_img, 0, 0, MagickTrue, exception);
if (!new_img) return MagickFalse;
#if IMV6OR7==6
if (!FunctionImageChannel(new_img, RedChannel,
PolynomialFunction, psi->polyDegree+1,
cols2mats.polyCoeff[0], exception)) return MagickFalse;
if (!FunctionImageChannel(new_img, GreenChannel,
PolynomialFunction, psi->polyDegree+1,
cols2mats.polyCoeff[1], exception)) return MagickFalse;
if (!FunctionImageChannel(new_img, BlueChannel,
PolynomialFunction, psi->polyDegree+1,
cols2mats.polyCoeff[2], exception)) return MagickFalse;
#else
ChannelType channel_mask = SetImageChannelMask (new_img, RedChannel);
if (!FunctionImage(new_img,
PolynomialFunction, psi->polyDegree+1,
cols2mats.polyCoeff[0], exception)) return MagickFalse;
SetImageChannelMask (new_img, GreenChannel);
if (!FunctionImage(new_img,
PolynomialFunction, psi->polyDegree+1,
cols2mats.polyCoeff[1], exception)) return MagickFalse;
SetImageChannelMask (new_img, BlueChannel);
if (!FunctionImage(new_img,
PolynomialFunction, psi->polyDegree+1,
cols2mats.polyCoeff[2], exception)) return MagickFalse;
SetImageChannelMask(new_img,channel_mask);
#endif
break;
default:
new_img = ColorMatrixImage (src_img, cols2mats.kernel, exception);
if (!new_img) return MagickFalse;
}
DeleteImageFromList (&imgB);
if (ListLen == 3) DeleteImageFromList (&src_img);
ReplaceImageInList (images, new_img);
// Replace messes up the images pointer. Make it good:
*images = GetFirstImageInList (new_img);
}
if (cols2mats.kernel) {
cols2mats.kernel = DestroyKernelInfo (cols2mats.kernel);
}
return MagickTrue;
}
ModuleExport size_t cols2matImage (
Image **images,
const int argc,
const char **argv,
ExceptionInfo *exception)
{
cols2matT si;
assert(images != (Image **) NULL);
assert(*images != (Image *) NULL);
assert((*images)->signature == MAGICK_CORE_SIG);
MagickBooleanType status = menu (argc, argv, &si);
if (!status) return -1;
si.precision = GetMagickPrecision();
status = cols2mat (images, &si, exception);
if (!status) return (-1);
return(MagickImageFilterSignature);
}
/* Updated:
3-April-2018 for v7.0.7-28
*/
#ifndef COLS2MAT_INC
#define COLS2MAT_INC
#include "chklist.h"
typedef enum {
ctCross,
ctNoCross,
ctNoCrossPoly,
ctGainOnly
} c2mTypeT;
#define NUM_CH 3
typedef struct {
MagickBooleanType
weightAlpha,
verbose,
doWarn;
c2mTypeT
c2mType;
int
polyDegree;
double
**matrix,
**vectors,
*terms,
*results;
double
*polyCoeff[NUM_CH];
double
BottomLineWeight;
// Used internally
MagickBooleanType
imgAhasAlpha,
imgBhasAlpha;
// Returned results
KernelInfo
*kernel;
MagickBooleanType
solutionFound;
} cols2matsT;
// Option to add zero and 100% for matrix calculation?
static void InitCols2mat (
cols2matsT * c2m)
{
c2m->verbose = MagickFalse;
c2m->weightAlpha = MagickFalse;
c2m->doWarn = MagickTrue;
c2m->c2mType = ctCross;
c2m->polyDegree = 2;
c2m->matrix = c2m->vectors = NULL;
c2m->terms = c2m->results = NULL;
c2m->kernel = NULL;
c2m->BottomLineWeight = 1.0;
int c;
for (c = 0; c < NUM_CH; c++) c2m->polyCoeff[c] = NULL;
}
static MagickBooleanType AllocCols2mat (
cols2matsT * c2m,
int nTerms,
int nResults
)
{
c2m->terms = (double *)AcquireMagickMemory (nTerms * sizeof(double));
if (!c2m->terms) return MagickFalse;
c2m->results = (double *)AcquireMagickMemory (nResults * sizeof(double));
if (!c2m->results) return MagickFalse;
c2m->matrix = AcquireMagickMatrix (nTerms, nTerms);
if (!c2m->matrix) return MagickFalse;
c2m->vectors = AcquireMagickMatrix (nResults, nTerms);
if (!c2m->vectors) return MagickFalse;
return MagickTrue;
}
static void DeAllocCols2mat (
cols2matsT * c2m,
int nTerms,
int nResults
)
{
c2m->vectors = RelinquishMagickMatrix (c2m->vectors, nResults);
c2m->matrix = RelinquishMagickMatrix (c2m->matrix, nTerms);
c2m->results = RelinquishMagickMemory (c2m->results);
c2m->terms = RelinquishMagickMemory (c2m->terms);
}
static void LeastSquaresAddTermsWt (
double **matrix,
double **vectors,
const double *terms,
const double *results,
const size_t rank,
const size_t number_vectors,
double weight)
{
register ssize_t
i,
j;
for (j=0; j < (ssize_t) rank; j++)
{
for (i=0; i < (ssize_t) rank; i++)
matrix[i][j] += weight*terms[i]*terms[j];
for (i=0; i < (ssize_t) number_vectors; i++)
vectors[i][j] += weight*results[i]*terms[j];
}
}
static double inline AlphaWeight (
cols2matsT *c2m,
Image * imgA,
Image * imgB,
const VIEW_PIX_PTR *pA,
const VIEW_PIX_PTR *pB
)
{
double wt = 1.0;
if (c2m->weightAlpha) {
if (c2m->imgAhasAlpha) {
wt = GET_PIXEL_ALPHA (imgA, pA) / QuantumRange;
}
if (c2m->imgBhasAlpha) {
wt *= GET_PIXEL_ALPHA (imgB, pB) / QuantumRange;
}
}
return wt;
}
static MagickBooleanType cols2matCross (
Image * imgA,
Image * imgB,
cols2matsT *c2m,
ExceptionInfo *exception
)
/* From two images, same size,
creates color matrix with 36 elements
that if applied to imgA would make it look like imgB.
"Cross" -- includes parameter for cross-freed between channels.
*/
{
c2m->solutionFound = MagickFalse;
if (imgA->columns != imgB->columns || imgA->rows != imgB->rows) {
fprintf (stderr, "cols2mat: images not the same size\n");
return MagickFalse;
}
#define nTermsC 4
#define nResultsC 3
if (!AllocCols2mat (c2m, nTermsC, nResultsC)) { fprintf (stderr, "alloc fail\n"); return MagickFalse; }
CacheView * imgA_view = AcquireVirtualCacheView (imgA, exception);
CacheView * imgB_view = AcquireVirtualCacheView (imgB, exception);
c2m->terms[3] = 1;
ssize_t y;
for (y=0; y < imgA->rows; y++) {
const VIEW_PIX_PTR *pA = GetCacheViewVirtualPixels (
imgA_view, 0, y, imgA->columns, 1, exception);
if (!pA) return MagickFalse;
const VIEW_PIX_PTR *pB = GetCacheViewVirtualPixels (
imgB_view, 0, y, imgB->columns, 1, exception);
if (!pB) return MagickFalse;
double LnWeight = (y==imgA->rows-1) ? c2m->BottomLineWeight : 1.0;
ssize_t x;
for (x=0; x < imgA->columns; x++) {
c2m->terms[0] = GET_PIXEL_RED (imgA, pA) / QuantumRange;
c2m->terms[1] = GET_PIXEL_GREEN (imgA, pA) / QuantumRange;
c2m->terms[2] = GET_PIXEL_BLUE (imgA, pA) / QuantumRange;
c2m->results[0] = GET_PIXEL_RED (imgB, pB) / QuantumRange;
c2m->results[1] = GET_PIXEL_GREEN (imgB, pB) / QuantumRange;
c2m->results[2] = GET_PIXEL_BLUE (imgB, pB) / QuantumRange;
LeastSquaresAddTermsWt (
c2m->matrix, c2m->vectors, c2m->terms, c2m->results,
nTermsC, nResultsC, LnWeight * AlphaWeight (c2m,imgA,imgB,pA,pB));
pA += Inc_ViewPixPtr (imgA);
pB += Inc_ViewPixPtr (imgB);
}
}
if (c2m->verbose > 1) {
fprintf (stderr, "cols2mat: Matrix is:\n");
int u, v;
for (u = 0; u < nTermsC; u++) {
for (v = 0; v < nTermsC; v++) {
fprintf (stderr, " %g", c2m->matrix[u][v]);
}
fprintf (stderr, "\n");
}
}
if (!GaussJordanElimination(c2m->matrix,c2m->vectors,nTermsC,nResultsC) ) {
fprintf (stderr, "No solution.\n");
} else {
if (c2m->verbose > 1) {
int j, k;
fprintf (stderr, "cols2mat: Vectors is:\n");
for (j = 0; j < nResultsC; j++) {
for (k = 0; k < nTermsC; k++) {
fprintf (stderr, " %g", c2m->vectors[j][k]);
}
fprintf (stderr, "\n");
}
}
c2m->kernel->values[0] = c2m->vectors[0][0];
c2m->kernel->values[1] = c2m->vectors[0][1];
c2m->kernel->values[2] = c2m->vectors[0][2];
c2m->kernel->values[5] = c2m->vectors[0][3];
c2m->kernel->values[6] = c2m->vectors[1][0];
c2m->kernel->values[7] = c2m->vectors[1][1];
c2m->kernel->values[8] = c2m->vectors[1][2];
c2m->kernel->values[11] = c2m->vectors[1][3];
c2m->kernel->values[12] = c2m->vectors[2][0];
c2m->kernel->values[13] = c2m->vectors[2][1];
c2m->kernel->values[14] = c2m->vectors[2][2];
c2m->kernel->values[17] = c2m->vectors[2][3];
c2m->solutionFound = MagickTrue;
}
imgB_view = DestroyCacheView (imgB_view);
imgA_view = DestroyCacheView (imgA_view);
DeAllocCols2mat (c2m, nTermsC, nResultsC);
#undef nTermsC
#undef nResultsC
// if (c2m->verbose) {
// ShowKernelInfo (c2m->kernel);
// }
return MagickTrue;
}
static MagickBooleanType cols2matNoCross (
Image * imgA,
Image * imgB,
cols2matsT * c2m,
ExceptionInfo *exception
)
/* From two images, same size,
creates color matrix with 36 elements
that if applied to imgA would make it look like imgB.
"NoCross" -- excludes parameters for cross-freed between channels.
*/
{
c2m->solutionFound = MagickFalse;
#define nTermsNC 2
#define nResultsNC 1
if (!AllocCols2mat (c2m, nTermsNC, nResultsNC)) { fprintf (stderr, "alloc fail\n"); return MagickFalse; }
CacheView * imgA_view = AcquireVirtualCacheView (imgA, exception);
CacheView * imgB_view = AcquireVirtualCacheView (imgB, exception);
ssize_t y;
int nFound = 0;
// Solve for red.
//
for (y=0; y < imgA->rows; y++) {
const VIEW_PIX_PTR *pA = GetCacheViewVirtualPixels (
imgA_view, 0, y, imgA->columns, 1, exception);
if (!pA) return MagickFalse;
const VIEW_PIX_PTR *pB = GetCacheViewVirtualPixels (
imgB_view, 0, y, imgB->columns, 1, exception);
if (!pB) return MagickFalse;
double LnWeight = (y==imgA->rows-1) ? c2m->BottomLineWeight : 1.0;
ssize_t x;
for (x=0; x < imgA->columns; x++) {
c2m->terms[0] = GET_PIXEL_RED (imgA, pA) / QuantumRange;
c2m->terms[1] = 1;
c2m->results[0] = GET_PIXEL_RED (imgB, pB) / QuantumRange;
LeastSquaresAddTermsWt (
c2m->matrix, c2m->vectors, c2m->terms, c2m->results,
nTermsNC, nResultsNC, LnWeight * AlphaWeight (c2m,imgA,imgB,pA,pB));
pA += Inc_ViewPixPtr (imgA);
pB += Inc_ViewPixPtr (imgB);
}
}
if (!GaussJordanElimination(c2m->matrix,c2m->vectors,nTermsNC,nResultsNC) ) {
fprintf (stderr, "No solution R.\n");
} else {
c2m->kernel->values[0] = c2m->vectors[0][0];
c2m->kernel->values[5] = c2m->vectors[0][1];
nFound++;
}
DeAllocCols2mat (c2m, nTermsNC, nResultsNC);
if (!AllocCols2mat (c2m, nTermsNC, nResultsNC)) { fprintf (stderr, "alloc fail\n"); return MagickFalse; }
// Solve for green.
//
for (y=0; y < imgA->rows; y++) {
const VIEW_PIX_PTR *pA = GetCacheViewVirtualPixels (
imgA_view, 0, y, imgA->columns, 1, exception);
if (!pA) return MagickFalse;
const VIEW_PIX_PTR *pB = GetCacheViewVirtualPixels (
imgB_view, 0, y, imgB->columns, 1, exception);
if (!pB) return MagickFalse;
double LnWeight = (y==imgA->rows-1) ? c2m->BottomLineWeight : 1.0;
ssize_t x;
for (x=0; x < imgA->columns; x++) {
c2m->terms[0] = GET_PIXEL_GREEN (imgA, pA) / QuantumRange;
c2m->terms[1] = 1;
c2m->results[0] = GET_PIXEL_GREEN (imgB, pB) / QuantumRange;
LeastSquaresAddTermsWt (
c2m->matrix, c2m->vectors, c2m->terms, c2m->results,
nTermsNC, nResultsNC, LnWeight * AlphaWeight (c2m,imgA,imgB,pA,pB));
pA += Inc_ViewPixPtr (imgA);
pB += Inc_ViewPixPtr (imgB);
}
}
if (!GaussJordanElimination(c2m->matrix,c2m->vectors,nTermsNC,nResultsNC) ) {
fprintf (stderr, "No solution G.\n");
} else {
c2m->kernel->values[7] = c2m->vectors[0][0];
c2m->kernel->values[11] = c2m->vectors[0][1];
nFound++;
}
DeAllocCols2mat (c2m, nTermsNC, nResultsNC);
if (!AllocCols2mat (c2m, nTermsNC, nResultsNC)) { fprintf (stderr, "alloc fail\n"); return MagickFalse; }
// Solve for blue.
//
for (y=0; y < imgA->rows; y++) {
const VIEW_PIX_PTR *pA = GetCacheViewVirtualPixels (
imgA_view, 0, y, imgA->columns, 1, exception);
if (!pA) return MagickFalse;
const VIEW_PIX_PTR *pB = GetCacheViewVirtualPixels (
imgB_view, 0, y, imgB->columns, 1, exception);
if (!pB) return MagickFalse;
double LnWeight = (y==imgA->rows-1) ? c2m->BottomLineWeight : 1.0;
ssize_t x;
for (x=0; x < imgA->columns; x++) {
c2m->terms[0] = GET_PIXEL_BLUE (imgA, pA) / QuantumRange;
c2m->terms[1] = 1;
c2m->results[0] = GET_PIXEL_BLUE (imgB, pB) / QuantumRange;
LeastSquaresAddTermsWt (
c2m->matrix, c2m->vectors, c2m->terms, c2m->results,
nTermsNC, nResultsNC, LnWeight * AlphaWeight (c2m,imgA,imgB,pA,pB));
pA += Inc_ViewPixPtr (imgA);
pB += Inc_ViewPixPtr (imgB);
}
}
if (!GaussJordanElimination(c2m->matrix,c2m->vectors,nTermsNC,nResultsNC) ) {
fprintf (stderr, "No solution B.\n");
} else {
c2m->kernel->values[14] = c2m->vectors[0][0];
c2m->kernel->values[17] = c2m->vectors[0][1];
nFound++;
}
c2m->solutionFound = (nFound ==3);
imgB_view = DestroyCacheView (imgB_view);
imgA_view = DestroyCacheView (imgA_view);
DeAllocCols2mat (c2m, nTermsNC, nResultsNC);
#undef nTermsNC
#undef nResultsNC
// if (c2m->verbose) {
// ShowKernelInfo (c2m->kernel);
// }
return MagickTrue;
}
static MagickBooleanType cols2matGainOnly (
Image * imgA,
Image * imgB,
cols2matsT * c2m,
ExceptionInfo *exception
)
/* From two images, same size,
creates color matrix with 36 elements
that if applied to imgA would make it look like imgB.
"GainOnly" -- no cross-feed, no bias.
*/
{
c2m->solutionFound = MagickFalse;
#define nTermsGO 1
#define nResultsGO 1
if (!AllocCols2mat (c2m, nTermsGO, nResultsGO)) { fprintf (stderr, "alloc fail\n"); return MagickFalse; }
CacheView * imgA_view = AcquireVirtualCacheView (imgA, exception);
CacheView * imgB_view = AcquireVirtualCacheView (imgB, exception);
ssize_t y;
int nFound = 0;
// Solve for red.
//
for (y=0; y < imgA->rows; y++) {
const VIEW_PIX_PTR *pA = GetCacheViewVirtualPixels (
imgA_view, 0, y, imgA->columns, 1, exception);
if (!pA) return MagickFalse;
const VIEW_PIX_PTR *pB = GetCacheViewVirtualPixels (
imgB_view, 0, y, imgB->columns, 1, exception);
if (!pB) return MagickFalse;
double LnWeight = (y==imgA->rows-1) ? c2m->BottomLineWeight : 1.0;
ssize_t x;
for (x=0; x < imgA->columns; x++) {
c2m->terms[0] = GET_PIXEL_RED (imgA, pA) / QuantumRange;
c2m->results[0] = GET_PIXEL_RED (imgB, pB) / QuantumRange;
LeastSquaresAddTermsWt (
c2m->matrix, c2m->vectors, c2m->terms, c2m->results,
nTermsGO, nResultsGO, LnWeight * AlphaWeight (c2m,imgA,imgB,pA,pB));
pA += Inc_ViewPixPtr (imgA);
pB += Inc_ViewPixPtr (imgB);
}
}
if (!GaussJordanElimination(c2m->matrix,c2m->vectors,nTermsGO,nResultsGO) ) {
fprintf (stderr, "No solution R.\n");
} else {
c2m->kernel->values[0] = c2m->vectors[0][0];
nFound++;
}
DeAllocCols2mat (c2m, nTermsGO, nResultsGO);
if (!AllocCols2mat (c2m, nTermsGO, nResultsGO)) { fprintf (stderr, "alloc fail\n"); return MagickFalse; }
// Solve for green.
//
for (y=0; y < imgA->rows; y++) {
const VIEW_PIX_PTR *pA = GetCacheViewVirtualPixels (
imgA_view, 0, y, imgA->columns, 1, exception);
if (!pA) return MagickFalse;
const VIEW_PIX_PTR *pB = GetCacheViewVirtualPixels (
imgB_view, 0, y, imgB->columns, 1, exception);
if (!pB) return MagickFalse;
double LnWeight = (y==imgA->rows-1) ? c2m->BottomLineWeight : 1.0;
ssize_t x;
for (x=0; x < imgA->columns; x++) {
c2m->terms[0] = GET_PIXEL_GREEN (imgA, pA) / QuantumRange;
c2m->results[0] = GET_PIXEL_GREEN (imgB, pB) / QuantumRange;
LeastSquaresAddTermsWt (
c2m->matrix, c2m->vectors, c2m->terms, c2m->results,
nTermsGO, nResultsGO, LnWeight * AlphaWeight (c2m,imgA,imgB,pA,pB));
pA += Inc_ViewPixPtr (imgA);
pB += Inc_ViewPixPtr (imgB);
}
}
if (!GaussJordanElimination(c2m->matrix,c2m->vectors,nTermsGO,nResultsGO) ) {
fprintf (stderr, "No solution G.\n");
} else {
c2m->kernel->values[7] = c2m->vectors[0][0];
nFound++;
}
DeAllocCols2mat (c2m, nTermsGO, nResultsGO);
if (!AllocCols2mat (c2m, nTermsGO, nResultsGO)) { fprintf (stderr, "alloc fail\n"); return MagickFalse; }
// Solve for blue.
//
for (y=0; y < imgA->rows; y++) {
const VIEW_PIX_PTR *pA = GetCacheViewVirtualPixels (
imgA_view, 0, y, imgA->columns, 1, exception);
if (!pA) return MagickFalse;
const VIEW_PIX_PTR *pB = GetCacheViewVirtualPixels (
imgB_view, 0, y, imgB->columns, 1, exception);
if (!pB) return MagickFalse;
double LnWeight = (y==imgA->rows-1) ? c2m->BottomLineWeight : 1.0;
ssize_t x;
for (x=0; x < imgA->columns; x++) {
c2m->terms[0] = GET_PIXEL_BLUE (imgA, pA) / QuantumRange;
c2m->results[0] = GET_PIXEL_BLUE (imgB, pB) / QuantumRange;
LeastSquaresAddTermsWt (
c2m->matrix, c2m->vectors, c2m->terms, c2m->results,
nTermsGO, nResultsGO, LnWeight * AlphaWeight (c2m,imgA,imgB,pA,pB));
pA += Inc_ViewPixPtr (imgA);
pB += Inc_ViewPixPtr (imgB);
}
}
if (!GaussJordanElimination(c2m->matrix,c2m->vectors,nTermsGO,nResultsGO) ) {
fprintf (stderr, "No solution B.\n");
} else {
c2m->kernel->values[14] = c2m->vectors[0][0];
nFound++;
}
c2m->solutionFound = (nFound ==3);
imgB_view = DestroyCacheView (imgB_view);
imgA_view = DestroyCacheView (imgA_view);
DeAllocCols2mat (c2m, nTermsGO, nResultsGO);
#undef nTermsGO
#undef nResultsGO
// if (c2m->verbose) {
// ShowKernelInfo (c2m->kernel);
// }
return MagickTrue;
}
static MagickBooleanType cols2matNoCrossPoly (
Image * imgA,
Image * imgB,
cols2matsT * c2m,
ExceptionInfo *exception
)
{
c2m->solutionFound = MagickFalse;
int nTerms = c2m->polyDegree + 1;
int nResults = 1;
// Note: terms[0] is term with largest degree.
int c;
for (c = 0; c < NUM_CH; c++) {
// FIXME: This is never Relinguished.
c2m->polyCoeff[c] = (double *)AcquireMagickMemory (nTerms * sizeof(double));
if (!c2m->polyCoeff[c]) return MagickFalse;
}
CacheView * imgA_view = AcquireVirtualCacheView (imgA, exception);
CacheView * imgB_view = AcquireVirtualCacheView (imgB, exception);
int i;
ssize_t y;
int nFound = 0;
if (!AllocCols2mat (c2m, nTerms, nResults)) { fprintf (stderr, "alloc fail\n"); return MagickFalse; }
// Solve for red.
//
for (y=0; y < imgA->rows; y++) {
const VIEW_PIX_PTR *pA = GetCacheViewVirtualPixels (
imgA_view, 0, y, imgA->columns, 1, exception);
if (!pA) return MagickFalse;
const VIEW_PIX_PTR *pB = GetCacheViewVirtualPixels (
imgB_view, 0, y, imgB->columns, 1, exception);
if (!pB) return MagickFalse;
double LnWeight = (y==imgA->rows-1) ? c2m->BottomLineWeight : 1.0;
ssize_t x;
for (x=0; x < imgA->columns; x++) {
double v = GET_PIXEL_RED (imgA, pA) / QuantumRange;
c2m->terms[nTerms-1] = 1;
for (i = 1; i < nTerms; i++) {
int j = nTerms-1-i;
c2m->terms[j] = v * c2m->terms[j+1];
// printf ("term %i is %g\n", j, c2m->terms[j]);
}
c2m->results[0] = GET_PIXEL_RED (imgB, pB) / QuantumRange;
LeastSquaresAddTermsWt (
c2m->matrix, c2m->vectors, c2m->terms, c2m->results,
nTerms, nResults, LnWeight * AlphaWeight (c2m,imgA,imgB,pA,pB));
pA += Inc_ViewPixPtr (imgA);
pB += Inc_ViewPixPtr (imgB);
}
}
for (i = 0; i < nTerms; i++) {
// printf ("term %i is %g\n", i, c2m->terms[i]);
}
if (!GaussJordanElimination(c2m->matrix,c2m->vectors,nTerms,nResults) ) {
fprintf (stderr, "No solution R.\n");
} else {
for (i = 0; i < nTerms; i++) {
// printf ("term %i is %g; coeff is ", i, c2m->terms[i]);
// printf (" %g\n", c2m->vectors[0][i]);
c2m->polyCoeff[0][i] = c2m->vectors[0][i];
}
// printf ("\n");
nFound++;
}
DeAllocCols2mat (c2m, nTerms, nResults);
if (!AllocCols2mat (c2m, nTerms, nResults)) { fprintf (stderr, "alloc fail\n"); return MagickFalse; }
// Solve for green.
//
for (y=0; y < imgA->rows; y++) {
const VIEW_PIX_PTR *pA = GetCacheViewVirtualPixels (
imgA_view, 0, y, imgA->columns, 1, exception);
if (!pA) return MagickFalse;
const VIEW_PIX_PTR *pB = GetCacheViewVirtualPixels (
imgB_view, 0, y, imgB->columns, 1, exception);
if (!pB) return MagickFalse;
double LnWeight = (y==imgA->rows-1) ? c2m->BottomLineWeight : 1.0;
ssize_t x;
for (x=0; x < imgA->columns; x++) {
double v = GET_PIXEL_GREEN (imgA, pA) / QuantumRange;
c2m->terms[nTerms-1] = 1;
for (i = 1; i < nTerms; i++) {
int j = nTerms-1-i;
c2m->terms[j] = v * c2m->terms[j+1];
// printf ("term %i is %g\n", j, c2m->terms[j]);
}
c2m->results[0] = GET_PIXEL_GREEN (imgB, pB) / QuantumRange;
LeastSquaresAddTermsWt (
c2m->matrix, c2m->vectors, c2m->terms, c2m->results,
nTerms, nResults, LnWeight * AlphaWeight (c2m,imgA,imgB,pA,pB));
pA += Inc_ViewPixPtr (imgA);
pB += Inc_ViewPixPtr (imgB);
}
}
for (i = 0; i < nTerms; i++) {
// printf ("term %i is %g\n", i, c2m->terms[i]);
}
if (!GaussJordanElimination(c2m->matrix,c2m->vectors,nTerms,nResults) ) {
fprintf (stderr, "No solution G.\n");
} else {
for (i = 0; i < nTerms; i++) {
// printf ("term %i is %g; coeff is ", i, c2m->terms[i]);
// printf (" %g\n", c2m->vectors[0][i]);
c2m->polyCoeff[1][i] = c2m->vectors[0][i];
}
// printf ("\n");
nFound++;
}
DeAllocCols2mat (c2m, nTerms, nResults);
if (!AllocCols2mat (c2m, nTerms, nResults)) { fprintf (stderr, "alloc fail\n"); return MagickFalse; }
// Solve for blue.
//
for (y=0; y < imgA->rows; y++) {
const VIEW_PIX_PTR *pA = GetCacheViewVirtualPixels (
imgA_view, 0, y, imgA->columns, 1, exception);
if (!pA) return MagickFalse;
const VIEW_PIX_PTR *pB = GetCacheViewVirtualPixels (
imgB_view, 0, y, imgB->columns, 1, exception);
if (!pB) return MagickFalse;
double LnWeight = (y==imgA->rows-1) ? c2m->BottomLineWeight : 1.0;
ssize_t x;
for (x=0; x < imgA->columns; x++) {
double v = GET_PIXEL_BLUE (imgA, pA) / QuantumRange;
c2m->terms[nTerms-1] = 1;
for (i = 1; i < nTerms; i++) {
int j = nTerms-1-i;
c2m->terms[j] = v * c2m->terms[j+1];
// printf ("term %i is %g\n", j, c2m->terms[j]);
}
c2m->results[0] = GET_PIXEL_BLUE (imgB, pB) / QuantumRange;
LeastSquaresAddTermsWt (
c2m->matrix, c2m->vectors, c2m->terms, c2m->results,
nTerms, nResults, LnWeight * AlphaWeight (c2m,imgA,imgB,pA,pB));
pA += Inc_ViewPixPtr (imgA);
pB += Inc_ViewPixPtr (imgB);
}
}
// for (i = 0; i < nTerms; i++) {
// printf ("term %i is %g\n", i, c2m->terms[i]);
// }
if (!GaussJordanElimination(c2m->matrix,c2m->vectors,nTerms,nResults) ) {
fprintf (stderr, "No solution B.\n");
} else {
for (i = 0; i < nTerms; i++) {
// printf ("term %i is %g; coeff is ", i, c2m->terms[i]);
// printf (" %g\n", c2m->vectors[0][i]);
c2m->polyCoeff[2][i] = c2m->vectors[0][i];
}
// printf ("\n");
nFound++;
}
DeAllocCols2mat (c2m, nTerms, nResults);
c2m->solutionFound = (nFound == NUM_CH);
imgB_view = DestroyCacheView (imgB_view);
imgA_view = DestroyCacheView (imgA_view);
return MagickTrue;
}
static void WrKernelVals (cols2matsT * c2m, FILE * fh, char * prefix)
{
int precision = GetMagickPrecision();
fprintf (fh, "%s", prefix);
int i;
for (i = 0; i < c2m->kernel->height * c2m->kernel->width; i++) {
if (i) fprintf (fh, ",");
fprintf (fh, "%.*g", precision, c2m->kernel->values[i]);
}
fprintf (fh, "\n");
if (c2m->doWarn) {
char *chNames[] = {"Red", "Green", "Blue"};
int c,k;
i = 0;
for (c = 0; c < 2; c++) {
double sumPosRow = 0.0;
double last = 0.0;
for (k = 0; k < 6; k++) {
if (c2m->kernel->values[i] > 0) sumPosRow += c2m->kernel->values[i];
if (k==5) last = c2m->kernel->values[i];
i++;
}
if (last < 0.0) {
fprintf (fh, "Warning: may clip %s shadows\n", chNames[c]);
}
if (sumPosRow > 1.0) {
fprintf (fh, "Warning: may clip %s highlights\n", chNames[c]);
}
}
}
}
static void WrPolynomials (cols2matsT * c2m, FILE * fh)
{
char *chNames[] = {"Red", "Green", "Blue"};
int precision = GetMagickPrecision();
int nTerms = c2m->polyDegree + 1;
int c, i;
for (c = 0; c < NUM_CH; c++) {
double sumPoly = 0.0;
fprintf (fh, "Poly%s=", chNames[c]);
for (i = 0; i < nTerms; i++) {
if (i) fprintf (fh, ",");
fprintf (fh, "%.*g", precision, c2m->polyCoeff[c][i]);
sumPoly += c2m->polyCoeff[c][i];
}
fprintf (fh, "\n");
if (c2m->doWarn) {
if (c2m->polyCoeff[c][nTerms-1] < 0.0) {
fprintf (fh, "Warning: may clip %s shadows\n", chNames[c]);
}
if (sumPoly > 1.0) {
fprintf (fh, "Warning: may clip %s highlights\n", chNames[c]);
}
}
}
}
static MagickBooleanType cols2matGen (
Image * imgA,
Image * imgB,
cols2matsT * c2m,
FILE *fh_out,
ExceptionInfo *exception
)
// This may acquire a kernel. Caller should destroy it.
{
c2m->solutionFound = MagickFalse;
if (imgA->columns != imgB->columns || imgA->rows != imgB->rows) {
fprintf (stderr, "cols2mat: images not the same size\n");
return MagickFalse;
}
// FIXME: Following is a kludge.
// We merely want to ensure we have three colour channels.
#if IMV6OR7==6
TransformImageColorspace (imgA, sRGBColorspace);
TransformImageColorspace (imgB, sRGBColorspace);
#else
TransformImageColorspace (imgA, sRGBColorspace, exception);
TransformImageColorspace (imgB, sRGBColorspace, exception);
#endif
c2m->kernel = ACQUIRE_KERNEL(
"6x6:1,0,0,0,0,0,0,1,0,0,0,0,0,0,1,0,0,0,0,0,0,1,0,0,0,0,0,0,1,0,0,0,0,0,0,1",
exception);
if (!c2m->kernel) {
fprintf (stderr, "AcquireKernelInfo failed\n");
return MagickFalse;
}
c2m->imgAhasAlpha = IS_ALPHA_CH(imgA);
c2m->imgBhasAlpha = IS_ALPHA_CH(imgB);
MagickBooleanType okay = MagickFalse;
switch (c2m->c2mType) {
case ctCross:
okay = cols2matCross (imgA, imgB, c2m, exception);
break;
case ctNoCross:
okay = cols2matNoCross (imgA, imgB, c2m, exception);
break;
case ctNoCrossPoly:
okay = cols2matNoCrossPoly (imgA, imgB, c2m, exception);
break;
case ctGainOnly:
okay = cols2matGainOnly (imgA, imgB, c2m, exception);
break;
}
if (okay && fh_out) {
if (c2m->solutionFound) {
switch (c2m->c2mType) {
case ctNoCrossPoly:
WrPolynomials (c2m, fh_out);
break;
default:
WrKernelVals (c2m, fh_out, "c2matrix=");
}
} else {
fprintf (fh_out, "cols2mat: no solution found\n");
}
}
return okay;
}
static KernelInfo * MultKernels (KernelInfo *kA, KernelInfo *kB)
// From two equal-sized square kernels,
// create a new kernel, which caller should eventually destroy.
{
int nElements = kA->width;
if (nElements != kA->height
|| nElements != kB->height
|| nElements != kB->width)
{
return NULL;
}
KernelInfo * newK = CloneKernelInfo (kA);
if (!newK) return NULL;
int x, y, i;
for (i = 0; i < nElements*nElements; i++) {
newK->values[i] = 0;
}
for (y = 0; y < nElements; y++) {
for (x = 0; x < nElements; x++) {
int nNew = y * nElements + x;
for (i = 0; i < nElements; i++) {
int nA = y * nElements + i;
int nB = i * nElements + x;
newK->values[nNew] = kA->values[nA] * kB->values[nB];
}
}
}
return newK;
}
#endif
/* Updated:
11-December-2018 corrected typos in options.
23-December-2022 for blend method. Also loop through channels, instead of explicit R,G and B.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <math.h>
#include <ctype.h>
#include <float.h>
#include "vsn_defines.h"
typedef enum {
omLinear,
omPower,
omBlend
} oogMethodT;
typedef struct {
// Settings:
MagickBooleanType
verbose,
doForceMin,
doForceMax,
loGradSet,
hiGradSet;
oogMethodT
oogMethod;
double
loLimit,
hiLimit,
forceMin,
forceMax,
loGrad, // For blend method.
hiGrad; // For blend method.
int
// chanNum, // -1 for all channels together.
precision;
// Used internally:
MagickBooleanType
doLo,
doHi;
double
min, max,
a, b, c, d,
A0, B0,
A1, B1;
} oogboxT;
#define VERSION "oogbox v1.1 Copyright (c) 2017-2022 Alan Gibson"
static void usage (void)
{
printf ("Usage: -process 'oogbox [OPTION]...'\n");
printf ("Puts out-of-gamut values back in the box.\n");
printf ("\n");
printf (" m, method string Linear or Power or Blend [Power]\n");
printf (" l, loLimit number low limit for shadow processing [0.1]\n");
printf (" h, hiLimit number high limit for highlight processing [0.9]\n");
printf (" lg, loGradient number gradient for shadows [auto]\n");
printf (" hg, hiGradient number gradient for highlights [auto]\n");
printf (" fmn, forceMin number force minimum value (override calculation)\n");
printf (" fmx, forceMax number force maximum value (override calculation)\n");
//printf (" c, channel integer channel number, or -1 for all [-1]\n");
printf (" v, verbose write text information to stdout\n");
printf (" version write version information to stdout\n");
printf ("\n");
}
static MagickBooleanType IsArg (char * pa, char * ShtOpt, char * LongOpt)
{
// LocaleCompare is not case-sensitive,
// so we use strcmp for the short option.
if ((strcmp(pa, ShtOpt)==0) || (LocaleCompare(pa, LongOpt)==0))
return MagickTrue;
return MagickFalse;
}
#define NEXTARG \
if (i < argc-1) i++; \
else { \
fprintf (stderr, "oogbox: ERROR: [%s] needs argument\n", pa); \
status = MagickFalse; \
}
static MagickBooleanType menu(
const int argc,
const char **argv,
oogboxT * pob
)
// Returns MagickTrue if okay.
{
int
i;
MagickBooleanType
status = MagickTrue;
pob->oogMethod = omPower;
pob->loLimit = 0.1;
pob->hiLimit = 0.9;
//pob->chanNum = -1;
pob->doLo = pob->doHi = MagickFalse;
pob->doForceMin = pob->doForceMax = MagickFalse;
pob->forceMin = 0.0;
pob->forceMax = 1.0;
pob->a = pob->b = pob->c = pob->d = 0;
pob->A0 = pob->B0 = 0;
pob->A1 = pob->B1 = 0;
pob->loGradSet = pob->hiGradSet = MagickFalse;
pob->loGrad = 0;
pob->hiGrad = 0;
pob->verbose = MagickFalse;
for (i=0; i < argc; i++) {
char * pa = (char *)argv[i];
if (IsArg (pa, "m", "method")==MagickTrue) {
NEXTARG;
if (LocaleCompare(argv[i], "Linear")==0) pob->oogMethod = omLinear;
else if (LocaleCompare(argv[i], "Power")==0) pob->oogMethod = omPower;
else if (LocaleCompare(argv[i], "Blend")==0) pob->oogMethod = omBlend;
else {
fprintf (stderr, "oogbox: ERROR: unknown method [%s]\n", argv[i]);
status = MagickFalse;
}
} else if (IsArg (pa, "l", "loLimit")==MagickTrue) {
NEXTARG;
pob->loLimit = atof(argv[i]);
} else if (IsArg (pa, "h", "hiLimit")==MagickTrue) {
NEXTARG;
pob->hiLimit = atof(argv[i]);
} else if (IsArg (pa, "lg", "loGradient")==MagickTrue) {
NEXTARG;
pob->loGrad = atof(argv[i]);
pob->loGradSet = MagickTrue;
} else if (IsArg (pa, "hg", "hiGradient")==MagickTrue) {
NEXTARG;
pob->hiGrad = atof(argv[i]);
pob->hiGradSet = MagickTrue;
} else if (IsArg (pa, "fmn", "forceMin")==MagickTrue) {
NEXTARG;
pob->forceMin = atof(argv[i]);
pob->doForceMin = MagickTrue;
} else if (IsArg (pa, "fmx", "forceMax")==MagickTrue) {
NEXTARG;
pob->forceMax = atof(argv[i]);
pob->doForceMax = MagickTrue;
// } else if (IsArg (pa, "c", "channel")==MagickTrue) {
// NEXTARG;
// pob->chanNum = atoi(argv[i]);
} else if (IsArg (pa, "v", "verbose")==MagickTrue) {
pob->verbose = MagickTrue;
} else if (IsArg (pa, "version", "version")==MagickTrue) {
fprintf (stdout, "%s\n", VERSION);
} else {
fprintf (stderr, "oogbox: ERROR: unknown option [%s]\n", pa);
status = MagickFalse;
}
}
if (pob->verbose) {
fprintf (stderr, "oogbox options: ");
fprintf (stderr, " method ");
switch (pob->oogMethod) {
case omLinear: fprintf (stderr, "Linear"); break;
case omPower: fprintf (stderr, "Power"); break;
case omBlend: fprintf (stderr, "Blend"); break;
}
fprintf (stderr, " loLimit %.*g", pob->precision, pob->loLimit);
fprintf (stderr, " hiLimit %.*g", pob->precision, pob->hiLimit);
if (pob->doForceMin) fprintf (stderr, " forceMin %.*g", pob->precision, pob->forceMin);
if (pob->doForceMax) fprintf (stderr, " forceMax %.*g", pob->precision, pob->forceMax);
if (pob->oogMethod == omBlend) {
if (pob->loGradSet) fprintf (stderr, " loGrad %.*g", pob->precision, pob->loGrad);
if (pob->hiGradSet) fprintf (stderr, " hiGrad %.*g", pob->precision, pob->hiGrad);
}
// fprintf (stderr, " channel %i", pob->chanNum);
if (pob->verbose) fprintf (stderr, " verbose");
fprintf (stderr, "\n");
}
if (status == MagickFalse)
usage ();
return (status);
}
static double inline CalcLow (
oogboxT * pob,
double v)
{
switch (pob->oogMethod) {
case omLinear: return pob->a*v + pob->b;
case omPower: {
double dv = v - pob->min;
if (dv < 0) return v;
//printf ("lo v=%g %g ", v, pob->A0 * pow (dv, pob->B0));
return pob->A0 * pow (dv, pob->B0);
}
case omBlend: {
double dv = v - pob->min;
double t = dv / (pob->loLimit - pob->min);
if (t < 0) t = 0;
double vLin = dv * pob->loGrad;
double vPow = pob->A0 * pow (dv, pob->B0);
return vLin * (1.0-t) + vPow * t;
}
}
return 0; // Keep compiler happy.
}
static double inline CalcHigh (
oogboxT * pob,
double v)
{
switch (pob->oogMethod) {
case omLinear: return pob->c*v + pob->d;
case omPower: {
double dv = pob->max - v;
if (dv < 0) return v;
//printf ("hi v=%g %g ", v, pob->A0 * pow (dv, 1 - (pob->A1 * pow (dv, pob->B1))));
return 1 - (pob->A1 * pow (dv, pob->B1));
}
case omBlend: {
// t is 1.0 at pob->hiLimit, decreasing to 0.0 at pob->max.
double dv = pob->max - v;
double t = dv / (pob->max - pob->hiLimit);
if (t < 0) t = 0;
double vLin = 1.0 - dv * pob->hiGrad;
double vPow = 1 - (pob->A1 * pow (dv, pob->B1));
return vLin * (1.0-t) + vPow * t;
}
}
return 0; // Keep compiler happy.
}
// The next function corresponds in style to functions in transform.c
// It takes one image, and returns an image.
//
static Image *oogbox (
Image *image,
oogboxT * pob,
ExceptionInfo *exception)
{
if (image->debug != MagickFalse)
(void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
if (pob->verbose) {
fprintf (stderr, "oogbox: Input image [%s] %lux%lu depth is %lu channels=%lu metachannels=%lu\n",
image->filename,
image->columns, image->rows,
image->depth,
image->number_channels,
image->number_meta_channels);
}
pob->doLo = (pob->loLimit > 0.0);
pob->doHi = (pob->hiLimit < 1.0);
Image * new_img = CloneImage (image, 0, 0, MagickTrue, exception);
if (!new_img) return NULL;
if (!SetNoPalette (new_img, exception)) return NULL;
// Find min and max.
CacheView * in_view = AcquireVirtualCacheView (new_img, exception);
MagickBooleanType okay = MagickTrue;
ssize_t y;
pob->min = DBL_MAX;
pob->max = DBL_MIN;
// MagickBooleanType DoR = (pob->chanNum == -1) || (pob->chanNum == 0);
// MagickBooleanType DoG = (pob->chanNum == -1) || (pob->chanNum == 1);
// MagickBooleanType DoB = (pob->chanNum == -1) || (pob->chanNum == 2);
//
// if (image->number_channels==1) {
// DoG = MagickFalse;
// DoB = MagickFalse;
// }
//
// if (pob->verbose) {
// fprintf (stderr, "oogbox: DoR=%i DoG=%i DoB=%i\n",
// DoR, DoG, DoB);
// }
for (y = 0; y < new_img->rows; y++) {
if (!okay) continue;
const VIEW_PIX_PTR *pIn = GetCacheViewVirtualPixels(
in_view,0,y,new_img->columns,1,exception);
if (!pIn) {okay = MagickFalse; continue; }
ssize_t x;
for (x = 0; x < image->columns; x++) {
/*---
if (DoR) {
double v = GET_PIXEL_RED (new_img, pIn);
if (pob->min > v) pob->min = v;
if (pob->max < v) pob->max = v;
}
if (DoG) {
double v = GET_PIXEL_GREEN (new_img, pIn);
if (pob->min > v) pob->min = v;
if (pob->max < v) pob->max = v;
}
if (DoB) {
double v = GET_PIXEL_BLUE (new_img, pIn);
if (pob->min > v) pob->min = v;
if (pob->max < v) pob->max = v;
}
---*/
ssize_t
i;
PixelChannel
channel;
PixelTrait
traits;
Quantum
v;
for (i=0; i < (ssize_t) GetPixelChannels (new_img); i++) {
channel = GetPixelChannelChannel (new_img, i);
if (channel <= BlackPixelChannel) {
traits = GetPixelChannelTraits (new_img, channel);
if ((traits & UpdatePixelTrait) == 0) continue;
v = GetPixelChannel (new_img, channel, pIn);
if (pob->min > v) pob->min = v;
if (pob->max < v) pob->max = v;
}
}
pIn += Inc_ViewPixPtr (image);
}
}
in_view = DestroyCacheView (in_view);
pob->min /= QuantumRange;
pob->max /= QuantumRange;
if (pob->verbose) {
fprintf (stderr, "oogbox: calculated range %.*g to %.*g\n",
pob->precision, pob->min,
pob->precision, pob->max);
}
if (pob->doForceMin) pob->min = pob->forceMin;
if (pob->doForceMax) pob->max = pob->forceMax;
// What do we need to do?
pob->doLo &= (pob->min < 0.0);
pob->doHi &= (pob->max > 1.0);
if (pob->verbose) {
fprintf (stderr, "oogbox: min=%.*g max=%.*g doLo=%i doH=%i\n",
pob->precision, pob->min,
pob->precision, pob->max,
pob->doLo,
pob->doHi);
}
if (!pob->doLo && !pob->doHi) return new_img;
// Calculate the parameters.
switch (pob->oogMethod) {
case omLinear: {
if (pob->doLo) {
pob->a = -pob->loLimit / (pob->min - pob->loLimit);
pob->b = pob->loLimit * (pob->min) / (pob->min - pob->loLimit);
}
if (pob->doHi) {
pob->c = (1 - pob->hiLimit) / (pob->max - pob->hiLimit);
pob->d = pob->hiLimit * (pob->max - 1) / (pob->max - pob->hiLimit);
}
if (pob->verbose) {
fprintf (stderr, "oogbox: Linear a=%.*g b=%.*g c=%.*g d=%.*g\n",
pob->precision, pob->a,
pob->precision, pob->b,
pob->precision, pob->c,
pob->precision, pob->d);
}
break;
}
case omPower:
case omBlend:
{
if (pob->doLo) {
pob->B0 = (pob->loLimit - pob->min) / (pob->loLimit);
pob->A0 = pob->loLimit / pow(pob->loLimit - pob->min,pob->B0);
}
if (pob->doHi) {
pob->B1 = (pob->max - pob->hiLimit) / (1 - pob->hiLimit);
pob->A1 = (1 - pob->hiLimit) / pow(pob->max - pob->hiLimit,pob->B1);
}
if (pob->verbose) {
fprintf (stderr, "oogbox: Power A0=%.*g B0=%.*g A1=%.*g B1=%.*g\n",
pob->precision, pob->A0,
pob->precision, pob->B0,
pob->precision, pob->A1,
pob->precision, pob->B1);
}
if (pob->oogMethod == omBlend) {
if (pob->doLo && !pob->loGradSet) {
pob->loGrad = pob->loLimit / (pob->loLimit - pob->min);
}
if (pob->doHi && !pob->hiGradSet) {
pob->hiGrad = (1 - pob->hiLimit) / (pob->max - pob->hiLimit);
}
if (pob->verbose) {
fprintf (stderr, "oogbox: Blend loGrad=%.*g hiGrad=%.*g\n",
pob->precision, pob->loGrad,
pob->precision, pob->hiGrad);
}
}
break;
}
}
// FIXME:
// Methods Power and Blend have problems with high values of pob->max, eg 232.
// This gives B1=2311, and A1 = 0.1 / pow(231.1, 2311),
// which is rounded to A1=0.
// Calculate the new values.
CacheView * upd_view = AcquireAuthenticCacheView (new_img, exception);
#if defined(MAGICKCORE_OPENMP_SUPPORT)
#pragma omp parallel for schedule(static) shared(progress,status) \
magick_number_threads(image,new_img,new_img->rows,1)
#endif
for (y = 0; y < new_img->rows; y++) {
if (!okay) continue;
VIEW_PIX_PTR *pIn = GetCacheViewAuthenticPixels(
upd_view,0,y,new_img->columns,1,exception);
if (!pIn) {okay = MagickFalse; continue; }
ssize_t
i;
PixelChannel
channel;
PixelTrait
traits;
Quantum
v;
ssize_t x;
for (x = 0; x < image->columns; x++) {
if (pob->doLo) {
/*---
if (DoR) {
double v = GET_PIXEL_RED (new_img, pIn) / QuantumRange;
if (v < pob->loLimit) {
SET_PIXEL_RED (new_img, QuantumRange * CalcLow (pob, v), pIn);
}
}
if (DoG) {
double v = GET_PIXEL_GREEN (new_img, pIn) / QuantumRange;
if (v < pob->loLimit) {
SET_PIXEL_GREEN (new_img, QuantumRange * CalcLow (pob, v), pIn);
}
}
if (DoB) {
double v = GET_PIXEL_BLUE (new_img, pIn) / QuantumRange;
if (v < pob->loLimit) {
SET_PIXEL_BLUE (new_img, QuantumRange * CalcLow (pob, v), pIn);
}
}
---*/
for (i=0; i < (ssize_t) GetPixelChannels (new_img); i++) {
channel = GetPixelChannelChannel (new_img, i);
traits = GetPixelChannelTraits (new_img, channel);
if ((traits & UpdatePixelTrait) == 0) continue;
v = GetPixelChannel (new_img, channel, pIn) / QuantumRange;
if (v < pob->loLimit) {
SetPixelChannel (new_img, channel, QuantumRange * CalcLow (pob, v), pIn);
}
}
}
if (pob->doHi) {
/*---
if (DoR) {
double v = GET_PIXEL_RED (new_img, pIn) / QuantumRange;
if (v > pob->hiLimit) {
SET_PIXEL_RED (new_img, QuantumRange * CalcHigh (pob, v), pIn);
}
}
if (DoG) {
double v = GET_PIXEL_GREEN (new_img, pIn) / QuantumRange;
if (v > pob->hiLimit) {
SET_PIXEL_GREEN (new_img, QuantumRange * CalcHigh (pob, v), pIn);
}
}
if (DoB) {
double v = GET_PIXEL_BLUE (new_img, pIn) / QuantumRange;
if (v > pob->hiLimit) {
SET_PIXEL_BLUE (new_img, QuantumRange * CalcHigh (pob, v), pIn);
}
}
---*/
for (i=0; i < (ssize_t) GetPixelChannels (new_img); i++) {
channel = GetPixelChannelChannel (new_img, i);
traits = GetPixelChannelTraits (new_img, channel);
if ((traits & UpdatePixelTrait) == 0) continue;
v = GetPixelChannel (new_img, channel, pIn) / QuantumRange;
if (v > pob->hiLimit) {
SetPixelChannel (new_img, channel, QuantumRange * CalcHigh (pob, v), pIn);
}
}
}
pIn += Inc_ViewPixPtr (image);
}
if (SyncCacheViewAuthenticPixels(upd_view,exception) == MagickFalse)
okay = MagickFalse;
}
upd_view = DestroyCacheView (upd_view);
if (!okay) return NULL;
return (new_img);
}
ModuleExport size_t oogboxImage (
Image **images,
const int argc,
const char **argv,
ExceptionInfo *exception)
{
Image
*image,
*new_image;
MagickBooleanType
status;
oogboxT ob;
assert(images != (Image **) NULL);
assert(*images != (Image *) NULL);
assert((*images)->signature == MAGICK_CORE_SIG);
ob.precision = GetMagickPrecision();
status = menu (argc, argv, &ob);
if (status == MagickFalse)
return (-1);
for (image=(*images); image != (Image *) NULL; image=GetNextImageInList(image))
{
new_image = oogbox (image, &ob, exception);
if (!new_image) return (-1);
ReplaceImageInList(&image,new_image);
*images=GetFirstImageInList(image);
}
return(MagickImageFilterSignature);
}
/*
Reference: http://im.snibgo.com/paintpatches.htm
Updated:
3-April-2018 for v7.0.7-28
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <math.h>
#include <ctype.h>
#include "vsn_defines.h"
#include "chklist.h"
#include "usercoord.inc"
#include "rmsealpha.inc"
#include "resetpage.inc"
#define VERSION "paintpatches v1.0 Copyright (c) 2017 Alan Gibson"
// FIXME: composites shouldn't clamp.
typedef enum {
cfUndef,
cfAvg,
cfCent,
cfGradient,
cfPalette,
cfSample
} colFromT;
typedef enum {
psRect,
psEllipse,
psMin
} patchShapeT;
typedef struct {
int
precision,
verbose;
Image
*master,
*canvas,
*saliency, // grayscale: white=important, black=unimportant
*samples,
*subsrch;
int
patchWi,
patchHt,
maxIter;
UserCoordXyT
patchDims;
// patch width and height, min and max
double
patchOpacity, // 0.0 to 1.0.
multSal,
wrngThresh,
adjLcComp,
adjLcPat,
featherEdges;
colFromT
colFrom;
patchShapeT
patchShape;
MagickBooleanType
debug,
setRectCanv,
hotSpot,
doMebc;
char
*frameName;
ssize_t
wrngX,
wrngY;
double
wrongness;
int
frameNum;
RmseAlphaT
rmseAlpha;
} paintpatchesT;
static void usage (void)
{
printf ("Usage: -process 'paintpatches [OPTION]...'\n");
printf ("Paint an image with patches.\n");
printf ("\n");
printf (" c, colFrom string 'Avg', 'Cent', 'Gradient', 'Palette' or 'Sample'\n");
printf (" ps, patchShape string 'Rect', 'Ellipse' or 'Min'\n");
printf (" pd, patchDims W,H maximum patch dimensions\n");
printf (" o, opacity number 0.0 to 1.0\n");
printf (" ms, multSal number multiplier for saliency 0.0 to 1.0\n");
printf (" wt, wrngThresh number wrongness threshold 0.0 to 1.0\n");
printf (" ac, adjLcComp number adjust lightness & contrast for comparisons\n");
printf (" ap, adjLcPat number adjust lightness & contrast of patches\n");
printf (" fe, feather number feather patch edges (blur sigma)\n");
printf (" x, maxIter integer maximum iterations\n");
printf (" m, mebc do minimum error boundary cuts\n");
printf (" rc, rectCanv set rectangle to canvas\n");
printf (" hs, hotSpot hotspot only (for ps Min)\n");
printf (" w, write filename write iterations eg frame_%%06d.png\n");
printf (" d, debug write debugging information\n");
printf (" v, verbose write text information\n");
printf (" v2, verbose2 write more text information\n");
printf (" version write version information to stdout\n");
printf ("\n");
}
// FIXME: option for centre of interest: generates saliency map.
static MagickBooleanType IsArg (char * pa, char * ShtOpt, char * LongOpt)
{
// LocaleCompare is not case-sensitive,
// so we use strcmp for the short option.
if ((strcmp(pa, ShtOpt)==0) || (LocaleCompare(pa, LongOpt)==0))
return MagickTrue;
return MagickFalse;
}
#define NEXTARG \
if (i < argc-1) i++; \
else { \
fprintf (stderr, "paintpatches: ERROR: [%s] needs argument\n", pa); \
status = MagickFalse; \
}
static MagickBooleanType menu(
const int argc,
const char **argv,
paintpatchesT * ppp
)
// Returns MagickTrue if okay.
{
int
i;
MagickBooleanType
status = MagickTrue;
ppp->master = ppp->canvas = ppp->saliency
= ppp->samples = ppp->subsrch = NULL;
ppp->verbose = 0;
ppp->debug = MagickFalse;
ppp->patchOpacity = 1.0;
ppp->multSal = 0.5;
ppp->wrngThresh = 0.1;
ppp->adjLcComp = 0.0;
ppp->adjLcPat = 0.0;
ppp->featherEdges = 0.0;
ppp->colFrom = cfUndef;
ppp->patchShape = psRect;
ppp->doMebc = MagickFalse;
ppp->setRectCanv = MagickFalse;
ppp->hotSpot = MagickFalse;
ppp->patchWi = 50;
ppp->patchHt = 50;
ppp->maxIter = 1000;
ppp->frameName = NULL;
ppp->frameNum = 0;
if (!ParseXy ("40c,40c", &ppp->patchDims)) return MagickFalse;
// FIXME: If patchDims not specified, default according to mebc etc?
char ** pargv = (char **)argv;
for (i=0; i < argc; i++) {
char * pa = (char *)argv[i];
if (IsArg (pa, "c", "colFrom")==MagickTrue) {
NEXTARG;
if (LocaleCompare(argv[i], "avg")==0) ppp->colFrom = cfAvg;
else if (LocaleCompare(argv[i], "cent")==0) ppp->colFrom = cfCent;
else if (LocaleCompare(argv[i], "gradient")==0) ppp->colFrom = cfGradient;
else if (LocaleCompare(argv[i], "palette")==0) ppp->colFrom = cfPalette;
else if (LocaleCompare(argv[i], "sample")==0) ppp->colFrom = cfSample;
else {
fprintf (stderr, "kcluster: ERROR: unknown colFrom [%s]\n", argv[i]);
status = MagickFalse;
}
} else if (IsArg (pa, "ps", "patchShape")==MagickTrue) {
NEXTARG;
if (LocaleCompare(argv[i], "rect")==0) ppp->patchShape = psRect;
else if (LocaleCompare(argv[i], "ellipse")==0) ppp->patchShape = psEllipse;
else if (LocaleCompare(argv[i], "min")==0) ppp->patchShape = psMin;
else {
fprintf (stderr, "kcluster: ERROR: unknown patchShape [%s]\n", argv[i]);
status = MagickFalse;
}
} else if (IsArg (pa, "o", "opacity")==MagickTrue) {
NEXTARG;
ppp->patchOpacity = atof (argv[i]);
} else if (IsArg (pa, "ms", "multSal")==MagickTrue) {
NEXTARG;
ppp->multSal = atof (argv[i]);
} else if (IsArg (pa, "wt", "wrngThresh")==MagickTrue) {
NEXTARG;
ppp->wrngThresh = atof (argv[i]);
} else if (IsArg (pa, "ac", "adjLcComp")==MagickTrue) {
NEXTARG;
ppp->adjLcComp = atof (argv[i]);
} else if (IsArg (pa, "ap", "adjLcPat")==MagickTrue) {
NEXTARG;
ppp->adjLcPat = atof (argv[i]);
} else if (IsArg (pa, "fe", "feather")==MagickTrue) {
NEXTARG;
ppp->featherEdges = atof (argv[i]);
} else if (IsArg (pa, "pd", "patchdims")==MagickTrue) {
NEXTARG;
if (!ParseXy (argv[i], &ppp->patchDims)) {
fprintf (stderr, "kcluster: ERROR: bad XY [%s]\n", argv[i]);
status = MagickFalse;
}
} else if (IsArg (pa, "x", "maxiter")==MagickTrue) {
NEXTARG;
ppp->maxIter = atoi (argv[i]);
} else if (IsArg (pa, "m", "mebc")==MagickTrue) {
ppp->doMebc = MagickTrue;
} else if (IsArg (pa, "rc", "rectCanv")==MagickTrue) {
ppp->setRectCanv = MagickTrue;
} else if (IsArg (pa, "hs", "hotSpot")==MagickTrue) {
ppp->hotSpot = MagickTrue;
} else if (IsArg (pa, "w", "write")==MagickTrue) {
NEXTARG;
ppp->frameName = pargv[i];
} else if (IsArg (pa, "d", "debug")==MagickTrue) {
ppp->debug = MagickTrue;
} else if (IsArg (pa, "v", "verbose")==MagickTrue) {
ppp->verbose = 1;
} else if (IsArg (pa, "v2", "verbose2")==MagickTrue) {
ppp->verbose = 2;
} else if (IsArg (pa, "version", "version")==MagickTrue) {
fprintf (stdout, "%s\n", VERSION);
} else {
fprintf (stderr, "paintpatches: ERROR: unknown option [%s]\n", pa);
status = MagickFalse;
}
}
if (ppp->verbose) {
fprintf (stderr, "paintpatches options: ");
if (ppp->verbose==1) fprintf (stderr, " verbose");
if (ppp->verbose==2) fprintf (stderr, " verbose2");
fprintf (stderr, " colFrom ");
switch (ppp->colFrom) {
case cfUndef:
fprintf (stderr, "Undefined");
break;
case cfAvg:
fprintf (stderr, "Avg");
break;
case cfCent:
fprintf (stderr, "Cent");
break;
case cfGradient:
fprintf (stderr, "Gradient");
break;
case cfPalette:
fprintf (stderr, "Palette");
break;
case cfSample:
fprintf (stderr, "Sample");
break;
}
fprintf (stderr, " patchShape ");
switch (ppp->patchShape) {
case psRect:
fprintf (stderr, "Rect");
break;
case psEllipse:
fprintf (stderr, "Ellipse");
break;
case psMin:
fprintf (stderr, "Min");
break;
}
fprintf (stderr, " patchDims ");
WrUserCoord (&ppp->patchDims);
fprintf (stderr, " opacity %g", ppp->patchOpacity);
fprintf (stderr, " multSal %g", ppp->multSal);
fprintf (stderr, " wrngThresh %g", ppp->wrngThresh);
fprintf (stderr, " adjLcComp %g", ppp->adjLcComp);
fprintf (stderr, " adjLcPat %g", ppp->adjLcPat);
fprintf (stderr, " maxIter %i", ppp->maxIter);
fprintf (stderr, " featherEdges %g", ppp->featherEdges);
if (ppp->doMebc) fprintf (stderr, " mebc ");
if (ppp->setRectCanv) fprintf (stderr, " rectCanv ");
if (ppp->hotSpot) fprintf (stderr, " hotSpot ");
if (ppp->frameName) fprintf (stderr, " write %s", ppp->frameName);
if (ppp->debug) fprintf (stderr, " debug ");
fprintf (stderr, "\n");
}
if (status == MagickFalse)
usage ();
return (status);
}
static MagickBooleanType SameSize (Image * i0, Image * i1)
{
if (!i1) return MagickFalse;
return ((i0->columns == i1->columns) && (i0->rows == i1->rows));
}
static void WrImage (char * title, Image * img)
{
fprintf (stderr, "%s: ", title);
if (img) {
if (img->filename)
fprintf (stderr, "[%s] ", img->filename);
fprintf (stderr, "%lix%li\n", img->columns, img->rows);
chkentry ("WrImage", &img);
} else {
fprintf (stderr, "none\n");
}
}
static MagickStatusType WriteFrame (
paintpatchesT * ppp,
Image * img,
ExceptionInfo *exception)
{
if (!ppp->frameName) return MagickTrue;
ImageInfo *ii = AcquireImageInfo ();
Image * copy_img = CloneImage (img, 0, 0, MagickTrue, exception);
if (!copy_img) return MagickFalse;
copy_img->scene = ppp->frameNum++;
CopyMagickString (copy_img->filename, ppp->frameName, MaxTextExtent);
MagickBooleanType okay = WRITEIMAGE(ii, copy_img, exception);
DestroyImageList (copy_img);
ii = DestroyImageInfo (ii);
return okay;
}
static MagickBooleanType MostWrong (
paintpatchesT * ppp,
ExceptionInfo *exception)
// FIXME: Or least wrong first? To get the broad-brush stuff first?
{
Image * wrng_img = CloneImage (ppp->master, 0, 0, MagickTrue, exception);
if (!wrng_img) {
fprintf (stderr, "MostWrong: clone failed\n");
return MagickFalse;
}
// FIXME: If we have adjusted patches,
// we should do likewise for the comparison.
if (!COMPOSITE(wrng_img, DifferenceCompositeOp, ppp->canvas, 0,0, exception))
{
fprintf (stderr, "MostWrong: composite failed\n");
return MagickFalse;
}
// Blurring removes outliers, but it's expensive.
//
// Image * wrng_img2 = BlurImage (wrng_img, 0, 3, exception);
// if (!wrng_img2) return MagickFalse;
// ReplaceImageInList (&wrng_img, wrng_img2);
if (ppp->verbose > 1) WrImage ("wrong", wrng_img);
MagickBooleanType
status;
CacheView * wrng_view = AcquireVirtualCacheView (wrng_img, exception);
CacheView * slnc_view = AcquireVirtualCacheView (ppp->saliency, exception);
status = MagickTrue;
double val;
ppp->wrongness = 99e9; // for most wrong last.
ppp->wrongness = 0; // for most wrong first.
ppp->wrngX = ppp->wrngY = 0;
ssize_t y;
for (y=0; y < (ssize_t) wrng_img->rows; y++)
{
ssize_t x;
if (status == MagickFalse) continue;
VIEW_PIX_PTR const *wp = GetCacheViewVirtualPixels(
wrng_view,0,y,wrng_img->columns,1,exception);
if (!wp) status=MagickFalse;
VIEW_PIX_PTR const *sp = GetCacheViewVirtualPixels(
slnc_view,0,y,ppp->saliency->columns,1,exception);
if (!sp) status=MagickFalse;
for (x=0; x < (ssize_t) wrng_img->columns; x++) {
val = GetPixelIntensity (wrng_img, wp)
* GET_PIXEL_RED (ppp->saliency, sp) / (double)QuantumRange;
// '<' for most wrong first
if (ppp->wrongness < val) {
ppp->wrongness = val;
ppp->wrngX = x;
ppp->wrngY = y;
if (val < 0) {
fprintf (stderr, "val<0 %li,%li %g %g\n",
x, y,
(double)GetPixelIntensity (wrng_img, wp),
(double)GET_PIXEL_RED (ppp->saliency, sp));
}
}
wp += Inc_ViewPixPtr (wrng_img);
sp += Inc_ViewPixPtr (ppp->saliency);
}
}
ppp->wrongness /= QuantumRange;
// FIXME: for ordinary images, how can wrongness be negative?
if (ppp->wrongness < 0) ppp->wrongness = 0;
if (ppp->wrongness > 1) ppp->wrongness = 1;
slnc_view = DestroyCacheView (slnc_view);
wrng_view = DestroyCacheView (wrng_view);
wrng_img = DestroyImage (wrng_img);
return MagickTrue;
}
static double GetSalientG (
paintpatchesT * ppp,
ssize_t x,
ssize_t y,
ExceptionInfo *exception)
{
CacheView * slnc_view = AcquireVirtualCacheView (ppp->saliency, exception);
VIEW_PIX_PTR const *sp = GetCacheViewVirtualPixels(
slnc_view,x,y,1,1,exception);
if (!sp) return 0;
//double val = GetPixelIntensity (ppp->saliency, sp) / (double)QuantumRange;
double val = GET_PIXEL_GREEN (ppp->saliency, sp) / (double)QuantumRange;
slnc_view = DestroyCacheView (slnc_view);
// FIXME: For ordinary images, how can this be negative?
if (val < 0) val = 0;
if (val > 1) val = 1;
if (ppp->verbose > 1) {
fprintf (stderr, "GetSalientG %li,%li %g\n",
x, y,
val);
}
return val;
}
static MagickBooleanType GetRect (
paintpatchesT *ppp,
RectangleInfo *prect,
ExceptionInfo *exception)
// Gets a rectangle that includes ppp->wrngX and ppp->wrngY.
// Rectangle may straddle canvas edges.
{
double slntFact = 1.0 - GetSalientG (ppp, ppp->wrngX, ppp->wrngY, exception);
//if (slntFact==0) return MagickFalse; // FIXME: so white saliency does nothing?!?!?
// FIXME: we could vary the width and height randomly.
prect->x = floor (ppp->wrngX - slntFact * ppp->patchWi/2.0 + 0.5);
prect->y = floor (ppp->wrngY - slntFact * ppp->patchHt/2.0 + 0.5);
prect->width = floor (slntFact * ppp->patchWi + 0.5);
prect->height = floor (slntFact * ppp->patchHt + 0.5);
if (prect->width < 1) prect->width = 1;
if (prect->height < 1) prect->height = 1;
if (ppp->verbose > 1) {
fprintf (stderr, "GetRect %lix%li+%li+%li slntFact=%g\n",
prect->width, prect->height, prect->x, prect->y, slntFact);
}
return MagickTrue;
}
static MagickBooleanType SetImgAlpha (
paintpatchesT * ppp,
Image * img,
ExceptionInfo *exception)
{
if (ppp->patchOpacity == 1.0) return MagickTrue;
CacheView * img_view = AcquireAuthenticCacheView (img, exception);
// FIXME: enable alpha for the image?
double alph = ppp->patchOpacity * QuantumRange;
MagickBooleanType okay = MagickTrue;
ssize_t y;
#if defined(MAGICKCORE_OPENMP_SUPPORT)
#pragma omp parallel for schedule(static,4) \
shared(okay) \
MAGICK_THREADS(img,img,img->rows,1)
#endif
for (y = 0; y < img->rows; y++) {
ssize_t x;
if (!okay) continue;
VIEW_PIX_PTR *sp = GetCacheViewAuthenticPixels(
img_view,0,y,img->columns,1,exception);
if (!sp) okay = MagickFalse;
for (x = 0; x < img->columns; x++) {
SET_PIXEL_ALPHA (img, alph, sp);
sp += Inc_ViewPixPtr (img);
}
if (!SyncCacheViewAuthenticPixels(img_view,exception)) {
okay = MagickFalse;
}
}
if (!okay) return MagickFalse;
img_view = DestroyCacheView (img_view);
return MagickTrue;
}
static MagickBooleanType EllipseOnly (
paintpatchesT * ppp,
Image * pat_img,
ExceptionInfo *exception)
// Makes pixels outside an ellipse transparent.
{
CacheView * img_view = AcquireAuthenticCacheView (pat_img, exception);
// FIXME: enable alpha for the image?
// Quick and simple anti-aliasing.
if (pat_img->columns <= 2 || pat_img->rows <= 2) return MagickTrue;
// FIXME: following isn't symmetrical.
double a = pat_img->columns / 2.0;
double b = pat_img->rows / 2.0;
double a2 = a*a;
double b2 = b*b;
double a2b2 = a2*b2;
MagickBooleanType okay = MagickTrue;
ssize_t y;
#if defined(MAGICKCORE_OPENMP_SUPPORT)
#pragma omp parallel for schedule(static,4) \
shared(okay) \
MAGICK_THREADS(pat_img,pat_img,pat_img->rows,1)
#endif
for (y = 0; y < pat_img->rows; y++) {
ssize_t x;
VIEW_PIX_PTR *sp = GetCacheViewAuthenticPixels(
img_view,0,y,pat_img->columns,1,exception);
if (!sp) okay = MagickFalse;
if (!okay) continue;
double a2y2 = a2 * (y-b) * (y-b);
double a2y2m1 = a2 * (y-b-0.5) * (y-b-0.5);
double a2y2p1 = a2 * (y-b+0.5) * (y-b+0.5);
for (x = 0; x < pat_img->columns; x++) {
double b2x2 = b2 * (x-a) * (x-a);
double b2x2m1 = b2 * (x-a-0.5) * (x-a-0.5);
double b2x2p1 = b2 * (x-a+0.5) * (x-a+0.5);
int nIn = 0;
if (b2x2m1 + a2y2m1 < a2b2) nIn++;
if (b2x2 + a2y2m1 < a2b2) nIn++;
if (b2x2p1 + a2y2m1 < a2b2) nIn++;
if (b2x2m1 + a2y2 < a2b2) nIn++;
if (b2x2 + a2y2 < a2b2) nIn++;
if (b2x2p1 + a2y2 < a2b2) nIn++;
if (b2x2m1 + a2y2p1 < a2b2) nIn++;
if (b2x2 + a2y2p1 < a2b2) nIn++;
if (b2x2p1 + a2y2p1 < a2b2) nIn++;
if (nIn < 9) {
SET_PIXEL_ALPHA (pat_img,
nIn / 9.0 * GET_PIXEL_ALPHA(pat_img,sp),
sp);
}
sp += Inc_ViewPixPtr (pat_img);
}
if (SyncCacheViewAuthenticPixels(img_view,exception) == MagickFalse)
okay = MagickFalse;
}
if (!okay) return MagickFalse;
img_view = DestroyCacheView (img_view);
return MagickTrue;
}
static Image * SearchSamples (
paintpatchesT * ppp,
Image *pat_image,
ExceptionInfo *exception)
// Search samples image for best match to pat_image.
// Creates and returns an image, which caller should destroy.
{
// Note: InitRmseAlpha/DeInit are at a higher level.
Image * srch = ppp->subsrch;
if (!srch) srch = ppp->samples;
ppp->rmseAlpha.adjustMeanSd = ppp->adjLcComp;
ppp->rmseAlpha.saveInpScales = MagickTrue;
ppp->rmseAlpha.do_verbose = (ppp->verbose > 1);
if (!subRmseAlphaMS (&ppp->rmseAlpha, srch, pat_image, exception)) {
fprintf (stderr, "SearchSamples: subRmseAlphaMS failed\n");
return NULL;
}
if (ppp->verbose > 1) {
fprintf (stderr, "SearchSamples: found %g at %li,%li\n",
ppp->rmseAlpha.score, ppp->rmseAlpha.solnX, ppp->rmseAlpha.solnY);
}
RectangleInfo fndRect;
fndRect.x = ppp->rmseAlpha.solnX;
fndRect.y = ppp->rmseAlpha.solnY;
fndRect.width = pat_image->columns;
fndRect.height = pat_image->rows;
// FIXME: Option to get larger patch, same size as canvas.
if (ppp->verbose > 1) {
fprintf (stderr, "fndRect %lix%li+%li+%li canvas %lix%li\n",
fndRect.width, fndRect.height, fndRect.x, fndRect.y,
ppp->canvas->columns, ppp->canvas->rows);
}
if (ppp->setRectCanv) {
ssize_t dx = ppp->canvas->columns - fndRect.width;
ssize_t dy = ppp->canvas->rows - fndRect.height;
fndRect.x -= dx/2;
fndRect.y -= dy/2;
if (fndRect.x < 0) fndRect.x = 0;
if (fndRect.y < 0) fndRect.y = 0;
fndRect.width = ppp->canvas->columns;
fndRect.height = ppp->canvas->rows;
if (fndRect.x + fndRect.width > ppp->samples->columns) fndRect.x = ppp->samples->columns - fndRect.width;
if (fndRect.y + fndRect.height > ppp->samples->rows) fndRect.y = ppp->samples->rows - fndRect.height;
}
if (ppp->verbose > 1) {
fprintf (stderr, "fndRect %lix%li+%li+%li\n",
fndRect.width, fndRect.height, fndRect.x, fndRect.y);
}
Image * fnd_img = CropImage (ppp->samples, &fndRect, exception);
if (!fnd_img) {
fprintf (stderr, "SearchSamples crop failed\n");
return NULL;
}
return fnd_img;
}
static void ClipPatchRect (
paintpatchesT * ppp,
RectangleInfo * patchRect,
RectangleInfo * clippedRect,
int *dx,
int *dy)
{
*clippedRect = *patchRect;
if (clippedRect->x < 0) {
*dx = -clippedRect->x;
clippedRect->width += clippedRect->x;
clippedRect->x = 0;
} else {
*dx = 0;
}
if (clippedRect->y < 0) {
*dy = -clippedRect->y;
clippedRect->height += clippedRect->y;
clippedRect->y = 0;
} else {
*dy = 0;
}
if (clippedRect->width + clippedRect->x > ppp->canvas->columns)
clippedRect->width = ppp->canvas->columns - clippedRect->x;
if (clippedRect->height + clippedRect->y > ppp->canvas->rows)
clippedRect->height = ppp->canvas->rows - clippedRect->y;
}
static MagickBooleanType AdjLcFndToPat (
paintpatchesT * ppp,
Image * fnd_pat,
Image * pat_img,
ExceptionInfo *exception)
// Adjust lightness and contrast of found patch to match pat_img.
{
MeanSdT msFnd, msPat;
if (!CalcMeanSdImg (fnd_pat, &msFnd, exception)) return MagickFalse;
if (!CalcMeanSdImg (pat_img, &msPat, exception)) return MagickFalse;
GainBiasT gb;
MeanSdToGainBias (ppp->adjLcPat, &msFnd, &msPat, &gb);
// FIXME: apply the gain and bias to fnd_pat.
CacheView * img_view = AcquireAuthenticCacheView (fnd_pat, exception);
MagickBooleanType okay = MagickTrue;
ssize_t y;
#if defined(MAGICKCORE_OPENMP_SUPPORT)
#pragma omp parallel for schedule(static,4) \
shared(okay) \
MAGICK_THREADS(fnd_pat,pat_img,fnd_pat->rows,1)
#endif
for (y = 0; y < fnd_pat->rows; y++) {
ssize_t x;
VIEW_PIX_PTR *sp = GetCacheViewAuthenticPixels(
img_view,0,y,fnd_pat->columns,1,exception);
if (!sp) okay = MagickFalse;
if (!okay) continue;
for (x = 0; x < fnd_pat->columns; x++) {
SET_PIXEL_RED (fnd_pat,
GET_PIXEL_RED(pat_img,sp)*gb.gainR+gb.biasR,
sp);
SET_PIXEL_GREEN (fnd_pat,
GET_PIXEL_GREEN(pat_img,sp)*gb.gainG+gb.biasG,
sp);
SET_PIXEL_BLUE (fnd_pat,
GET_PIXEL_BLUE(pat_img,sp)*gb.gainB+gb.biasB,
sp);
sp += Inc_ViewPixPtr (fnd_pat);
}
if (SyncCacheViewAuthenticPixels(img_view,exception) == MagickFalse)
okay = MagickFalse;
}
if (!okay) return MagickFalse;
img_view = DestroyCacheView (img_view);
return MagickTrue;
}
static void SetRectCanv (
paintpatchesT * ppp,
RectangleInfo * rect
)
{
rect->x = rect->y = 0;
rect->width = ppp->canvas->columns;
rect->height = ppp->canvas->rows;
}
static Image *GetPatchImg (
paintpatchesT * ppp,
RectangleInfo * patchRect,
ExceptionInfo *exception)
// Creates and returns an image, which caller should destroy.
{
if (ppp->verbose > 1)
fprintf (stderr, "GetPatchImg: %lix%li+%li+%li\n",
patchRect->width, patchRect->height, patchRect->x, patchRect->y);
Image * pat_img = CropImage (ppp->master, patchRect, exception);
if (!pat_img) {
fprintf (stderr, "GetPatchImg: crop failed\n");
return NULL;
}
ResetPage (pat_img);
// Caution: this may be smaller than requested, when at edge of ppp->master.
if (ppp->colFrom == cfAvg) {
Image * pat_img2 = ScaleImage (pat_img, 1, 1, exception);
if (!pat_img2) return NULL;
ReplaceImageInList (&pat_img, pat_img2);
if (ppp->setRectCanv) SetRectCanv (ppp, patchRect);
pat_img2 = SampleImage (pat_img, patchRect->width,
patchRect->height, exception);
if (!pat_img2) return NULL;
ReplaceImageInList (&pat_img, pat_img2);
} else if (ppp->colFrom == cfCent) {
RectangleInfo rect;
rect.x = patchRect->width / 2;
rect.y = patchRect->height / 2;
rect.width = 1;
rect.height = 1;
// If patch overlaps edge, we may not have the centre point.
if (rect.x < 0) rect.x = 0;
if (rect.y < 0) rect.y = 0;
if (rect.x >= pat_img->columns) rect.x = pat_img->columns-1;
if (rect.y >= pat_img->rows) rect.y = pat_img->rows-1;
Image * pat_img2 = CropImage (pat_img, &rect, exception);
if (!pat_img2) {
fprintf (stderr, "GetPatchImg: pat_img2 fail: %lix%li+%li+%li\n",
rect.width, rect.height, rect.x, rect.y);
return NULL;
}
ResetPage (pat_img2);
ReplaceImageInList (&pat_img, pat_img2);
if (ppp->setRectCanv) SetRectCanv (ppp, patchRect);
pat_img2 = SampleImage (pat_img, patchRect->width,
patchRect->height, exception);
if (!pat_img2) return NULL;
ReplaceImageInList (&pat_img, pat_img2);
} else if (ppp->colFrom == cfSample) {
if (ppp->setRectCanv) SetRectCanv (ppp, patchRect);
Image * fnd_pat = SearchSamples (ppp, pat_img, exception);
if (!fnd_pat) {
return NULL;
}
// FIXME: better place for next?
if (ppp->adjLcPat > 0.0) {
if (!AdjLcFndToPat (ppp, fnd_pat, pat_img, exception)) return NULL;
}
ReplaceImageInList (&pat_img, fnd_pat);
// What if the image is smaller than requested?
// Answer: extent.
if (pat_img->columns < patchRect->width
|| pat_img->rows < patchRect->height)
{
RectangleInfo extRect;
int dx, dy;
ClipPatchRect (ppp, patchRect, &extRect, &dx, &dy);
// Ignore calculated extRect; we want only dx and dy.
extRect.width = patchRect->width;
extRect.height = patchRect->height;
extRect.x = -dx;
extRect.y = -dy;
Image * pat_img2 = ExtentImage (pat_img, &extRect, exception);
if (!pat_img2) return NULL;
ReplaceImageInList (&pat_img, pat_img2);
}
} else {
// FIXME: For now, get gradient from master.
// Gradient: chop to get the 4 corners, and resize.
// If too small to chop and resize, don't.
if (patchRect->width > 2 && patchRect->height > 2) {
RectangleInfo chpRect;
chpRect.x = chpRect.y = 1;
chpRect.width = pat_img->columns - 2;
chpRect.height = pat_img->rows - 2;
Image * pat_img2 = ChopImage (pat_img, &chpRect, exception);
if (!pat_img2) return NULL;
ReplaceImageInList (&pat_img, pat_img2);
if (pat_img->columns != 2 || pat_img->rows != 2) {
fprintf (stderr, "GetPatchImg: after chop %lix%li\n",
pat_img->columns, pat_img->rows);
}
if (ppp->setRectCanv) SetRectCanv (ppp, patchRect);
#if IMV6OR7==6
pat_img2 = ResizeImage (pat_img, patchRect->width,
patchRect->height, UndefinedFilter, 1.0, exception);
#else
pat_img2 = ResizeImage (pat_img, patchRect->width,
patchRect->height, UndefinedFilter, exception);
#endif
if (!pat_img2) return NULL;
ReplaceImageInList (&pat_img, pat_img2);
}
}
// FIXME: possibly adjust to match pat_img.
if (ppp->adjLcPat > 0.0) {
// Bugger, we've already replaced it.
//Image * pat_img2 = AdjLcFndToPat (ppp, ??, ??, exception);
}
#if IMV6OR7==6
SetImageAlphaChannel (pat_img, SetAlphaChannel);
#else
SetImageAlphaChannel (pat_img, SetAlphaChannel, exception);
#endif
if (!SetImgAlpha (ppp, pat_img, exception)) return NULL;
if (ppp->patchShape == psEllipse) {
if (!EllipseOnly (ppp, pat_img, exception)) return NULL;
}
return pat_img;
}
static MagickBooleanType PaintPatchImg (
paintpatchesT * ppp,
Image * dest_img,
RectangleInfo * patchRect,
Image * pat_img,
ExceptionInfo *exception)
{
if (!COMPOSITE(dest_img,
OverCompositeOp, pat_img, patchRect->x, patchRect->y, exception))
{
fprintf (stderr, "PaintPatch: composite failed\n");
return MagickFalse;
}
return MagickTrue;
}
static MagickBooleanType PaintPatchCanvas (
paintpatchesT * ppp,
RectangleInfo * patchRect,
Image * pat_img,
ExceptionInfo *exception)
{
return PaintPatchImg (ppp, ppp->canvas, patchRect, pat_img, exception);
}
static MagickBooleanType SetOneNonSalient (
paintpatchesT * ppp,
ssize_t x,
ssize_t y,
ExceptionInfo *exception)
{
// FIXME: This could draw the patch shape, in black, on the saliency image.
CacheView * slnc_view = AcquireAuthenticCacheView (ppp->saliency, exception);
VIEW_PIX_PTR *sp = GetCacheViewAuthenticPixels(
slnc_view,x,y,1,1,exception);
if (!sp) return MagickFalse;
SET_PIXEL_RED (ppp->saliency, 0, sp);
// SET_PIXEL_GREEN (ppp->saliency, 0, sp);
// SET_PIXEL_BLUE (ppp->saliency, 0, sp);
if (SyncCacheViewAuthenticPixels(slnc_view,exception) == MagickFalse)
return MagickFalse;
slnc_view = DestroyCacheView (slnc_view);
return MagickTrue;
}
static MagickBooleanType MultOneSalient (
paintpatchesT * ppp,
ssize_t x,
ssize_t y,
double fact,
ExceptionInfo *exception)
{
CacheView * slnc_view = AcquireAuthenticCacheView (ppp->saliency, exception);
VIEW_PIX_PTR *sp = GetCacheViewAuthenticPixels(
slnc_view,x,y,1,1,exception);
if (!sp) return MagickFalse;
SET_PIXEL_RED (ppp->saliency, fact * GET_PIXEL_RED(ppp->saliency,sp), sp);
// SET_PIXEL_GREEN (ppp->saliency, fact * GET_PIXEL_GREEN(ppp->saliency,sp), sp);
// SET_PIXEL_BLUE (ppp->saliency, fact * GET_PIXEL_BLUE(ppp->saliency,sp), sp);
if (SyncCacheViewAuthenticPixels(slnc_view,exception) == MagickFalse)
return MagickFalse;
slnc_view = DestroyCacheView (slnc_view);
return MagickTrue;
}
static MagickBooleanType MultSalientPatch (
paintpatchesT * ppp,
RectangleInfo * patchRect,
Image *pat_img,
double factor,
ExceptionInfo *exception)
// Where patch is not totally transparent, multiplies saliency by factor.
{
CacheView * slnc_view = AcquireAuthenticCacheView (ppp->saliency, exception);
CacheView * pat_view = AcquireVirtualCacheView (pat_img, exception);
if (ppp->verbose > 1)
fprintf (stderr, "MultSalientPatch: rect %lix%li+%li+%li x%g\n",
patchRect->width, patchRect->height, patchRect->x, patchRect->y,
factor);
RectangleInfo clippedRect;
int dx, dy;
ClipPatchRect (ppp, patchRect, &clippedRect, &dx, &dy);
if (ppp->verbose > 1)
fprintf (stderr, "MultSalientPatch: clpd %lix%li+%li+%li dx=%i dy=%i\n",
clippedRect.width, clippedRect.height, clippedRect.x, clippedRect.y,
dx, dy);
MagickBooleanType okay = MagickTrue;
ssize_t y;
#if defined(MAGICKCORE_OPENMP_SUPPORT)
#pragma omp parallel for schedule(static,4) \
shared(okay) \
MAGICK_THREADS(pat_img,pat_img,clippedRect.height,1)
#endif
for (y = clippedRect.y; y < clippedRect.y + clippedRect.height; y++) {
VIEW_PIX_PTR *sp = GetCacheViewAuthenticPixels(
slnc_view,clippedRect.x,y,clippedRect.width,1,exception);
if (!sp) okay = MagickFalse;
const VIEW_PIX_PTR *pp = GetCacheViewVirtualPixels(
pat_view,dx,y-clippedRect.y+dy,clippedRect.width,1,exception);
if (!pp) okay = MagickFalse;
const double opacThresh = ppp->patchOpacity * 0.5;
// Patch alpha may have been reduced.
ssize_t x;
for (x = 0; x < clippedRect.width; x++) {
if (GET_PIXEL_ALPHA (pat_img, pp) > opacThresh) {
SET_PIXEL_RED (ppp->saliency,
factor * GET_PIXEL_RED(ppp->saliency,sp),
sp);
}
sp += Inc_ViewPixPtr (ppp->saliency);
pp += Inc_ViewPixPtr (pat_img);
}
if (SyncCacheViewAuthenticPixels(slnc_view,exception) == MagickFalse)
okay = MagickFalse;
}
if (!okay) return MagickFalse;
pat_view = DestroyCacheView (pat_view);
slnc_view = DestroyCacheView (slnc_view);
return MagickTrue;
}
static double inline RelColDiff (
Image * img0,
Image * img1,
const VIEW_PIX_PTR *vp0,
const VIEW_PIX_PTR *vp1)
{
double dr = GET_PIXEL_RED(img0,vp0) - GET_PIXEL_RED(img1,vp1);
double dg = GET_PIXEL_GREEN(img0,vp0) - GET_PIXEL_GREEN(img1,vp1);
double db = GET_PIXEL_BLUE(img0,vp0) - GET_PIXEL_BLUE(img1,vp1);
return (dr*dr + dg+dg + db*db);
}
static double inline SumSq (
Image * img,
const VIEW_PIX_PTR *vp)
{
double dr = GET_PIXEL_RED(img,vp);
double dg = GET_PIXEL_GREEN(img,vp);
double db = GET_PIXEL_BLUE(img,vp);
return (dr*dr + dg+dg + db*db);
}
static MagickBooleanType NearestWhite (
paintpatchesT * ppp,
Image * img,
ssize_t cx,
ssize_t cy,
ssize_t *fndX,
ssize_t *fndY,
MagickBooleanType * isFound,
ExceptionInfo *exception)
// Returns the nearest white pixel to cx,cy.
{
CacheView * img_view = AcquireVirtualCacheView (img, exception);
#define LIMIT (QuantumRange - 1e-6)
//#define EPS 1e-6
MagickBooleanType okay = MagickTrue, found = MagickFalse;
double nearDistSq = 0;
ssize_t y, nearX=-1, nearY=-1;
#if defined(MAGICKCORE_OPENMP_SUPPORT)
#pragma omp parallel for schedule(static,4) \
shared(okay) \
MAGICK_THREADS(img,img,img->rows,1)
#endif
for (y = 0; y < img->rows; y++) {
if (!okay) continue;
double
dx, dy;
const VIEW_PIX_PTR *p = GetCacheViewVirtualPixels(
img_view,0,y,img->columns,1,exception);
if (!p) okay = MagickFalse;
ssize_t x;
for (x = 0; x < img->columns; x++) {
if ( GET_PIXEL_RED(img,p) >= LIMIT
&& GET_PIXEL_GREEN(img,p) >= LIMIT
&& GET_PIXEL_BLUE(img,p) >= LIMIT)
{
if (found) {
dx = cx - x;
dy = cy - y;
double DistSq = dx*dx + dy*dy;
if (nearDistSq > DistSq) {
nearDistSq = DistSq;
nearX = x;
nearY = y;
}
} else {
found = MagickTrue;
dx = cx - x;
dy = cy - y;
nearDistSq = dx*dx + dy*dy;
nearX = x;
nearY = y;
}
}
p += Inc_ViewPixPtr (img);
}
}
img_view = DestroyCacheView (img_view);
*fndX = nearX;
*fndY = nearY;
*isFound = found;
if (ppp->debug) {
fprintf (stderr, "NearestWhite: %li,%li => %li,%li\n",
cx, cy, *fndX, *fndY);
}
return okay;
}
static MagickBooleanType OnlyHotspot (
paintpatchesT * ppp,
Image * opaq_img,
ExceptionInfo *exception)
// opaq_img represents required opacity, white = opaque.
// This sets all pixels black, except those connected to wrngX,wrngY.
// If no pixels were white, returns a black image (and MagickTrue).
{
// Poss flood-fill at hotspot. But where is the hotspot?
// If we are doing rectCanv, the hotspot is wrngX,wrngY.
ssize_t hsX, hsY;
MagickBooleanType isFound;
if (! NearestWhite (ppp,
opaq_img, ppp->wrngX, ppp->wrngY,
&hsX, &hsY, &isFound, exception)) return MagickFalse;
if (!isFound) return MagickTrue;
//fprintf (stderr, "OnlyHotspot 1 %lix%li @ %li,%li => %li,%li\n",
// opaq_img->columns, opaq_img->rows,
// ppp->wrngX, ppp->wrngY, hsX, hsY);
DrawInfo *draw_info = AcquireDrawInfo();
PIX_INFO target; // white
#if IMV6OR7==6
QueryColorDatabase ("gray50", &draw_info->fill, exception);
GetMagickPixelPacket (opaq_img, &target);
QueryMagickColor ("white", &target, exception);
if (!FloodfillPaintImage (
opaq_img, DefaultChannels, draw_info,
&target, hsX, hsY, MagickFalse)) return MagickFalse;
#else
QueryColorCompliance ("gray50", AllCompliance, &draw_info->fill, exception);
GetPixelInfo (opaq_img, &target);
QueryColorCompliance ("white", AllCompliance, &target, exception);
if (!FloodfillPaintImage (
opaq_img, draw_info,
&target, hsX, hsY, MagickFalse, exception)) return MagickFalse;
#endif
draw_info = DestroyDrawInfo(draw_info);
// if (ppp->debug) {
// WriteFrame (ppp, opaq_img, exception);
// }
// Now change white to black, and gray to white.
CacheView * opaq_view = AcquireAuthenticCacheView (opaq_img, exception);
VIEW_PIX_PTR vpBlack, vpWhite;
SET_PIXEL_RED (opaq_img, 0, &vpBlack);
SET_PIXEL_GREEN (opaq_img, 0, &vpBlack);
SET_PIXEL_BLUE (opaq_img, 0, &vpBlack);
SET_PIXEL_RED (opaq_img, QuantumRange, &vpWhite);
SET_PIXEL_GREEN (opaq_img, QuantumRange, &vpWhite);
SET_PIXEL_BLUE (opaq_img, QuantumRange, &vpWhite);
const double NearWhite = QuantumRange * 0.9;
const double NearBlack = QuantumRange * 0.1;
MagickBooleanType okay = MagickTrue;
ssize_t y;
#if defined(MAGICKCORE_OPENMP_SUPPORT)
#pragma omp parallel for schedule(static,4) \
shared(okay) \
MAGICK_THREADS(opaq_img,opaq_img,opaq_img->rows,1)
#endif
for (y = 0; y < opaq_img->rows; y++) {
if (!okay) continue;
VIEW_PIX_PTR *pp = GetCacheViewAuthenticPixels(
opaq_view,0,y,opaq_img->columns,1,exception);
if (!pp) okay = MagickFalse;
double val;
ssize_t x;
for (x = 0; x < opaq_img->columns; x++) {
val = GetPixelIntensity (opaq_img, pp);
if (val > NearWhite) {
*pp = vpBlack;
} else if (val > NearBlack) {
*pp = vpWhite;
}
pp += Inc_ViewPixPtr (opaq_img);
}
if (SyncCacheViewAuthenticPixels(opaq_view,exception) == MagickFalse)
okay = MagickFalse;
}
opaq_view = DestroyCacheView (opaq_view);
return okay;
}
static MagickBooleanType TransNoImp (
paintpatchesT * ppp,
RectangleInfo * patchRect,
Image *pat_img,
MagickBooleanType * anyOpaque,
ExceptionInfo *exception)
// Where patch would not improve the canvas, make patch pixels transparent.
// Optionally also: make transparent all pixels
// except those that would be flood-filled from the hotspot.
//
{
if (ppp->verbose > 1) {
fprintf (stderr, "TransNoImp\n");
fprintf (stderr, "TransNoImp: rect %lix%li+%li+%li\n",
patchRect->width, patchRect->height, patchRect->x, patchRect->y);
}
// Paste the patch over a trial copy of the canvas.
Image * trial_canv = CloneImage(ppp->canvas, 0, 0, MagickTrue, exception);
if (!trial_canv) return MagickFalse;
if (!PaintPatchImg (ppp, trial_canv, patchRect, pat_img, exception))
return MagickFalse;
// Crop master, trial and canvas.
// Find the differences:
// crp_trial := abs (master - trial)
// crp_canv := abs (master - canvas)
// Possibly blur.
// The smallest tells us which we should keep.
// So where crp_trial > crp_canv, make patch transparent.
RectangleInfo clippedRect;
int dx, dy;
ClipPatchRect (ppp, patchRect, &clippedRect, &dx, &dy);
Image * crp_mast = CropImage (ppp->master, &clippedRect, exception);
if (!crp_mast) return MagickFalse;
ResetPage (crp_mast);
Image * crp_trial = CropImage (trial_canv, &clippedRect, exception);
if (!crp_trial) return MagickFalse;
ResetPage (crp_trial);
Image * crp_canv = CropImage (ppp->canvas, &clippedRect, exception);
if (!crp_canv) return MagickFalse;
ResetPage (crp_canv);
if (!COMPOSITE(crp_trial, DifferenceCompositeOp, crp_mast, 0,0, exception))
{
fprintf (stderr, "TransNoImp: composite1 failed\n");
return MagickFalse;
}
if (!COMPOSITE(crp_canv, DifferenceCompositeOp, crp_mast, 0,0, exception))
{
fprintf (stderr, "TransNoImp: composite2 failed\n");
return MagickFalse;
}
if (ppp->verbose > 1) {
fprintf (stderr, "TransNoImp3: rect %lix%li+%li+%li\n",
clippedRect.width, clippedRect.height, clippedRect.x, clippedRect.y);
}
Image * blr_img2 = BlurImage (crp_trial, 0, 3, exception);
if (!blr_img2) return MagickFalse;
ReplaceImageInList (&crp_trial, blr_img2);
blr_img2 = BlurImage (crp_canv, 0, 3, exception);
if (!blr_img2) return MagickFalse;
ReplaceImageInList (&crp_canv, blr_img2);
if (ppp->debug) {
WrImage ("trial", crp_trial);
WrImage (" canv", crp_canv);
WrImage (" pat", pat_img);
WriteFrame (ppp, crp_trial, exception);
WriteFrame (ppp, crp_canv, exception);
}
// Write opacity as black or white to a cloned image opaq_img,
// possibly flood-fill etc,
// then "-CopyOpacity" that to the patch.
Image * opaq_img = CloneImage (
pat_img, pat_img->columns, pat_img->rows, MagickTrue, exception);
if (!opaq_img) return MagickFalse;
SetAllOneCol (opaq_img, "White", exception);
CacheView * crp_trial_view = AcquireVirtualCacheView (crp_trial, exception);
CacheView * crp_canv_view = AcquireVirtualCacheView (crp_canv, exception);
CacheView * opaq_view = AcquireAuthenticCacheView (opaq_img, exception);
if (ppp->verbose > 1) fprintf (stderr, "TransNoImp4 dx=%i dy=%i\n", dx, dy);
*anyOpaque = MagickFalse;
MagickBooleanType okay = MagickTrue;
ssize_t y;
#if defined(MAGICKCORE_OPENMP_SUPPORT)
#pragma omp parallel for schedule(static,4) \
shared(okay) \
MAGICK_THREADS(crp_trial,crp_canv,clippedRect.height,1)
#endif
for (y = 0; y < clippedRect.height; y++) {
if (!okay) continue;
const VIEW_PIX_PTR *ctp = GetCacheViewVirtualPixels(
crp_trial_view,0,y,clippedRect.width,1,exception);
if (!ctp) okay = MagickFalse;
const VIEW_PIX_PTR *ccp = GetCacheViewVirtualPixels(
crp_canv_view,0,y,clippedRect.width,1,exception);
if (!ccp) okay = MagickFalse;
VIEW_PIX_PTR *pp = GetCacheViewAuthenticPixels(
opaq_view,dx,y+dy,clippedRect.width,1,exception);
if (!pp) okay = MagickFalse;
ssize_t x;
for (x = 0; x < clippedRect.width; x++) {
// FIXME: We could use a threshold here.
if (SumSq (crp_trial, ctp) >= SumSq (crp_canv, ccp)) {
SET_PIXEL_RED (opaq_img, 0, pp);
SET_PIXEL_GREEN (opaq_img, 0, pp);
SET_PIXEL_BLUE (opaq_img, 0, pp);
} else {
*anyOpaque = MagickTrue;
}
ctp += Inc_ViewPixPtr (crp_trial);
ccp += Inc_ViewPixPtr (crp_canv);
pp += Inc_ViewPixPtr (opaq_img);
}
if (SyncCacheViewAuthenticPixels(opaq_view,exception) == MagickFalse)
okay = MagickFalse;
}
if (!okay) return MagickFalse;
if (ppp->verbose > 1) fprintf (stderr, "TransNoImp5\n");
opaq_view = DestroyCacheView (opaq_view);
crp_canv_view = DestroyCacheView (crp_canv_view);
crp_trial_view = DestroyCacheView (crp_trial_view);
if (ppp->debug) {
WriteFrame (ppp, opaq_img, exception);
}
if (anyOpaque) {
if (ppp->hotSpot) {
if (!OnlyHotspot (ppp, opaq_img, exception)) {
fprintf (stderr, "TransNoImp: OnlyHotspot failed\n");
return MagickFalse;
}
}
if (ppp->featherEdges > 0) {
Image * blr_img2 = BlurImage (opaq_img,
4*ppp->featherEdges, ppp->featherEdges, exception);
if (!blr_img2) return MagickFalse;
ReplaceImageInList (&opaq_img, blr_img2);
}
if (ppp->debug) {
WriteFrame (ppp, opaq_img, exception);
}
#if IMV6OR7==6
SetImageAlphaChannel (opaq_img, DeactivateAlphaChannel);
#else
SetImageAlphaChannel (opaq_img, DeactivateAlphaChannel, exception);
#endif
if (!COMPOSITE(pat_img, COPY_OPACITY, opaq_img, 0,0, exception))
{
fprintf (stderr, "TransNoImp: composite CO failed\n");
return MagickFalse;
}
if (ppp->debug) {
WriteFrame (ppp, pat_img, exception);
}
}
opaq_img = DestroyImage (opaq_img);
crp_canv = DestroyImage (crp_canv);
crp_trial = DestroyImage (crp_trial);
crp_mast = DestroyImage (crp_mast);
trial_canv = DestroyImage (trial_canv);
return MagickTrue;
}
//#if IMV6OR7==6
//# define CLAMP(img,exc) ClampImage(img)
//#else
//# define CLAMP(img,exc) ClampImage(img,exc)
//#endif
// The next function is similar in style to functions in transform.c
// It takes one to five images, and returns one image.
//
static Image *paintpatches (
Image *image,
paintpatchesT * ppp,
ExceptionInfo *exception)
{
if (image->debug != MagickFalse)
(void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
if (ppp->verbose) {
fprintf (stderr, "paintpatches: Input image [%s] %ix%i depth is %i\n",
image->filename,
(int)image->columns, (int)image->rows,
(int)image->depth);
}
// Check the images.
ppp->master = ppp->canvas = ppp->saliency
= ppp->samples = ppp->subsrch = NULL;
ppp->master = image;
if (!ppp->master) return NULL;
CLAMP (ppp->master, exception);
Image *i2=NULL, *i3=NULL, *i4=NULL;
i2 = GetNextImageInList(ppp->master);
if (i2) i3 = GetNextImageInList(i2);
if (i3) i4 = GetNextImageInList(i3);
if (SameSize (ppp->master, i2)) {
ppp->canvas = i2;
if (SameSize (ppp->canvas, i3)) {
ppp->saliency = i3;
ppp->samples = i4;
} else {
ppp->samples = i3;
}
} else {
ppp->samples = i2;
}
if (ppp->saliency) {
CLAMP (ppp->saliency, exception);
}
if (ppp->samples) {
CLAMP (ppp->samples, exception);
ppp->subsrch = GetNextImageInList(ppp->samples);
}
if (ppp->subsrch) {
CLAMP (ppp->subsrch, exception);
}
if (ppp->verbose) {
WrImage ("master", ppp->master);
WrImage ("canvas", ppp->canvas);
WrImage ("saliency", ppp->saliency);
WrImage ("samples", ppp->samples);
WrImage ("subsrch", ppp->subsrch);
}
if (ppp->master->rows < 2) {
fprintf (stderr, "Master < 2 rows.\n");
return NULL;
}
if (ppp->samples) {
if ((ppp->samples->columns <= ppp->master->columns) ||
(ppp->samples->rows <= ppp->master->rows))
{
fprintf (stderr, "Samples smaller than master.\n");
return NULL;
}
}
if (ppp->samples && ppp->subsrch) {
if ((ppp->samples->columns != ppp->subsrch->columns) ||
(ppp->samples->rows != ppp->subsrch->rows))
{
fprintf (stderr, "Samples and subsrch are different sizes.\n");
return NULL;
}
}
if (!SetNoPalette (ppp->master, exception))
return (Image *)NULL;
if (ppp->canvas) {
if (!SetNoPalette (ppp->canvas, exception)) return (Image *)NULL;
} else {
if (ppp->verbose) fprintf (stderr, "Creating canvas\n");
ppp->canvas = CloneImage (
ppp->master, ppp->master->columns, ppp->master->rows, MagickTrue, exception);
if (!ppp->canvas) return NULL;
SetAllBlack (ppp->canvas, exception);
}
if (ppp->verbose) WrImage ("canvas", ppp->canvas);
if (ppp->saliency) {
if (!SetNoPalette (ppp->saliency, exception)) return (Image *)NULL;
} else {
if (ppp->verbose) fprintf (stderr, "Creating saliency\n");
ppp->saliency = CloneImage (
ppp->master, ppp->master->columns, ppp->master->rows, MagickTrue, exception);
if (!ppp->saliency) return NULL;
SetAllOneCol (ppp->saliency, "gray(50%)", exception);
}
if (ppp->verbose) WrImage ("saliency", ppp->saliency);
// FIXME: negate red channel?
if (ppp->colFrom == cfUndef && ppp->samples != NULL)
ppp->colFrom = cfSample;
if (ppp->colFrom == cfSample && !ppp->samples) {
fprintf (stderr, "colFrom Samples but no samples image.\n");
return (Image *)NULL;
}
if (!ResolveUserDims (&ppp->patchDims, ppp->master->columns, ppp->master->rows, 0.0))
return NULL;
ppp->patchWi = ppp->patchDims.x.Pix;
ppp->patchHt = ppp->patchDims.y.Pix;
if (ppp->verbose)
fprintf (stderr, "patches dims: %ix%i\n", ppp->patchWi, ppp->patchHt);
InitRmseAlpha (&ppp->rmseAlpha);
ssize_t prev_wrngX = -1, prev_wrngY = -1;
int cnt_same = 0;
int iter;
for (iter=0; iter < ppp->maxIter; iter++) {
if (!MostWrong (ppp, exception)) {
fprintf (stderr, "MostWrong failed\n");
return NULL;
}
if (ppp->verbose) {
fprintf (stderr, "iter=%i wrng: %g @ %li,%li\n",
iter, ppp->wrongness, ppp->wrngX, ppp->wrngY);
}
if (ppp->wrongness < ppp->wrngThresh) break;
RectangleInfo patchRect;
if (GetRect (ppp, &patchRect, exception)) {
Image * pat_img = GetPatchImg (ppp, &patchRect, exception);
if (!pat_img) return NULL;
if (ppp->verbose > 1) WrImage ("Patch", pat_img);
MagickBooleanType anyOpaque = MagickTrue;
if (ppp->patchShape == psMin) {
if (!TransNoImp (ppp, &patchRect, pat_img, &anyOpaque, exception)) {
fprintf (stderr, "TransNoImp failed\n");
return NULL;
}
}
if (anyOpaque) {
if (!PaintPatchCanvas (ppp, &patchRect, pat_img, exception))
return NULL;
if (!MultSalientPatch (ppp, &patchRect, pat_img, ppp->multSal, exception))
return NULL;
}
pat_img = DestroyImage (pat_img);
if (ppp->frameName && anyOpaque) {
// For performance: don't need to create and destroy every time.
Image * frame_img = CloneImage(ppp->canvas, 0, 0, MagickTrue, exception);
if (frame_img == (Image *) NULL)
return MagickFalse;
WriteFrame (ppp, frame_img, exception);
DestroyImage (frame_img);
}
cnt_same = 0;
if (!MultOneSalient (ppp, ppp->wrngX, ppp->wrngY, 0.25, exception)) {
fprintf (stderr, "MultOneSalient failed\n");
return NULL;
}
}
if (ppp->wrngX == prev_wrngX && ppp->wrngY == prev_wrngY) {
cnt_same++;
if (cnt_same > 3) {
cnt_same = 0;
if (!SetOneNonSalient (ppp, ppp->wrngX, ppp->wrngY, exception)) {
fprintf (stderr, "SetOneSalient failed\n");
return NULL;
}
}
} else {
prev_wrngX = ppp->wrngX;
prev_wrngY = ppp->wrngY;
cnt_same = 0;
}
if (ppp->verbose > 2) fprintf (stderr, "loop\n");
}
if (ppp->verbose) {
fprintf (stderr, "Finished paintpatches iter=%i\n", iter);
if (ppp->frameNum)
fprintf (stderr, " Created %i frames\n", ppp->frameNum);
}
chkentry ("canvas1", &ppp->canvas);
DeInitRmseAlpha (&ppp->rmseAlpha);
Image * new_img = CloneImage (ppp->canvas, 0, 0, MagickTrue, exception);
if (!new_img) {
fprintf (stderr, "new_img: clone failed\n");
return NULL;
}
// Destroy all the images except for master.
Image * nxt_img = GetNextImageInList(ppp->master);
while (nxt_img) {
fprintf (stderr, "DeleteImageFromList: ");
WrImage (" difl", nxt_img);
DeleteImageFromList (&nxt_img);
nxt_img = GetNextImageInList(ppp->master);
}
chkentry ("new_img1", &new_img);
return (new_img);
}
ModuleExport size_t paintpatchesImage (
Image **images,
const int argc,
const char **argv,
ExceptionInfo *exception)
{
Image
*new_image;
MagickBooleanType
status;
paintpatchesT ppp;
assert(images != (Image **) NULL);
assert(*images != (Image *) NULL);
assert((*images)->signature == MAGICK_CORE_SIG);
status = menu (argc, argv, &ppp);
if (status == MagickFalse)
return (-1);
ppp.precision = GetMagickPrecision();
chklist ("main0", images);
new_image = paintpatches (*images, &ppp, exception);
if (!new_image) {
fprintf (stderr, "No new_image\n");
return -1;
}
chkentry ("main1 newimage", &new_image);
chkentry ("main1 image", images);
chkentry ("main1 bef repl", images);
ReplaceImageInList (images, new_image);
chkentry ("main1 aft repl", images);
chkentry ("main1 after repl", &new_image);
return(MagickImageFilterSignature);
}
/* Updated:
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <float.h>
#include "vsn_defines.h"
#define VERSION "plotrg v1.0 Copyright (c) 2018 Alan Gibson"
#define countT double
typedef struct {
int countsDim; // width and height
int precision;
MagickBooleanType
verbose,
regardAlpha,
do_norm,
do_norm_sum,
calcOnly;
FILE *
fh_data;
countT
*counts,
minCount,
maxCount,
numOutside;
MagickRealType
minVal,
maxVal;
} plotrgT;
static void usage (void)
{
printf ("Usage: -process 'plotrg [OPTION]...'\n");
printf ("plot histogram of Red and Green channels.\n");
printf ("\n");
printf (" d, dim integer width and height of square output\n");
printf (" ra, regardAlpha use alpha for increment\n");
printf (" n, norm make max count 100%% of quantum\n");
printf (" ns, normSum make sum of counts 100%% of quantum\n");
printf (" co, calcOnly calculate only; don't replace image\n");
printf (" f, file string write to file stream stdout or stderr\n");
printf (" v, verbose write text information to stdout\n");
printf (" version write version information to stdout\n");
printf ("\n");
}
static MagickBooleanType IsArg (char * pa, char * ShtOpt, char * LongOpt)
{
if ((LocaleCompare(pa, ShtOpt)==0) || (LocaleCompare(pa, LongOpt)==0))
return MagickTrue;
return MagickFalse;
}
#define NEXTARG \
if (i < argc-1) i++; \
else { \
fprintf (stderr, "plotrg: ERROR: [%s] needs argument\n", pa); \
status = MagickFalse; \
}
static MagickBooleanType menu(
const int argc,
const char **argv,
plotrgT * pprg
)
// Returns MagickTrue if okay.
{
int
i;
MagickBooleanType
status = MagickTrue;
pprg->countsDim = 256;
pprg->verbose = MagickFalse;
pprg->regardAlpha = MagickFalse;
pprg->do_norm = MagickFalse;
pprg->do_norm_sum = MagickFalse;
pprg->calcOnly = MagickFalse;
pprg->fh_data = stderr;
for (i=0; i < argc; i++) {
char * pa = (char *)argv[i];
if (IsArg (pa, "d", "dim")==MagickTrue) {
NEXTARG;
pprg->countsDim = atoi(argv[i]);
} else if (IsArg (pa, "ra", "regardAlpha")==MagickTrue) {
pprg->regardAlpha = MagickTrue;
} else if (IsArg (pa, "n", "norm")==MagickTrue) {
pprg->do_norm = MagickTrue;
pprg->do_norm_sum = MagickFalse;
} else if (IsArg (pa, "ns", "normSum")==MagickTrue) {
pprg->do_norm = MagickFalse;
pprg->do_norm_sum = MagickTrue;
} else if (IsArg (pa, "co", "calcConly")==MagickTrue) {
pprg->calcOnly = MagickTrue;
} else if (IsArg (pa, "f", "file")==MagickTrue) {
NEXTARG;
if (strcasecmp (argv[i], "stdout")==0) pprg->fh_data = stdout;
else if (strcasecmp (argv[i], "stderr")==0) pprg->fh_data = stderr;
else status = MagickFalse;
} else if (IsArg (pa, "v", "verbose")==MagickTrue) {
pprg->verbose = MagickTrue;
} else if (IsArg (pa, "version", "version")==MagickTrue) {
fprintf (stdout, "%s\n", VERSION);
} else {
fprintf (stderr, "plotrg: ERROR: unknown option\n");
status = MagickFalse;
}
}
if (pprg->countsDim < 1) {
fprintf (stderr, "dim must be at least one\n");
status = MagickFalse;
}
if (pprg->verbose) {
fprintf (stderr, "plotrg options:");
fprintf (stderr, " dim %i", pprg->countsDim);
if (pprg->regardAlpha) fprintf (stderr, " regardAlpha");
if (pprg->verbose) fprintf (stderr, " verbose");
if (pprg->do_norm) fprintf (stderr, " normalise");
if (pprg->do_norm_sum) fprintf (stderr, " normaliseSum");
if (pprg->calcOnly) fprintf (stderr, " calcOnly");
fprintf (stderr, "\n");
}
if (status == MagickFalse)
usage ();
return (status);
}
// The next function takes an image,
// and returns it or a new image.
//
static Image * plotrg (
Image *image,
plotrgT * pprg,
ExceptionInfo *exception)
{
MagickBooleanType
status = MagickTrue;
int nArray = pprg->countsDim*pprg->countsDim;
if (nArray < 1) return NULL;
pprg->counts = (countT *)malloc(nArray*sizeof(countT));
if (!pprg->counts) {
fprintf (stderr, "Array allocation failed.\n");
return NULL;
}
int i;
countT * pcnt = pprg->counts;
for (i=0; i < nArray; i++) *(pcnt++) = 0;
CacheView * image_view = AcquireVirtualCacheView (image,exception);
MagickRealType
minR = DBL_MAX, minG = DBL_MAX,
maxR = -DBL_MAX, maxG = -DBL_MAX;
MagickRealType
fact = pprg->countsDim / (MagickRealType)(QuantumRange+1);
double numOOG = 0;
double totalCnt = 0;
ssize_t y;
for (y = 0; y < image->rows; y++) {
VIEW_PIX_PTR const
*p;
if (status == MagickFalse)
continue;
p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
if (!p) {
status=MagickFalse;
continue;
}
MagickRealType r, g;
MagickRealType alph = 1.0;
ssize_t x;
for (x = 0; x < image->columns; x++) {
r = GET_PIXEL_RED (image, p);
g = GET_PIXEL_GREEN (image, p);
if (pprg->regardAlpha)
alph = GET_PIXEL_ALPHA (image, p) / (MagickRealType)QuantumRange;
if (minR > r) minR = r;
if (minG > g) minG = g;
if (maxR < r) maxR = r;
if (maxG < g) maxG = g;
int buckR = floor (fact * r);
int buckG = floor (fact * g);
// printf ("%i %i ", buckR, buckG);
if (buckR < 0 || buckG < 0 ||
buckR >= pprg->countsDim || buckG >= pprg->countsDim)
{
numOOG += alph;
} else {
pprg->counts[buckG * pprg->countsDim + buckR] += alph;
totalCnt += alph;
}
p += Inc_ViewPixPtr (image);
}
}
if (!status) return NULL;
image_view = DestroyCacheView (image_view);
if (pprg->verbose) {
fprintf (pprg->fh_data,
"totalCnt=%.*g\n",
pprg->precision, totalCnt);
fprintf (pprg->fh_data,
"minR=%.*g\nmaxR=%.*g\n",
pprg->precision, minR / (double)QuantumRange,
pprg->precision, maxR / (double)QuantumRange);
fprintf (pprg->fh_data,
"minG=%.*g\nmaxG=%.*g\n",
pprg->precision, minG / (double)QuantumRange,
pprg->precision, maxG / (double)QuantumRange);
fprintf (pprg->fh_data,
"minVal=%.*g\nmaxVal=%.*g\n",
pprg->precision,
((minR<minG) ? minR : minG) / (double)QuantumRange,
pprg->precision,
((maxR>maxG) ? maxR : maxG) / (double)QuantumRange);
fprintf (pprg->fh_data,
"numOOG=%.*g\n", pprg->precision, numOOG);
}
countT minCnt, maxCnt;
minCnt = maxCnt = pprg->counts[0];
pcnt = pprg->counts;
for (i=0; i < nArray; i++) {
if (minCnt > *pcnt) minCnt = *pcnt;
if (maxCnt < *pcnt) maxCnt = *pcnt;
pcnt++;
}
if (pprg->verbose) {
fprintf (pprg->fh_data,
"minCnt=%.*g\nmaxCnt=%.*g\n",
pprg->precision, minCnt,
pprg->precision, maxCnt);
}
if (pprg->do_norm) {
pcnt = pprg->counts;
MagickRealType fact = QuantumRange / (MagickRealType)maxCnt;
for (i=0; i < nArray; i++) {
*pcnt = *pcnt * fact;
pcnt++;
}
} else if (pprg->do_norm_sum) {
pcnt = pprg->counts;
MagickRealType fact = QuantumRange / (MagickRealType)totalCnt;
for (i=0; i < nArray; i++) {
*pcnt = *pcnt * fact;
pcnt++;
}
}
Image * newImg = NULL;
if (!pprg->calcOnly) {
newImg = CloneImage (image, pprg->countsDim, pprg->countsDim, MagickTrue, exception);
if (!newImg) return NULL;
CacheView * out_view = AcquireAuthenticCacheView (newImg, exception);
for (y = 0; y < newImg->rows; y++) {
if (status == MagickFalse)
continue;
VIEW_PIX_PTR *q = GetCacheViewAuthenticPixels(
out_view, 0, y, newImg->columns, 1, exception);
if (!q) {
status=MagickFalse;
continue;
}
pcnt = &pprg->counts[y * pprg->countsDim];
ssize_t x;
for (x = 0; x < newImg->columns; x++) {
SET_PIXEL_RED (newImg, *pcnt, q);
SET_PIXEL_GREEN (newImg, *pcnt, q);
SET_PIXEL_BLUE (newImg, *pcnt, q);
SET_PIXEL_ALPHA (newImg, QuantumRange, q);
q += Inc_ViewPixPtr (newImg);
pcnt++;
}
if (!SyncCacheViewAuthenticPixels(out_view,exception)) {
fprintf (stderr, "bad sync\n");
status=MagickFalse;
}
}
out_view = DestroyCacheView (out_view);
}
if (!status) return NULL;
free (pprg->counts);
return (newImg) ? newImg : image;
}
ModuleExport size_t plotrgImage(Image **images,const int argc,
const char **argv,ExceptionInfo *exception)
{
MagickBooleanType
status;
plotrgT
prg;
assert(images != (Image **) NULL);
assert(*images != (Image *) NULL);
assert((*images)->signature == MAGICK_CORE_SIG);
prg.precision = GetMagickPrecision();
status = menu (argc, argv, &prg);
if (status == MagickFalse)
return (-1);
Image * image;
for (image=(*images); image != (Image *) NULL; image=GetNextImageInList(image))
{
Image * newImg = plotrg (image, &prg, exception);
if (!newImg) return (-1);
if (newImg != image) {
ReplaceImageInList (&image, newImg);
*images=GetFirstImageInList (newImg);
}
}
return(MagickImageFilterSignature);
}
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include "vsn_defines.h"
/*
Find coordinate of the lightest pixel.
If there is more than one with equal lightness,
finds the one that is nearest the centroid of those pixels.
*/
// The next function corresponds in style to functions in enhance.c
// It takes one image, and returns a status.
//
static MagickBooleanType centroid(const Image *image,
ExceptionInfo *exception)
{
MagickBooleanType
status;
CacheView
*image_view;
ssize_t
y;
assert(image != (Image *) NULL);
assert(image->signature == MAGICK_CORE_SIG);
if (image->debug != MagickFalse)
(void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
image_view = AcquireVirtualCacheView(image,exception);
status = MagickTrue;
// valLightest = 0;
// xLightest = yLightest = 0;
// int nFound = 0;
// int sigX=0, sigY=0;
long double sigW = 0.0, sigWX = 0.0, sigWY = 0.0;
for (y=0; y < (ssize_t) image->rows; y++)
{
VIEW_PIX_PTR const *p;
register ssize_t x;
long double val;
if (status == MagickFalse) continue;
p=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
if (p == (const VIEW_PIX_PTR *) NULL) status=MagickFalse;
for (x=0; x < (ssize_t) image->columns; x++) {
val = GetPixelIntensity(image, p);
sigW += val;
sigWX += x * val;
sigWY += y * val;
p += Inc_ViewPixPtr (image);
}
}
if (!status) return MagickFalse;
int precision = GetMagickPrecision();
if (sigW > 0) {
fprintf (stderr, "centroid: %.*g,%.*g\n",
precision, (double)(sigWX/sigW),
precision, (double)(sigWY/sigW));
} else {
fprintf (stderr, "centroid: %.*g,%.*g\n",
precision, (double)((image->columns-1.0)/2.0),
precision, (double)((image->rows-1.0)/2.0));
}
return MagickTrue;
}
ModuleExport size_t centroidImage(Image **images,const int argc,
const char **argv,ExceptionInfo *exception)
{
Image
*image;
MagickBooleanType
status;
(void) argc;
(void) argv;
assert(images != (Image **) NULL);
assert(*images != (Image *) NULL);
assert((*images)->signature == MAGICK_CORE_SIG);
for (image=(*images); image != (Image *) NULL; image=GetNextImageInList(image))
{
status = centroid(image, exception);
if (status == MagickFalse)
continue;
}
assert (status == MagickTrue);
return(MagickImageFilterSignature);
}
/* Updated:
14-December-2019 Moved 3x3 matrix functions to mat3x3.inc
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <ctype.h>
#include "vsn_defines.h"
#include "mat3x3.inc"
#define VERSION "barymap v1.0 Copyright (c) 2019 Alan Gibson"
typedef struct {
double x;
double y;
} coordsT;
typedef struct {
double x;
double y;
double z;
} xyzCoordsT;
typedef struct {
double b1;
double b2;
double b3;
} baryCoordsT;
typedef struct {
coordsT primA; // usually for red
coordsT primB; // usually for green
coordsT primC; // usually for blue
// Calculated:
double
y23, x13, x32, y13, y31, x3, y3;
double det;
} triangleT;
typedef enum {
chRGB,
chXYY,
chXYZ
} channelsT;
typedef struct {
char * name;
double
power,
slope,
limit,
offset;
double
invPower,
limit2,
offsetP1;
} transT;
typedef struct {
channelsT chans;
triangleT triMain;
coordsT WP; // white point
double gamma; // -1 etc means sRGB etc.
coordsT XYZwp; // white point for XYZ
MagickBooleanType
menuSetChan, menuSetPrim, menuSetWP, menuSetGam, menuSetXYZwp;
// Calculated:
//
triangleT
subTris[4];
mat3x3T
RGB2XYZ,
XYZ2RGB,
chromAdapTrans,
RGB2XYZchr,
XYZ2RGBchr;
transT
trans;
} colSpT;
typedef struct {
double
direction,
width,
factor;
// Calculated:
double
halfWidth,
minDir,
maxDir;
} AngMultT;
#define MaxAngMults 10
typedef struct {
colSpT colSpIn;
colSpT colSpOut;
int ndxCat; // index into cats array
int
verbose,
precision;
double
gain2,
gain,
bias,
power;
int nAngMults;
AngMultT AngMults[MaxAngMults];
MagickBooleanType
clampXy,
clampBary,
ignoreWP,
doTriangles,
round16;
FILE *
fh_data;
mat3x3T
chromAdapTrans;
} barymT;
typedef struct {
char * name;
coordsT
coordsR,
coordsG,
coordsB,
coordsWP;
double gamma;
} colSpNumsT;
// These are CIE 2 degrees
const colSpNumsT colSpNums[] = {
{"sRGB", // From https://en.wikipedia.org/wiki/SRGB,
// also IM "-colorspace sRGB"
{0.64, 0.33},
{0.3, 0.6},
{0.15, 0.06},
{0.3127, 0.3290}, // D65
-1
},
{"sRgbD50", // From http://www.brucelindbloom.com/index.html?WorkingSpaceInfo.html
{0.648431, 0.330856},
{0.321152, 0.597871},
{0.155886, 0.066044},
{0.34567, 0.35850}, // D50
-1
},
{"Rec709", // From https://en.wikipedia.org/wiki/Rec._709
{0.64, 0.33},
{0.3, 0.6},
{0.15, 0.06},
{0.3127, 0.3290}, // D65
2.4 // But not really, see https://en.wikipedia.org/wiki/Rec._709
},
{"P3D65", // From https://en.wikipedia.org/wiki/DCI-P3
{0.680, 0.320},
{0.265, 0.690},
{0.150, 0.060},
{0.3127, 0.3290}, // D65
2.6
},
{"DisplayP3", // From https://en.wikipedia.org/wiki/DCI-P3
{0.680, 0.320},
{0.265, 0.690},
{0.150, 0.060},
{0.3127, 0.3290}, // D65
-1
},
{"AdobeRGB", // From https://en.wikipedia.org/wiki/Adobe_RGB_color_space
{0.6400, 0.3300},
{0.2100, 0.7100},
{0.1500, 0.0600},
{0.3127, 0.3290}, // D65
563/256 // = 2.19921875 exactly
},
{"Rec2020", // From https://en.wikipedia.org/wiki/Rec._2020
{0.708, 0.292},
{0.170, 0.797},
{0.131, 0.046},
{0.3127, 0.3290}, // D65
2.4 // But not really, see https://en.wikipedia.org/wiki/Rec._2020#Transfer_characteristics
},
{"ProPhotoRGB", // From https://en.wikipedia.org/wiki/ProPhoto_RGB_color_space
{0.7347, 0.2653},
{0.1596, 0.8404},
{0.0366, 0.0001},
{0.3457, 0.3585},
1.8 // Not really, see https://en.wikipedia.org/wiki/ProPhoto_RGB_color_space#Encoding_function
},
{"ACEScg", // From https://www.oscars.org/science-technology/aces/aces-documentation
{0.713, 0.293},
{0.165, 0.830},
{0.128, 0.044},
{0.32168, 0.33767}, // Close to D60, apparently.
1
},
{"ACES2065-1", // From https://en.wikipedia.org/wiki/Academy_Color_Encoding_System
{0.7347, 0.2653},
{0.0000, 1.0000},
{0.0001, -0.0770},
{0.32168, 0.33767}, // Close to D60, apparently.
1
},
{"xyY",
{1.0, 0.0},
{0.0, 1.0},
{0.0, 0.0},
{0.34567, 0.35850}, // Assumed to be D50
1
},
{"XYZ",
{1.0, 0.0},
{0.0, 1.0},
{0.0, 0.0},
{0.34567, 0.35850}, // Assumed to be D50
1
}
};
typedef struct {
char * name;
coordsT
coordsWP;
} wpCoordsT;
// White points,
// mostly from https://en.wikipedia.org/wiki/Standard_illuminant#White_point
//
// CIE 1931 2 degrees.
const wpCoordsT wpCoords [] = {
{"A", {0.44757, 0.40745}},
{"D50", {0.34567, 0.35850}},
{"D60", {0.32168, 0.33767}}, // from https://en.wikipedia.org/wiki/Academy_Color_Encoding_System
{"D65", {0.31271, 0.32902}},
{"D65s", {0.3127, 0.3290}},
{"D75", {0.29902, 0.31485}},
{"D100", {0.2824, 0.2898}}, // from Vendian
{"D200", {0.2580, 0.2574}}, // from Vendian
{"D300", {0.2516, 0.2481}}, // from Vendian
{"D400", {0.2487, 0.2438}}, // from Vendian
{"E", {1/3.0, 1/3.0}}
};
// Vendian = http://www.vendian.org/mncharity/dir3/blackbody/UnstableURLs/bbr_color.html
// Vendian actually gives xy for 10000K etc, which is close to D100.
// "a" suffix from http://www.brucelindbloom.com/index.html?Eqn_ChromAdapt.html,
// citing "ASTM E308-01 except B which comes from Wyszecki & Stiles, p. 769".
// "i" suffix from ICC1v43_2010-12.
// These are values of X and Z (Y=1), not x and y.
const wpCoordsT wpCoordsXZ [] = {
{"Aa", {1.09850, 0.35585}},
{"D50a", {0.96422, 0.82521}},
{"D55a", {0.95682, 0.92149}},
{"D65a", {0.95047, 1.08883}},
{"D75a", {0.94972, 1.22638}},
{"Ea", {1, 1}},
{"D50i", {0.9642, 0.8249}}
};
//--------------------------------------------
//
// Colour conversion.
//
typedef struct {
char * name;
mat3x3T mat;
} catT;
// Ref: http://www.ivl.disco.unimib.it/download/bianco2010two-new.pdf
//
// http://www.brucelindbloom.com/index.html?Eqn_ChromAdapt.html
catT cats[] = {
{ "None",
{1,0,0, 0,1,0, 0,0,1}
},
{ "XYZ",
{1,0,0, 0,1,0, 0,0,1}
},
{ "VonKries",
{ 0.3897, 0.6890, -0.0787,
-0.2298, 1.1834, 0.0464,
0, 0, 1}
},
{ "Bradford",
{ 0.8951, 0.2664, -0.1614,
-0.7502, 1.7135, 0.0367,
0.0389, -0.0685, 1.0296}
// Bianco gives -0.0686; Lindbloom gives -0.0685
},
{ "Sharp",
{ 1.2694, 0.0988, -0.1706,
-0.8364, 1.8006, 0.0357,
0.0297, -0.0315, 1.0018}
},
{ "CMCCAT2000",
{ 0.7982, 0.3389, -0.1371,
-0.5918, 1.5512, 0.0406,
0.0008, 0.239, 0.9753 }
},
{ "CAT02",
// See also http://www.rit-mcsl.org/fairchild/PDFs/PRO19.pdf eqn (7).
{ 0.7328, 0.4296, -0.1624,
-0.7036, 1.6975, 0.0061,
0.0030, 0.0136, 0.9834 }
},
{ "BS",
{ 0.8752, 0.2787, -0.1539,
-0.8904, 1.8709, 0.0195,
-0.0061, 0.0162, 0.9899}
},
{ "BS-PC",
{ 0.6489, 0.3915, -0.0404,
-0.3775, 1.3055, 0.0720,
-0.0271, 0.0888, 0.9383}
},
{ "Fairchild",
// From Spectral Sharpening of Color Sensors: Diagonal Color Constancy and Beyond
// https://www.ncbi.nlm.nih.gov/pmc/articles/PMC4003926/
{ 0.8562, 0.3372, -0.1934,
-0.8360, 1.8327, 0.0033,
0.0357, -0.0469, 1.0112}
}
};
#define dfltCat 3 // Bradford
typedef struct {
char * name;
double
power,
slope,
limit,
offset;
} trcT;
trcT trcs[] = {
{ "None",
1, 1, 0, 0
},
{ "sRGB",
2.4, 12.92, 0.04045, 0.055
},
{ "Rec709",
1.0/0.45, 4.5, 0.018, 0.099
},
{ "Rec2020",
1.0/0.45, 4.5, 0.018053968510807, 0.09929682680944
},
{ "ProPhoto", // also see http://www.color.org/ROMMRGB.pdf
1.8, 16, 1.0/512.0, 0
}
};
//--------------------------------------------
static MagickBooleanType ParseNamedPrims (
barymT *pbary,
const char * name,
colSpT * pcs)
// return TRUE if okay.
{
MagickBooleanType ok = MagickFalse;
int i;
for (i = 0; i < sizeof(colSpNums) / sizeof(colSpNumsT); i++) {
const colSpNumsT * csn = &colSpNums[i];
if (LocaleCompare(name, csn->name)==0) {
if (pbary->verbose)
fprintf (pbary->fh_data, "Setting colSp from %s\n", csn->name);
pcs->triMain.primA.x = csn->coordsR.x;
pcs->triMain.primA.y = csn->coordsR.y;
pcs->triMain.primB.x = csn->coordsG.x;
pcs->triMain.primB.y = csn->coordsG.y;
pcs->triMain.primC.x = csn->coordsB.x;
pcs->triMain.primC.y = csn->coordsB.y;
pcs->WP.x = csn->coordsWP.x;
pcs->WP.y = csn->coordsWP.y;
pcs->XYZwp = pcs->WP;
pcs->gamma = csn->gamma;
ok = MagickTrue;
break;
}
}
return ok;
}
static MagickBooleanType ParseNamedWP (
barymT *pbary,
const char * name,
coordsT * pwp)
// return TRUE if okay.
{
MagickBooleanType ok = MagickFalse;
int i;
for (i = 0; i < sizeof(wpCoords) / sizeof(wpCoordsT); i++) {
if (LocaleCompare(name, wpCoords[i].name)==0) {
if (pbary->verbose)
fprintf (pbary->fh_data, "Setting WP from %s\n", wpCoords[i].name);
pwp->x = wpCoords[i].coordsWP.x;
pwp->y = wpCoords[i].coordsWP.y;
ok = MagickTrue;
break;
}
}
if (!ok) {
for (i = 0; i < sizeof(wpCoordsXZ) / sizeof(wpCoordsT); i++) {
if (LocaleCompare(name, wpCoordsXZ[i].name)==0) {
if (pbary->verbose)
fprintf (pbary->fh_data, "Setting WP from %s\n", wpCoords[i].name);
pwp->x = wpCoordsXZ[i].coordsWP.x / (wpCoordsXZ[i].coordsWP.x+wpCoordsXZ[i].coordsWP.y+1);
pwp->y = 1.0 / (wpCoordsXZ[i].coordsWP.x+wpCoordsXZ[i].coordsWP.y+1);
ok = MagickTrue;
break;
}
}
}
return ok;
}
static MagickBooleanType ParseNamedCat (
barymT *pbary,
const char * name)
// return TRUE if okay.
{
MagickBooleanType ok = MagickFalse;
int i;
for (i = 0; i < sizeof(cats) / sizeof(catT); i++) {
if (LocaleCompare(name, cats[i].name)==0) {
pbary->ndxCat = i;
ok = MagickTrue;
break;
}
}
return ok;
}
static MagickBooleanType ParseNamedTrc (
barymT *pbary,
const char * name,
colSpT * pcs)
// return TRUE if okay.
{
// fprintf (stderr, "ParseNamedTrc: [%s]\n", name);
MagickBooleanType ok = MagickFalse;
int i;
for (i = 0; i < sizeof(trcs) / sizeof(trcT); i++) {
// fprintf (stderr, " [%s]\n", trcs[i].name);
if (LocaleCompare(name, trcs[i].name)==0) {
pcs->gamma = -i;
ok = MagickTrue;
break;
}
}
// if (ok) fprintf (stderr, "ParseNamedTrc: [%s] found %g\n", name, pcs->gamma);
// else fprintf (stderr, "ParseNamedTrc: [%s] not found\n", name);
return ok;
}
static void ListColSp (FILE * fh)
{
int i;
for (i = 0; i < sizeof(colSpNums) / sizeof(colSpNumsT); i++) {
fprintf (fh, "%s\n", colSpNums[i].name);
}
}
static void ListWP (FILE * fh)
{
int i;
for (i = 0; i < sizeof(wpCoords) / sizeof(wpCoordsT); i++) {
fprintf (fh, "%s\n", wpCoords[i].name);
}
for (i = 0; i < sizeof(wpCoordsXZ) / sizeof(wpCoordsT); i++) {
fprintf (fh, "%s\n", wpCoordsXZ[i].name);
}
}
static void ListTrans (FILE * fh)
{
int i;
for (i = 0; i < sizeof(trcs) / sizeof(trcT); i++) {
fprintf (fh, "%s\n", trcs[i].name);
}
}
static void ListCats (FILE * fh)
{
int i;
for (i = 0; i < sizeof(cats) / sizeof(catT); i++) {
fprintf (fh, "%s\n", cats[i].name);
}
}
static void WrCoords (barymT * pbary, coordsT * pc, FILE * fh)
{
fprintf (fh, "%.*g,%.*g",
pbary->precision, pc->x,
pbary->precision, pc->y);
}
static void WrTri (barymT * pbary, triangleT * ptri, FILE * fh)
{
WrCoords (pbary, &ptri->primA, fh);
fprintf (fh, ",");
WrCoords (pbary, &ptri->primB, fh);
fprintf (fh, ",");
WrCoords (pbary, &ptri->primC, fh);
}
static void WrColSp (barymT * pbary, FILE * fh, colSpT * cs, char * pref)
{
fprintf (stderr, " %sChannels ", pref);
if (cs->menuSetChan) fprintf (fh, "* ");
switch (cs->chans) {
case chRGB: fprintf (stderr, "RGB"); break;
case chXYY: fprintf (stderr, "xyY "); break;
case chXYZ: fprintf (stderr, "XYZ "); break;
}
fprintf (fh, "\n %sPrim ", pref);
if (cs->menuSetPrim) fprintf (fh, "* ");
WrTri (pbary, &cs->triMain, fh);
fprintf (fh, "\n %sWP ", pref);
if (cs->menuSetWP) fprintf (fh, "* ");
WrCoords (pbary, &cs->WP, fh);
fprintf (fh, "\n %sTransfer ", pref);
if (cs->menuSetGam) fprintf (fh, "* ");
if (cs->gamma >= 0) {
fprintf (fh, "%.*g",
pbary->precision, cs->gamma);
} else {
fprintf (fh, "%s",
trcs[(int)floor(-cs->gamma)].name);
}
fprintf (fh, "\n XYZwp ");
if (cs->menuSetXYZwp) fprintf (fh, "* ");
WrCoords (pbary, &cs->XYZwp, fh);
fprintf (fh, "\n");
}
static void WrBarymap (barymT * pbary, FILE * fh)
{
fprintf (fh, "In:\n");
WrColSp (pbary, fh, &pbary->colSpIn, "in");
fprintf (fh, "Out:\n");
WrColSp (pbary, fh, &pbary->colSpOut, "out");
}
//--------------------------------------------
//
// Colour conversion functions.
//
typedef struct {
double X;
double Y;
double Z;
} XYZT;
typedef struct {
double R;
double G;
double B;
} RGBT;
static void xyYtoXYZ (
double x,
double y,
double UY,
XYZT * out
)
{
// Convert xyY to xYZ.
out->Y = UY;
if (y==0) {
out->X = out->Z = 0;
} else {
out->X = UY * x / y;
out->Z = UY * (1 - x - y) / y;
}
}
static void xyOneToXYZ (
double x,
double y,
XYZT * out
)
{
// Convert xyY to XYZ where Y==1.
out->Y = 1;
if (y==0) {
out->X = out->Z = 0;
} else {
out->X = x / y;
out->Z = (1 - x - y) / y;
}
}
static double inline Ro16If (double v, MagickBooleanType DoRound)
{
return (DoRound) ? round (v * 65536) / 65536 : v;
}
static void inline Round16 (double * v)
{
*v = Ro16If (*v, MagickTrue);
}
static void inline Round16xy (barymT * pbary, coordsT * xy)
{
if (pbary->round16) {
Round16 (&xy->x);
Round16 (&xy->y);
}
}
static void inline Round16XYZ (barymT * pbary, XYZT * XYZ)
{
if (pbary->round16) {
Round16 (&XYZ->X);
Round16 (&XYZ->Y);
Round16 (&XYZ->Z);
}
}
static void inline Round16mat3x3 (barymT * pbary, mat3x3T m)
{
if (pbary->round16) {
int i;
for (i=0; i<9; i++) Round16 (&m[i]);
}
}
static void WrWpNums (FILE * fh, barymT * pbary)
{
int i;
XYZT XYZ;
fprintf (fh, "WpNums:\n");
fprintf (fh, " name, x,y,z X,Y,Z\n");
for (i = 0; i < sizeof(wpCoords) / sizeof(wpCoordsT); i++) {
const wpCoordsT * pwp = &wpCoords[i];
fprintf (fh, " %s", pwp->name);
xyOneToXYZ (pwp->coordsWP.x, pwp->coordsWP.y, &XYZ);
fprintf (fh, " %.*g, %.*g, %.*g, %.*g, %.*g, %.*g\n",
pbary->precision, Ro16If (pwp->coordsWP.x, pbary->round16),
pbary->precision, Ro16If (pwp->coordsWP.y, pbary->round16),
pbary->precision, Ro16If (1.0 - pwp->coordsWP.x - pwp->coordsWP.y, pbary->round16),
pbary->precision, Ro16If (XYZ.X, pbary->round16),
pbary->precision, Ro16If (XYZ.Y, pbary->round16),
pbary->precision, Ro16If (XYZ.Z, pbary->round16)
);
}
for (i = 0; i < sizeof(wpCoordsXZ) / sizeof(wpCoordsT); i++) {
const wpCoordsT * pwp = &wpCoordsXZ[i];
xyzCoordsT xyzLo;
double div = pwp->coordsWP.x + 1 + pwp->coordsWP.y;
xyzLo.x = pwp->coordsWP.x / div;
xyzLo.y = 1.0 / div;
xyzLo.z = 1.0 - xyzLo.x - xyzLo.y;
fprintf (fh, " %s", pwp->name);
xyOneToXYZ (xyzLo.x, xyzLo.y, &XYZ);
fprintf (fh, " %.*g, %.*g, %.*g, %.*g, %.*g, %.*g\n",
pbary->precision, Ro16If (xyzLo.x, pbary->round16),
pbary->precision, Ro16If (xyzLo.y, pbary->round16),
pbary->precision, Ro16If (xyzLo.z, pbary->round16),
pbary->precision, Ro16If (XYZ.X, pbary->round16),
pbary->precision, Ro16If (XYZ.Y, pbary->round16),
pbary->precision, Ro16If (XYZ.Z, pbary->round16)
);
}
}
static void WrXYZ (barymT * pbary, const XYZT * in, char * title)
{
fprintf (pbary->fh_data, "%s XYZ = %.*g, %.*g, %.*g\n",
title,
pbary->precision, in->X,
pbary->precision, in->Y,
pbary->precision, in->Z);
}
static void inline MultMatXYZ (const mat3x3T m, const XYZT *XYZ, XYZT * out)
// [out] = [m][XYZ]
{
out->X = XYZ->X*m[0] + XYZ->Y*m[1] + XYZ->Z*m[2];
out->Y = XYZ->X*m[3] + XYZ->Y*m[4] + XYZ->Z*m[5];
out->Z = XYZ->X*m[6] + XYZ->Y*m[7] + XYZ->Z*m[8];
}
static void WrMat3x3Bary (barymT * pbary, const mat3x3T in, char * title)
{
WrMat3x3 (pbary->fh_data, pbary->precision, in, title);
}
static void TstInvMat3x3 (barymT * pbary)
{
mat3x3T out;
// http://www.brucelindbloom.com/index.html?Eqn_chromAdap.html
// Bradford MA:
//
double in[] = {
0.8951000, 0.2664000, -0.1614000,
-0.7502000, 1.7135000, 0.0367000,
0.0389000, -0.0685000, 1.0296000};
WrMat3x3Bary (pbary, in, "in:");
IdentMat3x3 (out);
if (!InvMat3x3 (in, out)) {
fprintf (pbary->fh_data, "Not invertible.\n");
}
WrMat3x3Bary (pbary, out, "out");
mat3x3T mult;
MultMat3x3 (in, out, mult);
WrMat3x3Bary (pbary, mult, "mult");
}
static MagickBooleanType ChromAdapInOut (
barymT * pbary,
coordsT * WPin,
coordsT * WPout,
mat3x3T matCa)
// From the in and out white points,
// returns a 3x3 Chromatic Adaptation matrix.
// Also calculates unadapted and adapted primaries,
// but does nothing with them.
// Ref http://www.brucelindbloom.com/index.html?Eqn_chromAdap.html
// Also https://ninedegreesbelow.com/photography/srgb-color-space-to-profile.html
// Also ICC spec, Annex E.
//
// Note: in ICC profiles, numbers are recorded as fixed-point
// (not floating-point), with 16 bits for the fractional part,
// about 4 decimal places.
{
if (pbary->verbose >= 2)
fprintf (pbary->fh_data, "ChromAdap: WP from %.*g, %.*g to %.*g, %.*g\n",
pbary->precision, WPin->x,
pbary->precision, WPin->y,
pbary->precision, WPout->x,
pbary->precision, WPout->y);
if ( (WPin->x != WPout->x) || (WPin->y != WPout->y)) {
XYZT XYZsrc, XYZdst, coneSrc, coneDst;
mat3x3T Ma, MaInv;
CopyMat3x3 (cats[pbary->ndxCat].mat, Ma);
if (!InvMat3x3 (Ma, MaInv)) {
fprintf (pbary->fh_data, "Ma not invertible.\n");
return MagickFalse;
}
if (pbary->verbose >= 2) {
WrMat3x3Bary (pbary, Ma, "chromAdap: Ma");
WrMat3x3Bary (pbary, MaInv, "chromAdap: MaInv:");
}
// For the white points, get XYZ from xy.
xyOneToXYZ (WPin->x, WPin->y, &XYZsrc);
xyOneToXYZ (WPout->x, WPout->y, &XYZdst);
if (pbary->verbose >= 2) {
WrXYZ (pbary, &XYZsrc, "src WP");
WrXYZ (pbary, &XYZdst, "dst WP");
}
// Transform WP XYZs to cone response domains (rho, gamma, beta).
MultMatXYZ (Ma, &XYZsrc, &coneSrc);
MultMatXYZ (Ma, &XYZdst, &coneDst);
if (pbary->verbose >= 2) {
WrXYZ (pbary, &coneSrc, "cone src");
WrXYZ (pbary, &coneDst, "cone dst");
}
mat3x3T crdMat;
ZeroMat3x3 (crdMat);
crdMat[0] = coneDst.X / coneSrc.X;
crdMat[4] = coneDst.Y / coneSrc.Y;
crdMat[8] = coneDst.Z / coneSrc.Z;
mat3x3T tmp;
MultMat3x3 (MaInv, crdMat, tmp);
if (pbary->verbose >= 2) {
WrMat3x3Bary (pbary, crdMat, "chromAdap: crdMat:");
WrMat3x3Bary (pbary, tmp, "chromAdap: tmp:");
}
MultMat3x3 (tmp, Ma, matCa);
Round16mat3x3 (pbary, matCa);
} else {
IdentMat3x3 (matCa);
}
if (pbary->verbose >= 2) {
WrMat3x3Bary (pbary, matCa, "chromAdap: matCa:");
}
// Following Elle,
// we calculate unadjusted and adjusted XYZ of primaries,
// but we do nothing with these.
XYZT rXYZ, gXYZ, bXYZ;
xyOneToXYZ (pbary->colSpIn.triMain.primA.x, pbary->colSpIn.triMain.primA.y, &rXYZ);
xyOneToXYZ (pbary->colSpIn.triMain.primB.x, pbary->colSpIn.triMain.primB.y, &gXYZ);
xyOneToXYZ (pbary->colSpIn.triMain.primC.x, pbary->colSpIn.triMain.primC.y, &bXYZ);
if (pbary->verbose >= 2) {
WrXYZ (pbary, &rXYZ, "interim unadap r");
WrXYZ (pbary, &gXYZ, "interim unadap g");
WrXYZ (pbary, &bXYZ, "interim unadap b");
}
mat3x3T tmp;
tmp[0] = rXYZ.X;
tmp[1] = gXYZ.X;
tmp[2] = bXYZ.X;
tmp[3] = rXYZ.Y;
tmp[4] = gXYZ.Y;
tmp[5] = bXYZ.Y;
tmp[6] = rXYZ.Z;
tmp[7] = gXYZ.Z;
tmp[8] = bXYZ.Z;
if (pbary->verbose >= 2) {
WrMat3x3Bary (pbary, tmp, "chromAdap: tmp:");
}
mat3x3T tmpInv; // Elle's table 5c.
if (!InvMat3x3 (tmp, tmpInv)) {
fprintf (pbary->fh_data, "tmp not invertible.\n");
return MagickFalse;
}
if (pbary->verbose >= 2) {
WrMat3x3Bary (pbary, tmpInv, "chromAdap: tmpInv:");
}
// For the source white point, get XYZ from xy.
XYZT XYZsrc;
xyOneToXYZ (pbary->colSpIn.WP.x, pbary->colSpIn.WP.y, &XYZsrc);
XYZT adjY; // Calculated unadapted Y for red, green and blue primaries
MultMatXYZ (tmpInv, &XYZsrc, &adjY);
xyYtoXYZ (pbary->colSpIn.triMain.primA.x, pbary->colSpIn.triMain.primA.y, adjY.X, &rXYZ);
xyYtoXYZ (pbary->colSpIn.triMain.primB.x, pbary->colSpIn.triMain.primB.y, adjY.Y, &gXYZ);
xyYtoXYZ (pbary->colSpIn.triMain.primC.x, pbary->colSpIn.triMain.primC.y, adjY.Z, &bXYZ);
if (pbary->verbose >= 2) {
WrXYZ (pbary, &rXYZ, "unadapted r");
WrXYZ (pbary, &gXYZ, "unadapted g");
WrXYZ (pbary, &bXYZ, "unadapted b");
}
XYZT rAdXYZ, gAdXYZ, bAdXYZ;
MultMatXYZ (matCa, &rXYZ, &rAdXYZ);
MultMatXYZ (matCa, &gXYZ, &gAdXYZ);
MultMatXYZ (matCa, &bXYZ, &bAdXYZ);
if (pbary->verbose >= 2) {
WrXYZ (pbary, &rAdXYZ, "adapted r");
WrXYZ (pbary, &gAdXYZ, "adapted g");
WrXYZ (pbary, &bAdXYZ, "adapted b");
}
return MagickTrue;
}
static MagickBooleanType ChromAdap (barymT * pbary)
{
if (pbary->verbose >= 2) fprintf (pbary->fh_data, "ChromAdap WP->WP\n");
if (!ChromAdapInOut (
pbary,
&pbary->colSpIn.WP, &pbary->colSpOut.WP,
pbary->chromAdapTrans)) return MagickFalse;
if (pbary->verbose >= 2) fprintf (pbary->fh_data, "ChromAdap in WP->X\n");
if (!ChromAdapInOut (
pbary,
&pbary->colSpIn.WP, &pbary->colSpIn.XYZwp,
pbary->colSpIn.chromAdapTrans)) return MagickFalse;
if (pbary->verbose >= 2) WrMat3x3Bary (pbary, pbary->colSpIn.chromAdapTrans, "ChromAdapTrans in ");
if (pbary->verbose >= 2) fprintf (pbary->fh_data, "ChromAdap out X->WP\n");
if (!ChromAdapInOut (
pbary,
&pbary->colSpOut.XYZwp, &pbary->colSpOut.WP,
pbary->colSpOut.chromAdapTrans)) return MagickFalse;
if (pbary->verbose >= 2) WrMat3x3Bary (pbary, pbary->colSpOut.chromAdapTrans, "ChromAdapTrans out ");
// Multiply matrices to incorporate chromatic adaptation.
if (pbary->verbose >= 2) {
WrMat3x3Bary (pbary, pbary->colSpIn.chromAdapTrans, "RGB2XYZchr A");
WrMat3x3Bary (pbary, pbary->colSpIn.RGB2XYZ, "RGB2XYZchr B");
}
MultMat3x3 (pbary->colSpIn.chromAdapTrans, pbary->colSpIn.RGB2XYZ, pbary->colSpIn.RGB2XYZchr);
Round16mat3x3 (pbary, pbary->colSpIn.RGB2XYZchr);
if (pbary->verbose >= 2) WrMat3x3Bary (pbary, pbary->colSpIn.RGB2XYZchr, "RGB2XYZchr");
if (pbary->verbose >= 2) {
WrMat3x3Bary (pbary, pbary->colSpOut.XYZ2RGB, "RGB2XYZchr C");
WrMat3x3Bary (pbary, pbary->colSpOut.chromAdapTrans, "RGB2XYZchr D");
}
MultMat3x3 (pbary->colSpOut.XYZ2RGB, pbary->colSpOut.chromAdapTrans, pbary->colSpOut.XYZ2RGBchr);
Round16mat3x3 (pbary, pbary->colSpIn.XYZ2RGBchr);
if (pbary->verbose >= 2) WrMat3x3Bary (pbary, pbary->colSpOut.XYZ2RGBchr, "XYZ2RGBchr");
return MagickTrue;
}
static MagickBooleanType RgbXyzMat (
barymT * pbary,
colSpT * colSp)
// Given a colorspace primaries and WP,
// Calculates the 3x3 matrices to convert between linear RGB and XYZ.
// Ref http://www.brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html
{
double Yr = 1.0;
double Yg = 1.0;
double Yb = 1.0;
double Xr, Zr, Xg, Zg, Xb, Zb;
if (colSp->triMain.primA.y==0) {
Xr = Zr = 0;
} else {
Xr = colSp->triMain.primA.x / colSp->triMain.primA.y;
Zr = (1 - colSp->triMain.primA.x - colSp->triMain.primA.y) /
colSp->triMain.primA.y;
}
if (colSp->triMain.primB.y==0) {
Xg = Zg = 0;
} else {
Xg = colSp->triMain.primB.x / colSp->triMain.primB.y;
Zg = (1 - colSp->triMain.primB.x - colSp->triMain.primB.y) /
colSp->triMain.primB.y;
}
if (colSp->triMain.primC.y==0) {
Xb = Zb = 0;
} else {
Xb = colSp->triMain.primC.x / colSp->triMain.primC.y;
Zb = (1 - colSp->triMain.primC.x - colSp->triMain.primC.y) /
colSp->triMain.primC.y;
}
mat3x3T tmp, tmpInv;
tmp[0] = Xr;
tmp[1] = Xg;
tmp[2] = Xb;
tmp[3] = Yr;
tmp[4] = Yg;
tmp[5] = Yb;
tmp[6] = Zr;
tmp[7] = Zg;
tmp[8] = Zb;
if (pbary->verbose >= 2) {
WrMat3x3Bary (pbary, tmp, "RgbXyzMat: tmp:");
}
if (!InvMat3x3 (tmp, tmpInv)) {
fprintf (pbary->fh_data, "RgbXyzMat: tmp not invertible.\n");
return MagickFalse;
}
if (pbary->verbose >= 2) {
WrMat3x3Bary (pbary, tmpInv, "RgbXyzMat: tmpInv:");
}
// For the white point, get XYZ from xy.
XYZT XYZsrc;
xyOneToXYZ (colSp->WP.x, colSp->WP.y, &XYZsrc);
if (pbary->verbose >= 2) {
WrXYZ (pbary, &XYZsrc, "src WP");
}
XYZT S;
MultMatXYZ (tmpInv, &XYZsrc, &S);
if (pbary->verbose >= 2) {
WrXYZ (pbary, &S, "S");
}
colSp->RGB2XYZ[0] = S.X * Xr;
colSp->RGB2XYZ[1] = S.Y * Xg;
colSp->RGB2XYZ[2] = S.Z * Xb;
colSp->RGB2XYZ[3] = S.X * Yr;
colSp->RGB2XYZ[4] = S.Y * Yg;
colSp->RGB2XYZ[5] = S.Z * Yb;
colSp->RGB2XYZ[6] = S.X * Zr;
colSp->RGB2XYZ[7] = S.Y * Zg;
colSp->RGB2XYZ[8] = S.Z * Zb;
if (!InvMat3x3 (colSp->RGB2XYZ, colSp->XYZ2RGB)) {
fprintf (pbary->fh_data, "RgbXyzMat: colSp->RGB2XYZ not invertible.\n");
return MagickFalse;
}
Round16mat3x3 (pbary, colSp->RGB2XYZ);
Round16mat3x3 (pbary, colSp->XYZ2RGB);
// FIXME: or round before invert?
if (pbary->verbose >= 2) {
WrMat3x3Bary (pbary, colSp->RGB2XYZ, "RgbXyzMat: RGB2XYZ:");
WrMat3x3Bary (pbary, colSp->XYZ2RGB, "RgbXyzMat: XYZ2RGB:");
}
return MagickTrue;
}
/* RGB to XYZ:
Ref http://www.brucelindbloom.com/index.html?Eqn_RGB_to_XYZ.html
inverse companding to linear RGB
linear RGB to XYZ via 3x3 matrix
if needed, apply chromatic adaptation transform to XYZ
*/
static double inline powchk (double v, double p)
{
return (v <= 0) ? v : pow (v, p);
// FIXME: Crude. When v<0, should use slope at zero.
}
static void ZeroTrans (transT * pt)
{
pt->name = NULL;
pt->power = 1;
pt->slope = 1;
pt->limit = 0;
pt->offset = 0;
pt->invPower = 1;
pt->limit2 = 0;
pt->offsetP1 = 1;
}
static void CalcTrans (barymT * pbary, transT * pt, int ndxTrc)
{
trcT * ptf = &trcs[ndxTrc];
pt->name = ptf->name;
pt->power = Ro16If (ptf->power, pbary->round16);
pt->slope = Ro16If (ptf->slope, pbary->round16);
pt->limit = Ro16If (ptf->limit, pbary->round16);
pt->offset = Ro16If (ptf->offset, pbary->round16);
pt->invPower = Ro16If (1.0 / pt->power, pbary->round16);
pt->limit2 = Ro16If (pt->limit / pt->slope, pbary->round16);
pt->offsetP1 = Ro16If (pt->offset + 1.0, pbary->round16);
if (pbary->verbose >= 2) {
fprintf (pbary->fh_data, "%s %.*g %.*g %.*g %.*g %.*g %.*g %.*g\n",
pt->name,
pbary->precision, pt->power,
pbary->precision, pt->slope,
pbary->precision, pt->limit,
pbary->precision, pt->offset,
pbary->precision, pt->invPower,
pbary->precision, pt->limit2,
pbary->precision, pt->offsetP1);
}
}
static double inline TrcToLin (const transT * pt, double v)
{
if (v < pt->limit) return v / pt->slope;
return pow ((v + pt->offset) / pt->offsetP1, pt->power);
}
static double inline TrcFromLin (const transT * pt, double v)
{
if (v < pt->limit2) return pt->slope * v;
return pow (v, pt->invPower) * pt->offsetP1 - pt->offset;
}
static void inline Rgb2Xyz (const colSpT * colSp, RGBT * rgb, XYZT * XYZ)
{
// From non-lin to lin.
if (colSp->gamma != 1) {
if (colSp->gamma > 0) {
rgb->R = powchk(rgb->R, colSp->gamma);
rgb->G = powchk(rgb->G, colSp->gamma);
rgb->B = powchk(rgb->B, colSp->gamma);
} else {
rgb->R = TrcToLin(&colSp->trans, rgb->R);
rgb->G = TrcToLin(&colSp->trans, rgb->G);
rgb->B = TrcToLin(&colSp->trans, rgb->B);
}
}
MultMatXYZ (colSp->RGB2XYZ, (XYZT *)rgb, XYZ);
}
/* XYZ to RGB:
Ref http://www.brucelindbloom.com/index.html?Eqn_XYZ_to_RGB.html
if needed, apply chromatic adaptation transform to XYZ
XYZ to linear RGB via 3x3 matrix
companding from linear RGB to non-linear RGB
*/
static void inline Xyz2Rgb (const colSpT * colSp, XYZT * XYZ, RGBT * rgb)
{
MultMatXYZ (colSp->XYZ2RGB, XYZ, (XYZT *)rgb);
// From lin to non-lin
if (colSp->gamma != 1) {
if (colSp->gamma > 0) {
double g = 1.0 / colSp->gamma;
rgb->R = powchk(rgb->R, g);
rgb->G = powchk(rgb->G, g);
rgb->B = powchk(rgb->B, g);
} else {
rgb->R = TrcFromLin(&colSp->trans, rgb->R);
rgb->G = TrcFromLin(&colSp->trans, rgb->G);
rgb->B = TrcFromLin(&colSp->trans, rgb->B);
}
}
}
static void inline ChromAdapXYZ (
barymT * pbary,
XYZT * xyz)
{
XYZT tmp;
MultMatXYZ (pbary->chromAdapTrans, xyz, &tmp);
xyz->X = tmp.X;
xyz->Y = tmp.Y;
xyz->Z = tmp.Z;
}
static void inline xy2XYZ (
coordsT * xy,
XYZT * XYZ)
{
if (xy->y == 0) {
XYZ->X = 0;
XYZ->Z = 0;
} else {
XYZ->X = XYZ->Y * xy->x / xy->y;
XYZ->Z = XYZ->Y * (1 - xy->x - xy->y) / xy->y;
}
}
static MagickBooleanType ImageHasChromaticity (Image * im)
{
return (
im->chromaticity.red_primary.x != 0 &&
im->chromaticity.red_primary.y &&
im->chromaticity.white_point.x &&
im->chromaticity.white_point.y);
}
static void ColSpFromIm (
Image * im,
barymT * pbary)
/* Copy metadata from image _only_ where these were not set in menu.
*/
{
if (!pbary->colSpIn.menuSetChan) {
if (im->colorspace == XYZColorspace)
pbary->colSpIn.chans = chXYZ;
else if (im->colorspace == xyYColorspace)
pbary->colSpIn.chans = chXYY;
else pbary->colSpIn.chans = chRGB;
}
if (!pbary->colSpIn.menuSetPrim && ImageHasChromaticity (im)) {
// These might not be set in im->chromaticity.
pbary->colSpIn.triMain.primA.x = im->chromaticity.red_primary.x;
pbary->colSpIn.triMain.primA.y = im->chromaticity.red_primary.y;
pbary->colSpIn.triMain.primB.x = im->chromaticity.green_primary.x;
pbary->colSpIn.triMain.primB.y = im->chromaticity.green_primary.y;
pbary->colSpIn.triMain.primC.x = im->chromaticity.blue_primary.x;
pbary->colSpIn.triMain.primC.y = im->chromaticity.blue_primary.y;
if (!pbary->colSpIn.menuSetWP) {
pbary->colSpIn.WP.x = im->chromaticity.white_point.x;
pbary->colSpIn.WP.y = im->chromaticity.white_point.y;
}
if (!pbary->colSpIn.menuSetGam) {
// FIXME: and gamma
}
// FIXME: and XYZwp
}
}
static void ColSpInToOut (barymT * pbary)
/* Copy input metadata to output metadata _only_ where these were
not set in menu.
*/
{
if (!pbary->colSpOut.menuSetChan) {
pbary->colSpOut.chans = pbary->colSpIn.chans;
}
if (!pbary->colSpOut.menuSetPrim) {
pbary->colSpOut.triMain.primA.x = pbary->colSpIn.triMain.primA.x;
pbary->colSpOut.triMain.primA.y = pbary->colSpIn.triMain.primA.y;
pbary->colSpOut.triMain.primB.x = pbary->colSpIn.triMain.primB.x;
pbary->colSpOut.triMain.primB.y = pbary->colSpIn.triMain.primB.y;
pbary->colSpOut.triMain.primC.x = pbary->colSpIn.triMain.primC.x;
pbary->colSpOut.triMain.primC.y = pbary->colSpIn.triMain.primC.y;
if (!pbary->colSpOut.menuSetWP) {
pbary->colSpOut.WP.x = pbary->colSpIn.WP.x;
pbary->colSpOut.WP.y = pbary->colSpIn.WP.y;
}
if (!pbary->colSpOut.menuSetGam) {
pbary->colSpOut.gamma = pbary->colSpIn.gamma;
}
}
}
static void ColSpToIm (
barymT * pbary,
Image * im)
{
switch (pbary->colSpOut.chans) {
case chRGB: im->colorspace = RGBColorspace; break;
case chXYY: im->colorspace = xyYColorspace; break;
case chXYZ: im->colorspace = XYZColorspace; break;
}
im->chromaticity.red_primary.x = pbary->colSpOut.triMain.primA.x;
im->chromaticity.red_primary.y = pbary->colSpOut.triMain.primA.y;
im->chromaticity.green_primary.x = pbary->colSpOut.triMain.primB.x;
im->chromaticity.green_primary.y = pbary->colSpOut.triMain.primB.y;
im->chromaticity.blue_primary.x = pbary->colSpOut.triMain.primC.x;
im->chromaticity.blue_primary.y = pbary->colSpOut.triMain.primC.y;
im->chromaticity.white_point.x = pbary->colSpOut.WP.x;
im->chromaticity.white_point.y = pbary->colSpOut.WP.y;
// FIXME: and gamma. Save negative values in attribute?
}
//--------------------------------------------
static void usage (void)
{
printf ("Usage: -process 'barymap [OPTION]...'\n");
printf ("process barycentric coordinates.\n");
printf ("\n");
printf (" ic, inChannels string RGB or xyY or XYZ [xyY]\n");
printf (" ip, inPrim 6_floats xy coords of input primaries [sRGB]\n");
printf (" iw, inWP 2_floats xy coords of input white point\n");
printf (" it, inTransfer number name or gamma of input\n");
printf (" xw, XYZwp 2_floats xy coords of XYZ white point\n");
printf (" gn2, gain2 number gain factor for squared distance from WP [0]\n");
printf (" gn, gain number gain factor for distance from WP [1]\n");
printf (" bs, bias number bias for distance from WP [0]\n");
printf (" pow, power number power factor for distance from WP [1]\n");
printf (" am, angleMult string list of: direction,width,factor\n");
printf (" st, skipTri skip triangle processing\n");
printf (" ign, ignoreWP ignore white point for triangulation\n");
printf (" clC, clampCartesian clamp cartesian coordinates\n");
printf (" clB, clampBarycentric clamp barycentric coordinates\n");
printf (" oc, outChannels string RGB or xyY or XYZ [xyY]\n");
printf (" op, outPrim 6_floats xy coords of output primaries [sRGB]\n");
printf (" ow, outWP 2_floats xy coords of output white point\n");
printf (" ot, outTransfer number name or gamma of output\n");
printf (" ca, chromAdap string name of Chromatic Adaptation Transform [Bradford]\n");
printf (" r16, round16 round some fractions to 16 binary places\n");
printf (" li, list string write list to stdout or stderr\n");
printf (" f, file string write to file stream stdout or stderr\n");
printf (" v, verbose write text information to stdout\n");
printf (" v2, verbose2 write more text information\n");
printf (" v9, verbose9 write voluminous text information\n");
printf (" version write version information to stdout\n");
/*
printf ("\nNamed primaries:\n");
ListColSp (stdout);
printf ("\nNamed illuminants:\n");
ListWP (stdout);
printf ("\nNamed transfers:\n");
ListTrans (stdout);
printf ("\nChromatic Adaptation Transforms:\n");
ListCats (stdout);
printf ("\n");
*/
}
static MagickBooleanType ParseCoord (const char * s, coordsT * pc)
{
double d;
int n;
char * p = (char *)s;
sscanf (p, "%lg%n", &d, &n);
if (!n) return MagickFalse;
p += n;
if (*p != ',') return MagickFalse;
p++;
pc->x = d;
sscanf (p, "%lg%n", &d, &n);
if (!n) return MagickFalse;
p += n;
if (*p != '\0') return MagickFalse;
pc->y = d;
return MagickTrue;
}
static MagickBooleanType Parse3Coord (
const char * s,
coordsT * pc0,
coordsT * pc1,
coordsT * pc2)
{
double d;
int n;
char * p = (char *)s;
sscanf (p, "%lg%n", &d, &n);
if (!n) return MagickFalse;
p += n;
if (*p != ',') return MagickFalse;
p++;
pc0->x = d;
sscanf (p, "%lg%n", &d, &n);
if (!n) return MagickFalse;
p += n;
if (*p != ',') return MagickFalse;
p++;
pc0->y = d;
sscanf (p, "%lg%n", &d, &n);
if (!n) return MagickFalse;
p += n;
if (*p != ',') return MagickFalse;
p++;
pc1->x = d;
sscanf (p, "%lg%n", &d, &n);
if (!n) return MagickFalse;
p += n;
if (*p != ',') return MagickFalse;
p++;
pc1->y = d;
sscanf (p, "%lg%n", &d, &n);
if (!n) return MagickFalse;
p += n;
if (*p != ',') return MagickFalse;
p++;
pc2->x = d;
sscanf (p, "%lg%n", &d, &n);
if (!n) return MagickFalse;
p += n;
if (*p != '\0') return MagickFalse;
pc2->y = d;
return MagickTrue;
}
static MagickBooleanType ParseGamma (
barymT * pbary,
const char * s,
colSpT * pcs)
{
ZeroTrans (&pcs->trans);
int n;
char * p = (char *)s;
pcs->gamma = 0;
sscanf (p, "%lg%n", &pcs->gamma, &n);
if (!n) return MagickFalse;
p += n;
if (*p != '\0') return MagickFalse;
if (pcs->gamma != 0) return MagickTrue;
if (ParseNamedTrc (pbary, s, pcs)) {
CalcTrans (pbary, &pcs->trans, -(int)floor(pcs->gamma));
return MagickTrue;
}
return MagickFalse;
}
static MagickBooleanType Parse3Prims (
barymT * pbary,
const char * s,
colSpT * pcs)
{
if (Parse3Coord (s, &pcs->triMain.primA, &pcs->triMain.primB, &pcs->triMain.primC))
return MagickTrue;
if (ParseNamedPrims (pbary, s, pcs)) {
ParseGamma (pbary, s, pcs);
// (If not successful, no problem.)
return MagickTrue;
}
return MagickFalse;
}
static MagickBooleanType ParseWPkelvin (
barymT * pbary,
const char * s,
coordsT * pwp)
{
// Calculate x,y from correlated colour temperature.
int slen = strlen(s);
if (slen < 2) return MagickFalse;
if (toupper(s[slen-1]) != 'K') return MagickFalse;
if (!isdigit(s[0])) return MagickFalse;
char * p = (char *)s;
double T = 0;
int n = 0;
sscanf (p, "%lg%n", &T, &n);
if (!n) return MagickFalse;
p += n+1; // +1 to skip over terminal K.
if (*p != '\0') return MagickFalse;
// Adjust from nominal.
// See https://vdocuments.mx/colorimetry-cie-colorimetry.html
if (s[slen-1]=='k') T *= 1.4388/1.4380;
// Formulae from https://en.wikipedia.org/wiki/Standard_illuminant#Illuminant_series_D
// Also in http://www.brucelindbloom.com/index.html?Eqn_T_to_xy.html
if (T < 4000 || T > 25000) return MagickFalse;
if (T <= 7000) {
pwp->x = 0.244063 + 0.09911e3/T + 2.9678e6/(T*T) - 4.6070e9 / (T*T*T);
} else {
pwp->x = 0.237040 + 0.24748e3/T + 1.9018e6/(T*T) - 2.0064e9 / (T*T*T);
}
pwp->y = -3.000*pwp->x*pwp->x + 2.870*pwp->x - 0.275;
if (pbary->verbose >= 2) fprintf (pbary->fh_data, "ParseWPkelvin3 T=%g xy=%g,%g\n", T, pwp->x, pwp->y);
return MagickTrue;
}
static MagickBooleanType ParseWP (
barymT * pbary,
const char * s,
coordsT * pwp)
{
MagickBooleanType okay = ParseWPkelvin (pbary, s, pwp);
if (!okay) okay = ParseCoord (s, pwp);
if (!okay) okay = ParseNamedWP (pbary, s, pwp);
if (okay) Round16xy (pbary, pwp);
return okay;
}
static MagickBooleanType ParseAngleMult (barymT * pbary, const char * s)
{
double d;
int n;
char * p = (char *)s;
int i = 0;
AngMultT *pam = &pbary->AngMults[0];
MagickBooleanType Finished = MagickFalse;
while (i < MaxAngMults && !Finished) {
sscanf (p, "%lg%n", &d, &n);
if (!n) return MagickFalse;
p += n;
if (*p != ',') {
printf ("AngMult: expected ',' at %s\n", p);
return MagickFalse;
}
p++;
while (d >= 180.0) d -= 360.0;
while (d < -180.0) d += 360.0;
pam->direction = d * M_PI/180.0;
sscanf (p, "%lg%n", &d, &n);
if (!n) return MagickFalse;
p += n;
if (*p != ',') {
printf ("AngMult: expected ',' at %s\n", p);
return MagickFalse;
}
p++;
pam->width = d * M_PI/180.0;
pam->halfWidth = pam->width / 2.0;
pam->minDir = pam->direction - pam->halfWidth;
pam->maxDir = pam->direction + pam->halfWidth;
sscanf (p, "%lg%n", &d, &n);
if (!n) return MagickFalse;
p += n;
pam->factor = d;
if (*p == '\0') Finished = MagickTrue;
else {
if (*p != ';') {
printf ("AngMult: expected ';' at %s\n", p);
return MagickFalse;
}
p++;
}
pam++;
i++;
}
if (!Finished) {
printf ("AngMult: too many\n");
}
pbary->nAngMults = i;
return Finished;
}
static MagickBooleanType IsArg (char * pa, char * ShtOpt, char * LongOpt)
{
if ((LocaleCompare(pa, ShtOpt)==0) || (LocaleCompare(pa, LongOpt)==0))
return MagickTrue;
return MagickFalse;
}
#define NEXTARG \
if (i < argc-1) i++; \
else { \
fprintf (stderr, "barymap: ERROR: [%s] needs argument\n", pa); \
status = MagickFalse; \
}
static MagickBooleanType menu(
const int argc,
const char **argv,
barymT * pbary
)
// Returns MagickTrue if okay.
{
int
i;
MagickBooleanType
status = MagickTrue;
pbary->verbose = 0;
pbary->fh_data = stderr;
pbary->colSpIn.chans = chXYY;
pbary->colSpOut.chans = chXYY;
pbary->ndxCat = dfltCat;
pbary->ignoreWP = MagickFalse;
pbary->clampXy = MagickFalse;
pbary->clampBary = MagickFalse;
pbary->doTriangles = MagickTrue;
pbary->round16 = MagickFalse;
pbary->gain2 = 0.0;
pbary->gain = 1.0;
pbary->bias = 0.0;
pbary->power = 1.0;
pbary->nAngMults = 0;
pbary->colSpIn.menuSetChan =
pbary->colSpIn.menuSetPrim =
pbary->colSpIn.menuSetWP =
pbary->colSpIn.menuSetGam =
pbary->colSpIn.menuSetXYZwp = MagickFalse;
pbary->colSpOut.menuSetChan =
pbary->colSpOut.menuSetPrim =
pbary->colSpOut.menuSetWP =
pbary->colSpOut.menuSetGam =
pbary->colSpOut.menuSetXYZwp = MagickFalse;
ParseNamedPrims (pbary, "sRGB", &pbary->colSpIn);
ParseNamedPrims (pbary, "sRGB", &pbary->colSpOut);
for (i=0; i < argc; i++) {
char * pa = (char *)argv[i];
if (IsArg (pa, "ic", "inChannels")==MagickTrue) {
NEXTARG;
pbary->colSpIn.menuSetChan = MagickTrue;
if (strcasecmp (argv[i], "rgb")==0) pbary->colSpIn.chans = chRGB;
else if (strcasecmp (argv[i], "xyy")==0) pbary->colSpIn.chans = chXYY;
else if (strcasecmp (argv[i], "xyz")==0) pbary->colSpIn.chans = chXYZ;
else status = MagickFalse;
} else if (IsArg (pa, "oc", "outChannels")==MagickTrue) {
NEXTARG;
pbary->colSpOut.menuSetChan = MagickTrue;
if (strcasecmp (argv[i], "rgb")==0) pbary->colSpOut.chans = chRGB;
else if (strcasecmp (argv[i], "xyy")==0) pbary->colSpOut.chans = chXYY;
else if (strcasecmp (argv[i], "xyz")==0) pbary->colSpOut.chans = chXYZ;
else status = MagickFalse;
} else if (IsArg (pa, "st", "skipTri")==MagickTrue) {
pbary->doTriangles = MagickFalse;
} else if (IsArg (pa, "ign", "ignoreWP")==MagickTrue) {
pbary->ignoreWP = MagickTrue;
} else if (IsArg (pa, "clC", "clampCartesian")==MagickTrue) {
pbary->clampXy = MagickTrue;
} else if (IsArg (pa, "clB", "clampBarycentric")==MagickTrue) {
pbary->clampBary = MagickTrue;
} else if (IsArg (pa, "gn", "gain")==MagickTrue) {
NEXTARG;
pbary->gain = atof(argv[i]);
} else if (IsArg (pa, "gn2", "gain2")==MagickTrue) {
NEXTARG;
pbary->gain2 = atof(argv[i]);
} else if (IsArg (pa, "bs", "bias")==MagickTrue) {
NEXTARG;
pbary->bias = atof(argv[i]);
} else if (IsArg (pa, "pow", "power")==MagickTrue) {
NEXTARG;
pbary->power = atof(argv[i]);
} else if (IsArg (pa, "ip", "inPrim")==MagickTrue) {
NEXTARG;
pbary->colSpIn.menuSetPrim = MagickTrue;
if (!Parse3Prims (pbary, argv[i], &pbary->colSpIn))
{
fprintf (stderr, "barymap: problem parsing inPrim\n");
status = MagickFalse;
}
} else if (IsArg (pa, "iw", "inWP")==MagickTrue) {
NEXTARG;
pbary->colSpIn.menuSetWP = MagickTrue;
if (!ParseWP (pbary, argv[i], &pbary->colSpIn.WP)) {
fprintf (stderr, "barymap: problem parsing inWP\n");
status = MagickFalse;
}
} else if (IsArg (pa, "xw", "XYZwp")==MagickTrue) {
NEXTARG;
pbary->colSpIn.menuSetXYZwp = MagickTrue;
pbary->colSpOut.menuSetXYZwp = MagickTrue;
if (!ParseWP (pbary, argv[i], &pbary->colSpIn.XYZwp)) {
fprintf (stderr, "barymap: problem parsing XYZwp\n");
status = MagickFalse;
}
else pbary->colSpOut.XYZwp = pbary->colSpIn.XYZwp;
} else if (IsArg (pa, "it", "inTransfer")==MagickTrue) {
NEXTARG;
pbary->colSpIn.menuSetGam = MagickTrue;
if (!ParseGamma (pbary, argv[i], &pbary->colSpIn)) {
fprintf (stderr, "barymap: problem parsing inTransfer\n");
status = MagickFalse;
}
} else if (IsArg (pa, "op", "outPrim")==MagickTrue) {
NEXTARG;
pbary->colSpOut.menuSetPrim = MagickTrue;
if (!Parse3Prims (pbary, argv[i], &pbary->colSpOut))
{
fprintf (stderr, "barymap: problem parsing outPrim\n");
status = MagickFalse;
}
} else if (IsArg (pa, "ow", "outWP")==MagickTrue) {
NEXTARG;
pbary->colSpOut.menuSetWP = MagickTrue;
if (!ParseWP (pbary, argv[i], &pbary->colSpOut.WP)) {
fprintf (stderr, "barymap: problem parsing outWP\n");
status = MagickFalse;
}
} else if (IsArg (pa, "ot", "outTransfer")==MagickTrue) {
NEXTARG;
pbary->colSpOut.menuSetGam = MagickTrue;
if (!ParseGamma (pbary, argv[i], &pbary->colSpOut)) {
fprintf (stderr, "barymap: problem parsing outTransfer\n");
status = MagickFalse;
}
} else if (IsArg (pa, "ca", "chromAdap")==MagickTrue) {
NEXTARG;
if (!ParseNamedCat (pbary, argv[i])) {
fprintf (stderr, "barymap: problem parsing chromAdap\n");
status = MagickFalse;
}
} else if (IsArg (pa, "am", "angleMult")==MagickTrue) {
NEXTARG;
if (!ParseAngleMult (pbary, argv[i])) {
fprintf (stderr, "barymap: problem parsing angleMult\n");
status = MagickFalse;
}
} else if (IsArg (pa, "r16", "round16")==MagickTrue) {
pbary->round16 = MagickTrue;
} else if (IsArg (pa, "li", "list")==MagickTrue) {
NEXTARG;
if (strcasecmp (argv[i], "list")==0) {
fprintf (pbary->fh_data, "list\nprimaries\nilluminants\ntransfers\ncats\nwpnums\n");
} else if (strcasecmp (argv[i], "primaries")==0) {
ListColSp (pbary->fh_data);
} else if (strcasecmp (argv[i], "illuminants")==0) {
ListWP (pbary->fh_data);
} else if (strcasecmp (argv[i], "transfers")==0) {
ListTrans (pbary->fh_data);
} else if (strcasecmp (argv[i], "cats")==0) {
ListCats (pbary->fh_data);
} else if (strcasecmp (argv[i], "wpnums")==0) {
WrWpNums (pbary->fh_data, pbary);
} else {
fprintf (stderr, "barymap: ERROR: unknown option '%s' for list\n", argv[i]);
status = MagickFalse;
}
} else if (IsArg (pa, "f", "file")==MagickTrue) {
NEXTARG;
if (strcasecmp (argv[i], "stdout")==0) pbary->fh_data = stdout;
else if (strcasecmp (argv[i], "stderr")==0) pbary->fh_data = stderr;
else {
fprintf (stderr, "barymap: ERROR: unknown option '%s' for file\n", argv[i]);
status = MagickFalse;
}
} else if (IsArg (pa, "v", "verbose")==MagickTrue) {
pbary->verbose = 1;
} else if (IsArg (pa, "v2", "verbose2")==MagickTrue) {
pbary->verbose = 2;
} else if (IsArg (pa, "v9", "verbose9")==MagickTrue) {
pbary->verbose = 9;
} else if (IsArg (pa, "version", "version")==MagickTrue) {
fprintf (stdout, "%s\n", VERSION);
} else {
fprintf (stderr, "barymap: ERROR: unknown option '%s'\n", pa);
status = MagickFalse;
}
}
if (!pbary->colSpIn.menuSetXYZwp)
pbary->colSpIn.XYZwp = pbary->colSpIn.WP;
if (!pbary->colSpOut.menuSetXYZwp)
pbary->colSpOut.XYZwp = pbary->colSpIn.XYZwp;
Round16xy (pbary, &pbary->colSpIn.WP);
Round16xy (pbary, &pbary->colSpOut.WP);
Round16xy (pbary, &pbary->colSpIn.XYZwp);
Round16xy (pbary, &pbary->colSpOut.XYZwp);
if (pbary->verbose) {
fprintf (stderr, "barymap options:");
if (pbary->colSpIn.menuSetChan) {
fprintf (stderr, " inChannels ");
switch (pbary->colSpIn.chans) {
case chRGB: fprintf (stderr, "RGB"); break;
case chXYY: fprintf (stderr, "xyY "); break;
case chXYZ: fprintf (stderr, "XYZ "); break;
}
}
if (pbary->colSpIn.menuSetChan) {
fprintf (stderr, " outChannels ");
switch (pbary->colSpOut.chans) {
case chRGB: fprintf (stderr, "RGB"); break;
case chXYY: fprintf (stderr, "xyY "); break;
case chXYZ: fprintf (stderr, "XYZ "); break;
}
}
if (!pbary->doTriangles) fprintf (stderr, " skipTri");
if (pbary->ignoreWP) fprintf (stderr, " ignoreWP");
if (pbary->clampXy) fprintf (stderr, " clampCartesian");
if (pbary->clampBary) fprintf (stderr, " clampBarycentric");
if (pbary->gain2 != 0) fprintf (stderr, " gain2 %g", pbary->gain2);
if (pbary->gain != 1) fprintf (stderr, " gain %g", pbary->gain);
if (pbary->bias != 0) fprintf (stderr, " bias %g", pbary->bias);
if (pbary->power != 1) fprintf (stderr, " power %g", pbary->power);
fprintf (stderr, " chromAdap %s", cats[pbary->ndxCat].name);
if (pbary->round16) fprintf (stderr, " round16");
if (pbary->verbose >= 2) fprintf (stderr, " verbose%i", pbary->verbose);
else if (pbary->verbose) fprintf (stderr, " verbose");
fprintf (stderr, "\n");
WrBarymap (pbary, stderr); // FIXME: Prefer different words?
}
if (pbary->verbose) {
fprintf (stderr, "nAngMults %i\n", pbary->nAngMults);
int i;
for (i=0; i < pbary->nAngMults; i++) {
AngMultT *pam = &pbary->AngMults[i];
fprintf (stderr, "%g %g %g %g %g\n",
pam->direction, pam->minDir, pam->maxDir, pam->halfWidth, pam->factor);
}
}
if (status && pbary->verbose) {
// FIXME: temporary, for testing.
if (pbary->verbose >= 2) {
WrWpNums (pbary->fh_data, pbary);
//TstInvMat3x3 (pbary);
}
}
if (status == MagickFalse)
usage ();
return (status);
}
static MagickBooleanType CalcTri (triangleT *ptri)
{
ptri->y23 = ptri->primB.y - ptri->primC.y;
ptri->x13 = ptri->primA.x - ptri->primC.x;
ptri->x32 = ptri->primC.x - ptri->primB.x;
ptri->y13 = ptri->primA.y - ptri->primC.y;
ptri->y31 = -ptri->y13;
ptri->x3 = ptri->primC.x;
ptri->y3 = ptri->primC.y;
ptri->det = ptri->y23 * ptri->x13 + ptri->x32 * ptri->y13;
// FIXME: error if det is zero.
if (fabs(ptri->det) < 1e-6) {
printf ("ERROR: ptri->det is %g\n", ptri->det);
return MagickFalse;
}
return MagickTrue;
}
static MagickBooleanType PopSubTrisInOut (colSpT * cs)
{
MagickBooleanType okay = MagickTrue;
if (!CalcTri (&cs->triMain)) okay = MagickFalse;
cs->subTris[0] = cs->triMain;
cs->subTris[1].primA = cs->WP;
cs->subTris[1].primB = cs->triMain.primA;
cs->subTris[1].primC = cs->triMain.primB;
if (!CalcTri (&cs->subTris[1])) okay = MagickFalse;
cs->subTris[2].primA = cs->WP;
cs->subTris[2].primB = cs->triMain.primB;
cs->subTris[2].primC = cs->triMain.primC;
if (!CalcTri (&cs->subTris[2])) okay = MagickFalse;
cs->subTris[3].primA = cs->WP;
cs->subTris[3].primB = cs->triMain.primC;
cs->subTris[3].primC = cs->triMain.primA;
if (!CalcTri (&cs->subTris[3])) okay = MagickFalse;
return okay;
}
static MagickBooleanType PopSubTris (barymT * pbary)
{
MagickBooleanType okay = MagickTrue;
if (!PopSubTrisInOut (&pbary->colSpIn)) okay = MagickFalse;
if (!PopSubTrisInOut (&pbary->colSpOut)) okay = MagickFalse;
return okay;
}
static void inline CartToBary (
const coordsT * xy,
baryCoordsT * cb,
const triangleT * ptri)
{
// Assumes det is not zero.
double dx = xy->x - ptri->x3;
double dy = xy->y - ptri->y3;
cb->b1 = (ptri->y23 * dx + ptri->x32 * dy) / ptri->det;
cb->b2 = (ptri->y31 * dx + ptri->x13 * dy) / ptri->det;
cb->b3 = 1 - cb->b1 - cb->b2;
}
static void inline ClampCart (coordsT * xy)
{
if (xy->x < 0) xy->x = 0;
else if (xy->x > 1) xy->x = 1;
if (xy->y < 0) xy->y = 0;
else if (xy->y > 1) xy->y = 1;
}
static void inline ClampBary (baryCoordsT * cb)
{
double div;
if (cb->b1 < 0) {
cb->b1 = 0;
div = cb->b2 + cb->b3;
cb->b2 /= div;
cb->b3 /= div;
}
if (cb->b2 < 0) {
cb->b2 = 0;
div = cb->b1 + cb->b3;
cb->b1 /= div;
cb->b3 /= div;
}
if (cb->b3 < 0) {
cb->b3 = 0;
div = cb->b1 + cb->b2;
cb->b1 /= div;
cb->b2 /= div;
}
// FIXME: Next can never happen?
if (cb->b1 > 1) cb->b1 = 1;
if (cb->b2 > 1) cb->b2 = 1;
if (cb->b3 > 1) cb->b3 = 1;
}
static void inline BaryToCart (
const baryCoordsT * cb,
coordsT * xy,
const triangleT * ptri)
{
xy->x = cb->b1 * ptri->primA.x + cb->b2 * ptri->primB.x + cb->b3 * ptri->primC.x;
xy->y = cb->b1 * ptri->primA.y + cb->b2 * ptri->primB.y + cb->b3 * ptri->primC.y;
}
static MagickBooleanType inline IsInside (baryCoordsT * cb)
{
return (cb->b1 >= 0) && (cb->b2 >= 0) && (cb->b3 >= 0);
}
static void WhichTri (
barymT * pbary,
colSpT * cs,
coordsT * xy,
baryCoordsT * cb,
int * triNum)
{
// Finds which triangle the cartesian coordinate is in.
// Returns those barycentric coords in cb,
// and triangle number in triNum.
baryCoordsT cbs[4];
if (pbary->ignoreWP) {
CartToBary (xy, &cbs[0], &cs->subTris[0]);
*triNum = 0;
} else {
*triNum = 0; // Means we don't know yet.
CartToBary (xy, &cbs[1], &cs->subTris[1]);
if (IsInside (&cbs[1])) {
*triNum = 1;
} else {
CartToBary (xy, &cbs[2], &cs->subTris[2]);
if (IsInside (&cbs[2])) {
*triNum = 2;
} else {
CartToBary (xy, &cbs[3], &cs->subTris[3]);
if (IsInside (&cbs[3])) {
*triNum = 3;
} else {
// Outside all triangles.
// Fall back to main triangle.
CartToBary (xy, &cbs[0], &cs->subTris[0]);
*triNum = 0;
}
}
}
}
*cb = cbs[*triNum];
}
// The next function takes an image, and returns it.
//
static Image * barymap (
Image *image,
barymT * pbary,
ExceptionInfo *exception)
{
// This function ignores alpha.
// When in and out are xyY,
// this function reads and writes the R and G channels.
// leaving the B channel unchanged.
MagickBooleanType
status = MagickTrue;
ColSpFromIm (image, pbary);
ColSpInToOut (pbary);
if (pbary->verbose >= 2) {
fprintf (pbary->fh_data, "For this image:\n");
WrBarymap (pbary, stderr);
}
ZeroTrans (&pbary->colSpIn.trans);
ZeroTrans (&pbary->colSpOut.trans);
if (pbary->colSpIn.gamma < 0) {
CalcTrans (pbary, &pbary->colSpIn.trans, -(int)floor(pbary->colSpIn.gamma));
}
if (pbary->colSpOut.gamma < 0) {
CalcTrans (pbary, &pbary->colSpOut.trans, -(int)floor(pbary->colSpOut.gamma));
}
if (!RgbXyzMat (pbary, &pbary->colSpIn)) {
printf ("Problem in RgbXyzMat in\n");
status = MagickFalse;
}
if (!RgbXyzMat (pbary, &pbary->colSpOut)) {
printf ("Problem in RgbXyzMat out\n");
status = MagickFalse;
}
if (!ChromAdap (pbary)) {
printf ("Problem in ChromAdap\n");
status = MagickFalse;
}
MagickBooleanType
doGBP = (pbary->gain2 != 0.0) ||
(pbary->gain != 1.0) ||
(pbary->bias != 0.0) ||
(pbary->power != 1.0);
if (!PopSubTris (pbary)) {
printf ("Problem in PopSubTris\n");
status = MagickFalse;
}
CacheView * image_view = AcquireAuthenticCacheView (image,exception);
if (pbary->verbose == 9)
fprintf (pbary->fh_data, "Pixels:");
ssize_t y;
#if defined(MAGICKCORE_OPENMP_SUPPORT)
#pragma omp parallel for schedule(static,4) \
shared(okay) \
MAGICK_THREADS(image,image,image->rows,1)
#endif
for (y = 0; y < image->rows; y++) {
VIEW_PIX_PTR *p;
if (status == MagickFalse)
continue;
p=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
if (!p) {
status=MagickFalse;
continue;
}
ssize_t x;
RGBT rgb;
double div;
for (x = 0; x < image->columns; x++) {
if (pbary->verbose == 9)
fprintf (pbary->fh_data, "%li,%li:", x, y);
coordsT xy = {0,0};
XYZT XYZ = {0,0,0};
switch (pbary->colSpIn.chans) {
case chRGB:
rgb.R = GET_PIXEL_RED (image, p) / (MagickRealType)QuantumRange;
rgb.G = GET_PIXEL_GREEN (image, p) / (MagickRealType)QuantumRange;
rgb.B = GET_PIXEL_BLUE (image, p) / (MagickRealType)QuantumRange;
if (pbary->verbose == 9)
fprintf (pbary->fh_data, "RGB: %.*g, %.*g, %.*g\n",
pbary->precision, rgb.R,
pbary->precision, rgb.G,
pbary->precision, rgb.B);
Rgb2Xyz (&pbary->colSpIn, &rgb, &XYZ);
if (pbary->verbose == 9)
fprintf (pbary->fh_data, "XYZ: %.*g, %.*g, %.*g\n",
pbary->precision, XYZ.X,
pbary->precision, XYZ.Y,
pbary->precision, XYZ.Z);
div = XYZ.X + XYZ.Y + XYZ.Z;
if (div == 0) {
xy.x = 0;
xy.y = 0;
} else {
xy.x = XYZ.X / div;
xy.y = XYZ.Y / div;
}
break;
case chXYY:
xy.x = GET_PIXEL_RED (image, p) / (MagickRealType)QuantumRange;
xy.y = GET_PIXEL_GREEN (image, p) / (MagickRealType)QuantumRange;
XYZ.Y = GET_PIXEL_BLUE (image, p) / (MagickRealType)QuantumRange;
break;
case chXYZ:
XYZ.X = GET_PIXEL_RED (image, p) / (MagickRealType)QuantumRange;
XYZ.Y = GET_PIXEL_GREEN (image, p) / (MagickRealType)QuantumRange;
XYZ.Z = GET_PIXEL_BLUE (image, p) / (MagickRealType)QuantumRange;
if (pbary->verbose == 9)
fprintf (pbary->fh_data, "XYZ: %.*g, %.*g, %.*g\n",
pbary->precision, XYZ.X,
pbary->precision, XYZ.Y,
pbary->precision, XYZ.Z);
div = XYZ.X + XYZ.Y + XYZ.Z;
if (div == 0) {
xy.x = 0;
xy.y = 0;
} else {
xy.x = XYZ.X / div;
xy.y = XYZ.Y / div;
}
break;
}
if (pbary->clampXy) ClampCart (&xy);
if (pbary->verbose == 9)
fprintf (pbary->fh_data, "xy: %.*g, %.*g\n",
pbary->precision, xy.x,
pbary->precision, xy.y);
if (doGBP) {
double dx = xy.x - pbary->colSpIn.WP.x;
double dy = xy.y - pbary->colSpIn.WP.y;
double v = hypot (dx, dy);
double vrat = pow (v * v * pbary->gain2
+ v * pbary->gain
+ pbary->bias,
pbary->power)
/ v;
xy.x = dx * vrat + pbary->colSpIn.WP.x;
xy.y = dy * vrat + pbary->colSpIn.WP.y;
if (pbary->clampXy) ClampCart (&xy);
if (pbary->verbose == 9)
fprintf (pbary->fh_data, " xy: %.*g, %.*g\n",
pbary->precision, xy.x,
pbary->precision, xy.y);
}
if (pbary->nAngMults > 0) {
double dx = xy.x - pbary->colSpIn.WP.x;
double dy = xy.y - pbary->colSpIn.WP.y;
double t = atan2 (dx, dy);
int i;
for (i = 0; i < pbary->nAngMults; i++) {
AngMultT * pam = &pbary->AngMults[i];
double f = fabs (t - pam->direction);
if (f > pam->halfWidth) {
f = fabs (t - M_TWOPI - pam->direction);
}
if (f < pam->halfWidth) {
f = (f / pam->halfWidth);
f = sin ((f-0.5) * M_PI);
f = f * (1 - pam->factor) + pam->factor;
xy.x = dx * f + pbary->colSpIn.WP.x;
xy.y = dy * f + pbary->colSpIn.WP.y;
if (pbary->clampXy) ClampCart (&xy);
}
}
if (pbary->verbose == 9)
fprintf (pbary->fh_data, " xy: %.*g, %.*g\n",
pbary->precision, xy.x,
pbary->precision, xy.y);
}
if (pbary->doTriangles) {
baryCoordsT cb;
int triNum;
WhichTri (pbary, &pbary->colSpIn, &xy, &cb, &triNum);
if (pbary->clampBary) ClampBary (&cb);
if (pbary->verbose == 9)
fprintf (pbary->fh_data, " t=%i b: %.*g, %.*g, %.*g\n",
triNum,
pbary->precision, cb.b1,
pbary->precision, cb.b2,
pbary->precision, cb.b3);
BaryToCart (&cb, &xy, &pbary->colSpOut.subTris[triNum]);
if (pbary->clampXy) ClampCart (&xy);
if (pbary->verbose == 9)
fprintf (pbary->fh_data, " xy: %.*g, %.*g\n",
pbary->precision, xy.x,
pbary->precision, xy.y);
}
switch (pbary->colSpOut.chans) {
case chRGB:
xy2XYZ (&xy, &XYZ);
if (pbary->verbose == 9)
fprintf (pbary->fh_data, " XYZ: %.*g, %.*g, %.*g\n",
pbary->precision, XYZ.X,
pbary->precision, XYZ.Y,
pbary->precision, XYZ.Z);
// ChromAdapXYZ (pbary, &XYZ);
if (pbary->verbose == 9)
fprintf (pbary->fh_data, " aca XYZ: %.*g, %.*g, %.*g\n",
pbary->precision, XYZ.X,
pbary->precision, XYZ.Y,
pbary->precision, XYZ.Z);
Xyz2Rgb (&pbary->colSpOut, &XYZ, &rgb);
if (pbary->verbose == 9)
fprintf (pbary->fh_data, " RGB: %.*g, %.*g, %.*g\n",
pbary->precision, rgb.R,
pbary->precision, rgb.G,
pbary->precision, rgb.B);
SET_PIXEL_RED (image, rgb.R * QuantumRange, p);
SET_PIXEL_GREEN (image, rgb.G * QuantumRange, p);
SET_PIXEL_BLUE (image, rgb.B * QuantumRange, p);
break;
case chXYY:
SET_PIXEL_RED (image, xy.x * QuantumRange, p);
SET_PIXEL_GREEN (image, xy.y * QuantumRange, p);
SET_PIXEL_BLUE (image, XYZ.Y * QuantumRange, p);
break;
case chXYZ:
xy2XYZ (&xy, &XYZ);
// We need CA, even if we stay in XYZ.
// FIXME: this is the simple version, directly from in to out.
ChromAdapXYZ (pbary, &XYZ);
SET_PIXEL_RED (image, XYZ.X * QuantumRange, p);
SET_PIXEL_GREEN (image, XYZ.Y * QuantumRange, p);
SET_PIXEL_BLUE (image, XYZ.Z * QuantumRange, p);
break;
}
p += Inc_ViewPixPtr (image);
}
if (!SyncCacheViewAuthenticPixels(image_view,exception)) {
fprintf (stderr, "bad sync\n");
status=MagickFalse;
}
}
if (!status) return NULL;
image_view = DestroyCacheView (image_view);
ColSpToIm (pbary, image);
return image;
}
ModuleExport size_t barymapImage(Image **images,const int argc,
const char **argv,ExceptionInfo *exception)
{
MagickBooleanType
status;
barymT
pbary;
assert(images != (Image **) NULL);
assert(*images != (Image *) NULL);
assert((*images)->signature == MAGICK_CORE_SIG);
pbary.precision = GetMagickPrecision();
status = menu (argc, argv, &pbary);
if (status == MagickFalse)
return (-1);
Image * image;
for (image=(*images); image != (Image *) NULL; image=GetNextImageInList(image))
{
Image * newImg = barymap (image, &pbary, exception);
if (!newImg) return (-1);
if (newImg != image) {
ReplaceImageInList (&image, newImg);
*images=GetFirstImageInList (newImg);
}
}
return(MagickImageFilterSignature);
}
typedef double mat3x3T[9];
static void ZeroMat3x3 (mat3x3T out)
{
int i;
for (i=0; i<9; i++) out[i] = 0.0;
}
static void IdentMat3x3 (mat3x3T out)
{
ZeroMat3x3 (out);
out[0] = out[4] = out[8] = 1.0;
}
static void CopyMat3x3 (const mat3x3T in, mat3x3T out)
{
int i;
for (i=0; i<9; i++) out[i] = in[i];
}
static void TransposeMat3x3self (mat3x3T inout)
{
double t;
t = inout[1];
inout[1] = inout[3];
inout[3] = t;
t = inout[2];
inout[2] = inout[6];
inout[6] = t;
t = inout[5];
inout[5] = inout[7];
inout[7] = t;
}
static void MultMat3x3 (const mat3x3T inA, const mat3x3T inB, mat3x3T out)
{
out[0] = inA[0]*inB[0] + inA[1]*inB[3] + inA[2]*inB[6];
out[1] = inA[0]*inB[1] + inA[1]*inB[4] + inA[2]*inB[7];
out[2] = inA[0]*inB[2] + inA[1]*inB[5] + inA[2]*inB[8];
out[3] = inA[3]*inB[0] + inA[4]*inB[3] + inA[5]*inB[6];
out[4] = inA[3]*inB[1] + inA[4]*inB[4] + inA[5]*inB[7];
out[5] = inA[3]*inB[2] + inA[4]*inB[5] + inA[5]*inB[8];
out[6] = inA[6]*inB[0] + inA[7]*inB[3] + inA[8]*inB[6];
out[7] = inA[6]*inB[1] + inA[7]*inB[4] + inA[8]*inB[7];
out[8] = inA[6]*inB[2] + inA[7]*inB[5] + inA[8]*inB[8];
}
static MagickBooleanType InvMat3x3 (const mat3x3T in, mat3x3T out)
{
// Ref: https://en.wikipedia.org/wiki/Invertible_matrix#Inversion_of_3_%C3%97_3_matrices
out[0] = (in[4]*in[8] - in[5]*in[7]);
out[1] = -(in[3]*in[8] - in[5]*in[6]);
out[2] = (in[3]*in[7] - in[4]*in[6]);
out[3] = -(in[1]*in[8] - in[2]*in[7]);
out[4] = (in[0]*in[8] - in[2]*in[6]);
out[5] = -(in[0]*in[7] - in[1]*in[6]);
out[6] = (in[1]*in[5] - in[2]*in[4]);
out[7] = -(in[0]*in[5] - in[2]*in[3]);
out[8] = (in[0]*in[4] - in[1]*in[3]);
double det = in[0] * out[0] + in[1] * out[1] + in[2] * out[2];
if (fabs(det) < MagickEpsilon) return MagickFalse;
TransposeMat3x3self (out);
int i;
for (i=0; i < 9; i++) {
out[i] /= det;
}
return MagickTrue;
}
static void WrMat3x3 (FILE * fh, int precision, const mat3x3T in, char * title)
{
fprintf (fh, "%s\n", title);
fprintf (fh, " %.*g, %.*g, %.*g\n",
precision, in[0],
precision, in[1],
precision, in[2]);
fprintf (fh, " %.*g, %.*g, %.*g\n",
precision, in[3],
precision, in[4],
precision, in[5]);
fprintf (fh, " %.*g, %.*g, %.*g\n",
precision, in[6],
precision, in[7],
precision, in[8]);
}
This also needs polypix.inc.
/*
Reference:
http://im.snibgo.com/polypix.htm
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <MagickCore/MagickCore.h>
#define STANDALONE 0
#include "polypix.inc"
ModuleExport size_t polypixImage(Image **images,const int argc,
const char **argv,ExceptionInfo *exception)
{
polyPixT
pp;
assert(images != (Image **) NULL);
assert(*images != (Image *) NULL);
assert((*images)->signature == MagickCoreSignature);
if (!menu (argc, argv, &pp))
return (~MagickImageFilterSignature);
if (!polypix (images, &pp, exception))
return (~MagickImageFilterSignature);
return (MagickImageFilterSignature);
}
This is needed by polypix.c.
/*
Reference:
http://im.snibgo.com/polypix.htm
*/
typedef enum {
mMean,
mCentroid
} methodT;
typedef struct {
FILE *
fh_data;
char
*infile,
*mapfile,
*outfile;
methodT
method;
MagickBooleanType
do_verbose;
size_t
numUniqGrays;
MagickBooleanType
HasAlpha;
} polyPixT;
static void usage (void)
{
printf ("Pixelates an image.\n");
#if STANDALONE==1
printf ("Usage: polyPixB [OPTION]...\n");
#else
printf ("Usage: -process 'polypix [OPTION]...'\n");
#endif
printf ("Options are:\n");
#if STANDALONE==1
printf (" i, infile string main input file\n");
printf (" map, map string input map file\n");
printf (" o, outfile string output file\n");
#endif
printf (" m, method string 'mean' or 'centroid'\n");
printf (" f, file string Write verbose text to 'stderr' or 'stdout'\n");
printf (" v, verbose write 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 MagickBooleanType menu (
const int argc,
const char **argv,
polyPixT * ppp
)
/* Returns MagickTrue if okay. */
{
int
i=0;
MagickBooleanType
status;
status = MagickTrue;
ppp->fh_data = stderr;
ppp->infile = NULL;
ppp->mapfile = NULL;
ppp->outfile = NULL;
ppp->method = mMean;
ppp->do_verbose = MagickFalse;
#if STANDALONE==1
i = 1;
#endif
for (; i < argc; i++) {
char * pa = (char *)argv[i];
#if STANDALONE==1
if (IsArg (pa, "i", "infile")==MagickTrue) {
i++;
ppp->infile = (char *)argv[i];
} else if (IsArg (pa, "map", "map")==MagickTrue) {
i++;
ppp->mapfile = (char *)argv[i];
} else if (IsArg (pa, "o", "outfile")==MagickTrue) {
i++;
ppp->outfile = (char *)argv[i];
} else
#endif
if (IsArg (pa, "m", "method")==MagickTrue) {
i++;
if (LocaleCompare (argv[i], "mean")==0) ppp->method = mMean;
else if (LocaleCompare (argv[i], "centroid")==0) ppp->method = mCentroid;
else status = MagickFalse;
} else if (IsArg (pa, "f", "file")==MagickTrue) {
i++;
if (LocaleCompare (argv[i], "stdout")==0) ppp->fh_data = stdout;
else if (LocaleCompare (argv[i], "stderr")==0) ppp->fh_data = stderr;
else status = MagickFalse;
} else if (IsArg (pa, "v", "verbose")==MagickTrue) {
ppp->do_verbose = MagickTrue;
} else {
fprintf (stderr, "polypix: ERROR: unknown option [%s]\n", pa);
status = MagickFalse;
}
}
if (ppp->do_verbose) {
fprintf (stderr, "polypix options:");
#if STANDALONE==1
fprintf (stderr, " infile %s ", ppp->infile);
fprintf (stderr, " mapfile %s ", ppp->mapfile);
fprintf (stderr, " outfile %s ", ppp->outfile);
#endif
fprintf (stderr, " method ");
if (ppp->method==mMean) fprintf (stderr, "mean");
else if (ppp->method==mCentroid) fprintf (stderr, "centroid");
else fprintf (stderr, "??");
if (ppp->fh_data == stdout) fprintf (stderr, " file stdout");
if (ppp->do_verbose) fprintf (stderr, " verbose");
fprintf (stderr, "\n");
}
if (status == MagickFalse)
usage ();
return (status);
}
static inline ssize_t WhichPixel (const Quantum * pMap, const Quantum * puc1, const size_t nVals)
/* Assuming both images are grayscale,
and *puc1 is (nVals)x1 pixels, sorted by increasing value,
finds which pixel in *puc1 is equal to pMap
by binary search ("binary chop").
On success, returns index into *puc1, between 0 and nVals-1.
On error (not found), returns -1.
*/
{
size_t Lo=0, Hi=nVals-1, Mid;
Quantum q;
while (Lo <= Hi) {
Mid = (Lo + Hi) / 2;
/* Assume exactly one channel. */
q = *(puc1+Mid);
if (q < *pMap) {
Lo = Mid+1;
} else if (q > *pMap) {
Hi = Mid-1;
} else {
return (ssize_t)Mid;
}
}
return -1;
}
static int compare_pixels (const void *a, const void *b)
{
const Quantum qa = *(const Quantum *) a;
const Quantum qb = *(const Quantum *) b;
return (qa > qb) - (qa < qb);
}
static Quantum* FindUniqueGreys (
Image *img_map,
CacheView *map_view,
polyPixT * ppp,
ExceptionInfo *exception)
/* From the image, which should have only one channel (gray),
returns an array of the unique gray values.
Also sets ppp->numUniqGrays to the number of unique values.
*/
{
int status = 0;
Quantum *uniq_grays = NULL;
Quantum *qp, *grays = NULL;
Quantum prevVal;
size_t y, x;
size_t countVals=0;
size_t numElements = img_map->columns * img_map->rows;
size_t i;
ppp->numUniqGrays = 0;
grays = (Quantum *)AcquireMagickMemory (numElements * sizeof (Quantum));
if (!grays) {
fprintf (stderr, "oom grays\n");
status = -1;
goto error_cleanup;
}
memset (grays, 0, numElements * sizeof (Quantum));
if (ppp->do_verbose) fprintf (ppp->fh_data, "Populate the grays array.\n");
qp = grays;
{
for (y=0; y < (size_t) img_map->rows; y++) {
const Quantum *p = GetCacheViewVirtualPixels (
map_view, 0, (ssize_t)y, img_map->columns, 1, exception);
if (!p) {
status = -1;
goto error_cleanup;
}
for (x=0; x < (size_t) img_map->columns; x++) {
*qp++ = *p;
p += GetPixelChannels (img_map);
}
}
}
if (ppp->do_verbose) fprintf (ppp->fh_data, "Sort the array.\n");
qsort (
(void *)grays,
img_map->columns * img_map->rows,
sizeof (Quantum),
compare_pixels);
if (ppp->do_verbose) fprintf (ppp->fh_data, "Count the unique elements.\n");
prevVal = grays[0];
countVals = 1;
for (x=1; x < numElements; x++) {
if (prevVal != grays[x]) {
prevVal = grays[x];
countVals++;
}
}
if (ppp->do_verbose) fprintf (ppp->fh_data, "countVals=%li\n", countVals);
ppp->numUniqGrays = countVals;
if (ppp->do_verbose) fprintf (ppp->fh_data, "Make another array, one element per unique value.\n");
uniq_grays = (Quantum *)AcquireMagickMemory (countVals * sizeof (Quantum));
if (!uniq_grays) {
fprintf (stderr, "oom uniq_grays\n");
status = -1;
goto error_cleanup;
}
memset (uniq_grays, 0, countVals * sizeof (Quantum));
if (ppp->do_verbose) fprintf (ppp->fh_data, "Populate the unique values array.\n");
prevVal = grays[0];
uniq_grays[0] = grays[0];
i=1;
for (x=1; x < numElements; x++) {
if (prevVal != grays[x]) {
prevVal = grays[x];
assert (i < countVals);
uniq_grays[i] = prevVal;
i++;
}
}
error_cleanup:
if (ppp->do_verbose) fprintf (ppp->fh_data, "Cleanup.\n");
if (status < 0) {
if (uniq_grays) uniq_grays = (Quantum *)RelinquishMagickMemory (uniq_grays);
}
if (grays) grays = (Quantum *)RelinquishMagickMemory (grays);
return uniq_grays;
}
static size_t * MakeUcIndex (
Image * img_map,
CacheView *map_view,
Quantum *uniq_grays,
polyPixT * ppp,
ExceptionInfo *exception)
/*
Makes and populates array, one entry per map pixel, an index into uniq_grays and uc2.
On error, returns NULL.
*/
{
int status = 0;
size_t * uc_index = (size_t *)AcquireMagickMemory (img_map->columns * img_map->rows * sizeof(*uc_index));
if (!uc_index) {
return NULL;
}
/* Walk through pixelation map pixels.
For each one, find closest (identical) entry in uniq_grays, and set uc_index to the index.
*/
{
size_t y, x;
if (ppp->do_verbose) fprintf (ppp->fh_data, "Populate uc_index\n");
for (y=0; y < (size_t) img_map->rows; y++) {
size_t prevJ = 0;
size_t yoffs = y*img_map->columns;
const Quantum *p = GetCacheViewVirtualPixels (
map_view, 0, (ssize_t)y, img_map->columns, 1, exception);
if (!p) {
status = -1;
break;
}
for (x=0; x < (size_t) img_map->columns; x++) {
/* Which pixel in uniq_grays is equal to img_map pixel?
*/
/* Most likely is the previously found match. */
if (*(p+x)==uniq_grays[prevJ]) {
uc_index[yoffs + x] = prevJ;
} else {
ssize_t ndx = WhichPixel (p+x, uniq_grays, ppp->numUniqGrays);
if (ndx < 0) {
fprintf (stderr, "WhichPixel failed x=%li ndx=%li\n", x, ndx);
status = -1;
goto error_cleanup;
}
uc_index[yoffs + x] = (size_t)ndx;
prevJ = (size_t)ndx;
}
}
}
}
error_cleanup:
if (status != 0) {
if (uc_index) uc_index = (size_t *)RelinquishMagickMemory (uc_index);
}
return uc_index;
}
static int CalcMeanColours (
Image * image,
CacheView *image_view,
Image * img_uc2,
CacheView *uc2_view,
size_t *uc_index,
polyPixT * ppp,
ExceptionInfo *exception)
/* Populates image uc2 with mean colours from image.
Returns 0 iff okay.
*/
{
int status = 0;
Quantum *q_uc2;
double
*uniq_cnt = NULL; /* One element per unique map colour;
count of pixels (or sum of alphas) with that colour. */
size_t siz_uniq_cnt = image->columns * image->rows * sizeof(*uniq_cnt);
uniq_cnt = (double *)AcquireMagickMemory (siz_uniq_cnt);
if (!uniq_cnt) {
fprintf (stderr, "oom uniq_cnt\n");
status = -1;
goto error_cleanup;
}
memset (uniq_cnt, 0, siz_uniq_cnt);
if (ppp->do_verbose) fprintf (ppp->fh_data, "Populate uc2 with mean colours from image.\n");
if (ppp->do_verbose) fprintf (ppp->fh_data, "img_uc2->columns=%li\n", img_uc2->columns);
q_uc2 = GetCacheViewAuthenticPixels (
uc2_view,0,0,img_uc2->columns,1,exception);
if (!q_uc2) {
status = -1;
goto error_cleanup;
}
{
size_t y, x;
for (y=0; y < (size_t) image->rows; y++) {
Quantum alpha = 1.0;
size_t yoffs = y * image->columns;
const Quantum *p = GetCacheViewVirtualPixels (
image_view, 0, (ssize_t)y, image->columns, 1, exception);
if (!p) {
status = -1;
break;
}
for (x=0; x < (size_t) image->columns; x++) {
size_t ndx = uc_index [yoffs + x];
Quantum * qptr = q_uc2 + ndx * GetPixelChannels (img_uc2);
if (ppp->HasAlpha) alpha = GetPixelAlpha (image, p);
SetPixelRed (
img_uc2,
alpha * GetPixelRed (image, p) + GetPixelRed (img_uc2, qptr),
qptr);
SetPixelGreen (
img_uc2,
alpha * GetPixelGreen (image, p) + GetPixelGreen (img_uc2, qptr),
qptr);
SetPixelBlue (
img_uc2,
alpha * GetPixelBlue (image, p) + GetPixelBlue (img_uc2, qptr),
qptr);
if (ppp->HasAlpha) {
SetPixelAlpha (
img_uc2,
alpha * GetPixelAlpha (image, p) + GetPixelAlpha (img_uc2, qptr),
qptr);
}
uniq_cnt[ndx] += alpha;
p += GetPixelChannels (image);
}
}
}
if (status < 0) goto error_cleanup;
if (!SyncCacheViewAuthenticPixels (uc2_view,exception)) {
fprintf (stderr, "Can't sync uc2");
status = -1;
goto error_cleanup;
}
{
size_t x;
Quantum * qptr = q_uc2;
if (ppp->do_verbose) fprintf (ppp->fh_data, "Divide the colours in uc2 by uniq_cnt.\n");
for (x=0; x < (size_t) img_uc2->columns; x++) {
double div = uniq_cnt[x];
if (div != 0) {
SetPixelRed (img_uc2, GetPixelRed (img_uc2, qptr) / div, qptr);
SetPixelGreen (img_uc2, GetPixelGreen (img_uc2, qptr) / div, qptr);
SetPixelBlue (img_uc2, GetPixelBlue (img_uc2, qptr) / div, qptr);
if (ppp->HasAlpha) {
SetPixelAlpha (img_uc2, GetPixelAlpha (img_uc2, qptr) / div, qptr);
}
}
qptr += GetPixelChannels (img_uc2);
}
if (!SyncCacheViewAuthenticPixels (uc2_view,exception)) {
fprintf (stderr, "Can't sync uc2");
status = -1;
goto error_cleanup;
}
}
error_cleanup:
if (uniq_cnt) uniq_cnt = (double *)RelinquishMagickMemory (uniq_cnt);
return status;
}
static int CalcCentroidColours (
Image * image,
CacheView *image_view,
Image * img_uc2,
CacheView *uc2_view,
size_t *uc_index,
polyPixT * ppp,
ExceptionInfo *exception)
/* Populates image uc2 with centroid colours from image.
Returns 0 iff okay.
*/
{
typedef struct {
size_t sumX;
size_t sumY;
size_t count;
} centroidT;
centroidT * centroids = NULL;
int status = 0;
size_t y, x;
size_t siz_uniq_cnt;
if (ppp->do_verbose) fprintf (ppp->fh_data, "Populate uc2 with centroid colours from image.\n");
siz_uniq_cnt = image->columns * image->rows * sizeof(centroidT);
centroids = (centroidT *)AcquireMagickMemory (siz_uniq_cnt * sizeof (centroidT));
if (!centroids) {
fprintf (stderr, "oom centroids\n");
status = -1;
goto error_cleanup;
}
memset (centroids, 0, siz_uniq_cnt * sizeof (centroidT));
{
for (y=0; y < (size_t) image->rows; y++) {
size_t yoffs = y * image->columns;
for (x=0; x < (size_t) image->columns; x++) {
size_t ndx = uc_index [yoffs + x];
centroidT *pcent = &(centroids[ndx]);
pcent->sumX += x;
pcent->sumY += y;
pcent->count++;
}
}
}
{
size_t i;
Quantum *q_uc2 = GetCacheViewAuthenticPixels (
uc2_view,0,0,img_uc2->columns,1,exception);
if (!q_uc2) {
status = -1;
goto error_cleanup;
}
for (i=0; i < ppp->numUniqGrays; i++) {
PixelInfo src_pixel;
centroidT *pcent = &(centroids[i]);
double div = (double)pcent->count;
double fx = (double)pcent->sumX / div;
double fy = (double)pcent->sumY / div;
InterpolatePixelInfo (
image, image_view,
image->interpolate,
fx, fy, &src_pixel, exception);
SetPixelRed (img_uc2, src_pixel.red, q_uc2);
SetPixelGreen (img_uc2, src_pixel.green, q_uc2);
SetPixelBlue (img_uc2, src_pixel.blue, q_uc2);
q_uc2 += GetPixelChannels (img_uc2);
}
}
error_cleanup:
if (centroids) centroids = (centroidT *)RelinquishMagickMemory (centroids);
return status;
}
static Image * polypix (Image ** images, polyPixT * ppp, ExceptionInfo *exception)
{
int status=0;
Image
*image=NULL,
*img_map=NULL,
*img_uc2=NULL,
*img_out=NULL;
ImageInfo
*image_info = NULL;
CacheView
*image_view=NULL,
*map_view=NULL,
*uc2_view=NULL,
*out_view=NULL;
size_t
*uc_index = NULL; /* One element per input pixel. Index into uniq_grays array, uc2 and uniq_cnt. */
Quantum
*uniq_grays = NULL; /* One element per unique map colour; the gray value. */
size_t listlen = GetImageListLength (*images);
if (listlen != 2) {
fprintf (stderr, "Need two images; found %li.\n", listlen);
status = -1;
goto error_cleanup;
}
image_info = CloneImageInfo((ImageInfo *) NULL);
image = *images;
img_map = GetNextImageInList (image);
ppp->HasAlpha = (MagickBooleanType)(image->alpha_trait != UndefinedPixelTrait);
{ /* Check dimensions. */
if (image->rows != img_map->rows || image->columns != img_map->columns) {
fprintf (stderr, "Error: Input image dimensions don't match\n");
goto error_cleanup;
}
}
if (!SetImageStorageClass (image, DirectClass, exception)) {
fprintf (stderr, "SetImageStorageClass failed\n");
goto error_cleanup;
}
if (!SetImageStorageClass (img_map, DirectClass, exception)) {
fprintf (stderr, "SetImageStorageClass failed\n");
goto error_cleanup;
}
if (GetPixelChannels (img_map) != 1) {
fprintf (stderr, "Pixelation map image has %li channels instead of exactly one.\n", GetPixelChannels (img_map));
goto error_cleanup;
}
map_view = AcquireVirtualCacheView (img_map, exception);
uniq_grays = FindUniqueGreys (img_map, map_view, ppp, exception);
if (!uniq_grays) {
fprintf (stderr, "FindUniqueGrays failed\n");
status = -1;
goto error_cleanup;
}
img_uc2 = CloneImage (image, ppp->numUniqGrays, 1, MagickTrue, exception);
if (!img_uc2) {
fprintf (stderr, "clone into uc2 failed\n");
status = -1;
goto error_cleanup;
}
TransformImageColorspace (img_uc2, sRGBColorspace, exception);
{ /* Make uc2 pixels black. */
PixelInfo mppBlack;
GetPixelInfo (img_uc2, &mppBlack);
SetImageColor(img_uc2, &mppBlack, exception);
}
uc2_view = AcquireAuthenticCacheView (img_uc2, exception);
image_view = AcquireVirtualCacheView (image, exception);
uc_index = MakeUcIndex (img_map, map_view, uniq_grays, ppp, exception);
if (!uc_index) {
fprintf (stderr, "MakeUcIndex failed\n");
goto error_cleanup;
}
if (uniq_grays) uniq_grays = (Quantum *)RelinquishMagickMemory (uniq_grays);
if (ppp->method == mMean) {
if (CalcMeanColours (image, image_view, img_uc2, uc2_view, uc_index, ppp, exception) != 0) {
fprintf (stderr, "CalcMeanColours failed\n");
status = -1;
goto error_cleanup;
}
} else {
if (CalcCentroidColours (image, image_view, img_uc2, uc2_view, uc_index, ppp, exception) != 0) {
fprintf (stderr, "CalcCentroidColours failed\n");
status = -1;
goto error_cleanup;
}
}
img_out = CloneImage (image, 0, 0, MagickTrue, exception);
if (!img_out) {
fprintf (stderr, "clone image to out failed\n");
status = -1;
goto error_cleanup;
}
out_view = AcquireAuthenticCacheView (img_out, exception);
{
size_t y, x;
Quantum *q_uc2 = GetCacheViewAuthenticPixels (
uc2_view,0,0,img_uc2->columns,1,exception);
if (!q_uc2) {
status = -1;
goto error_cleanup;
}
if (ppp->do_verbose) fprintf (ppp->fh_data, "Set output colours.\n");
#if defined(MAGICKCORE_OPENMP_SUPPORT)
#pragma omp parallel for schedule(static,4) shared(status) \
MAGICK_THREADS(img_out,img_out,img_out->rows,1)
#endif
for (y=0; y < (size_t) img_out->rows; y++) {
size_t yoffs = y * img_out->columns;
Quantum *q = GetCacheViewAuthenticPixels (
out_view, 0, (ssize_t)y, img_out->columns, 1, exception);
if (!q) {
status = -1;
break;
}
for (x=0; x < (size_t) img_out->columns; x++) {
size_t ndx = uc_index [yoffs + x];
Quantum * pptr = q_uc2 + ndx * GetPixelChannels (img_uc2);
SetPixelRed (img_out, GetPixelRed (img_uc2, pptr), q);
SetPixelGreen (img_out, GetPixelGreen (img_uc2, pptr), q);
SetPixelBlue (img_out, GetPixelBlue (img_uc2, pptr), q);
q += GetPixelChannels (img_out);
}
if (!SyncCacheViewAuthenticPixels (out_view,exception)) {
fprintf (stderr, "Can't sync out");
status = -1;
goto error_cleanup;
}
}
}
error_cleanup:
if (ppp->do_verbose) fprintf (ppp->fh_data, "Cleanup\n");
if (uniq_grays) uniq_grays = (Quantum *)RelinquishMagickMemory (uniq_grays);
if (uc_index) uc_index = (size_t *)RelinquishMagickMemory (uc_index);
if (out_view) out_view = DestroyCacheView (out_view);
if (uc2_view) uc2_view = DestroyCacheView (uc2_view);
if (map_view) map_view = DestroyCacheView (map_view);
if (image_view) image_view = DestroyCacheView (image_view);
if (status < 0 && img_out) img_out = DestroyImage (img_out);
if (img_uc2) img_uc2 = DestroyImage (img_uc2);
if (image_info) image_info = DestroyImageInfo (image_info);
if (img_out) {
DeleteImageFromList (&img_map);
ReplaceImageInList (&image, img_out);
/* Replace messes up the images pointer. Make it good: */
*images = GetFirstImageInList (image);
img_out = *images;
}
return img_out;
}
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include "vsn_defines.h"
#include <MagickCore/MagickCore.h>
#include <MagickCore/matrix-private.h> // for GaussJordanElimination()
#define VERSION "polyreg v1.0 Copyright (c) 2025 Alan Gibson"
typedef struct {
size_t
powX,
powY;
} powerT;
typedef enum {
synBash,
synWindows
} syntaxT;
typedef struct {
int
precision;
syntaxT
syntax;
size_t
limitDegree,
limitTerms,
outputWidth,
outputHeight,
numColChan,
polyDegree, /* This is used only to calculate the number of terms. */
nDims, /* Number of dimensions: 1 or 2. */
nTerms,
nResults;
powerT
*power;
double
**matrix,
**vectors,
*terms,
*results;
size_t
verbose;
MagickBooleanType
calcOnly,
doWarn,
solutionFound;
FILE *
fh_data;
} PolyRegT;
static void usage (void)
{
printf ("Usage: -process 'polyreg [OPTION]...'\n");
printf ("Polynomial regression, using non-transparent pixels.\n");
printf ("\n");
printf (" ld, limitDegree integer limit polynomial degree\n");
printf (" lt, limitTerms integer limit number of terms\n");
printf (" ow, outputWidth integer width of generated output\n");
printf (" oh, outputHeight integer height of generated output\n");
printf (" co, calcOnly calculate only; don't replace the image\n");
printf (" sy, syntax string verbose syntax: bash or windows\n");
printf (" f, file string write to file stream stdout or stderr\n");
printf (" v, verbose write text information to stdout\n");
printf (" v2, verbose2 write more text information to stdout\n");
printf (" version write version information to stdout\n");
printf ("\n");
}
static MagickBooleanType IsArg (char * pa, char * ShtOpt, char * LongOpt)
{
/* LocaleCompare is not case-sensitive,
so we use strcmp for the short option.
*/
if ((strcmp(pa, ShtOpt)==0) || (LocaleCompare(pa, LongOpt)==0))
return MagickTrue;
return MagickFalse;
}
#define NEXTARG \
if (i < argc-1) i++; \
else { \
fprintf (stderr, "polyreg: ERROR: [%s] needs argument\n", pa); \
status = MagickFalse; \
}
static MagickBooleanType menu(
const int argc,
const char **argv,
PolyRegT * pr
)
/* Returns MagickTrue if okay. */
{
int
i;
MagickBooleanType
status = MagickTrue;
char ** pargv = (char **)argv;
pr->verbose = 0;
for (i=0; i < argc; i++) {
char * pa = (char *)argv[i];
if (IsArg (pa, "ld", "limitDegree")==MagickTrue) {
NEXTARG;
pr->limitDegree = atoi(pargv[i]);
} else if (IsArg (pa, "lt", "limitTerms")==MagickTrue) {
NEXTARG;
pr->limitTerms = atoi(pargv[i]);
if (pr->limitTerms < 1) {
fprintf (stderr, "limitTerms must be 1 or greater.\n");
status = MagickFalse;
}
} else if (IsArg (pa, "ow", "outputWidth")==MagickTrue) {
NEXTARG;
pr->outputWidth = atoi(pargv[i]);
} else if (IsArg (pa, "oh", "outputHeight")==MagickTrue) {
NEXTARG;
pr->outputHeight = atoi(pargv[i]);
} else if (IsArg (pa, "co", "calcOnly")==MagickTrue) {
pr->calcOnly = MagickTrue;
} else if (IsArg (pa, "sy", "syntax")==MagickTrue) {
NEXTARG;
if (LocaleCompare (argv[i], "bash")==0) pr->syntax = synBash;
else if (LocaleCompare (argv[i], "windows")==0) pr->syntax = synWindows;
else status = MagickFalse;
} else if (IsArg (pa, "f", "file")==MagickTrue) {
NEXTARG;
if (LocaleCompare (argv[i], "stdout")==0) pr->fh_data = stdout;
else if (LocaleCompare (argv[i], "stderr")==0) pr->fh_data = stderr;
else status = MagickFalse;
} else if (IsArg (pa, "v", "verbose")==MagickTrue) {
pr->verbose = 1;
} else if (IsArg (pa, "v2", "verbose2")==MagickTrue) {
pr->verbose = 2;
} else if (IsArg (pa, "version", "version")==MagickTrue) {
fprintf (stdout, "%s\n", VERSION);
} else {
fprintf (stderr, "polyreg: ERROR: unknown option [%s]\n", pa);
status = MagickFalse;
}
}
if (pr->verbose) {
fprintf (stderr, "polyreg options: ");
if (pr->limitDegree != 0) fprintf (stderr, " limitDegree %lu", pr->limitDegree);
if (pr->limitTerms != 0) fprintf (stderr, " limitTerms %lu", pr->limitTerms);
if (pr->outputWidth != 0) fprintf (stderr, " outputWidth %lu", pr->outputWidth);
if (pr->outputHeight != 0) fprintf (stderr, " outputHeight %lu", pr->outputHeight);
if (pr->calcOnly) fprintf (stderr, " calcOnly");
if (pr->syntax==synWindows) fprintf (stderr, " syntax windows");
if (pr->fh_data == stdout) fprintf (stderr, " file stdout");
if (pr->verbose == 1) fprintf (stderr, " verbose");
if (pr->verbose >= 2) fprintf (stderr, " verbose%lu", pr->verbose);
fprintf (stderr, "\n");
}
if (status == MagickFalse)
usage ();
return (status);
}
static void InitPolyReg (PolyRegT * pr)
{
pr->precision = GetMagickPrecision();
pr->syntax = synBash;
pr->limitDegree = 0;
pr->limitTerms = 0;
pr->outputWidth = 0;
pr->outputHeight = 0;
pr->doWarn = MagickTrue;
pr->nDims = 0;
pr->calcOnly = MagickFalse;
pr->numColChan = 0;
pr->polyDegree = 2;
pr->power = NULL;
pr->matrix = pr->vectors = NULL;
pr->terms = pr->results = NULL;
pr->fh_data = stderr;
pr->verbose = 0;
}
static void DeAllocGj (PolyRegT * pr)
{
if (pr->vectors) pr->vectors = RelinquishMagickMatrix (pr->vectors, pr->nResults);
if (pr->matrix) pr->matrix = RelinquishMagickMatrix (pr->matrix, pr->nTerms);
if (pr->results) pr->results = (double *)RelinquishMagickMemory (pr->results);
if (pr->terms) pr->terms = (double *)RelinquishMagickMemory (pr->terms);
if (pr->power) pr->power = (powerT *)RelinquishMagickMemory (pr->power);
}
static MagickBooleanType AllocGj (
PolyRegT * pr,
size_t nTerms,
size_t nResults
)
{
pr->nTerms = nTerms;
pr->nResults = nResults;
pr->terms = (double *)AcquireMagickMemory (nTerms * sizeof(double));
pr->results = (double *)AcquireMagickMemory (nResults * sizeof(double));
pr->matrix = AcquireMagickMatrix (nTerms, nTerms);
pr->vectors = AcquireMagickMatrix (nResults, nTerms);
if (!pr->terms || !pr->results || !pr->matrix || !pr->vectors) {
DeAllocGj (pr);
fprintf (stderr, "AllocGj: oom\n");
return MagickFalse;
}
/* Build table of powers. */
{
size_t tmNum;
size_t degree = 0;
size_t termsInDeg = 1; /* Number of terms in this degree */
if (pr->verbose) fprintf (pr->fh_data, "Build powers table.\n");
pr->power = (powerT *)AcquireMagickMemory (pr->nTerms * sizeof(powerT));
if (!pr->power) {
DeAllocGj (pr);
return MagickFalse;
}
for (tmNum = 0; tmNum < pr->nTerms; ) {
size_t t; /* term within degree */
for (t=0; t < termsInDeg; t++) {
size_t powY = t;
size_t powX = degree - t;
if (tmNum < pr->nTerms) {
// size_t ndx = tmNum;
// if (pr->verbose) fprintf (pr->fh_data, "ndx=%lu degree=%lu t=%lu powX=%lu powY=%lu\n", ndx, degree, t, powX, powY);
pr->power[tmNum].powX = powX;
pr->power[tmNum].powY = powY;
}
tmNum++;
}
termsInDeg++;
degree++;
}
if (pr->verbose) fprintf (pr->fh_data, "Build powers table: done.\n");
}
return MagickTrue;
}
static MagickBooleanType TermPowers2d (
const PolyRegT * pr,
const size_t TermNum,
size_t * powX, size_t * powY)
/* For the given term number (zero upwards),
returns the (integer) powers of the x and y coordinates, from zero upwards.
*/
{
assert (TermNum < pr->nTerms);
*powX = pr->power[TermNum].powX;
*powY = pr->power[TermNum].powY;
return MagickTrue;
}
static void LeastSquaresAddTermsWt (
PolyRegT * pr,
const size_t rank,
const size_t number_vectors,
const double weight)
{
ssize_t
j;
// FIXME: parallelize this?
for (j=0; j < (ssize_t) rank; j++)
{
ssize_t
i;
for (i=0; i < (ssize_t) rank; i++)
pr->matrix[i][j] += weight*pr->terms[i]*pr->terms[j];
for (i=0; i < (ssize_t) number_vectors; i++)
pr->vectors[i][j] += weight*pr->results[i]*pr->terms[j];
}
}
static void WrMats (const PolyRegT * pr)
{
ssize_t
i,
j;
fprintf (pr->fh_data, "matrix:\n");
for (j=0; j < (ssize_t) pr->nTerms; j++) {
fprintf (pr->fh_data, " ");
for (i=0; i < (ssize_t) pr->nTerms; i++) {
fprintf (pr->fh_data, "%g ", pr->matrix[i][j]);
}
fprintf (pr->fh_data, "\n");
}
fprintf (pr->fh_data, "vectors:\n");
for (j=0; j < (ssize_t) pr->nTerms; j++) {
fprintf (pr->fh_data, " ");
for (i=0; i < (ssize_t) pr->nResults; i++) {
fprintf (pr->fh_data, "%g ", pr->vectors[i][j]);
}
fprintf (pr->fh_data, "\n");
}
fprintf (pr->fh_data, "terms:\n ");
for (j=0; j < (ssize_t) pr->nTerms; j++) {
fprintf (pr->fh_data, "%g ", pr->terms[j]);
}
fprintf (pr->fh_data, "\n");
fprintf (pr->fh_data, "results:\n ");
for (j=0; j < (ssize_t) pr->nResults; j++) {
fprintf (pr->fh_data, "%g ", pr->results[j]);
}
fprintf (pr->fh_data, "\n");
}
/* Coordinates to AddTerm and CalcPoly must be in image coordinates, 0.0 to 1.0.
*/
static void AddTerm1d (PolyRegT * pr, double x, double weight)
{
size_t i;
double v = x;
pr->terms[0] = 1;
for (i = 1; i < pr->nTerms; i++) {
pr->terms[i] = v * pr->terms[i-1];
}
LeastSquaresAddTermsWt (pr, pr->nTerms, pr->nResults, weight);
}
static inline double intpow (double x, size_t n)
{
double v = 1;
size_t i;
for (i = 0; i < n; i++) {
v *= x;
}
return v;
}
static void AddTerm2d (PolyRegT * pr, double x, double y, double weight)
{
size_t i;
for (i = 0; i < pr->nTerms; i++) {
size_t powX=0, powY=0;
if (!TermPowers2d (pr, i, &powX, &powY)) {
fprintf (stderr, "TermPowers2d failed\n");
break;
}
pr->terms[i] = intpow(x, powX) * intpow(y, powY);
}
LeastSquaresAddTermsWt (pr, pr->nTerms, pr->nResults, weight);
}
static double CalcPoly1d (const PolyRegT * pr, double x, size_t ch)
{
double c = 0;
size_t i;
for (i = 0; i < pr->nTerms; i++) {
c += intpow(x, i) * pr->vectors[ch][i];
}
return c;
}
static double CalcPoly2d (const PolyRegT * pr, double x, double y, size_t ch)
{
double c = 0;
size_t i;
for (i = 0; i < pr->nTerms; i++) {
size_t powX=0, powY=0;
if (!TermPowers2d (pr, i, &powX, &powY)) {
fprintf (stderr, "TermPowers2d failed\n");
break;
}
c += intpow(x, powX) * intpow(y, powY) * pr->vectors[ch][i];
}
return c;
}
/*
Some calculated coefficients are small, eg 6e-4 where largest absolute is 6.0.
We might set small coeffs to zero.
*/
static void WrEquations (const PolyRegT * pr)
{
size_t c;
char * chNames[] = {"R", "G", "B"};
char chQuote = (pr->syntax==synBash) ? '\'' : '"';
char chCont = (pr->syntax==synBash) ? '\\' : '^';
fprintf (pr->fh_data, "Fx equivalent:\n");
fprintf (pr->fh_data, " -alpha off\n");
for (c = 0; c < pr->numColChan; c++) {
size_t i;
fprintf (pr->fh_data, " -channel %s %c\n", chNames[c], chCont);
fprintf (pr->fh_data, " -fx %cXX = (i+0.5)/w;", chQuote);
if (pr->nDims == 2)
fprintf (pr->fh_data, " YY = (j+0.5)/h;");
if (pr->syntax == synBash)
fprintf (pr->fh_data, " \\\n ");
if (pr->nDims == 1) {
for (i = 0; i < pr->nTerms; i++) {
if (pr->vectors[c][i] != 0) {
size_t powX = i;
if (pr->vectors[c][i] >= 0)
fprintf (pr->fh_data, " + ");
else
fprintf (pr->fh_data, " - ");
fprintf (pr->fh_data, "%.*g", pr->precision, fabs(pr->vectors[c][i]));
if (powX > 2) fprintf (pr->fh_data, "*pow(XX,%lu)", powX);
else if (powX==2) fprintf (pr->fh_data, "*XX*XX");
else if (powX==1) fprintf (pr->fh_data, "*XX");
}
}
}
if (pr->nDims == 2) {
for (i = 0; i < pr->nTerms; i++) {
size_t powX=0, powY=0;
if (!TermPowers2d (pr, i, &powX, &powY)) {
fprintf (stderr, "TermPowers2d failed\n");
break;
}
if (pr->vectors[c][i] >= 0)
fprintf (pr->fh_data, " + ");
else
fprintf (pr->fh_data, " - ");
fprintf (pr->fh_data, "%.*g", pr->precision, fabs(pr->vectors[c][i]));
if (powX > 2) fprintf (pr->fh_data, "*pow(XX,%lu)", powX);
else if (powX==2) fprintf (pr->fh_data, "*XX*XX");
else if (powX==1) fprintf (pr->fh_data, "*XX");
if (powY > 2) fprintf (pr->fh_data, "*pow(YY,%lu)", powY);
else if (powY==2) fprintf (pr->fh_data, "*YY*YY");
else if (powY==1) fprintf (pr->fh_data, "*YY");
}
}
fprintf (pr->fh_data, "%c %c\n", chQuote, chCont);
}
fprintf (pr->fh_data, " +channel\n");
fprintf (pr->fh_data, "End Fx equivalent.\n");
}
static void WrSamples (const PolyRegT * pr)
{
fprintf (pr->fh_data, "Sample values:\n");
if (pr->nDims==1) {
double x;
for (x=0.0; x <= 1.0; x += 0.1) {
fprintf (pr->fh_data, " %g: %g\n", x, CalcPoly1d (pr, x, 0));
}
}
if (pr->nDims==2) {
double x, y;
for (y=0.0; y <= 1.0; y += 0.1) {
fprintf (pr->fh_data, " %g ", y);
for (x=0.0; x <= 1.0; x += 0.1) {
fprintf (pr->fh_data, " %g", CalcPoly2d (pr, x, y, 0));
}
fprintf (pr->fh_data, "\n");
}
}
}
static inline double PixToImgY (const Image * img, const size_t y)
{
// return ((double)y+0.5)/(double)img->rows;
return ((double)y)/(double)(img->rows-1);
}
static inline double PixToImgX (const Image * img, const size_t x)
{
// return ((double)x+0.5)/(double)img->columns;
return ((double)x)/(double)(img->columns-1);
}
static MagickBooleanType ReadPixels (PolyRegT * pr, const Image * image, ExceptionInfo *exception)
{
MagickBooleanType
status = MagickTrue;
size_t
y;
size_t count = 0;
CacheView * image_view = AcquireVirtualCacheView (image, exception);
/* Count the number of non-transparent pixels.
*/
// FIXME: shortcut: if no alpha channel, then all pixels are opaque.
for (y = 0; y < image->rows; y++)
{
VIEW_PIX_PTR const
*p;
size_t
x;
if (status == MagickFalse)
continue;
p = GetCacheViewVirtualPixels (image_view, 0, (ssize_t)y, image->columns, 1, exception);
if (p == (const VIEW_PIX_PTR *) NULL) {
status=MagickFalse;
break;
}
for (x=0; x < image->columns; x++)
{
if ( GET_PIXEL_ALPHA(image,p) > 0 ) {
count++;
}
p += Inc_ViewPixPtr (image);
}
}
pr->nDims = (image->rows == 1) ? 1 : 2;
pr->numColChan = NumColChannels (image);
switch (pr->nDims) {
case 1:
pr->polyDegree = count - 1;
if (pr->limitDegree && pr->polyDegree > pr->limitDegree)
pr->polyDegree = pr->limitDegree;
pr->nTerms = pr->polyDegree + 1;
if (pr->limitTerms && pr->nTerms > pr->limitTerms)
pr->nTerms = pr->limitTerms;
break;
default: {
size_t d = 0;
while (count > (d+1)*(d+2)/2) d++;
pr->polyDegree = d;
if (pr->limitDegree && pr->polyDegree > pr->limitDegree)
pr->polyDegree = pr->limitDegree;
pr->nTerms = (pr->polyDegree+1)*(pr->polyDegree+2)/2;
if (pr->limitTerms && pr->nTerms > pr->limitTerms)
pr->nTerms = pr->limitTerms;
break;
}
}
if (pr->verbose)
fprintf (pr->fh_data,
"ReadPixels: numColChan=%lu non-trans=%lu nDims=%lu polyDegree=%lu nTerms=%lu\n",
pr->numColChan, count, pr->nDims, pr->polyDegree, pr->nTerms);
pr->nResults = pr->numColChan; /* one output per colour channel. */
if (!AllocGj (pr, pr->nTerms, pr->nResults)) return MagickFalse;
if (pr->verbose >= 2) fprintf (pr->fh_data, "Add non-transparent pixels.\n");
{
double qr = (double)QuantumRange;
for (y = 0; y < image->rows; y++)
{
VIEW_PIX_PTR const
*p;
size_t
x;
double yf = PixToImgY (image, y);
if (status == MagickFalse)
continue;
p = GetCacheViewVirtualPixels (image_view, 0, (ssize_t)y, image->columns, 1, exception);
if (p == (const VIEW_PIX_PTR *) NULL) {
status=MagickFalse;
break;
}
for (x=0; x < image->columns; x++)
{
double alpha = GET_PIXEL_ALPHA(image,p) / qr;
if (alpha > 0 ) {
pr->results[0] = GET_PIXEL_RED(image,p) / qr;
pr->results[1] = GET_PIXEL_GREEN(image,p) / qr;
pr->results[2] = GET_PIXEL_BLUE(image,p) / qr;
// {
// ssize_t dx = (ssize_t)(x - image->columns);
// ssize_t dy = (ssize_t)(y - image->rows);
// weight = (double)(1.0 / dx*dx + dy*dy);
// }
switch (pr->nDims) {
case 1:
AddTerm1d (pr, PixToImgX (image, x), alpha);
break;
default:
AddTerm2d (pr, PixToImgX (image, x), yf, alpha);
break;
}
}
p += Inc_ViewPixPtr (image);
}
}
}
image_view = DestroyCacheView (image_view);
return (status);
}
static Image * MkNewImage (PolyRegT * pr, const Image * image, ExceptionInfo *exception)
{
MagickBooleanType
status = MagickTrue;
size_t
y;
CacheView
*image_view;
// fixme for different output size
Image * new_img = CloneImage (image, pr->outputWidth, pr->outputHeight, MagickTrue, exception);
if (!new_img) return NULL;
// FIXME: turn alpha off
SetImageAlphaChannel (new_img, OpaqueAlphaChannel, exception);
image_view = AcquireAuthenticCacheView (new_img, exception);
if (pr->verbose >= 2) fprintf (pr->fh_data, "MkNewImage\n");
#if defined(MAGICKCORE_OPENMP_SUPPORT)
#pragma omp parallel for schedule(static,4) shared(status) \
MAGICK_THREADS(new_img,new_img,new_img->rows,1)
#endif
for (y = 0; y < new_img->rows; y++)
{
VIEW_PIX_PTR
*p;
size_t
x;
double yf = PixToImgY (new_img, y);
if (status == MagickFalse)
continue;
p = GetCacheViewAuthenticPixels (image_view, 0, (ssize_t)y, new_img->columns, 1, exception);
if (p == (const VIEW_PIX_PTR *) NULL) {
status=MagickFalse;
break;
}
for (x=0; x < new_img->columns; x++)
{
double xf = PixToImgX (new_img, x);
switch (pr->nDims) {
case 1:
SET_PIXEL_RED (new_img, QuantumRange * CalcPoly1d (pr, xf, 0), p);
SET_PIXEL_GREEN (new_img, QuantumRange * CalcPoly1d (pr, xf, 1), p);
SET_PIXEL_BLUE (new_img, QuantumRange * CalcPoly1d (pr, xf, 2), p);
break;
default:
SET_PIXEL_RED (new_img, QuantumRange * CalcPoly2d (pr, xf, yf, 0), p);
SET_PIXEL_GREEN (new_img, QuantumRange * CalcPoly2d (pr, xf, yf, 1), p);
SET_PIXEL_BLUE (new_img, QuantumRange * CalcPoly2d (pr, xf, yf, 2), p);
break;
}
p += Inc_ViewPixPtr (new_img);
}
if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
status=MagickFalse;
}
image_view = DestroyCacheView (image_view);
return new_img;
}
static MagickBooleanType CalcRMSE (
PolyRegT * pr,
const Image * inp_img,
const Image * new_img,
ExceptionInfo *exception)
// FIXME: but what if they are different sizes?
{
MagickBooleanType
status = MagickTrue;
size_t
y;
CacheView * inp_img_view = AcquireVirtualCacheView (inp_img, exception);
CacheView * new_img_view = AcquireVirtualCacheView (new_img, exception);
double err = 0;
size_t count = 0;
for (y = 0; y < inp_img->rows; y++)
{
VIEW_PIX_PTR const
*p, *q;
size_t
x;
if (status == MagickFalse)
continue;
p = GetCacheViewVirtualPixels (inp_img_view, 0, (ssize_t)y, inp_img->columns, 1, exception);
if (p == (const VIEW_PIX_PTR *) NULL) {
status=MagickFalse;
break;
}
// FIXME: maybe different y for new_img
q = GetCacheViewVirtualPixels (new_img_view, 0, (ssize_t)y, new_img->columns, 1, exception);
if (q == (const VIEW_PIX_PTR *) NULL) {
status=MagickFalse;
break;
}
for (x=0; x < inp_img->columns; x++)
{
if ( GET_PIXEL_ALPHA(inp_img,p) > 0 ) {
// FIXME: maybe different x for new_img
double dr = GET_PIXEL_RED (inp_img, p) - GET_PIXEL_RED (new_img, q);
double dg = GET_PIXEL_GREEN (inp_img, p) - GET_PIXEL_GREEN (new_img, q);
double db = GET_PIXEL_BLUE (inp_img, p) - GET_PIXEL_BLUE (new_img, q);
err += dr*dr + dg*dg + db*db;
count++; // FIXME: +=3 ??
}
p += Inc_ViewPixPtr (inp_img);
q += Inc_ViewPixPtr (new_img);
}
}
if (count > 0)
fprintf (pr->fh_data, "RMSE = %.*g\n",
pr->precision,
sqrt (err / ((double)count * QuantumRange * QuantumRange) ));
new_img_view = DestroyCacheView (new_img_view);
inp_img_view = DestroyCacheView (inp_img_view);
return (status);
}
static Image* PolyRegOne (PolyRegT * pr, const Image * image, ExceptionInfo *exception)
{
MagickBooleanType status = MagickTrue;
Image *new_img = NULL;
if (!ReadPixels (pr, image, exception)) {
fprintf (stderr, "ReadPixels failed\n");
return NULL;
}
if (pr->verbose >= 2) WrMats (pr);
if (pr->verbose)
fprintf (pr->fh_data,
"GaussJordanElimination nTerms=%lu nResults=%lu\n",
pr->nTerms, pr->nResults);
if (!GaussJordanElimination (pr->matrix, pr->vectors, pr->nTerms, pr->nResults) ) {
fprintf (stderr, "No solution.\n"); // FIXME: fatal error?
} else {
if (pr->verbose >= 2) WrMats (pr);
if (pr->verbose >= 1) WrEquations (pr);
}
if (pr->verbose >= 2) WrSamples (pr);
if (pr->calcOnly) {
new_img = (Image *)image;
} else {
new_img = MkNewImage (pr, image, exception);
if (pr->verbose) CalcRMSE (pr, image, new_img, exception);
}
DeAllocGj (pr);
if (!status) return NULL;
if (new_img) return new_img;
return NULL;
}
ModuleExport size_t polyregImage (
Image **images,
const int argc,
const char **argv,
ExceptionInfo *exception)
{
Image
*image,
*new_img;
MagickBooleanType
status;
PolyRegT
pr;
assert(images != (Image **) NULL);
assert(*images != (Image *) NULL);
assert((*images)->signature == MAGICK_CORE_SIG);
InitPolyReg (&pr);
status = menu (argc, argv, &pr);
if (status == MagickFalse)
return (~MagickImageFilterSignature);
for (image=(*images); image != (Image *) NULL; image=GetNextImageInList(image))
{
new_img = PolyRegOne (&pr, image, exception);
if (!new_img) continue;
if (new_img != image) {
ReplaceImageInList(&image,new_img);
*images=GetFirstImageInList(image);
}
}
return(MagickImageFilterSignature);
}
This uses delaunay.c and delaunay.h, below.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include "vsn_defines.h"
#include "MagickCore/MagickCore.h"
/*
Uses:
Relatively Robust Divide and Conquer 2D Delaunay Construction Algorithm
by Wael El Oraiby
AGPL-3.0 license
https://github.com/eloraiby/delaunay
Beware: the delaunay library crashes with less than three input points,
and generated triangles can have zero determinants.
*/
#include <snibgo/delaunay.c>
/*
Output image could be:
- When colouring pixels, "-channels" should control which channels are used, incl alpha.
Options:
- Whether to apply sigmoidal or inverse sigmoidal to barycentric distances.
Barycentric ref: https://en.wikipedia.org/wiki/Barycentric_coordinate_system#Conversion_between_barycentric_and_Cartesian_coordinates
normalised barycentric coordinates are:
b0 = [ (y1-y2)*(x-x2) + (x2-x1)*(y-y2) ] / det
b1 = [ (y2-y0)*(x-x2) + (x0-x2)*(y-y2) ] / det
b2 = 1 - b1 - b2
*/
#define VERSION "deltri v1.0 Copyright (c) 2025 Alan Gibson"
typedef struct {
size_t
x, y;
MagickBooleanType
protect,
RemoveThis;
} ChkPointT;
typedef struct {
double
x0, y0, x1, y1, x2, y2,
x02, x21, y02, y20, y12,
y12d, x21d, y20d, x02d,
det;
size_t
minX, maxX, minY, maxY;
Quantum
colVal;
PixelInfo
pi0, pi1, pi2;
} triangleT;
typedef struct {
size_t
pnt0, pnt1;
size_t
numTris,
tri0, tri1;
PixelInfo
*pi0, *pi1;
} edgeT;
typedef struct {
size_t
pnt0, pnt1;
size_t
tri0;
double
dx, dy, sqLen;
PixelInfo
*pi0, *pi1;
} hullEdgeT;
typedef enum {
ftTransparent,
ftLabel,
ftMean,
ftCoords,
ftBarycent,
} fillT;
char * strFills[] = {"transparent", "label", "mean", "coords", "barycentric"};
typedef struct {
int
precision;
size_t
verbose;
double
minLen,
minDist,
maxRatio;
double
minLenSq,
minDistSq,
maxRatioSq;
fillT
fillTri,
fillOutside;
MagickBooleanType
calcOnly,
protectHull,
drawPoints,
drawEdges,
drawText,
clampBary;
FILE *
fh_data;
size_t
numEdgeList,
numDistinctEdges,
numHullEdges;
RandomInfo
* magick_restrict random_info;
size_t
numPoints,
x0, y0, x1, y1; /* When we have just 1 or 2 points. */
PixelInfo
pi0, pi1; /* When we have just 1 or 2 points. */
triangleT
*triangles;
edgeT
*edges;
hullEdgeT
*hullEdges;
delaunay2d_t
*del;
tri_delaunay2d_t
*tri_del;
} DelTriT;
static void usage (void)
{
printf ("Usage: -process 'deltri [OPTION]...'\n");
printf ("Delaunay triangles filled via barycentric coordinates.\n");
printf ("\n");
printf (" ml, minLen integer removes points until no edges are shorter than this\n");
printf (" md, minDist integer removes points that are closer than this to opposite edge in triangles\n");
printf (" mr, maxRatio number removes points where triangle ratio is more than this\n");
printf (" ph, protectHull don't remove points on the convex hull\n");
printf (" t, triangle string one of: transparent, label, mean, coords, barycentric\n");
printf (" o, outside string one of: transparent, label, mean, coords, barycentric\n");
printf (" \"outside\" means pixels outside the convex hull.\n");
printf (" \"mean\" means colour is set to the mean of the triangle's vertices.\n");
printf (" dp, drawPoints draw points on main image\n");
printf (" de, drawEdges draw edges on separate image\n");
printf (" dt, drawText label points etc with text numbers\n");
printf (" nc, noClampBary don't clamp barycentric coordinates\n");
printf (" co, calcOnly don't draw anything or replace images\n");
printf (" f, file string write to file stream stdout or stderr\n");
printf (" v, verbose write text information to stdout\n");
printf (" v2, verbose2 write more text information to stdout\n");
printf (" version write version information to stdout\n");
printf ("\n");
}
static MagickBooleanType IsArg (char * pa, char * ShtOpt, char * LongOpt)
{
/* LocaleCompare is not case-sensitive,
so we use strcmp for the short option.
*/
if ((strcmp(pa, ShtOpt)==0) || (LocaleCompare(pa, LongOpt)==0))
return MagickTrue;
return MagickFalse;
}
#define NEXTARG \
if (i < argc-1) i++; \
else { \
fprintf (stderr, "polyreg: ERROR: [%s] needs argument\n", pa); \
status = MagickFalse; \
}
static MagickBooleanType menu(
const int argc,
const char **argv,
DelTriT * pdt
)
/* Returns MagickTrue if okay. */
{
int
i;
MagickBooleanType
status = MagickTrue;
char ** pargv = (char **)argv;
pdt->verbose = 0;
for (i=0; i < argc; i++) {
char * pa = (char *)argv[i];
if (IsArg (pa, "ml", "minLen")==MagickTrue) {
NEXTARG;
pdt->minLen = strtof (pargv[i], (char **) NULL);
} else if (IsArg (pa, "md", "minDist")==MagickTrue) {
NEXTARG;
pdt->minDist = strtof (pargv[i], (char **) NULL);
} else if (IsArg (pa, "mr", "maxRatio")==MagickTrue) {
NEXTARG;
pdt->maxRatio = strtof (pargv[i], (char **) NULL);
} else if (IsArg (pa, "ph", "protectHull")==MagickTrue) {
pdt->protectHull = MagickTrue;
} else if (IsArg (pa, "t", "triangle")==MagickTrue) {
NEXTARG;
if (LocaleCompare(pargv[i], "transparent")==0) {
pdt->fillTri = ftTransparent;
} else if (LocaleCompare(pargv[i], "label")==0) {
pdt->fillTri = ftLabel;
} else if (LocaleCompare(pargv[i], "mean")==0) {
pdt->fillTri = ftMean;
} else if (LocaleCompare(pargv[i], "coords")==0) {
pdt->fillTri = ftCoords;
} else if (LocaleCompare(pargv[i], "barycentric")==0) {
pdt->fillTri = ftBarycent;
} else {
fprintf (stderr, "Bad option for \"%s\": %s\n", pa, pargv[i]);
status = MagickFalse;
}
} else if (IsArg (pa, "o", "outside")==MagickTrue) {
NEXTARG;
if (LocaleCompare(pargv[i], "transparent")==0) {
pdt->fillOutside = ftTransparent;
} else if (LocaleCompare(pargv[i], "label")==0) {
pdt->fillOutside = ftLabel;
} else if (LocaleCompare(pargv[i], "mean")==0) {
pdt->fillOutside = ftMean;
} else if (LocaleCompare(pargv[i], "coords")==0) {
pdt->fillOutside = ftCoords;
} else if (LocaleCompare(pargv[i], "barycentric")==0) {
pdt->fillOutside = ftBarycent;
} else {
fprintf (stderr, "Bad option for \"%s\": %s\n", pa, pargv[i]);
status = MagickFalse;
}
} else if (IsArg (pa, "dp", "drawPoints")==MagickTrue) {
pdt->drawPoints = MagickTrue;
} else if (IsArg (pa, "de", "drawEdges")==MagickTrue) {
pdt->drawEdges = MagickTrue;
} else if (IsArg (pa, "dt", "drawText")==MagickTrue) {
pdt->drawText = MagickTrue;
} else if (IsArg (pa, "nc", "noClampBary")==MagickTrue) {
pdt->clampBary = MagickFalse;
} else if (IsArg (pa, "co", "calcOnly")==MagickTrue) {
pdt->calcOnly = MagickTrue;
} else if (IsArg (pa, "f", "file")==MagickTrue) {
NEXTARG;
if (LocaleCompare (argv[i], "stdout")==0) pdt->fh_data = stdout;
else if (LocaleCompare (argv[i], "stderr")==0) pdt->fh_data = stderr;
else status = MagickFalse;
} else if (IsArg (pa, "v", "verbose")==MagickTrue) {
pdt->verbose = 1;
} else if (IsArg (pa, "v2", "verbose2")==MagickTrue) {
pdt->verbose = 2;
} else if (IsArg (pa, "version", "version")==MagickTrue) {
fprintf (stdout, "%s\n", VERSION);
} else {
fprintf (stderr, "deltri: ERROR: unknown option [%s]\n", pa);
status = MagickFalse;
}
}
if (pdt->verbose) {
fprintf (stderr, "deltri options: ");
if (pdt->minLen != FLT_MAX) fprintf (stderr, " minLen %g", pdt->minLen);
if (pdt->minDist != FLT_MAX) fprintf (stderr, " minDist %g", pdt->minDist);
if (pdt->maxRatio != FLT_MAX) fprintf (stderr, " maxRatio %g", pdt->maxRatio);
if (pdt->protectHull) fprintf (stderr, " protectHull");
fprintf (stderr, " triangle %s", strFills[pdt->fillTri]);
fprintf (stderr, " outside %s", strFills[pdt->fillOutside]);
if (pdt->drawPoints) fprintf (stderr, " drawPoints");
if (pdt->drawEdges) fprintf (stderr, " drawEdges");
if (pdt->drawText) fprintf (stderr, " drawText");
if (!pdt->clampBary) fprintf (stderr, " noClampBary");
if (pdt->calcOnly) fprintf (stderr, " calcOnly");
if (pdt->fh_data == stdout) fprintf (stderr, " file stdout");
if (pdt->verbose == 1) fprintf (stderr, " verbose");
if (pdt->verbose >= 2) fprintf (stderr, " verbose%lu", pdt->verbose);
fprintf (stderr, "\n");
}
if (status == MagickFalse)
usage ();
return (status);
}
static void InitDelTri (DelTriT * pdt)
{
pdt->precision = GetMagickPrecision();
pdt->minLen = FLT_MAX;
pdt->minDist = FLT_MAX;
pdt->maxRatio = FLT_MAX;
pdt->del = NULL;
pdt->tri_del = NULL;
pdt->verbose = 0;
pdt->fh_data = stderr;
pdt->fillTri = ftBarycent;
pdt->fillOutside = ftBarycent;
pdt->clampBary = MagickTrue;
pdt->triangles = NULL;
pdt->edges = NULL;
pdt->hullEdges = NULL;
pdt->numEdgeList = 0;
pdt->numDistinctEdges = 0;
pdt->numHullEdges = 0;
pdt->protectHull = MagickFalse;
pdt->calcOnly = MagickFalse;
pdt->drawPoints = MagickFalse;
pdt->drawEdges = MagickFalse;
pdt->drawText = MagickFalse;
}
static void DeInitDelTri (DelTriT * pdt)
{
tri_delaunay2d_release (pdt->tri_del);
delaunay2d_release (pdt->del);
if (pdt->hullEdges) pdt->hullEdges = (hullEdgeT *)RelinquishMagickMemory (pdt->hullEdges);
if (pdt->edges) pdt->edges = (edgeT *)RelinquishMagickMemory (pdt->edges);
if (pdt->triangles) pdt->triangles = (triangleT *)RelinquishMagickMemory (pdt->triangles);
pdt->numEdgeList = 0;
pdt->numHullEdges = 0;
}
static MagickBooleanType RecordEdge (DelTriT * pdt, size_t pnt0, size_t pnt1, size_t triNum,
PixelInfo *pi0,
PixelInfo *pi1)
{
/* We may already have it, with points in the reverse order.
We don't check for this, because a linear search would be very slow.
*/
edgeT
*e;
assert (pdt->edges);
assert (pdt->numEdgeList < pdt->tri_del->num_triangles * 3);
e = &pdt->edges[pdt->numEdgeList];
/* It would simplify sorting if we ensured here that pnt0 < pnt1.
But then hullEdges might not be in same direction.
*/
e->pnt0 = pnt0;
e->pnt1 = pnt1;
e->numTris = 1;
e->tri0 = triNum;
e->tri1 = 0;
e->pi0 = pi0;
e->pi1 = pi1;
pdt->numEdgeList++;
return MagickTrue;
}
static int compareEdges (const void *a, const void *b)
{
const edgeT * ea = (edgeT *)a;
const edgeT * eb = (edgeT *)b;
size_t ap0, ap1, bp0, bp1;
if (ea->pnt0 < ea->pnt1) {
ap0 = ea->pnt0;
ap1 = ea->pnt1;
} else {
ap0 = ea->pnt1;
ap1 = ea->pnt0;
}
if (eb->pnt0 < eb->pnt1) {
bp0 = eb->pnt0;
bp1 = eb->pnt1;
} else {
bp0 = eb->pnt1;
bp1 = eb->pnt0;
}
if (ap0 < bp0) return -1;
if (ap0 > bp0) return +1;
if (ap1 < bp1) return -1;
if (ap1 > bp1) return +1;
return 0;
}
static void DumpFaces (DelTriT * pdt)
{
size_t i, j=0;
fprintf (pdt->fh_data, "Faces (%u):\n", pdt->del->num_faces);
for (i = 0; i < pdt->del->num_faces; i++) {
size_t numVerts = pdt->del->faces[j];
size_t f;
j++;
fprintf (pdt->fh_data, " numVerts=%lu ", numVerts);
for (f = 0; f < numVerts; f++) {
fprintf (pdt->fh_data, " %u", pdt->del->faces[j]);
j++;
}
fprintf (pdt->fh_data, "\n");
}
}
static void DumpData (DelTriT * pdt)
{
size_t
i;
if (pdt->numPoints < 3) return; // FIXME
DumpFaces (pdt);
fprintf (pdt->fh_data, "Points (%u):\n # x y\n",
pdt->tri_del->num_points);
for (i=0; i < pdt->tri_del->num_points; i++) {
fprintf (pdt->fh_data, " %lu: %g %g\n",
i,
pdt->tri_del->points[i].x,
pdt->tri_del->points[i].y);
}
fprintf (pdt->fh_data, "Triangles (%u):\n # id: pnt0 pnt1 pnt2\n",
pdt->tri_del->num_triangles);
for (i=0; i < pdt->tri_del->num_triangles; i++) {
size_t offset = i*3;
fprintf (pdt->fh_data, " %lu: %u %u %u",
i,
pdt->tri_del->tris[offset],
pdt->tri_del->tris[offset+1],
pdt->tri_del->tris[offset+2]);
if (pdt->triangles[i].det == 0) fprintf (pdt->fh_data, " det==0");
fprintf (pdt->fh_data, "\n");
}
fprintf (pdt->fh_data, "Edge list (%lu):\n # id: pnt0 pnt1 numTris tri0 tri1\n",
pdt->numEdgeList);
fprintf (pdt->fh_data, "DistinctEdges (%lu):\n",
pdt->numDistinctEdges);
for (i = 0; i < pdt->numEdgeList; i++) {
edgeT *e = &pdt->edges[i];
fprintf (pdt->fh_data, " %lu: %lu %lu %lu %lu %lu\n",
i, e->pnt0, e->pnt1, e->numTris, e->tri0, e->tri1);
}
fprintf (pdt->fh_data, "HullEdges (%lu):\n # id: pnt0 pnt1 tri0\n",
pdt->numHullEdges);
for (i = 0; i < pdt->numHullEdges; i++) {
hullEdgeT *h = &pdt->hullEdges[i];
fprintf (pdt->fh_data, " %lu: %lu %lu %lu\n", i, h->pnt0, h->pnt1, h->tri0);
}
fprintf (pdt->fh_data, "endDumpData\n");
}
static MagickBooleanType CalcTriangles (DelTriT * pdt, Image * image, ExceptionInfo *exception)
{
size_t
i, j = 0;
MagickRealType
colFact = (MagickRealType)QuantumRange / (MagickRealType)pdt->tri_del->num_triangles;
CacheView * image_view = AcquireVirtualCacheView (image, exception);
if (pdt->verbose) fprintf (pdt->fh_data, "CalcTriangles\n");
assert (!pdt->triangles);
assert (!pdt->edges);
pdt->triangles = (triangleT *)AcquireMagickMemory (pdt->tri_del->num_triangles * sizeof(triangleT));
pdt->edges = (edgeT *)AcquireMagickMemory (pdt->tri_del->num_triangles * 3 * sizeof(edgeT));
if (!pdt->triangles || !pdt->edges) {
fprintf (stderr, "CalcTriangles oom\n");
return MagickFalse;
}
// memset (&pdt->triangles, 0, sizeof(pdt->tri_del->num_triangles * sizeof(triangleT)));
// memset (&pdt->edges, 0, sizeof(pdt->tri_del->num_triangles * 3 * sizeof(edgeT)));
for (i=0; i < pdt->tri_del->num_triangles; i++) {
size_t offset = i*3;
triangleT * pt = &pdt->triangles[i];
pt->colVal = (Quantum)i * colFact;
pt->x0 = pdt->tri_del->points[pdt->tri_del->tris[offset]].x;
pt->y0 = pdt->tri_del->points[pdt->tri_del->tris[offset]].y;
pt->x1 = pdt->tri_del->points[pdt->tri_del->tris[offset+1]].x;
pt->y1 = pdt->tri_del->points[pdt->tri_del->tris[offset+1]].y;
pt->x2 = pdt->tri_del->points[pdt->tri_del->tris[offset+2]].x;
pt->y2 = pdt->tri_del->points[pdt->tri_del->tris[offset+2]].y;
pt->y12 = pt->y1 - pt->y2;
pt->x02 = pt->x0 - pt->x2;
pt->x21 = pt->x2 - pt->x1;
pt->y02 = pt->y0 - pt->y2;
pt->y20 = pt->y2 - pt->y0;
/* determinant: */
pt->det = (pt->y12 * pt->x02 + pt->x21 * pt->y02);
if (pt->det == 0) {
//printf ("tri %lu det==0\n", i);
pt->y12d = pt->y12;
pt->x21d = pt->x21;
pt->y20d = pt->y20;
pt->x02d = pt->x02;
} else {
pt->y12d = pt->y12 / pt->det;
pt->x21d = pt->x21 / pt->det;
pt->y20d = pt->y20 / pt->det;
pt->x02d = pt->x02 / pt->det;
pt->minX = (size_t)pt->x0;
pt->maxX = (size_t)pt->x0;
pt->minY = (size_t)pt->y0;
pt->maxY = (size_t)pt->y0;
if (pt->minX > pt->x1) pt->minX = (size_t)pt->x1;
if (pt->minX > pt->x2) pt->minX = (size_t)pt->x2;
if (pt->maxX < pt->x1) pt->maxX = (size_t)pt->x1;
if (pt->maxX < pt->x2) pt->maxX = (size_t)pt->x2;
if (pt->minY > pt->y1) pt->minY = (size_t)pt->y1;
if (pt->minY > pt->y2) pt->minY = (size_t)pt->y2;
if (pt->maxY < pt->y1) pt->maxY = (size_t)pt->y1;
if (pt->maxY < pt->y2) pt->maxY = (size_t)pt->y2;
}
GetOneCacheViewVirtualPixelInfo (image_view, (ssize_t)pt->x0, (ssize_t)pt->y0, &pt->pi0, exception);
GetOneCacheViewVirtualPixelInfo (image_view, (ssize_t)pt->x1, (ssize_t)pt->y1, &pt->pi1, exception);
GetOneCacheViewVirtualPixelInfo (image_view, (ssize_t)pt->x2, (ssize_t)pt->y2, &pt->pi2, exception);
RecordEdge (pdt, pdt->tri_del->tris[offset], pdt->tri_del->tris[offset+1], i, &pt->pi0, &pt->pi1);
RecordEdge (pdt, pdt->tri_del->tris[offset+1], pdt->tri_del->tris[offset+2], i, &pt->pi1, &pt->pi2);
RecordEdge (pdt, pdt->tri_del->tris[offset+2], pdt->tri_del->tris[offset], i, &pt->pi2, &pt->pi0);
}
if (pdt->verbose) {
fprintf (pdt->fh_data, " numEdgeList=%lu\n", pdt->numEdgeList);
fprintf (pdt->fh_data, " num_triangles=%u\n", pdt->tri_del->num_triangles);
}
assert (pdt->numEdgeList = pdt->tri_del->num_triangles * 3);
/* We find duplicate edges (made from adjacent triangles)
by sorting them, then comparing adjacent entries in the list.
*/
qsort (pdt->edges, pdt->numEdgeList, sizeof(edgeT), compareEdges);
pdt->numHullEdges = 0;
pdt->numDistinctEdges = pdt->numEdgeList;
for (i = 0; i < pdt->numEdgeList-1; i++) {
edgeT *e0 = &pdt->edges[i];
edgeT *e1 = &pdt->edges[i+1];
if (compareEdges (e0, e1) == 0) {
e0->numTris = 2;
e1->numTris = 0;
e0->tri1 = e1->tri0;
pdt->numDistinctEdges--;
i++;
} else if (e0->numTris==1) {
pdt->numHullEdges++;
}
}
/* We should now ignore edges that have numTris==0.
*/
pdt->numHullEdges++; /* In case last edge was a hull edge. */
assert (!pdt->hullEdges);
pdt->hullEdges = (hullEdgeT *)AcquireMagickMemory (pdt->numHullEdges * sizeof(hullEdgeT));
if (!pdt->hullEdges) {
fprintf (stderr, "CalcTriangles hullEdges oom\n");
return MagickFalse;
}
pdt->numHullEdges = 0;
for (i = 0; i < pdt->numEdgeList; i++) {
edgeT *e = &pdt->edges[i];
if (e->numTris ==1 ) {
double x0, y0, x1, y1;
hullEdgeT *h = &pdt->hullEdges[j];
h->pnt0 = e->pnt0;
h->pnt1 = e->pnt1;
h->tri0 = e->tri0;
h->pi0 = e->pi0;
h->pi1 = e->pi1;
x0 = pdt->tri_del->points[h->pnt0].x;
y0 = pdt->tri_del->points[h->pnt0].y;
x1 = pdt->tri_del->points[h->pnt1].x;
y1 = pdt->tri_del->points[h->pnt1].y;
h->dx = x1 - x0;
h->dy = y1 - y0;
h->sqLen = h->dx * h->dx + h->dy * h->dy;
pdt->numHullEdges++;
j++;
}
}
if (pdt->verbose) {
fprintf (pdt->fh_data, " numDistinctEdges=%lu\n", pdt->numDistinctEdges);
fprintf (pdt->fh_data, " numHullEdges=%lu\n", pdt->numHullEdges);
}
assert (pdt->numDistinctEdges*2 - pdt->numHullEdges == pdt->numEdgeList);
assert (pdt->tri_del->num_points - pdt->numDistinctEdges + pdt->tri_del->num_triangles == 1);
image_view = DestroyCacheView (image_view);
if (pdt->verbose) fprintf (pdt->fh_data, "end CalcTriangles\n");
return MagickTrue;
}
static MagickBooleanType WhichTriangle (DelTriT * pdt,
size_t x, size_t y,
size_t *prevBestTri,
size_t *triNum,
double *b0, double *b1, double *b2)
{
/* For this cartesian coordinate pair, calculates barycentric coordinates for triangles.
Returns a triangle for which no BC is negative.
If no triangle has only positive BCs, so point is outside convex hull, returns MagickFalse.
Due to arithmetic imprecision, some points within the convex hull have slightly negative BC.
Linear search through the edges.
Possible future performance improvement: use a quad tree structure.
*/
#define BARY_EPS 1e-9
size_t
i;
size_t ndx=0;
triangleT * pt;
/* We start search at previous "best" triangle.
*/
for (i=0; i < pdt->tri_del->num_triangles; i++) {
ndx = (i + *prevBestTri) % pdt->tri_del->num_triangles;
pt = &pdt->triangles[ndx];
if (x < pt->minX || x > pt->maxX || y < pt->minY || y > pt->maxY || pt->det == 0)
continue;
*b0 = pt->y12d * ((double)x - pt->x2) + pt->x21d * ((double)y - pt->y2);
if (*b0 < -BARY_EPS) continue;
*b1 = pt->y20d * ((double)x - pt->x2) + pt->x02d * ((double)y - pt->y2);
if (*b1 < -BARY_EPS) continue;
*b2 = 1.0 - *b0 - *b1;
if (*b2 >=- BARY_EPS) break;
}
if (i == pdt->tri_del->num_triangles) {
return MagickFalse;
}
assert (pt->det != 0);
*prevBestTri = ndx;
*triNum = ndx;
return MagickTrue;
}
static inline double SqDist (DelTriT * pdt,
size_t x, size_t y,
size_t hullEdgeNum,
double *frac_t)
{
/* Calculate squared distance from point to convex hull line segment.
This is either the perpendicular distance from point to segment,
or distance from point to one end or the other end.
*/
#define EPS_SQD 1e-18
hullEdgeT *h = &pdt->hullEdges[hullEdgeNum];
double x0 = pdt->tri_del->points[h->pnt0].x;
double y0 = pdt->tri_del->points[h->pnt0].y;
double dx, dy, sqd;
double t = (((double)x - x0) * h->dx + ((double)y - y0) * h->dy) / h->sqLen;
if (t < 0.0) t = 0.0;
else if (t > 1.0) t = 1.0;
*frac_t = t;
dx = x0 + t * h->dx - (double)x;
dy = y0 + t * h->dy - (double)y;
sqd = dx*dx + dy*dy;
if (sqd < EPS_SQD) sqd = 0.0;
return sqd;
}
static inline MagickBooleanType IsTrianglePoor (DelTriT * pdt,
size_t pnt0, size_t pnt1, size_t pnt2)
{
/* Calculate squared distance from pnt0 to line segment pnt1-pnt2.
This is either the perpendicular distance from point to segment,
or distance from point to one end or the other end.
*/
double x0 = pdt->tri_del->points[pnt0].x;
double y0 = pdt->tri_del->points[pnt0].y;
double x1 = pdt->tri_del->points[pnt1].x;
double y1 = pdt->tri_del->points[pnt1].y;
double x2 = pdt->tri_del->points[pnt2].x;
double y2 = pdt->tri_del->points[pnt2].y;
double dx = x2 - x1;
double dy = y2 - y1;
double sqLen = dx*dx + dy*dy;
double t = ((x0 - x1) * dx + (y0 - y1) * dy) / sqLen;
if (pdt->maxRatio != FLT_MAX && t >= 0.0 && t <= 1.0) {
dx = x1 + t * dx - x0;
dy = y1 + t * dy - y0;
if (sqLen / (dx*dx + dy*dy) > pdt->maxRatioSq) return MagickTrue;
}
if (pdt->minDist != FLT_MAX) {
if (t < 0.0) t = 0.0;
else if (t > 1.0) t = 1.0;
dx = x1 + t * dx - x0;
dy = y1 + t * dy - y0;
if (dx*dx + dy*dy < pdt->minDistSq) return MagickTrue;
}
return MagickFalse;
}
static inline double SqTriRatio (DelTriT * pdt,
size_t pnt0, size_t pnt1, size_t pnt2)
{
/* Calculate squared ratio of length of line segment pnt1-pnt2,
divided by distance from pnt0 to line segment pnt1-pnt2.
If closest distance is to extended pn1-pnt2, returns 1.0.
*/
double x0 = pdt->tri_del->points[pnt0].x;
double y0 = pdt->tri_del->points[pnt0].y;
double x1 = pdt->tri_del->points[pnt1].x;
double y1 = pdt->tri_del->points[pnt1].y;
double x2 = pdt->tri_del->points[pnt2].x;
double y2 = pdt->tri_del->points[pnt2].y;
double dx = x2 - x1;
double dy = y2 - y1;
double sqLen = dx*dx + dy*dy;
double t = ((x0 - x1) * dx + (y0 - y1) * dy) / sqLen;
if (t < 0.0 || t > 1.0) return 1.0;
dx = x1 + t * dx - x0;
dy = y1 + t * dy - y0;
return sqLen / (dx*dx + dy*dy);
}
static inline double PtsDistSqr (double x0, double y0, double x1, double y1)
/* Returns squared distance between A and B. */
{
double dx = x1 - x0;
double dy = y1 - y0;
return (dx*dx + dy*dy);
}
static inline double CosAng (DelTriT * pdt, size_t x, size_t y, size_t hullEdgeNum)
/* Returns cosine of angle BAC.
CosBAC = (b^2 + c^2 - a^2) / (2.b.c)
If b or c is close to zero, returns -1 (ie angle = 180 degrees).
*/
{
#define EPS_COS 1e-9
hullEdgeT *h = &pdt->hullEdges[hullEdgeNum];
double x0 = pdt->tri_del->points[h->pnt0].x;
double y0 = pdt->tri_del->points[h->pnt0].y;
double x1 = pdt->tri_del->points[h->pnt1].x;
double y1 = pdt->tri_del->points[h->pnt1].y;
double a2 = PtsDistSqr (x0, y0, x1, y1);
double b2 = PtsDistSqr (x1, y1, (double)x, (double)y);
double c2;
if (b2 < EPS_COS) return -1;
c2 = PtsDistSqr ((double)x, (double)y, x0, y0);
if (c2 < EPS_COS) return -1;
return (b2 + c2 - a2) / (2 * sqrt(b2) * sqrt(c2));
}
static MagickBooleanType OutsideWhichTriangle (DelTriT * pdt,
size_t x, size_t y,
size_t *triNum,
double *b0, double *b1, double *b2,
double *r, double *g, double *b)
{
/* For this cartesian coordinate pair outside the convex hull,
finds the best hull edge,
and hence the best triangle.
The "best" hull edge is the one, not extended, with the closest distance from x,y.
If two or more hull edges are equal best and the distance > 0,
then the best is the one of these that subtends the largest angle at x,y.
If two or more hull edges are equal best and the distance == 0,
then the best is the longest of these edges.
This copes with colinear points and hence colinear hull edges.
*/
#define EPS_DIST_SQ 1e-9
size_t i;
double minDistSq = DBL_MAX, c0, sqLenMax = 0;
size_t BestHullEdge = SIZE_MAX;
double frac_t0 = 99;
for (i = 0; i < pdt->numHullEdges; i++) {
double t;
double d = SqDist (pdt, x, y, i, &t);
if (minDistSq > d) {
minDistSq = d;
BestHullEdge = i;
frac_t0 = t;
sqLenMax = pdt->hullEdges[i].sqLen;
}
}
assert (BestHullEdge != SIZE_MAX);
c0 = CosAng (pdt, x, y, BestHullEdge);
// if (y < 10) printf ("%lu,%lu bhe=%lu c0=%g ", x, y, BestHullEdge, c0);
// if (minDistSq < 0.001) {
// printf ("%lu,%lu minDist==%g c0=%g\n", x, y, minDistSq, c0);
// }
/* Does another hull edge have the same smallest distance?
*/
for (i = 0; i < pdt->numHullEdges; i++) {
double t;
double d = SqDist (pdt, x, y, i, &t);
// FIXME: tolerance of d?
if (fabs (minDistSq - d) < EPS_DIST_SQ) {
if (d < EPS_DIST_SQ) {
double sqLen = pdt->hullEdges[i].sqLen;
if (sqLenMax < sqLen) {
sqLenMax = sqLen;
BestHullEdge = i;
frac_t0 = t;
// if (y < 10) printf ("t1=%lu ", BestHullEdge);
continue;
}
} else {
double c1 = CosAng (pdt, x, y, i);
if (c0 > c1) {
BestHullEdge = i;
frac_t0 = t;
c0 = c1;
// if (y < 10) printf ("t2=%lu c0=%g c1=%g ", BestHullEdge, c0, c1);
}
}
}
}
// if (y < 10) printf ("rbhe=%lu\n", BestHullEdge);
{
hullEdgeT *h = &pdt->hullEdges[BestHullEdge];
triangleT *pt = &pdt->triangles[h->tri0];
*triNum = h->tri0;
*b0 = pt->y12d * ((double)x - pt->x2) + pt->x21d * ((double)y - pt->y2);
*b1 = pt->y20d * ((double)x - pt->x2) + pt->x02d * ((double)y - pt->y2);
*b2 = 1.0 - *b0 - *b1;
}
if (pdt->clampBary) {
// Clamp to edge of convex hull.
/*--
if (*b0 < 0 && *b1 < 0) {*b0 = 0; *b1 = 0; *b2 = 1;}
else if (*b0 < 0 && *b2 < 0) {*b0 = 0; *b1 = 1; *b2 = 0;}
else if (*b1 < 0 && *b2 < 0) {*b0 = 1; *b1 = 0; *b2 = 0;}
else if (*b0 < 0) {
double div = *b1 + *b2;
*b0 = 0;
*b1 /= div;
*b2 /= div;
}
else if (*b1 < 0) {
double div = *b0 + *b2;
*b1 = 0;
*b0 /= div;
*b2 /= div;
}
else if (*b2 < 0) {
double div = *b0 + *b1;
*b2 = 0;
*b0 /= div;
*b1 /= div;
}
--*/
/*--
if (*b0 < 0) *b0 = 0; else if (*b0 > 1) *b0 = 1;
if (*b1 < 0) *b1 = 0; else if (*b1 > 1) *b1 = 1;
if (*b2 < 0) *b2 = 0; else if (*b2 > 1) *b2 = 1;
double div = *b0 + *b1 +*b2;
*b0 /= div;
*b1 /= div;
*b2 /= div;
--*/
/*--*/
/* This method denormalises the coordinates,
so they sum to more than 1.0.
*/
if (*b0 > 1) {
*b1 /= *b0;
*b2 /= *b0;
*b0 = 1;
}
if (*b1 > 1) {
*b0 /= *b1;
*b2 /= *b1;
*b1 = 1;
}
if (*b2 > 1) {
*b0 /= *b2;
*b1 /= *b2;
*b2 = 1;
}
/*--*/
{
hullEdgeT *h = &pdt->hullEdges[BestHullEdge];
*r = (1-frac_t0) * h->pi0->red + frac_t0 * h->pi1->red;
*g = (1-frac_t0) * h->pi0->green + frac_t0 * h->pi1->green;
*b = (1-frac_t0) * h->pi0->blue + frac_t0 * h->pi1->blue;
}
}
return MagickTrue;
}
/*
For labels outside the convex hull:
Make a list of the convex hull edges. (These are the edges that define only one triangle.)
For each point P, calc distance to each line AB.
The shortest distance does NOT identify the line.
Find the intersection of the perpendicular from P to AB (possible extended).
If the intersection is between A and B, and the area of PAB is positive,
then this is the required edge,
and hence the required triangle.
*/
static Image * DrawPixels (DelTriT * pdt,
Image *image,
ExceptionInfo *exception)
{
/* Walk through all pixels, checking barycentric coords, setting pixels in tri_img.
If any is negative, find which triangle contains this pixel.
If pixel is outside convex hull and ChkOutside,
also find best triangle for this pixel.
*/
Image
*tri_img = NULL;
CacheView
*tri_view;
MagickBooleanType
status = MagickTrue;
size_t
y;
if (pdt->verbose) printf ("DrawPixels:\n");
tri_img = CloneImage (image, 0, 0, MagickTrue, exception);
if (!tri_img) return NULL;
SetAllBlack (tri_img, exception);
if (!SetImageAlphaChannel (tri_img, TransparentAlphaChannel, exception)) return NULL;
if (pdt->numPoints < 3) {
/* Note: clampBary makes no difference. */ //??
if (pdt->numPoints == 0 || pdt->fillOutside == ftTransparent) {
return tri_img;
}
if (pdt->numPoints == 1) {
/* Fill with solid colour. */
switch (pdt->fillOutside) {
case ftLabel: {
SetAllBlack (tri_img, exception);
break;
}
case ftCoords: {
SetAllOneCol (tri_img, "red", exception);
break;
}
default: {
/* mean or barycent */
SetImageColor (tri_img, &pdt->pi0, exception);
break;
}
}
}
else {
// label and mean are solid colour. coords and barycent are gradients.
switch (pdt->fillOutside) {
case ftTransparent: {
break;
}
case ftLabel: {
SetAllBlack (tri_img, exception);
break;
}
case ftMean: {
PixelInfo col;
col.red = (pdt->pi0.red + pdt->pi1.red) / 2.0;
col.green = (pdt->pi0.green + pdt->pi1.green) / 2.0;
col.blue = (pdt->pi0.blue + pdt->pi1.blue) / 2.0;
SetImageColor (tri_img, &col, exception);
break;
}
case ftCoords:
case ftBarycent: {
PixelInfo col0 = pdt->pi0;
PixelInfo col1 = pdt->pi1;
double dx = (double)pdt->x1 - (double)pdt->x0;
double dy = (double)pdt->y1 - (double)pdt->y0;
double sqLen = dx*dx + dy*dy;
if (pdt->fillOutside == ftCoords) {
QueryColorCompliance ("#f00", AllCompliance, &col0, exception);
QueryColorCompliance ("#0f0", AllCompliance, &col1, exception);
}
tri_view = AcquireAuthenticCacheView (tri_img, exception);
#if defined(MAGICKCORE_OPENMP_SUPPORT)
#pragma omp parallel for schedule(static) \
shared(status) \
num_threads(GetMagickNumberThreads(tri_img,tri_img,tri_img->rows,1))
#endif
for (y = 0; y < tri_img->rows; y++)
{
VIEW_PIX_PTR
*p;
size_t
x;
p = GetCacheViewAuthenticPixels (tri_view, 0, (ssize_t)y, tri_img->columns, 1, exception);
if (p == (const VIEW_PIX_PTR *) NULL) {
status=MagickFalse;
break;
}
for (x=0; x < tri_img->columns; x++)
{
double t = (((double)x - (double)pdt->x0) * dx + ((double)y - (double)pdt->y0) * dy) / sqLen;
double tdash;
if (pdt->clampBary) {
if (t < 0.0) t = 0.0;
else if (t > 1.0) t = 1.0;
}
tdash = 1.0 - t;
SET_PIXEL_RED(tri_img, col0.red*tdash + col1.red*t, p);
SET_PIXEL_GREEN(tri_img, col0.green*tdash + col1.green*t, p);
SET_PIXEL_BLUE(tri_img, col0.blue*tdash + col1.blue*t, p);
SET_PIXEL_ALPHA(tri_img, QuantumRange, p);
p += Inc_ViewPixPtr (tri_img);
}
if (!SyncCacheViewAuthenticPixels (tri_view, exception)) {
fprintf (stderr, "bad sync\n");
status = MagickFalse;
}
}
tri_view = DestroyCacheView (tri_view);
break;
}
}
}
} else {
tri_view = AcquireAuthenticCacheView (tri_img, exception);
#if defined(MAGICKCORE_OPENMP_SUPPORT)
#pragma omp parallel for schedule(static) \
shared(status) \
num_threads(GetMagickNumberThreads(tri_img,tri_img,tri_img->rows,1))
#endif
for (y = 0; y < tri_img->rows; y++)
{
VIEW_PIX_PTR
*p;
size_t
x, prevBestTri = 0;
double
b0, b1, b2;
p = GetCacheViewAuthenticPixels (tri_view, 0, (ssize_t)y, tri_img->columns, 1, exception);
if (p == (const VIEW_PIX_PTR *) NULL) {
status=MagickFalse;
break;
}
for (x=0; x < tri_img->columns; x++)
{
size_t triNum;
if (pdt->numPoints < 3) continue; // FIXME
if (WhichTriangle (pdt, x, y, &prevBestTri, &triNum, &b0, &b1, &b2)) {
/* Inside convex hull. */
triangleT * pt = &pdt->triangles[triNum];
switch (pdt->fillTri) {
case ftTransparent: {
/* Do nothing. */
break;
}
case ftLabel: {
SET_PIXEL_RED(tri_img, pt->colVal, p);
SET_PIXEL_GREEN(tri_img, pt->colVal, p);
SET_PIXEL_BLUE(tri_img, pt->colVal, p);
SET_PIXEL_ALPHA(tri_img, QuantumRange, p);
break;
}
case ftMean: {
/* We could optimize by pre-calculating mean colours.
*/
Quantum r = (pt->pi0.red + pt->pi1.red + pt->pi2.red) / 3.0;
Quantum g = (pt->pi0.green + pt->pi1.green + pt->pi2.green) / 3.0;
Quantum b = (pt->pi0.blue + pt->pi1.blue + pt->pi2.blue) / 3.0;
SET_PIXEL_RED(tri_img, r, p);
SET_PIXEL_GREEN(tri_img, g, p);
SET_PIXEL_BLUE(tri_img, b, p);
SET_PIXEL_ALPHA(tri_img, QuantumRange, p);
break;
}
case ftCoords: {
SET_PIXEL_RED(tri_img, b0 * QuantumRange, p);
SET_PIXEL_GREEN(tri_img, b1 * QuantumRange, p);
SET_PIXEL_BLUE(tri_img, b2 * QuantumRange, p);
SET_PIXEL_ALPHA(tri_img, QuantumRange, p);
break;
}
case ftBarycent: {
Quantum r = b0 * pt->pi0.red + b1 * pt->pi1.red + b2 * pt->pi2.red;
Quantum g = b0 * pt->pi0.green + b1 * pt->pi1.green + b2 * pt->pi2.green;
Quantum b = b0 * pt->pi0.blue + b1 * pt->pi1.blue + b2 * pt->pi2.blue;
SET_PIXEL_RED(tri_img, r, p);
SET_PIXEL_GREEN(tri_img, g, p);
SET_PIXEL_BLUE(tri_img, b, p);
SET_PIXEL_ALPHA(tri_img, QuantumRange, p);
break;
}
}
} else {
/* Outside convex hull. */
double interpR, interpG, interpB;
if (OutsideWhichTriangle (pdt, x, y, &triNum, &b0, &b1, &b2, &interpR, &interpG, &interpB)) {
triangleT * pt = &pdt->triangles[triNum];
switch (pdt->fillOutside) {
case ftTransparent: {
/* Do nothing. */
break;
}
case ftLabel: {
SET_PIXEL_RED(tri_img, pt->colVal, p);
SET_PIXEL_GREEN(tri_img, pt->colVal, p);
SET_PIXEL_BLUE(tri_img, pt->colVal, p);
SET_PIXEL_ALPHA(tri_img, QuantumRange, p);
break;
}
case ftMean: {
Quantum r = (pt->pi0.red + pt->pi1.red + pt->pi2.red) / 3.0;
Quantum g = (pt->pi0.green + pt->pi1.green + pt->pi2.green) / 3.0;
Quantum b = (pt->pi0.blue + pt->pi1.blue + pt->pi2.blue) / 3.0;
SET_PIXEL_RED(tri_img, r, p);
SET_PIXEL_GREEN(tri_img, g, p);
SET_PIXEL_BLUE(tri_img, b, p);
SET_PIXEL_ALPHA(tri_img, QuantumRange, p);
break;
}
case ftCoords: {
SET_PIXEL_RED(tri_img, b0 * QuantumRange, p);
SET_PIXEL_GREEN(tri_img, b1 * QuantumRange, p);
SET_PIXEL_BLUE(tri_img, b2 * QuantumRange, p);
SET_PIXEL_ALPHA(tri_img, QuantumRange, p);
break;
}
case ftBarycent: {
if (pdt->clampBary) {
SET_PIXEL_RED(tri_img, interpR, p);
SET_PIXEL_GREEN(tri_img, interpG, p);
SET_PIXEL_BLUE(tri_img, interpB, p);
SET_PIXEL_ALPHA(tri_img, QuantumRange, p);
} else {
Quantum r = b0 * pt->pi0.red + b1 * pt->pi1.red + b2 * pt->pi2.red;
Quantum g = b0 * pt->pi0.green + b1 * pt->pi1.green + b2 * pt->pi2.green;
Quantum b = b0 * pt->pi0.blue + b1 * pt->pi1.blue + b2 * pt->pi2.blue;
SET_PIXEL_RED(tri_img, r, p);
SET_PIXEL_GREEN(tri_img, g, p);
SET_PIXEL_BLUE(tri_img, b, p);
SET_PIXEL_ALPHA(tri_img, QuantumRange, p);
}
break;
}
}
}
}
p += Inc_ViewPixPtr (tri_img);
}
if (!SyncCacheViewAuthenticPixels (tri_view, exception)) {
fprintf (stderr, "bad sync\n");
status = MagickFalse;
}
}
tri_view = DestroyCacheView (tri_view);
}
if (!status) return NULL;
if (pdt->verbose) printf ("end DrawPixels\n");
return tri_img;
}
static MagickBooleanType DrawOnePoint (
const Image * inImg,
Image * outImg,
const CacheView *in_view,
CacheView *out_view,
const ssize_t x, const ssize_t y,
ExceptionInfo *exception)
{
const VIEW_PIX_PTR
*p = GetCacheViewVirtualPixels (in_view, x, y, 1, 1, exception);
VIEW_PIX_PTR
*q = GetCacheViewAuthenticPixels (out_view, x, y, 1, 1, exception);
if (!p || !q) {
fprintf (stderr, "DrawOnePoint oom\n");
return MagickFalse;
}
SET_PIXEL_RED(outImg, GET_PIXEL_RED (inImg, p), q);
SET_PIXEL_GREEN(outImg, GET_PIXEL_GREEN (inImg, p), q);
SET_PIXEL_BLUE(outImg, GET_PIXEL_BLUE (inImg, p), q);
SET_PIXEL_ALPHA(outImg, GET_PIXEL_ALPHA (inImg, p), q);
if (!SyncCacheViewAuthenticPixels (out_view, exception)) {
fprintf (stderr, "bad sync\n");
return MagickFalse;
}
return MagickTrue;
}
static MagickBooleanType DrawPoints (DelTriT * pdt,
const Image * inImg,
Image * outImg,
ExceptionInfo *exception)
/* Draw points directly on outImg, using pixel values from inImg.
*/
{
size_t i;
CacheView
*in_view,
*out_view;
MagickBooleanType
status = MagickTrue;
printf ("DrawPoints (%u):\n", pdt->tri_del->num_points);
in_view = AcquireVirtualCacheView (inImg, exception);
out_view = AcquireAuthenticCacheView (outImg, exception);
if (pdt->numPoints < 3) {
if (pdt->numPoints >= 1) {
if (!DrawOnePoint (inImg, outImg, in_view, out_view, (ssize_t)pdt->x0, (ssize_t)pdt->y0, exception)) {
status = MagickFalse;
}
}
if (pdt->numPoints >= 2) {
if (!DrawOnePoint (inImg, outImg, in_view, out_view, (ssize_t)pdt->x1, (ssize_t)pdt->y1, exception)) {
status = MagickFalse;
}
}
} else {
for (i=0; i < pdt->tri_del->num_points; i++) {
ssize_t x = (ssize_t)pdt->tri_del->points[i].x;
ssize_t y = (ssize_t)pdt->tri_del->points[i].y;
if (!DrawOnePoint (inImg, outImg, in_view, out_view, x, y, exception)) {
status = MagickFalse;
break;
}
/*--
p = GetCacheViewVirtualPixels (in_view, x, y, 1, 1, exception);
q = GetCacheViewAuthenticPixels (out_view, x, y, 1, 1, exception);
if (!p || !q) {
status=MagickFalse;
break;
}
SET_PIXEL_RED(outImg, GET_PIXEL_RED (inImg, p), q);
SET_PIXEL_GREEN(outImg, GET_PIXEL_GREEN (inImg, p), q);
SET_PIXEL_BLUE(outImg, GET_PIXEL_BLUE (inImg, p), q);
SET_PIXEL_ALPHA(outImg, GET_PIXEL_ALPHA (inImg, p), q);
if (!SyncCacheViewAuthenticPixels (out_view, exception)) {
fprintf (stderr, "bad sync\n");
status = MagickFalse;
break;
}
--*/
}
}
out_view = DestroyCacheView (out_view);
in_view = DestroyCacheView (in_view);
printf ("end DrawPoints\n");
return status;
}
static Image * DrawEdges (DelTriT * pdt,
const Image * image,
ExceptionInfo *exception)
{
Image *
edge_img;
MagickBooleanType
status = MagickTrue;
size_t
i;
DrawInfo
*draw_info;
const char
*option;
char
primitive[MaxTextExtent];
if (pdt->verbose) fprintf (pdt->fh_data, "DrawEdges (%lu):\n", pdt->numEdgeList);
edge_img = CloneImage (image, 0, 0, MagickTrue, exception);
if (!edge_img) return NULL;
SetAllBlack (edge_img, exception);
if (!SetImageAlphaChannel (edge_img, TransparentAlphaChannel, exception)) return NULL;
draw_info = AcquireDrawInfo();
GetDrawInfo (image->image_info, draw_info);
GetPixelInfo (edge_img, &draw_info->stroke);
draw_info->stroke_antialias = image->image_info->antialias;
draw_info->fill_alpha = TransparentAlpha;
draw_info->stroke_alpha = OpaqueAlpha;
option = GetImageOption(draw_info->image_info,"stroke");
if (option)
QueryColorCompliance(option, AllCompliance, &draw_info->stroke, exception);
if (pdt->numPoints < 3) {
if (pdt->numPoints == 2) {
(void) FormatLocaleString (primitive, MaxTextExtent,
"line %lu,%lu %lu,%lu",
pdt->x0, pdt->y0, pdt->x1, pdt->y1);
(void) CloneString (&draw_info->primitive, primitive);
status = DrawImage (edge_img, draw_info, exception);
draw_info->primitive = DestroyString (draw_info->primitive);
}
} else {
for (i = 0; i < pdt->numEdgeList; i++) {
edgeT * e = &pdt->edges[i];
if (e->numTris > 0) {
double x0 = pdt->tri_del->points[e->pnt0].x;
double y0 = pdt->tri_del->points[e->pnt0].y;
double x1 = pdt->tri_del->points[e->pnt1].x;
double y1 = pdt->tri_del->points[e->pnt1].y;
(void) FormatLocaleString (primitive, MaxTextExtent,
"line %g,%g %g,%g",
x0, y0, x1, y1);
// printf (" primitive = %s\n", primitive);
(void) CloneString (&draw_info->primitive, primitive);
status = DrawImage (edge_img, draw_info, exception);
draw_info->primitive = DestroyString (draw_info->primitive);
if (!status) break;
}
}
}
draw_info = DestroyDrawInfo(draw_info);
if (!status) {
fprintf (stderr, "DrawEdges failed\n");
}
if (pdt->verbose) fprintf (pdt->fh_data, "end DrawEdges\n");
return edge_img;
}
static MagickBooleanType DrawText (DelTriT * pdt,
Image * image,
ExceptionInfo *exception)
{
MagickBooleanType
status = MagickTrue;
size_t
i;
DrawInfo
*draw_info;
const char
*option;
char
primitive[MaxTextExtent];
if (pdt->verbose) fprintf (pdt->fh_data, "DrawText\n");
draw_info = AcquireDrawInfo();
GetDrawInfo (image->image_info, draw_info);
draw_info->text_antialias = image->image_info->antialias;
draw_info->fill_alpha = OpaqueAlpha;
draw_info->stroke_alpha = TransparentAlpha;
option = GetImageOption(draw_info->image_info,"fill");
if (option)
QueryColorCompliance(option, AllCompliance, &draw_info->fill, exception);
draw_info->stroke.alpha = 0;
if (pdt->numPoints < 3) {
if (pdt->numPoints >= 1) {
(void) FormatLocaleString (primitive, MaxTextExtent,
"text %lu,%lu 'p0'",
pdt->x0, pdt->y0);
(void) CloneString (&draw_info->primitive, primitive);
status = DrawImage (image, draw_info, exception);
draw_info->primitive = DestroyString (draw_info->primitive);
}
if (pdt->numPoints >= 2) {
(void) FormatLocaleString (primitive, MaxTextExtent,
"text %lu,%lu 'p1'",
pdt->x1, pdt->y1);
(void) CloneString (&draw_info->primitive, primitive);
status = DrawImage (image, draw_info, exception);
draw_info->primitive = DestroyString (draw_info->primitive);
(void) FormatLocaleString (primitive, MaxTextExtent,
"text %lu,%lu 'e0'",
(pdt->x0 + pdt->x1) / 2, (pdt->y0 + pdt->y1) / 2);
(void) CloneString (&draw_info->primitive, primitive);
status = DrawImage (image, draw_info, exception);
draw_info->primitive = DestroyString (draw_info->primitive);
}
return MagickTrue; // FIXME
}
for (i=0; i < pdt->tri_del->num_points; i++) {
(void) FormatLocaleString (primitive, MaxTextExtent,
"text %g,%g 'p%lu'",
pdt->tri_del->points[i].x, pdt->tri_del->points[i].y, i);
(void) CloneString (&draw_info->primitive, primitive);
status = DrawImage (image, draw_info, exception);
draw_info->primitive = DestroyString (draw_info->primitive);
if (!status) break;
}
for (i=0; i < pdt->tri_del->num_triangles; i++) {
triangleT * pt = &pdt->triangles[i];
double x = ( pt->x0 + pt->x1 + pt->x2) / 3.0;
double y = ( pt->y0 + pt->y1 + pt->y2) / 3.0;
(void) FormatLocaleString (primitive, MaxTextExtent,
"text %g,%g 't%lu'",
x, y, i);
(void) CloneString (&draw_info->primitive, primitive);
status = DrawImage (image, draw_info, exception);
draw_info->primitive = DestroyString (draw_info->primitive);
if (!status) break;
}
for (i=0; i < pdt->numEdgeList; i++) {
edgeT * e = &pdt->edges[i];
if (e->numTris > 0) {
double x0 = pdt->tri_del->points[e->pnt0].x;
double y0 = pdt->tri_del->points[e->pnt0].y;
double x1 = pdt->tri_del->points[e->pnt1].x;
double y1 = pdt->tri_del->points[e->pnt1].y;
double x = ( x0 + x1) / 2.0;
double y = ( y0 + y1) / 2.0;
(void) FormatLocaleString (primitive, MaxTextExtent,
"text %g,%g 'e%lu'",
x, y, i);
(void) CloneString (&draw_info->primitive, primitive);
status = DrawImage (image, draw_info, exception);
draw_info->primitive = DestroyString (draw_info->primitive);
if (!status) break;
}
}
draw_info = DestroyDrawInfo(draw_info);
if (pdt->verbose) fprintf (pdt->fh_data, "end DrawText\n");
if (!status) {
fprintf (stderr, "DrawText failed\n");
}
return status;
}
static MagickBooleanType ChkVertices (DelTriT * pdt, Image * image, ExceptionInfo *exception)
{
MagickBooleanType status = MagickTrue;
size_t i, numOkay=0;
MagickBooleanType removeAny = MagickFalse;
size_t loopNum = 0;
pdt->minLenSq = (double)pdt->minLen * (double)pdt->minLen;
pdt->minDistSq = (double)pdt->minDist * (double)pdt->minDist;
pdt->maxRatioSq = pdt->maxRatio * pdt->maxRatio;
do { /* while removeAny */
/* Copy points to new checking array.
*/
ChkPointT *ChkPoints = (ChkPointT *)AcquireMagickMemory (pdt->tri_del->num_points * sizeof(ChkPointT));
if (!ChkPoints) {
printf ("ChkVertices oom\n");
return MagickFalse;
}
if (pdt->verbose) fprintf (pdt->fh_data, "ChkVertices loop %lu:\n", loopNum);
removeAny = MagickFalse;
for (i = 0; i < pdt->tri_del->num_points; i++) {
ChkPointT * cp = &ChkPoints[i];
cp->x = (size_t)pdt->tri_del->points[i].x;
cp->y = (size_t)pdt->tri_del->points[i].y;
// If we are protecting hull, check whether on hull edge
// FIXME: better performance if del_point2d_t had protect flag.
cp->protect = MagickFalse;
if (pdt->protectHull) {
size_t j;
for (j=0; j < pdt->numHullEdges; j++) {
hullEdgeT * h = &pdt->hullEdges[j];
del_point2d_t * pnt = &pdt->tri_del->points[h->pnt0];
if (cp->x == pnt->x && cp->y == pnt->y) cp->protect = MagickTrue;
else {
del_point2d_t * pnt = &pdt->tri_del->points[h->pnt1];
if (cp->x == pnt->x && cp->y == pnt->y) cp->protect = MagickTrue;
}
}
}
cp->RemoveThis = MagickFalse;
};
if (pdt->minLen != FLT_MAX) {
/* For each edge,
if neither end is to be removed, and len < min, set one end to be removed.
*/
for (i = 0; i < pdt->numEdgeList; i++) {
edgeT *e = &pdt->edges[i];
if (e->numTris > 0) {
double dx = pdt->tri_del->points[e->pnt1].x - pdt->tri_del->points[e->pnt0].x;
double dy = pdt->tri_del->points[e->pnt1].y - pdt->tri_del->points[e->pnt0].y;
ChkPointT * cp0 = &ChkPoints[e->pnt0];
ChkPointT * cp1 = &ChkPoints[e->pnt1];
if ( !cp0->RemoveThis
&& !cp1->RemoveThis
&& !(cp0->protect && cp1->protect)
&& dx * dx + dy * dy < pdt->minLenSq
)
{
ChkPointT * cpRem;
if (cp0->protect) cpRem = cp1;
else if (cp1->protect) cpRem = cp0;
else cpRem = &ChkPoints[
GetPseudoRandomValue (pdt->random_info) < 0.5 ? e->pnt0 : e->pnt1
];
cpRem->RemoveThis = MagickTrue;
removeAny = MagickTrue;
}
}
}
}
if (pdt->minDist != FLT_MAX || pdt->maxRatio != FLT_MAX) {
/* For each triangle, if no vertex is to be removed,
for each vertex,
if distance from vertex to opposite edge is less than minimum,
then set sertex to be removed and DON'T check other vertices in this triangle.
*/
for (i=0; i < pdt->tri_del->num_triangles; i++) {
size_t offset = i*3;
size_t pnt0 = pdt->tri_del->tris[offset];
size_t pnt1 = pdt->tri_del->tris[offset+1];
size_t pnt2 = pdt->tri_del->tris[offset+2];
// fprintf (pdt->fh_data, " %lu: %u %u %u\n", i, pdt->tri_del->tris[offset], pdt->tri_del->tris[offset+1], pdt->tri_del->tris[offset+2]);
if (!ChkPoints[pnt0].RemoveThis && !ChkPoints[pnt1].RemoveThis && !ChkPoints[pnt2].RemoveThis) {
/* Maybe only remove if opposite edge is more than a factor * distance. */
if (IsTrianglePoor (pdt, pnt0, pnt1, pnt2)) {
ChkPointT * cp = &ChkPoints[pnt0];
if (!cp->protect) {
cp->RemoveThis = MagickTrue;
removeAny = MagickTrue;
}
} else if (IsTrianglePoor (pdt, pnt1, pnt2, pnt0)) {
ChkPointT * cp = &ChkPoints[pnt1];
if (!cp->protect) {
cp->RemoveThis = MagickTrue;
removeAny = MagickTrue;
}
} else if (IsTrianglePoor (pdt, pnt2, pnt0, pnt1)) {
ChkPointT * cp = &ChkPoints[pnt2];
if (!cp->protect) {
cp->RemoveThis = MagickTrue;
removeAny = MagickTrue;
}
}
}
}
}
/* If any points are to be removed,
copy non-removed points to another array.
remove checking array.
Re-run triangulation.
Repeat from start.
*/
if (removeAny) {
size_t j = 0;
size_t numPnts = pdt->tri_del->num_points; /* Save this because we will deinit. */
del_point2d_t * points = NULL;
numOkay = 0;
for (i = 0; i < numPnts; i++) {
ChkPointT * cp = &ChkPoints[i];
//printf ("%lu %lu,%lu %s\n", i, cp->x, cp->y, cp->RemoveThis ? "Remove" : "");
if (!cp->RemoveThis) numOkay++;
};
if (pdt->verbose) fprintf (pdt->fh_data, " numOkay=%lu\n", numOkay);
if (numOkay < 3) {
MagickWarning (ModuleWarning, "deltri: Less than three points", "Description");
fprintf (stderr, "Less than three points (numOkay = %lu)\n", numOkay);
// FIXME: if 1 or 2 points, put them in pdt structure?
if (ChkPoints) ChkPoints = (ChkPointT *)RelinquishMagickMemory (ChkPoints);
return MagickFalse;
}
points = (del_point2d_t *)AcquireMagickMemory (numOkay * sizeof(del_point2d_t));
if (!points) {
printf ("oom\n");
if (ChkPoints) ChkPoints = (ChkPointT *)RelinquishMagickMemory (ChkPoints);
return MagickFalse;
}
for (i = 0; i < numPnts; i++) {
ChkPointT * cp = &ChkPoints[i];
if (!cp->RemoveThis) {
points[j].x = (real)cp->x;
points[j].y = (real)cp->y;
j++;
}
}
assert (j==numOkay);
DeInitDelTri (pdt);
pdt->del = delaunay2d_from (points, (unsigned int)numOkay);
points = (del_point2d_t *)RelinquishMagickMemory (points);
if (pdt->verbose) {
fprintf (pdt->fh_data, " num_points=%u\n", pdt->del->num_points);
fprintf (pdt->fh_data, " num_faces=%u\n", pdt->del->num_faces);
}
pdt->tri_del = tri_delaunay2d_from (pdt->del);
if (pdt->verbose) fprintf (pdt->fh_data, " num_triangles=%u\n", pdt->tri_del->num_triangles);
if (status) status = CalcTriangles (pdt, image, exception);
}
if (ChkPoints) ChkPoints = (ChkPointT *)RelinquishMagickMemory (ChkPoints);
loopNum++;
} while (status && removeAny);
if (pdt->verbose) fprintf (pdt->fh_data, "end ChkVertices\n");
return status;
}
static Image * ReadPixels (DelTriT * pdt, Image * image, ExceptionInfo *exception)
{
Image
*new_img = NULL,
*edge_img = NULL;
MagickBooleanType
status = MagickTrue;
size_t
y,
n;
del_point2d_t * points = NULL;
CacheView * image_view = AcquireVirtualCacheView (image, exception);
pdt->random_info = AcquireRandomInfo ();
pdt->numPoints = 0;
/* Count the number of non-transparent pixels.
*/
// FIXME: shortcut: if no alpha channel, then all pixels are opaque.
for (y = 0; y < image->rows; y++)
{
VIEW_PIX_PTR const
*p;
size_t
x;
if (status == MagickFalse)
continue;
p = GetCacheViewVirtualPixels (image_view, 0, (ssize_t)y, image->columns, 1, exception);
if (p == (const VIEW_PIX_PTR *) NULL) {
status=MagickFalse;
break;
}
for (x=0; x < image->columns; x++)
{
if ( GET_PIXEL_ALPHA(image,p) > 0 ) {
pdt->numPoints++;
}
p += Inc_ViewPixPtr (image);
}
}
if (pdt->verbose)
fprintf (pdt->fh_data,
"non-trans=%lu\n",
pdt->numPoints);
if (pdt->numPoints < 3) {
MagickWarning (ModuleWarning, "deltri: Less than three points", "Description");
fprintf (stderr, "Less than three points (numPoints = %lu)\n", pdt->numPoints);
// image_view = DestroyCacheView (image_view);
// return NULL;
}
if (pdt->verbose >= 2) fprintf (pdt->fh_data, "Add non-transparent pixels.\n");
points = (del_point2d_t *)AcquireMagickMemory (pdt->numPoints * sizeof(del_point2d_t));
n = 0;
{
for (y = 0; y < image->rows; y++)
{
VIEW_PIX_PTR const
*p;
size_t
x;
if (status == MagickFalse)
continue;
p = GetCacheViewVirtualPixels (image_view, 0, (ssize_t)y, image->columns, 1, exception);
if (p == (const VIEW_PIX_PTR *) NULL) {
status=MagickFalse;
break;
}
for (x=0; x < image->columns; x++)
{
if (GET_PIXEL_ALPHA(image,p) > 0 ) {
points[n].x = (real)x;
points[n].y = (real)y;
n++;
}
p += Inc_ViewPixPtr (image);
}
}
}
if (pdt->verbose) fprintf (pdt->fh_data, "Set first 2.\n");
if (pdt->numPoints > 0) {
pdt->x0 = (size_t)points[0].x;
pdt->y0 = (size_t)points[0].y;
GetOneCacheViewVirtualPixelInfo (image_view, (ssize_t)pdt->x0, (ssize_t)pdt->y0, &pdt->pi0, exception);
}
if (pdt->numPoints > 1) {
pdt->x1 = (size_t)points[1].x;
pdt->y1 = (size_t)points[1].y;
GetOneCacheViewVirtualPixelInfo (image_view, (ssize_t)pdt->x1, (ssize_t)pdt->y1, &pdt->pi1, exception);
}
image_view = DestroyCacheView (image_view);
if (!status) return NULL;
if (pdt->verbose) fprintf (pdt->fh_data, "Ready for delaunay2d_from.\n");
if (pdt->numPoints >= 3) {
if (pdt->verbose) fprintf (pdt->fh_data, "Doing delaunay2d_from.\n");
pdt->del = delaunay2d_from (points, (unsigned int)pdt->numPoints);
if (!pdt->del) {
printf ("delaunay2d_from failed");
return NULL;
}
}
points = (del_point2d_t *)RelinquishMagickMemory (points);
if (pdt->verbose && pdt->numPoints >= 3) {
fprintf (pdt->fh_data, "num_points=%u\n", pdt->del->num_points);
fprintf (pdt->fh_data, "num_faces=%u\n", pdt->del->num_faces);
}
if (pdt->numPoints < 3) {
MagickWarning (ModuleWarning, "deltri: Less than three points", "Description");
// fprintf (stderr, "Less than three points (numPoints = %lu)\n", pdt->numPoints);
// return NULL;
}
if (pdt->numPoints >= 3) {
pdt->tri_del = tri_delaunay2d_from (pdt->del);
if (pdt->verbose) fprintf (pdt->fh_data, "num_triangles=%u\n", pdt->tri_del->num_triangles);
if (status) status = CalcTriangles (pdt, image, exception);
if (status && (pdt->minLen != FLT_MAX || pdt->minDist != FLT_MAX || pdt->maxRatio != FLT_MAX)) {
status = ChkVertices (pdt, image, exception);
}
}
if (status && pdt->verbose >= 2) {
DumpData (pdt);
}
if (status) {
if (pdt->calcOnly) new_img = image;
else new_img = DrawPixels (pdt, image, exception);
if (!new_img) {
printf ("DrawPixels failed\n");
status = MagickFalse;
}
}
if (status && pdt->drawPoints && !pdt->calcOnly) {
status = DrawPoints (pdt, image, new_img, exception);
}
if (status && pdt->drawEdges && !pdt->calcOnly) {
edge_img = DrawEdges (pdt, new_img, exception);
if (!edge_img) {
printf ("DrawEdges failed\n");
new_img = DestroyImage (new_img);
status = MagickFalse;
} else {
AppendImageToList (&new_img, edge_img);
}
}
if (status && pdt->drawText && !pdt->calcOnly)
status = DrawText (pdt, edge_img ? edge_img : new_img, exception);
if (pdt->numPoints >= 3) {
if (pdt->verbose) fprintf (pdt->fh_data, "Ready for DeInitDelTri.\n");
DeInitDelTri (pdt);
if (pdt->verbose) fprintf (pdt->fh_data, "Done DeInitDelTri.\n");
}
pdt->random_info = DestroyRandomInfo (pdt->random_info);
return new_img;
}
static Image* DelTriOne (DelTriT * pdt, Image * image, ExceptionInfo *exception)
{
Image
*new_img = NULL;
new_img = ReadPixels (pdt, image, exception);
//printf ("QuantumRange=%.20g\n", QuantumRange);
//printf ("sizeof(Quantum)=%lu\n", sizeof(Quantum));
//printf ("sizeof(MagickRealType)=%lu\n", sizeof(MagickRealType));
// MagickRealType m = QuantumRange;
//printf ("m=%.20g\n", m);
return new_img;
}
ModuleExport size_t deltriImage (
Image **images,
const int argc,
const char **argv,
ExceptionInfo *exception)
{
Image
*image,
*new_img;
MagickBooleanType
status;
DelTriT
dt;
assert(images != (Image **) NULL);
assert(*images != (Image *) NULL);
assert((*images)->signature == MAGICK_CORE_SIG);
InitDelTri (&dt);
status = menu (argc, argv, &dt);
if (status == MagickFalse)
return (~MagickImageFilterSignature);
// FIXME: maybe only allow single input image.
for (image=(*images); image != (Image *) NULL; image=GetNextImageInList(image))
{
new_img = DelTriOne (&dt, image, exception);
if (!new_img) continue;
if (new_img != image && !dt.calcOnly) {
ReplaceImageInList (&image, new_img);
*images = GetFirstImageInList(image);
}
// BUG: If we created an edge image, we need to skip over it.
break;
}
return(MagickImageFilterSignature);
}
delaunay.c and delaunay.h are not my copyright. They are "Copyright (C) 2005 Wael El Oraiby", and distributed here under the GNU Affero General Public License.
/*
** delaunay.c : compute 2D delaunay triangulation in the plane.
** Copyright (C) 2005 Wael El Oraiby <wael.eloraiby@gmail.com>
**
**
** This program is free software: you can redistribute it and/or modify
** it under the terms of the GNU Affero General Public License as
** published by the Free Software Foundation, either version 3 of the
** License, or (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU Affero General Public License for more details.
**
** You should have received a copy of the GNU Affero General Public License
** along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <string.h>
#include <assert.h>
#include "delaunay.h"
#define ON_RIGHT 1
#define ON_SEG 0
#define ON_LEFT -1
#define OUTSIDE -1
#define ON_CIRCLE 0
#define INSIDE 1
struct point2d_s;
struct face_s;
struct halfedge_s;
struct delaunay_s;
#define REAL_ZERO 0.0l
#define REAL_ONE 1.0l
#define REAL_TWO 2.0l
#define REAL_FOUR 4.0l
typedef struct point2d_s point2d_t;
typedef struct face_s face_t;
typedef struct halfedge_s halfedge_t;
typedef struct delaunay_s delaunay_t;
typedef struct working_set_s working_set_t;
typedef long double lreal;
typedef lreal mat3_t[3][3];
struct point2d_s {
real x, y; /* point coordinates */
halfedge_t* he; /* point halfedge */
unsigned int idx; /* point index in input buffer */
};
struct face_s {
halfedge_t* he; /* a pointing half edge */
unsigned int num_verts; /* number of vertices on this face */
};
struct halfedge_s {
point2d_t* vertex; /* vertex */
halfedge_t* pair; /* pair */
halfedge_t* next; /* next */
halfedge_t* prev; /* next^-1 */
face_t* face; /* halfedge face */
};
struct delaunay_s {
halfedge_t* rightmost_he; /* right most halfedge */
halfedge_t* leftmost_he; /* left most halfedge */
point2d_t* points; /* pointer to points */
face_t* faces; /* faces of delaunay */
unsigned int num_faces; /* face count */
unsigned int start_point; /* start point index */
unsigned int end_point; /* end point index */
};
struct working_set_s {
halfedge_t* edges; /* all the edges (allocated in one shot) */
face_t* faces; /* all the faces (allocated in one shot) */
unsigned int max_edge; /* maximum edge count: 2 * 3 * n where n is point count */
unsigned int max_face; /* maximum face count: 2 * n where n is point count */
unsigned int num_edges; /* number of allocated edges */
unsigned int num_faces; /* number of allocated faces */
halfedge_t* free_edge; /* pointer to the first free edge */
face_t* free_face; /* pointer to the first free face */
};
/*
* 3x3 matrix determinant
*/
static lreal det3(mat3_t m)
{
lreal res = m[0][0] * (m[1][1] * m[2][2] - m[1][2] * m[2][1])
- m[0][1] * (m[1][0] * m[2][2] - m[1][2] * m[2][0])
+ m[0][2] * (m[1][0] * m[2][1] - m[1][1] * m[2][0]);
return res;
}
/*
* allocate a halfedge
*/
static halfedge_t* halfedge_alloc()
{
halfedge_t* d;
d = (halfedge_t*)malloc(sizeof(halfedge_t));
assert( NULL != d );
memset(d, 0, sizeof(halfedge_t));
return d;
}
/*
* free a halfedge
*/
static void halfedge_free( halfedge_t* d )
{
assert( d != NULL );
memset(d, 0, sizeof(halfedge_t));
free(d);
}
/*
* free all delaunay halfedges
*/
void del_free_halfedges( delaunay_t *del )
{
unsigned int i;
halfedge_t *d, *sig;
/* if there is nothing to do */
if( del->points == NULL )
return;
for( i = 0; i <= (del->end_point - del->start_point); i++ )
{
/* free all the halfedges around the point */
d = del->points[i].he;
if( d != NULL )
{
do {
sig = d->next;
halfedge_free( d );
d = sig;
} while( d != del->points[i].he );
del->points[i].he = NULL;
}
}
}
/*
* compare 2 points when sorting
*/
static int cmp_points( const void *_pt0, const void *_pt1 )
{
point2d_t *pt0, *pt1;
pt0 = (point2d_t*)(_pt0);
pt1 = (point2d_t*)(_pt1);
if( pt0->x < pt1->x )
return -1;
else if( pt0->x > pt1->x )
return 1;
else if( pt0->y < pt1->y )
return -1;
else if( pt0->y > pt1->y )
return 1;
assert(0 && "2 or more points share the same exact coordinate");
return 0; /* Should not be given! */
}
/*
* classify a point relative to a segment
*/
static int classify_point_seg( point2d_t *s, point2d_t *e, point2d_t *pt )
{
lreal se_x, se_y, spt_x, spt_y;
lreal res;
se_x = e->x - s->x;
se_y = e->y - s->y;
spt_x = pt->x - s->x;
spt_y = pt->y - s->y;
res = (( se_x * spt_y ) - ( se_y * spt_x ));
if( res < REAL_ZERO )
return ON_RIGHT;
else if( res > REAL_ZERO )
return ON_LEFT;
return ON_SEG;
}
/*
* classify a point relative to a halfedge, -1 is left, 0 is on, 1 is right
*/
static int del_classify_point( halfedge_t *d, point2d_t *pt )
{
point2d_t *s, *e;
s = d->vertex;
e = d->pair->vertex;
return classify_point_seg(s, e, pt);
}
/*
* test if a point is inside a circle given by 3 points, 1 if inside, 0 if outside
*/
static int in_circle( point2d_t *pt0, point2d_t *pt1, point2d_t *pt2, point2d_t *p )
{
// reduce the computational complexity by substracting the last row of the matrix
// ref: https://www.cs.cmu.edu/~quake/robust.html
lreal p0p_x, p0p_y, p1p_x, p1p_y, p2p_x, p2p_y, p0p, p1p, p2p, res;
mat3_t m;
p0p_x = pt0->x - p->x;
p0p_y = pt0->y - p->y;
p1p_x = pt1->x - p->x;
p1p_y = pt1->y - p->y;
p2p_x = pt2->x - p->x;
p2p_y = pt2->y - p->y;
p0p = p0p_x * p0p_x + p0p_y * p0p_y;
p1p = p1p_x * p1p_x + p1p_y * p1p_y;
p2p = p2p_x * p2p_x + p2p_y * p2p_y;
m[0][0] = p0p_x;
m[0][1] = p0p_y;
m[0][2] = p0p;
m[1][0] = p1p_x;
m[1][1] = p1p_y;
m[1][2] = p1p;
m[2][0] = p2p_x;
m[2][1] = p2p_y;
m[2][2] = p2p;
res = -det3(m);
if( res < REAL_ZERO )
return INSIDE;
else if( res > REAL_ZERO )
return OUTSIDE;
return ON_CIRCLE;
}
/*
* initialize delaunay segment
*/
static int del_init_seg( delaunay_t *del, int start )
{
halfedge_t *d0, *d1;
point2d_t *pt0, *pt1;
/* init delaunay */
del->start_point = start;
del->end_point = start + 1;
/* setup pt0 and pt1 */
pt0 = &(del->points[start]);
pt1 = &(del->points[start + 1]);
/* allocate the halfedges and setup them */
d0 = halfedge_alloc();
d1 = halfedge_alloc();
d0->vertex = pt0;
d1->vertex = pt1;
d0->next = d0->prev = d0;
d1->next = d1->prev = d1;
d0->pair = d1;
d1->pair = d0;
pt0->he = d0;
pt1->he = d1;
del->rightmost_he = d1;
del->leftmost_he = d0;
return 0;
}
/*
* initialize delaunay triangle
*/
static int del_init_tri( delaunay_t *del, int start )
{
halfedge_t *d0, *d1, *d2, *d3, *d4, *d5;
point2d_t *pt0, *pt1, *pt2;
/* initiate delaunay */
del->start_point = start;
del->end_point = start + 2;
/* setup the points */
pt0 = &(del->points[start]);
pt1 = &(del->points[start + 1]);
pt2 = &(del->points[start + 2]);
/* allocate the 6 halfedges */
d0 = halfedge_alloc();
d1 = halfedge_alloc();
d2 = halfedge_alloc();
d3 = halfedge_alloc();
d4 = halfedge_alloc();
d5 = halfedge_alloc();
if( classify_point_seg(pt0, pt2, pt1) == ON_LEFT ) /* first case */
{
/* set halfedges points */
d0->vertex = pt0;
d1->vertex = pt2;
d2->vertex = pt1;
d3->vertex = pt2;
d4->vertex = pt1;
d5->vertex = pt0;
/* set points halfedges */
pt0->he = d0;
pt1->he = d2;
pt2->he = d1;
/* next and next -1 setup */
d0->next = d5;
d0->prev = d5;
d1->next = d3;
d1->prev = d3;
d2->next = d4;
d2->prev = d4;
d3->next = d1;
d3->prev = d1;
d4->next = d2;
d4->prev = d2;
d5->next = d0;
d5->prev = d0;
/* set halfedges pair */
d0->pair = d3;
d3->pair = d0;
d1->pair = d4;
d4->pair = d1;
d2->pair = d5;
d5->pair = d2;
del->rightmost_he = d1;
del->leftmost_he = d0;
} else /* 2nd case */
{
/* set halfedges points */
d0->vertex = pt0;
d1->vertex = pt1;
d2->vertex = pt2;
d3->vertex = pt1;
d4->vertex = pt2;
d5->vertex = pt0;
/* set points halfedges */
pt0->he = d0;
pt1->he = d1;
pt2->he = d2;
/* next and next -1 setup */
d0->next = d5;
d0->prev = d5;
d1->next = d3;
d1->prev = d3;
d2->next = d4;
d2->prev = d4;
d3->next = d1;
d3->prev = d1;
d4->next = d2;
d4->prev = d2;
d5->next = d0;
d5->prev = d0;
/* set halfedges pair */
d0->pair = d3;
d3->pair = d0;
d1->pair = d4;
d4->pair = d1;
d2->pair = d5;
d5->pair = d2;
del->rightmost_he = d2;
del->leftmost_he = d0;
}
return 0;
}
/*
* remove an edge given a halfedge
*/
static void del_remove_edge( halfedge_t *d )
{
halfedge_t *next, *prev, *pair, *orig_pair;
orig_pair = d->pair;
next = d->next;
prev = d->prev;
pair = d->pair;
assert(next != NULL);
assert(prev != NULL);
next->prev = prev;
prev->next = next;
/* check to see if we have already removed pair */
if( pair )
pair->pair = NULL;
/* check to see if the vertex points to this halfedge */
if( d->vertex->he == d )
d->vertex->he = next;
d->vertex = NULL;
d->next = NULL;
d->prev = NULL;
d->pair = NULL;
next = orig_pair->next;
prev = orig_pair->prev;
pair = orig_pair->pair;
assert(next != NULL);
assert(prev != NULL);
next->prev = prev;
prev->next = next;
/* check to see if we have already removed pair */
if( pair )
pair->pair = NULL;
/* check to see if the vertex points to this halfedge */
if( orig_pair->vertex->he == orig_pair )
orig_pair->vertex->he = next;
orig_pair->vertex = NULL;
orig_pair->next = NULL;
orig_pair->prev = NULL;
orig_pair->pair = NULL;
/* finally free the halfedges */
halfedge_free(d);
halfedge_free(orig_pair);
}
/*
* pass through all the halfedges on the left side and validate them
*/
static halfedge_t* del_valid_left( halfedge_t* b )
{
point2d_t *g, *d, *u, *v;
halfedge_t *c, *du, *dg;
g = b->vertex; /* base halfedge point */
dg = b;
d = b->pair->vertex; /* pair(halfedge) point */
b = b->next;
u = b->pair->vertex; /* next(pair(halfedge)) point */
du = b->pair;
v = b->next->pair->vertex; /* pair(next(next(halfedge)) point */
if( classify_point_seg(g, d, u) == ON_LEFT )
{
/* 3 points aren't colinear */
/* as long as the 4 points belong to the same circle, do the cleaning */
assert( v != u && "1: floating point precision error");
while( v != d && v != g && in_circle(g, d, u, v) == INSIDE )
{
c = b->next;
du = b->next->pair;
del_remove_edge(b);
b = c;
u = du->vertex;
v = b->next->pair->vertex;
}
assert( v != u && "2: floating point precision error");
if( v != d && v != g && in_circle(g, d, u, v) == ON_CIRCLE )
{
du = du->prev;
del_remove_edge(b);
}
} else /* treat the case where the 3 points are colinear */
du = dg;
assert(du->pair);
return du;
}
/*
* pass through all the halfedges on the right side and validate them
*/
static halfedge_t* del_valid_right( halfedge_t *b )
{
point2d_t *rv, *lv, *u, *v;
halfedge_t *c, *dd, *du;
b = b->pair;
rv = b->vertex;
dd = b;
lv = b->pair->vertex;
b = b->prev;
u = b->pair->vertex;
du = b->pair;
v = b->prev->pair->vertex;
if( classify_point_seg(lv, rv, u) == ON_LEFT )
{
assert( v != u && "1: floating point precision error");
while( v != lv && v != rv && in_circle(lv, rv, u, v) == INSIDE )
{
c = b->prev;
du = c->pair;
del_remove_edge(b);
b = c;
u = du->vertex;
v = b->prev->pair->vertex;
}
assert( v != u && "1: floating point precision error");
if( v != lv && v != rv && in_circle(lv, rv, u, v) == ON_CIRCLE )
{
du = du->next;
del_remove_edge(b);
}
} else
du = dd;
assert(du->pair);
return du;
}
/*
* validate a link
*/
static halfedge_t* del_valid_link( halfedge_t *b )
{
point2d_t *g, *g_p, *d, *d_p;
halfedge_t *gd, *dd, *new_gd, *new_dd;
int a;
g = b->vertex;
gd = del_valid_left(b);
g_p = gd->vertex;
assert(b->pair);
d = b->pair->vertex;
dd = del_valid_right(b);
d_p = dd->vertex;
assert(b->pair);
if( g != g_p && d != d_p ) {
a = in_circle(g, d, g_p, d_p);
if( a != ON_CIRCLE ) {
if( a == INSIDE ) {
g_p = g;
gd = b;
} else {
d_p = d;
dd = b->pair;
}
}
}
/* create the 2 halfedges */
new_gd = halfedge_alloc();
new_dd = halfedge_alloc();
/* setup new_gd and new_dd */
new_gd->vertex = gd->vertex;
new_gd->pair = new_dd;
new_gd->prev = gd;
new_gd->next = gd->next;
gd->next->prev = new_gd;
gd->next = new_gd;
new_dd->vertex = dd->vertex;
new_dd->pair = new_gd;
new_dd->prev = dd->prev;
dd->prev->next = new_dd;
new_dd->next = dd;
dd->prev = new_dd;
return new_gd;
}
/*
* find the lower tangent between the two delaunay, going from left to right (returns the left half edge)
*/
static halfedge_t* del_get_lower_tangent( delaunay_t *left, delaunay_t *right )
{
point2d_t *pl, *pr;
halfedge_t *right_d, *left_d, *new_ld, *new_rd;
int sl, sr;
left_d = left->rightmost_he;
right_d = right->leftmost_he;
do {
pl = left_d->prev->pair->vertex;
pr = right_d->pair->vertex;
if( (sl = classify_point_seg(left_d->vertex, right_d->vertex, pl)) == ON_RIGHT ) {
left_d = left_d->prev->pair;
}
if( (sr = classify_point_seg(left_d->vertex, right_d->vertex, pr)) == ON_RIGHT ) {
right_d = right_d->pair->next;
}
} while( sl == ON_RIGHT || sr == ON_RIGHT );
/* create the 2 halfedges */
new_ld = halfedge_alloc();
new_rd = halfedge_alloc();
/* setup new_gd and new_dd */
new_ld->vertex = left_d->vertex;
new_ld->pair = new_rd;
new_ld->prev = left_d->prev;
left_d->prev->next = new_ld;
new_ld->next = left_d;
left_d->prev = new_ld;
new_rd->vertex = right_d->vertex;
new_rd->pair = new_ld;
new_rd->prev = right_d->prev;
right_d->prev->next = new_rd;
new_rd->next = right_d;
right_d->prev = new_rd;
return new_ld;
}
/*
* link the 2 delaunay together
*/
static void del_link( delaunay_t *result, delaunay_t *left, delaunay_t *right )
{
point2d_t *u, *v, *ml, *mr;
halfedge_t *base;
assert( left->points == right->points );
/* save the most right point and the most left point */
ml = left->leftmost_he->vertex;
mr = right->rightmost_he->vertex;
base = del_get_lower_tangent(left, right);
u = base->next->pair->vertex;
v = base->pair->prev->pair->vertex;
while( del_classify_point(base, u) == ON_LEFT ||
del_classify_point(base, v) == ON_LEFT )
{
base = del_valid_link(base);
u = base->next->pair->vertex;
v = base->pair->prev->pair->vertex;
}
right->rightmost_he = mr->he;
left->leftmost_he = ml->he;
/* TODO: this part is not needed, and can be optimized */
while( del_classify_point( right->rightmost_he, right->rightmost_he->prev->pair->vertex ) == ON_RIGHT )
right->rightmost_he = right->rightmost_he->prev;
while( del_classify_point( left->leftmost_he, left->leftmost_he->prev->pair->vertex ) == ON_RIGHT )
left->leftmost_he = left->leftmost_he->prev;
result->leftmost_he = left->leftmost_he;
result->rightmost_he = right->rightmost_he;
result->points = left->points;
result->start_point = left->start_point;
result->end_point = right->end_point;
}
/*
* divide and conquer delaunay
*/
void del_divide_and_conquer( delaunay_t *del, int start, int end )
{
delaunay_t left, right;
int i, n;
n = (end - start + 1);
if( n > 3 ) {
i = (n / 2) + (n & 1);
left.points = del->points;
right.points = del->points;
del_divide_and_conquer( &left, start, start + i - 1 );
del_divide_and_conquer( &right, start + i, end );
del_link( del, &left, &right );
} else {
if( n == 3 ) {
del_init_tri( del, start );
} else {
if( n == 2 ) {
del_init_seg( del, start );
}
}
}
}
static void build_halfedge_face( delaunay_t *del, halfedge_t *d )
{
halfedge_t *curr;
/* test if the halfedge has already a pointing face */
if( d->face != NULL )
return;
/* TODO: optimize this */
del->faces = (face_t*)realloc(del->faces, (del->num_faces + 1) * sizeof(face_t));
assert( NULL != del->faces );
face_t *f = &(del->faces[del->num_faces]);
curr = d;
f->he = d;
f->num_verts = 0;
do {
curr->face = f;
(f->num_verts)++;
curr = curr->pair->prev;
} while( curr != d );
(del->num_faces)++;
}
/*
* build the faces for all the halfedge
*/
void del_build_faces( delaunay_t *del )
{
unsigned int i;
halfedge_t *curr;
del->num_faces = 0;
del->faces = NULL;
/* build external face first */
build_halfedge_face(del, del->rightmost_he->pair);
for( i = del->start_point; i <= del->end_point; i++ )
{
curr = del->points[i].he;
do {
build_halfedge_face( del, curr );
curr = curr->next;
} while( curr != del->points[i].he );
}
}
/*
*/
delaunay2d_t* delaunay2d_from(del_point2d_t *points, unsigned int num_points) {
delaunay2d_t* res = NULL;
delaunay_t del;
unsigned int i, j, fbuff_size = 0;
unsigned int* faces = NULL;
/* allocate the points */
del.points = (point2d_t*)malloc(num_points * sizeof(point2d_t));
assert( NULL != del.points );
memset(del.points, 0, num_points * sizeof(point2d_t));
/* copy the points */
for( i = 0; i < num_points; i++ )
{
del.points[i].idx = i;
del.points[i].x = points[i].x;
del.points[i].y = points[i].y;
}
qsort(del.points, num_points, sizeof(point2d_t), cmp_points);
if( num_points >= 3 ) {
del_divide_and_conquer( &del, 0, num_points - 1 );
del_build_faces( &del );
fbuff_size = 0;
for( i = 0; i < del.num_faces; i++ )
fbuff_size += del.faces[i].num_verts + 1;
faces = (unsigned int*)malloc(sizeof(unsigned int) * fbuff_size);
assert( NULL != faces );
j = 0;
for( i = 0; i < del.num_faces; i++ )
{
halfedge_t *curr;
faces[j] = del.faces[i].num_verts;
j++;
curr = del.faces[i].he;
do {
faces[j] = curr->vertex->idx;
j++;
curr = curr->pair->prev;
} while( curr != del.faces[i].he );
}
del_free_halfedges( &del );
free(del.faces);
free(del.points);
}
res = (delaunay2d_t*)malloc(sizeof(delaunay2d_t));
assert( NULL != res );
res->num_points = num_points;
res->points = (del_point2d_t*)malloc(sizeof(del_point2d_t) * num_points);
assert( NULL != res->points );
memcpy(res->points, points, sizeof(del_point2d_t) * num_points);
res->num_faces = del.num_faces;
res->faces = faces;
return res;
}
void delaunay2d_release(delaunay2d_t *del) {
free(del->faces);
free(del->points);
free(del);
}
tri_delaunay2d_t* tri_delaunay2d_from(delaunay2d_t* del) {
unsigned int v_offset = del->faces[0] + 1; /* ignore external face */
unsigned int dst_offset = 0;
unsigned int i;
tri_delaunay2d_t* tdel = (tri_delaunay2d_t*)malloc(sizeof(tri_delaunay2d_t));
assert( NULL != tdel );
tdel->num_triangles = 0;
/* count the number of triangles */
if( 1 == del->num_faces ) { /* degenerate case: only external face exists */
unsigned int nv = del->faces[0];
tdel->num_triangles += nv - 2;
} else {
for( i = 1; i < del->num_faces; ++i ) {
unsigned int nv = del->faces[v_offset];
tdel->num_triangles += nv - 2;
v_offset += nv + 1;
}
}
/* copy points */
tdel->num_points = del->num_points;
tdel->points = (del_point2d_t*)malloc(sizeof(del_point2d_t) * del->num_points);
assert( NULL != tdel->points );
memcpy(tdel->points, del->points, sizeof(del_point2d_t) * del->num_points);
/* build the triangles */
tdel->tris = (unsigned int*)malloc(sizeof(unsigned int) * 3 * tdel->num_triangles);
assert( NULL != tdel->tris );
v_offset = del->faces[0] + 1; /* ignore external face */
if( 1 == del->num_faces ) {
/* handle the degenerated case where only the external face exists */
unsigned int nv = del->faces[0];
unsigned int j = 0;
v_offset = 1;
for( ; j < nv - 2; ++j ) {
tdel->tris[dst_offset] = del->faces[v_offset + j];
tdel->tris[dst_offset + 1] = del->faces[(v_offset + j + 1) % nv];
tdel->tris[dst_offset + 2] = del->faces[v_offset + j];
dst_offset += 3;
}
} else {
for( i = 1; i < del->num_faces; ++i ) {
unsigned int nv = del->faces[v_offset];
unsigned int j = 0;
unsigned int first = del->faces[v_offset + 1];
for( ; j < nv - 2; ++j ) {
tdel->tris[dst_offset] = first;
tdel->tris[dst_offset + 1] = del->faces[v_offset + j + 2];
tdel->tris[dst_offset + 2] = del->faces[v_offset + j + 3];
dst_offset += 3;
}
v_offset += nv + 1;
}
}
return tdel;
}
void tri_delaunay2d_release(tri_delaunay2d_t* tdel) {
free(tdel->tris);
free(tdel->points);
free(tdel);
}
delaunay.c and delaunay.h are not my copyright. They are "Copyright (C) 2005 Wael El Oraiby", and distributed here under the GNU Affero General Public License.
#ifndef DELAUNAY_H
#define DELAUNAY_H
/*
** delaunay.c : compute 2D delaunay triangulation in the plane.
** Copyright (C) 2005 Wael El Oraiby <wael.eloraiby@gmail.com>
**
**
** This program is free software: you can redistribute it and/or modify
** it under the terms of the GNU Affero General Public License as
** published by the Free Software Foundation, either version 3 of the
** License, or (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU Affero General Public License for more details.
**
** You should have received a copy of the GNU Affero General Public License
** along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifdef __cplusplus
extern "C" {
#endif
typedef double real;
typedef struct {
real x, y;
} del_point2d_t;
typedef struct {
/** input points count */
unsigned int num_points;
/** the input points */
del_point2d_t* points;
/** number of returned faces */
unsigned int num_faces;
/** the faces are given as a sequence: num verts, verts indices, num verts, verts indices...
* the first face is the external face */
unsigned int* faces;
} delaunay2d_t;
/*
* build the 2D Delaunay triangulation given a set of points of at least 3 points
*
* @points: point set given as a sequence of tuple x0, y0, x1, y1, ....
* @num_points: number of given point
* @preds: the incircle predicate
* @faces: the triangles given as a sequence: num verts, verts indices, num verts, verts indices.
* Note that the first face is the external face
* @return: the created topology
*/
delaunay2d_t* delaunay2d_from(del_point2d_t *points, unsigned int num_points);
/*
* release a delaunay2d object
*/
void delaunay2d_release(delaunay2d_t* del);
typedef struct {
/** input points count */
unsigned int num_points;
/** input points */
del_point2d_t* points;
/** number of triangles */
unsigned int num_triangles;
/** the triangles indices v0,v1,v2, v0,v1,v2 .... */
unsigned int* tris;
} tri_delaunay2d_t;
/**
* build a tri_delaunay2d_t out of a delaunay2d_t object
*/
tri_delaunay2d_t* tri_delaunay2d_from(delaunay2d_t* del);
/**
* release a tri_delaunay2d_t object
*/
void tri_delaunay2d_release(tri_delaunay2d_t* tdel);
#ifdef __cplusplus
}
#endif
#endif // DELAUNAY_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"); \
}
This file is in imdev/filters. IM installs it for the analyze module. I have added entries for my own modules.
# Copyright 1999-2014 ImageMagick Studio LLC, a non-profit organization
# dedicated to making software imaging solutions freely available.
#
# You may not use this file except in compliance with the License. You may
# obtain a copy of the License at
#
# http://www.imagemagick.org/script/license.php
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# Makefile for building ImageMagick filter modules.
# Updated by Alan Gibson ("snibgo"):
# 29-September-2016 removed sortlines, eqfish2rect, segscanmerge
#
# Where filter modules get installed
filtersdir = $(FILTER_PATH)
MAGICK_FILTER_CPPFLAGS = $(AM_CPPFLAGS)
MAGICK_FILTER_SRCS = \
filters/analyze.c \
filters/hellow.c \
filters/echostuff.c \
filters/dumpimage.c \
filters/onewhite.c \
filters/nearestwhite.c \
filters/allwhite.c \
filters/onelightest.c \
filters/midlightest.c \
filters/rmsealpha.c \
filters/addend.c \
filters/replacelast.c \
filters/replacefirst.c \
filters/replaceall.c \
filters/replaceeach.c \
filters/replacespec.c \
filters/grad2.c \
filters/drawcirc.c \
filters/sortpixels.c \
filters/sortpixelsblue.c \
filters/shadowsortpixels.c \
filters/img2knl.c \
filters/interppix.c \
filters/mkgauss.c \
filters/applines.c \
filters/mkhisto.c \
filters/cumulhisto.c \
filters/invdispmap.c \
filters/geodist.c \
filters/invclut.c \
filters/rect2eqfish.c \
filters/growcut.c \
filters/fillholes.c \
filters/fillholespri.c \
filters/pixmatch.c \
filters/darkestpath.c \
filters/darkestmeander.c \
filters/darkestpntpnt.c \
filters/srt3d.c \
filters/arctan2.c \
filters/pause.c \
filters/shell.c \
filters/sphaldcl.c \
filters/setmnsd.c \
filters/integim.c \
filters/deintegim.c \
filters/deintegim2.c \
filters/kcluster.c \
filters/srchimg.c \
filters/paintpatches.c \
filters/avgconcrings.c \
filters/multisrch.c \
filters/whatrot.c \
filters/whatscale.c \
filters/whatrotscale.c \
filters/findsinks.c \
filters/srchmask.c \
filters/aggrsrch.c \
filters/centsmcrop.c \
filters/cols2mat.c \
filters/oogbox.c \
filters/plotrg.c \
filters/centroid.c \
filters/barymap.c \
filters/rhotheta.c \
filters/colsp012.c \
filters/colredmrge.c \
filters/polypix.c \
filters/polyreg.c \
filters/deltri.c
if WITH_MODULES
filters_LTLIBRARIES = filters/analyze.la \
filters/hellow.la \
filters/echostuff.la \
filters/dumpimage.la \
filters/onewhite.la \
filters/nearestwhite.la \
filters/allwhite.la \
filters/onelightest.la \
filters/midlightest.la \
filters/rmsealpha.la \
filters/addend.la \
filters/replacelast.la \
filters/replacefirst.la \
filters/replaceall.la \
filters/replaceeach.la \
filters/replacespec.la \
filters/grad2.la \
filters/drawcirc.la \
filters/sortpixels.la \
filters/sortpixelsblue.la \
filters/shadowsortpixels.la \
filters/img2knl.la \
filters/interppix.la \
filters/mkgauss.la \
filters/applines.la \
filters/mkhisto.la \
filters/cumulhisto.la \
filters/invdispmap.la \
filters/geodist.la \
filters/invclut.la \
filters/rect2eqfish.la \
filters/growcut.la \
filters/fillholes.la \
filters/fillholespri.la \
filters/pixmatch.la \
filters/darkestpath.la \
filters/darkestmeander.la \
filters/darkestpntpnt.la \
filters/srt3d.la \
filters/arctan2.la \
filters/pause.la \
filters/shell.la \
filters/sphaldcl.la \
filters/setmnsd.la \
filters/integim.la \
filters/deintegim.la \
filters/deintegim2.la \
filters/kcluster.la \
filters/srchimg.la \
filters/paintpatches.la \
filters/avgconcrings.la \
filters/multisrch.la \
filters/whatrot.la \
filters/whatscale.la \
filters/whatrotscale.la \
filters/findsinks.la \
filters/srchmask.la \
filters/aggrsrch.la \
filters/centsmcrop.la \
filters/cols2mat.la \
filters/oogbox.la \
filters/plotrg.la \
filters/centroid.la \
filters/barymap.la \
filters/rhotheta.la \
filters/colsp012.la \
filters/colredmrge.la \
filters/polypix.la \
filters/polyreg.la \
filters/deltri.la
else
filters_LTLIBRARIES =
endif # WITH_MODULES
filters_CPPFLAGS = $(MAGICK_FILTER_CPPFLAGS)
# analyze filter module
filters_analyze_la_SOURCES = filters/analyze.c
filters_analyze_la_CPPFLAGS = $(MAGICK_FILTER_CPPFLAGS)
filters_analyze_la_LDFLAGS = $(MODULECOMMONFLAGS)
filters_analyze_la_LIBADD = $(MAGICKCORE_LIBS) $(MATH_LIBS)
# hellow filter module
filters_hellow_la_SOURCES = filters/hellow.c
filters_hellow_la_CPPFLAGS = $(MAGICK_FILTER_CPPFLAGS)
filters_hellow_la_LDFLAGS = $(MODULECOMMONFLAGS)
filters_hellow_la_LIBADD = $(MAGICKCORE_LIBS) $(MATH_LIBS)
# echostuff filter module
filters_echostuff_la_SOURCES = filters/echostuff.c
filters_echostuff_la_CPPFLAGS = $(MAGICK_FILTER_CPPFLAGS)
filters_echostuff_la_LDFLAGS = $(MODULECOMMONFLAGS)
filters_echostuff_la_LIBADD = $(MAGICKCORE_LIBS) $(MATH_LIBS)
# dumpimage filter module
filters_dumpimage_la_SOURCES = filters/dumpimage.c
filters_dumpimage_la_CPPFLAGS = $(MAGICK_FILTER_CPPFLAGS)
filters_dumpimage_la_LDFLAGS = $(MODULECOMMONFLAGS)
filters_dumpimage_la_LIBADD = $(MAGICKCORE_LIBS) $(MATH_LIBS)
# onewhite filter module
filters_onewhite_la_SOURCES = filters/onewhite.c
filters_onewhite_la_CPPFLAGS = $(MAGICK_FILTER_CPPFLAGS)
filters_onewhite_la_LDFLAGS = $(MODULECOMMONFLAGS)
filters_onewhite_la_LIBADD = $(MAGICKCORE_LIBS) $(MATH_LIBS)
# onewhite filter module
filters_nearestwhite_la_SOURCES = filters/nearestwhite.c
filters_nearestwhite_la_CPPFLAGS = $(MAGICK_FILTER_CPPFLAGS)
filters_nearestwhite_la_LDFLAGS = $(MODULECOMMONFLAGS)
filters_nearestwhite_la_LIBADD = $(MAGICKCORE_LIBS) $(MATH_LIBS)
# allwhite filter module
filters_allwhite_la_SOURCES = filters/allwhite.c
filters_allwhite_la_CPPFLAGS = $(MAGICK_FILTER_CPPFLAGS)
filters_allwhite_la_LDFLAGS = $(MODULECOMMONFLAGS)
filters_allwhite_la_LIBADD = $(MAGICKCORE_LIBS) $(MATH_LIBS)
# onelightest filter module
filters_onelightest_la_SOURCES = filters/onelightest.c
filters_onelightest_la_CPPFLAGS = $(MAGICK_FILTER_CPPFLAGS)
filters_onelightest_la_LDFLAGS = $(MODULECOMMONFLAGS)
filters_onelightest_la_LIBADD = $(MAGICKCORE_LIBS) $(MATH_LIBS)
# midlightest filter module
filters_midlightest_la_SOURCES = filters/midlightest.c
filters_midlightest_la_CPPFLAGS = $(MAGICK_FILTER_CPPFLAGS)
filters_midlightest_la_LDFLAGS = $(MODULECOMMONFLAGS)
filters_midlightest_la_LIBADD = $(MAGICKCORE_LIBS) $(MATH_LIBS)
# rmsealpha filter module
filters_rmsealpha_la_SOURCES = filters/rmsealpha.c
filters_rmsealpha_la_CPPFLAGS = $(MAGICK_FILTER_CPPFLAGS)
filters_rmsealpha_la_LDFLAGS = $(MODULECOMMONFLAGS)
filters_rmsealpha_la_LIBADD = $(MAGICKCORE_LIBS) $(MATH_LIBS)
# addend filter module
filters_addend_la_SOURCES = filters/addend.c
filters_addend_la_CPPFLAGS = $(MAGICK_FILTER_CPPFLAGS)
filters_addend_la_LDFLAGS = $(MODULECOMMONFLAGS)
filters_addend_la_LIBADD = $(MAGICKCORE_LIBS) $(MATH_LIBS)
# replacelast filter module
filters_replacelast_la_SOURCES = filters/replacelast.c
filters_replacelast_la_CPPFLAGS = $(MAGICK_FILTER_CPPFLAGS)
filters_replacelast_la_LDFLAGS = $(MODULECOMMONFLAGS)
filters_replacelast_la_LIBADD = $(MAGICKCORE_LIBS) $(MATH_LIBS)
# replacefirst filter module
filters_replacefirst_la_SOURCES = filters/replacefirst.c
filters_replacefirst_la_CPPFLAGS = $(MAGICK_FILTER_CPPFLAGS)
filters_replacefirst_la_LDFLAGS = $(MODULECOMMONFLAGS)
filters_replacefirst_la_LIBADD = $(MAGICKCORE_LIBS) $(MATH_LIBS)
# replaceall filter module
filters_replaceall_la_SOURCES = filters/replaceall.c
filters_replaceall_la_CPPFLAGS = $(MAGICK_FILTER_CPPFLAGS)
filters_replaceall_la_LDFLAGS = $(MODULECOMMONFLAGS)
filters_replaceall_la_LIBADD = $(MAGICKCORE_LIBS) $(MATH_LIBS)
# replaceeach filter module
filters_replaceeach_la_SOURCES = filters/replaceeach.c
filters_replaceeach_la_CPPFLAGS = $(MAGICK_FILTER_CPPFLAGS)
filters_replaceeach_la_LDFLAGS = $(MODULECOMMONFLAGS)
filters_replaceeach_la_LIBADD = $(MAGICKCORE_LIBS) $(MATH_LIBS)
# replacespec filter module
filters_replacespec_la_SOURCES = filters/replacespec.c
filters_replacespec_la_CPPFLAGS = $(MAGICK_FILTER_CPPFLAGS)
filters_replacespec_la_LDFLAGS = $(MODULECOMMONFLAGS)
filters_replacespec_la_LIBADD = $(MAGICKCORE_LIBS) $(MATH_LIBS)
# grad2 filter module
filters_grad2_la_SOURCES = filters/grad2.c
filters_grad2_la_CPPFLAGS = $(MAGICK_FILTER_CPPFLAGS)
filters_grad2_la_LDFLAGS = $(MODULECOMMONFLAGS)
filters_grad2_la_LIBADD = $(MAGICKCORE_LIBS) $(MATH_LIBS)
# drawcirc filter module
filters_drawcirc_la_SOURCES = filters/drawcirc.c
filters_drawcirc_la_CPPFLAGS = $(MAGICK_FILTER_CPPFLAGS)
filters_drawcirc_la_LDFLAGS = $(MODULECOMMONFLAGS)
filters_drawcirc_la_LIBADD = $(MAGICKCORE_LIBS) $(MATH_LIBS)
# sortpixels filter module
filters_sortpixels_la_SOURCES = filters/sortpixels.c
filters_sortpixels_la_CPPFLAGS = $(MAGICK_FILTER_CPPFLAGS)
filters_sortpixels_la_LDFLAGS = $(MODULECOMMONFLAGS)
filters_sortpixels_la_LIBADD = $(MAGICKCORE_LIBS) $(MATH_LIBS)
# sortpixelsblue filter module
filters_sortpixelsblue_la_SOURCES = filters/sortpixelsblue.c
filters_sortpixelsblue_la_CPPFLAGS = $(MAGICK_FILTER_CPPFLAGS)
filters_sortpixelsblue_la_LDFLAGS = $(MODULECOMMONFLAGS)
filters_sortpixelsblue_la_LIBADD = $(MAGICKCORE_LIBS) $(MATH_LIBS)
# shadowsortpixels filter module
filters_shadowsortpixels_la_SOURCES = filters/shadowsortpixels.c
filters_shadowsortpixels_la_CPPFLAGS = $(MAGICK_FILTER_CPPFLAGS)
filters_shadowsortpixels_la_LDFLAGS = $(MODULECOMMONFLAGS)
filters_shadowsortpixels_la_LIBADD = $(MAGICKCORE_LIBS) $(MATH_LIBS)
# img2knl filter module
filters_img2knl_la_SOURCES = filters/img2knl.c
filters_img2knl_la_CPPFLAGS = $(MAGICK_FILTER_CPPFLAGS)
filters_img2knl_la_LDFLAGS = $(MODULECOMMONFLAGS)
filters_img2knl_la_LIBADD = $(MAGICKCORE_LIBS) $(MATH_LIBS)
# interppix filter module
filters_interppix_la_SOURCES = filters/interppix.c
filters_interppix_la_CPPFLAGS = $(MAGICK_FILTER_CPPFLAGS)
filters_interppix_la_LDFLAGS = $(MODULECOMMONFLAGS)
filters_interppix_la_LIBADD = $(MAGICKCORE_LIBS) $(MATH_LIBS)
# mkgauss filter module
filters_mkgauss_la_SOURCES = filters/mkgauss.c
filters_mkgauss_la_CPPFLAGS = $(MAGICK_FILTER_CPPFLAGS)
filters_mkgauss_la_LDFLAGS = $(MODULECOMMONFLAGS)
filters_mkgauss_la_LIBADD = $(MAGICKCORE_LIBS) $(MATH_LIBS)
# applines filter module
filters_applines_la_SOURCES = filters/applines.c
filters_applines_la_CPPFLAGS = $(MAGICK_FILTER_CPPFLAGS)
filters_applines_la_LDFLAGS = $(MODULECOMMONFLAGS)
filters_applines_la_LIBADD = $(MAGICKCORE_LIBS) $(MATH_LIBS)
# cumulhisto filter module
filters_cumulhisto_la_SOURCES = filters/cumulhisto.c
filters_cumulhisto_la_CPPFLAGS = $(MAGICK_FILTER_CPPFLAGS)
filters_cumulhisto_la_LDFLAGS = $(MODULECOMMONFLAGS)
filters_cumulhisto_la_LIBADD = $(MAGICKCORE_LIBS) $(MATH_LIBS)
# invdispmap filter module
filters_invdispmap_la_SOURCES = filters/invdispmap.c
filters_invdispmap_la_CPPFLAGS = $(MAGICK_FILTER_CPPFLAGS)
filters_invdispmap_la_LDFLAGS = $(MODULECOMMONFLAGS)
filters_invdispmap_la_LIBADD = $(MAGICKCORE_LIBS) $(MATH_LIBS)
# mkhisto filter module
filters_mkhisto_la_SOURCES = filters/mkhisto.c
filters_mkhisto_la_CPPFLAGS = $(MAGICK_FILTER_CPPFLAGS)
filters_mkhisto_la_LDFLAGS = $(MODULECOMMONFLAGS)
filters_mkhisto_la_LIBADD = $(MAGICKCORE_LIBS) $(MATH_LIBS)
# geodist filter module
filters_geodist_la_SOURCES = filters/geodist.c
filters_geodist_la_CPPFLAGS = $(MAGICK_FILTER_CPPFLAGS)
filters_geodist_la_LDFLAGS = $(MODULECOMMONFLAGS)
filters_geodist_la_LIBADD = $(MAGICKCORE_LIBS) $(MATH_LIBS)
# invclut filter module
filters_invclut_la_SOURCES = filters/invclut.c
filters_invclut_la_CPPFLAGS = $(MAGICK_FILTER_CPPFLAGS)
filters_invclut_la_LDFLAGS = $(MODULECOMMONFLAGS)
filters_invclut_la_LIBADD = $(MAGICKCORE_LIBS) $(MATH_LIBS)
# rect2eqfish filter module
filters_rect2eqfish_la_SOURCES = filters/rect2eqfish.c
filters_rect2eqfish_la_CPPFLAGS = $(MAGICK_FILTER_CPPFLAGS)
filters_rect2eqfish_la_LDFLAGS = $(MODULECOMMONFLAGS)
filters_rect2eqfish_la_LIBADD = $(MAGICKCORE_LIBS) $(MATH_LIBS)
# growcut filter module
filters_growcut_la_SOURCES = filters/growcut.c
filters_growcut_la_CPPFLAGS = $(MAGICK_FILTER_CPPFLAGS)
filters_growcut_la_LDFLAGS = $(MODULECOMMONFLAGS)
filters_growcut_la_LIBADD = $(MAGICKCORE_LIBS) $(MATH_LIBS)
# fillholes filter module
filters_fillholes_la_SOURCES = filters/fillholes.c
filters_fillholes_la_CPPFLAGS = $(MAGICK_FILTER_CPPFLAGS)
filters_fillholes_la_LDFLAGS = $(MODULECOMMONFLAGS)
filters_fillholes_la_LIBADD = $(MAGICKCORE_LIBS) $(MATH_LIBS)
# fillholespri filter module
filters_fillholespri_la_SOURCES = filters/fillholespri.c
filters_fillholespri_la_CPPFLAGS = $(MAGICK_FILTER_CPPFLAGS)
filters_fillholespri_la_LDFLAGS = $(MODULECOMMONFLAGS)
filters_fillholespri_la_LIBADD = $(MAGICKCORE_LIBS) $(MATH_LIBS)
# pixmatch filter module
filters_pixmatch_la_SOURCES = filters/pixmatch.c
filters_pixmatch_la_CPPFLAGS = $(MAGICK_FILTER_CPPFLAGS)
filters_pixmatch_la_LDFLAGS = $(MODULECOMMONFLAGS)
filters_pixmatch_la_LIBADD = $(MAGICKCORE_LIBS) $(MATH_LIBS)
# darkestpath filter module
filters_darkestpath_la_SOURCES = filters/darkestpath.c
filters_darkestpath_la_CPPFLAGS = $(MAGICK_FILTER_CPPFLAGS)
filters_darkestpath_la_LDFLAGS = $(MODULECOMMONFLAGS)
filters_darkestpath_la_LIBADD = $(MAGICKCORE_LIBS) $(MATH_LIBS)
# darkestmeander filter module
filters_darkestmeander_la_SOURCES = filters/darkestmeander.c
filters_darkestmeander_la_CPPFLAGS = $(MAGICK_FILTER_CPPFLAGS)
filters_darkestmeander_la_LDFLAGS = $(MODULECOMMONFLAGS)
filters_darkestmeander_la_LIBADD = $(MAGICKCORE_LIBS) $(MATH_LIBS)
# darkestpntpnt filter module
filters_darkestpntpnt_la_SOURCES = filters/darkestpntpnt.c
filters_darkestpntpnt_la_CPPFLAGS = $(MAGICK_FILTER_CPPFLAGS)
filters_darkestpntpnt_la_LDFLAGS = $(MODULECOMMONFLAGS)
filters_darkestpntpnt_la_LIBADD = $(MAGICKCORE_LIBS) $(MATH_LIBS)
# srt3d filter module
filters_srt3d_la_SOURCES = filters/srt3d.c
filters_srt3d_la_CPPFLAGS = $(MAGICK_FILTER_CPPFLAGS)
filters_srt3d_la_LDFLAGS = $(MODULECOMMONFLAGS)
filters_srt3d_la_LIBADD = $(MAGICKCORE_LIBS) $(MATH_LIBS)
# arctan2 filter module
filters_arctan2_la_SOURCES = filters/arctan2.c
filters_arctan2_la_CPPFLAGS = $(MAGICK_FILTER_CPPFLAGS)
filters_arctan2_la_LDFLAGS = $(MODULECOMMONFLAGS)
filters_arctan2_la_LIBADD = $(MAGICKCORE_LIBS) $(MATH_LIBS)
# pause filter module
filters_pause_la_SOURCES = filters/pause.c
filters_pause_la_CPPFLAGS = $(MAGICK_FILTER_CPPFLAGS)
filters_pause_la_LDFLAGS = $(MODULECOMMONFLAGS)
filters_pause_la_LIBADD = $(MAGICKCORE_LIBS) $(MATH_LIBS)
# shell filter module
filters_shell_la_SOURCES = filters/shell.c
filters_shell_la_CPPFLAGS = $(MAGICK_FILTER_CPPFLAGS)
filters_shell_la_LDFLAGS = $(MODULECOMMONFLAGS)
filters_shell_la_LIBADD = $(MAGICKCORE_LIBS) $(MATH_LIBS)
# sphaldcl filter module
filters_sphaldcl_la_SOURCES = filters/sphaldcl.c
filters_sphaldcl_la_CPPFLAGS = $(MAGICK_FILTER_CPPFLAGS)
filters_sphaldcl_la_LDFLAGS = $(MODULECOMMONFLAGS)
filters_sphaldcl_la_LIBADD = $(MAGICKCORE_LIBS) $(MATH_LIBS)
# setmnsd filter module
filters_setmnsd_la_SOURCES = filters/setmnsd.c
filters_setmnsd_la_CPPFLAGS = $(MAGICK_FILTER_CPPFLAGS)
filters_setmnsd_la_LDFLAGS = $(MODULECOMMONFLAGS)
filters_setmnsd_la_LIBADD = $(MAGICKCORE_LIBS) $(MATH_LIBS)
# integim filter module
filters_integim_la_SOURCES = filters/integim.c
filters_integim_la_CPPFLAGS = $(MAGICK_FILTER_CPPFLAGS)
filters_integim_la_LDFLAGS = $(MODULECOMMONFLAGS)
filters_integim_la_LIBADD = $(MAGICKCORE_LIBS) $(MATH_LIBS)
# deintegim filter module
filters_deintegim_la_SOURCES = filters/deintegim.c
filters_deintegim_la_CPPFLAGS = $(MAGICK_FILTER_CPPFLAGS)
filters_deintegim_la_LDFLAGS = $(MODULECOMMONFLAGS)
filters_deintegim_la_LIBADD = $(MAGICKCORE_LIBS) $(MATH_LIBS)
# deintegim2 filter module
filters_deintegim2_la_SOURCES = filters/deintegim2.c
filters_deintegim2_la_CPPFLAGS = $(MAGICK_FILTER_CPPFLAGS)
filters_deintegim2_la_LDFLAGS = $(MODULECOMMONFLAGS)
filters_deintegim2_la_LIBADD = $(MAGICKCORE_LIBS) $(MATH_LIBS)
# kcluster filter module
filters_kcluster_la_SOURCES = filters/kcluster.c
filters_kcluster_la_CPPFLAGS = $(MAGICK_FILTER_CPPFLAGS)
filters_kcluster_la_LDFLAGS = $(MODULECOMMONFLAGS)
filters_kcluster_la_LIBADD = $(MAGICKCORE_LIBS) $(MATH_LIBS)
# colredmrge filter module
filters_colredmrge_la_SOURCES = filters/colredmrge.c
filters_colredmrge_la_CPPFLAGS = $(MAGICK_FILTER_CPPFLAGS)
filters_colredmrge_la_LDFLAGS = $(MODULECOMMONFLAGS)
filters_colredmrge_la_LIBADD = $(MAGICKCORE_LIBS) $(MATH_LIBS)
# srchimg filter module
filters_srchimg_la_SOURCES = filters/srchimg.c
filters_srchimg_la_CPPFLAGS = $(MAGICK_FILTER_CPPFLAGS)
filters_srchimg_la_LDFLAGS = $(MODULECOMMONFLAGS)
filters_srchimg_la_LIBADD = $(MAGICKCORE_LIBS) $(MATH_LIBS)
# paintpatches filter module
filters_paintpatches_la_SOURCES = filters/paintpatches.c
filters_paintpatches_la_CPPFLAGS = $(MAGICK_FILTER_CPPFLAGS)
filters_paintpatches_la_LDFLAGS = $(MODULECOMMONFLAGS)
filters_paintpatches_la_LIBADD = $(MAGICKCORE_LIBS) $(MATH_LIBS)
# avgconcrings filter module
filters_avgconcrings_la_SOURCES = filters/avgconcrings.c
filters_avgconcrings_la_CPPFLAGS = $(MAGICK_FILTER_CPPFLAGS)
filters_avgconcrings_la_LDFLAGS = $(MODULECOMMONFLAGS)
filters_avgconcrings_la_LIBADD = $(MAGICKCORE_LIBS) $(MATH_LIBS)
# findsinks filter module
filters_findsinks_la_SOURCES = filters/findsinks.c
filters_findsinks_la_CPPFLAGS = $(MAGICK_FILTER_CPPFLAGS)
filters_findsinks_la_LDFLAGS = $(MODULECOMMONFLAGS)
filters_findsinks_la_LIBADD = $(MAGICKCORE_LIBS) $(MATH_LIBS)
# multisrch filter module
filters_multisrch_la_SOURCES = filters/multisrch.c
filters_multisrch_la_CPPFLAGS = $(MAGICK_FILTER_CPPFLAGS)
filters_multisrch_la_LDFLAGS = $(MODULECOMMONFLAGS)
filters_multisrch_la_LIBADD = $(MAGICKCORE_LIBS) $(MATH_LIBS)
# whatrot filter module
filters_whatrot_la_SOURCES = filters/whatrot.c
filters_whatrot_la_CPPFLAGS = $(MAGICK_FILTER_CPPFLAGS)
filters_whatrot_la_LDFLAGS = $(MODULECOMMONFLAGS)
filters_whatrot_la_LIBADD = $(MAGICKCORE_LIBS) $(MATH_LIBS)
# whatscale filter module
filters_whatscale_la_SOURCES = filters/whatscale.c
filters_whatscale_la_CPPFLAGS = $(MAGICK_FILTER_CPPFLAGS)
filters_whatscale_la_LDFLAGS = $(MODULECOMMONFLAGS)
filters_whatscale_la_LIBADD = $(MAGICKCORE_LIBS) $(MATH_LIBS)
# whatrotscale filter module
filters_whatrotscale_la_SOURCES = filters/whatrotscale.c
filters_whatrotscale_la_CPPFLAGS = $(MAGICK_FILTER_CPPFLAGS)
filters_whatrotscale_la_LDFLAGS = $(MODULECOMMONFLAGS)
filters_whatrotscale_la_LIBADD = $(MAGICKCORE_LIBS) $(MATH_LIBS)
# srchmask filter module
filters_srchmask_la_SOURCES = filters/srchmask.c
filters_srchmask_la_CPPFLAGS = $(MAGICK_FILTER_CPPFLAGS)
filters_srchmask_la_LDFLAGS = $(MODULECOMMONFLAGS)
filters_srchmask_la_LIBADD = $(MAGICKCORE_LIBS) $(MATH_LIBS)
# aggrsrch filter module
filters_aggrsrch_la_SOURCES = filters/aggrsrch.c
filters_aggrsrch_la_CPPFLAGS = $(MAGICK_FILTER_CPPFLAGS)
filters_aggrsrch_la_LDFLAGS = $(MODULECOMMONFLAGS)
filters_aggrsrch_la_LIBADD = $(MAGICKCORE_LIBS) $(MATH_LIBS)
# cols2mat filter module
filters_cols2mat_la_SOURCES = filters/cols2mat.c
filters_cols2mat_la_CPPFLAGS = $(MAGICK_FILTER_CPPFLAGS)
filters_cols2mat_la_LDFLAGS = $(MODULECOMMONFLAGS)
filters_cols2mat_la_LIBADD = $(MAGICKCORE_LIBS) $(MATH_LIBS)
# oogbox filter module
filters_oogbox_la_SOURCES = filters/oogbox.c
filters_oogbox_la_CPPFLAGS = $(MAGICK_FILTER_CPPFLAGS)
filters_oogbox_la_LDFLAGS = $(MODULECOMMONFLAGS)
filters_oogbox_la_LIBADD = $(MAGICKCORE_LIBS) $(MATH_LIBS)
# centsmcrop filter module
filters_centsmcrop_la_SOURCES = filters/centsmcrop.c
filters_centsmcrop_la_CPPFLAGS = $(MAGICK_FILTER_CPPFLAGS)
filters_centsmcrop_la_LDFLAGS = $(MODULECOMMONFLAGS)
filters_centsmcrop_la_LIBADD = $(MAGICKCORE_LIBS) $(MATH_LIBS)
# plotrg filter module
filters_plotrg_la_SOURCES = filters/plotrg.c
filters_plotrg_la_CPPFLAGS = $(MAGICK_FILTER_CPPFLAGS)
filters_plotrg_la_LDFLAGS = $(MODULECOMMONFLAGS)
filters_plotrg_la_LIBADD = $(MAGICKCORE_LIBS) $(MATH_LIBS)
# centroid filter module
filters_centroid_la_SOURCES = filters/centroid.c
filters_centroid_la_CPPFLAGS = $(MAGICK_FILTER_CPPFLAGS)
filters_centroid_la_LDFLAGS = $(MODULECOMMONFLAGS)
filters_centroid_la_LIBADD = $(MAGICKCORE_LIBS) $(MATH_LIBS)
# barymap filter module
filters_barymap_la_SOURCES = filters/barymap.c
filters_barymap_la_CPPFLAGS = $(MAGICK_FILTER_CPPFLAGS)
filters_barymap_la_LDFLAGS = $(MODULECOMMONFLAGS)
filters_barymap_la_LIBADD = $(MAGICKCORE_LIBS) $(MATH_LIBS)
# rhotheta filter module
filters_rhotheta_la_SOURCES = filters/rhotheta.c
filters_rhotheta_la_CPPFLAGS = $(MAGICK_FILTER_CPPFLAGS)
filters_rhotheta_la_LDFLAGS = $(MODULECOMMONFLAGS)
filters_rhotheta_la_LIBADD = $(MAGICKCORE_LIBS) $(MATH_LIBS)
# colsp012 filter module
filters_colsp012_la_SOURCES = filters/colsp012.c
filters_colsp012_la_CPPFLAGS = $(MAGICK_FILTER_CPPFLAGS)
filters_colsp012_la_LDFLAGS = $(MODULECOMMONFLAGS)
filters_colsp012_la_LIBADD = $(MAGICKCORE_LIBS) $(MATH_LIBS)
# polypix filter module
filters_polypix_la_SOURCES = filters/polypix.c
filters_polypix_la_CPPFLAGS = $(MAGICK_FILTER_CPPFLAGS)
filters_polypix_la_LDFLAGS = $(MODULECOMMONFLAGS)
filters_polypix_la_LIBADD = $(MAGICKCORE_LIBS) $(MATH_LIBS)
# polyreg filter module
filters_polyreg_la_SOURCES = filters/polyreg.c
filters_polyreg_la_CPPFLAGS = $(MAGICK_FILTER_CPPFLAGS)
filters_polyreg_la_LDFLAGS = $(MODULECOMMONFLAGS)
filters_polyreg_la_LIBADD = $(MAGICKCORE_LIBS) $(MATH_LIBS)
# deltri filter module
filters_deltri_la_SOURCES = filters/deltri.c
filters_deltri_la_CPPFLAGS = $(MAGICK_FILTER_CPPFLAGS)
filters_deltri_la_LDFLAGS = $(MODULECOMMONFLAGS)
filters_deltri_la_LIBADD = $(MAGICKCORE_LIBS) $(MATH_LIBS)
rem From image %1, rem make contrast-limited histogram-equalised (with iterative redistribution) version. @rem @rem Optional parameters: @rem %2 is limiting factor SD_FACT, so limit = mean + SD_FACT * standard_deviation. @rem Default 1. @rem %3 is percentage lift for shadows. Maximum < 100. Default 0, no lift. @rem %4 is percentage drop for highlights. Maximum < 100. Default 0, no drop. @rem %5 is output file, or null: for no output. @rem @rem Can also use: @rem eqlDEBUG if 1, also creates curve histograms. @rem eqlDIFF_LIMIT iteration stops when the count redistributed is less than this percentage. @rem Set to a value >= 100 to prevent iteration. @rem Default 1.0. @rem eqlSUPPRESS_OUT if 1, suppresses output. @rem @rem Updated: @rem 26-July-2022 for IM v7. @rem @if "%1"=="" findstr /B "rem @rem" %~f0 & exit /B 1 @setlocal enabledelayedexpansion @call echoOffSave call %PICTBAT%setInOut %1 eql set SD_FACT=%2 if "%SD_FACT%"=="." set SD_FACT= if "%SD_FACT%"=="" set SD_FACT=1 set LIFT_SHADOW_PC=%3 if "%LIFT_SHADOW_PC%"=="." set LIFT_SHADOW_PC= if "%LIFT_SHADOW_PC%"=="" set LIFT_SHADOW_PC=0 set DROP_HIGHLIGHT_PC=%4 if "%DROP_HIGHLIGHT_PC%"=="." set DROP_HIGHLIGHT_PC= if "%DROP_HIGHLIGHT_PC%"=="" set DROP_HIGHLIGHT_PC=0 if not "%5"=="" set OUTFILE=%5 if "%5"=="" ( set EQL_BASE=%~n1_%sioCODE% ) else ( set EQL_BASE=%~n5_%sioCODE% ) if "%eqlDIFF_LIMIT%"=="" set eqlDIFF_LIMIT=1 set TMPEXT=miff for /F "usebackq" %%L in (`cygpath %TEMP%`) do set CYGTEMP=%%L %IM7DEV%magick ^ %INFILE% ^ -colorspace Lab -channel R -separate -set colorspace sRGB ^ -process 'mkhisto norm' ^ %CYGTEMP%\%EQL_BASE%_gch.%TMPEXT% if ERRORLEVEL 1 exit /B 1 for /F "usebackq" %%L in (`%IMG7%magick ^ %TEMP%\%EQL_BASE%_gch.%TMPEXT% ^ -precision 15 ^ -format "histcap=%%[fx:(mean+%SD_FACT%*standard_deviation)*100]" ^ info:`) do set %%L echo %0: histcap=%histcap% set nIter=0 :loop %IM7DEV%magick ^ %CYGTEMP%\%EQL_BASE%_gch.%TMPEXT% ^ -channel RGB ^ -evaluate Min %histcap%%% ^ +channel ^ %CYGTEMP%\%EQL_BASE%_gchc_cap.%TMPEXT% for /F "usebackq" %%L in (`%IMG7%magick ^ %TEMP%\%EQL_BASE%_gch.%TMPEXT% ^ %TEMP%\%EQL_BASE%_gchc_cap.%TMPEXT% ^ -compose MinusSrc -composite ^ -precision 15 ^ -format "MeanDiffPC=%%[fx:mean*100]" ^ info:`) do set %%L echo %0: MeanDiffPC=%MeanDiffPC% %IM7DEV%magick ^ %CYGTEMP%\%EQL_BASE%_gch.%TMPEXT% ^ -channel RGB ^ -evaluate Min %histcap%%% ^ +channel ^ -evaluate Add %MeanDiffPC%%% ^ %CYGTEMP%\%EQL_BASE%_gch.%TMPEXT% for /F "usebackq" %%L in (`%IMG7%magick identify ^ -format "DO_LOOP=%%[fx:%MeanDiffPC%>%eqlDIFF_LIMIT%?1:0]" ^ xc:`) do set %%L rem If max(eql_gch) > histcap + epsilon, repeat. rem OR rem if %MeanDiffPC% > epsilon, repeat set /A nIter+=1 if %DO_LOOP%==1 goto loop echo %0: nIter=%nIter% if %LIFT_SHADOW_PC%==0 if %DROP_HIGHLIGHT_PC%==0 %IM7DEV%magick ^ %CYGTEMP%\%EQL_BASE%_gch.%TMPEXT% ^ -auto-level ^ %CYGTEMP%\%EQL_BASE%_gch.%TMPEXT% if %LIFT_SHADOW_PC%==0 goto skipShad set WX=0 rem In following, "-blur" should really be "decomb". set DECOMB=-blur 0x1 for /F "usebackq tokens=2 delims=:, " %%A in (`%IM7DEV%magick ^ %CYGTEMP%\%EQL_BASE%_gch.%TMPEXT% ^ -auto-level ^ +write %CYGTEMP%\%EQL_BASE%_gch.%TMPEXT% ^ %DECOMB% ^ -threshold %LIFT_SHADOW_PC%%% ^ -process onewhite ^ NULL: 2^>^&1`) do set WX=%%A rem %0: echo WX=%WX% if "%WX%"=="none" ( %IMG7%magick ^ %TEMP%\%EQL_BASE%_gch.%TMPEXT% ^ -fill #fff -colorize 100 ^ %TEMP%\%EQL_BASE%_gch.%TMPEXT% ) else if not %WX%==0 %IM7DEV%magick ^ %CYGTEMP%\%EQL_BASE%_gch.%TMPEXT% ^ -size %WX%x1 xc:gray(%LIFT_SHADOW_PC%%%) ^ -gravity West -composite ^ %CYGTEMP%\%EQL_BASE%_gch.%TMPEXT% :skipShad if %DROP_HIGHLIGHT_PC%==0 goto skipHigh set WX=0 rem In following, "-blur" should really be "decomb". set DECOMB=-blur 0x1 for /F "usebackq tokens=2 delims=:, " %%A in (`%IM7DEV%magick ^ %CYGTEMP%\%EQL_BASE%_gch.%TMPEXT% ^ -auto-level ^ +write %CYGTEMP%\%EQL_BASE%_gch.%TMPEXT% ^ %DECOMB% ^ -threshold %DROP_HIGHLIGHT_PC%%% ^ -flop ^ -process onewhite ^ NULL: 2^>^&1`) do set WX=%%A rem %0: echo WX=%WX% if "%WX%"=="none" ( %IMG7%magick ^ %TEMP%\%EQL_BASE%_gch.%TMPEXT% ^ -fill #fff -colorize 100 ^ %TEMP%\%EQL_BASE%_gch.%TMPEXT% ) else if not %WX%==0 %IM7DEV%magick ^ %CYGTEMP%\%EQL_BASE%_gch.%TMPEXT% ^ -size %WX%x1 xc:gray(%DROP_HIGHLIGHT_PC%%%) ^ -gravity East -composite ^ %CYGTEMP%\%EQL_BASE%_gch.%TMPEXT% :skipHigh %IM7DEV%magick ^ %CYGTEMP%\%EQL_BASE%_gch.%TMPEXT% ^ -process 'cumulhisto norm' ^ %CYGTEMP%\%EQL_BASE%_gchc_clhe_red.%TMPEXT% if /I "%OUTFILE%" NEQ "null:" if not "%eqlSUPPRESS_OUT%"=="1" %IMG7%magick ^ %INFILE% ^ %TEMP%\%EQL_BASE%_gchc_clhe_red.%TMPEXT% ^ -clut ^ %OUTFILE% if "%eqlDEBUG%"=="1" ( call %PICTBAT%graphLineCol %TEMP%\%EQL_BASE%_gch.%TMPEXT% . . 0 %EQL_BASE%_gch.png call %PICTBAT%graphLineCol %TEMP%\%EQL_BASE%_gchc_cap.%TMPEXT% . . 0 %EQL_BASE%_gchc_cap.png call %PICTBAT%graphLineCol %TEMP%\%EQL_BASE%_gchc_clhe_red.%TMPEXT% . . 0 %EQL_BASE%_gchc_clhe_red.png ) call echoRestore @endlocal & set eqlOUTFILE=%OUTFILE% &set eqlCLUT=%TEMP%\%EQL_BASE%_gchc_clhe_red.%TMPEXT%
rem Makes version of image %1 with histogram that matches the histogram of image %2.
rem Optional %3 is output file.
@rem
@rem Can also use:
@rem mhCOL_SP_IN eg -colorspace Lab -channel R
@rem mhCOL_SP_OUT eg +channel -colorspace sRGB
@rem mhDEBUG if 1, makes a file of the clut used.
@rem
@rem Updated:
@rem 27-July-2022 for IM v7.
@rem
@if "%2"=="" findstr /B "rem @rem" %~f0 & exit /B 1
@setlocal enabledelayedexpansion
@call echoOffSave
call %PICTBAT%setInOut %1 mh
if not "%3"=="" set OUTFILE=%3
set DBG_NAME=
set WR_CLUT=
set DBG_H1=
set DBG_H2=
set WR_DBG_H1=
set WR_DBG_H2=
if "%mhDEBUG%"=="1" (
set DBG_NAME=%INNAME%_mhcl.miff
set WR_CLUT= ^( +clone +write %INNAME%_mhcl.miff +delete ^)
) else if "%mhDEBUG%"=="2" (
set DBG_NAME=%INNAME%_mhcl.miff
set WR_CLUT= ^( +clone +write %INNAME%_mhcl.miff +delete ^)
set DBG_H1=%INNAME%_h1.miff
set WR_DBG_H1=+write !DBG_H1!
set DBG_H2=%INNAME%_h2.miff
set WR_DBG_H2=+write !DBG_H2!
)
%IM7DEV%magick ^
%INFILE% ^
%mhCOL_SP_IN% ^
( -clone 0 ^
-process 'mkhisto cumul norm v' ^
%WR_DBG_H1% ^
) ^
( %2 ^
%mhCOL_SP_IN% ^
-process 'mkhisto cumul norm v' ^
%WR_DBG_H2% ^
-process 'mkhisto cumul norm v' ^
) ^
( -clone 1-2 -clut ) ^
-delete 1-2 ^
%WR_CLUT% ^
-clut ^
%mhCOL_SP_OUT% ^
%OUTFILE%
if ERRORLEVEL 1 exit /B1
if not "%DBG_NAME%"=="" (
echo Also created %DBG_NAME%
call %PICTBAT%graphLineCol %DBG_NAME%
)
if not "%DBG_H1%"=="" (
echo Also created %DBG_H1%
call %PICTBAT%graphLineCol %DBG_H1%
)
if not "%DBG_H2%"=="" (
echo Also created %DBG_H2%
call %PICTBAT%graphLineCol %DBG_H2%
)
call echoRestore
@endlocal & set mhOUTFILE=%OUTFILE%
rem Given %1 a quoted kernel string
rem or name of text file, prefixed with "@",
rem makes image %2 of that kernel.
rem %3 is optional scale parameter, format:
rem {kernel_scale}[!^] [,{origin_addition}] [%]
rem %4 is post-processing, eg "-auto-level".
@rem
@rem Updated:
@rem 1-August-2022 for IM v7. Assumes magick is HDRI.
@rem
@if "%2"=="" findstr /B "rem @rem" %~f0 & exit /B 1
@setlocal enabledelayedexpansion
@call echoOffSave
call %PICTBAT%setInOut %1 k2i
set qKNL=%1
set sKNL=%~1
set OUTFILE=%2
set SCALE=%~3
if "%SCALE%"=="." set SCALE=
set sPOST=%~4
if "%sPOST%"=="." set sPOST=
if "%SCALE%"=="" (
set sSCALE=
) else (
set sSCALE=-define "convolve:scale=%SCALE%"
)
if "%sPOST%"=="." set sPOST=
echo sSCALE=%sSCALE% sPOST=%sPOST%
:: We use an impulse image, same size as kernel,
:: black but with white pixel at kernel origin.
:: Sample showkernel output:
:: Kernel "Blur" of size 41x1+20+0 with values from 0 to 0.0796737
:: But "User defined" (two words).
set WW=
set IS_FIRST=1
for /F "usebackq tokens=* eol=: delims= " %%A in (`%IMG7%magick ^
xc: ^
-define morphology:showkernel^=1 ^
-morphology convolve:0 %qKNL% ^
NULL: 2^>^&1`) do if !IS_FIRST!==1 (
set SIZE=%%A
set IS_FIRST=0
)
for /F "tokens=1-4 eol=: delims=x+ " %%A in ("%SIZE:*size =%") do (
set WW=%%A
set HH=%%B
set X=%%C
set Y=%%D
)
if "%WW%"=="" exit /B 1
if %WW% LSS 0 exit /B 1
if %WW% GTR a exit /B 1
if %WW% GTR A exit /B 1
echo %0: %WW%x%HH%+%X%+%Y%
%IMG7%magick ^
-size %WW%x%HH% xc:Black ^
-fill White -draw "point %X%,%Y%" ^
-alpha off ^
-define morphology:showkernel^=1 ^
%sSCALE% ^
-morphology convolve %qKNL% ^
%sPOST% ^
%OUTFILE%
call echoRestore
@endlocal & set k2iOUTFILE=%OUTFILE%
rem Given %1, an image with RGBA channels, rem creates four kernel strings, rem writing to environment variable prefix %2, suffixed _R, _G, _B and _A. rem rem CAUTION: Do not use this with large kernels, eg > 100 pixels. @rem @rem Updated: @rem 1-August-2022 for IM v7. @rem @if "%2"=="" findstr /B "rem @rem" %~f0 & exit /B 1 @setlocal enabledelayedexpansion @call echoOffSave call %PICTBAT%setInOut %1 i2k set N=0 set sR= for /F "usebackq" %%L in (`%IM7DEV%magick ^ %INFILE% ^ -channel RGBA ^ -separate ^ +channel ^ -process img2knl ^ NULL:`) do ( if !N!==0 set sR=%%L if !N!==1 set sG=%%L if !N!==2 set sB=%%L if !N!==3 set sA=%%L set /A N+=1 ) if "%sR%"=="" exit /B 1 call echoRestore endlocal & set i2kOUTFILE=%OUTFILE%& set %2_R=%sR%& set %2_G=%sG%& set %2_B=%sB%& set %2_A=%sA%
rem Given %1, an image with RGBA channels, rem creates four kernel strings, rem writing to text files prefix %2, suffixed _R, _G, _B and _A. rem %3 is list of channels to extract, any of RGBA any case. rem %4 is string to insert before the colon. rem rem FIXME: When "-process img2knl" can insert text before the colon, use that instead of chStrs. @rem @rem Updated: @rem 1-August-2022 for IM v7. Assumes magick is HDRI. @rem @if "%2"=="" findstr /B "rem @rem" %~f0 & exit /B 1 @setlocal enabledelayedexpansion @call echoOffSave call %PICTBAT%setInOut %1 i2kf set OUTPREF=%~dpn2 set OUTEXT=%~x2 set CHAN=%3 if "%CHAN%"=="." set CHAN= if "%CHAN%"=="" set CHAN=RGBA set BEF_COL=%~4 if "%BEF_COL%"=="." set BEF_COL= call %PICTBAT%getrgba %CHAN% if %rgbaR%==1 ( %IM7DEV%magick ^ %INFILE% ^ -channel R ^ -separate ^ +channel ^ -process img2knl ^ NULL: >%OUTPREF%_R%OUTEXT% if not "%BEF_COL%"=="" chStrs /p0 /m1 /i%OUTPREF%_R%OUTEXT% /f":" /t%BEF_COL%: ) if %rgbaG%==1 ( %IM7DEV%magick ^ %INFILE% ^ -channel G ^ -separate ^ +channel ^ -process img2knl ^ NULL: >%OUTPREF%_G%OUTEXT% if not "%BEF_COL%"=="" chStrs /p0 /m1 /i%OUTPREF%_G%OUTEXT% /f":" /t%BEF_COL%: ) if %rgbaB%==1 ( %IM7DEV%magick ^ %INFILE% ^ -channel B ^ -separate ^ +channel ^ -process img2knl ^ NULL: >%OUTPREF%_B%OUTEXT% if not "%BEF_COL%"=="" chStrs /p0 /m1 /i%OUTPREF%_B%OUTEXT% /f":" /t%BEF_COL%: ) if %rgbaA%==1 ( %IM7DEV%magick ^ %INFILE% ^ -channel A ^ -separate ^ +channel ^ -process img2knl ^ NULL: >%OUTPREF%_A%OUTEXT% if not "%BEF_COL%"=="" chStrs /p0 /m1 /i%OUTPREF%_A%OUTEXT% /f":" /t%BEF_COL%: ) dir %OUTPREF%* call echoRestore @endlocal
All images on this page were created by the commands shown.
To improve internet download speeds, some images may have been automatically converted (by ImageMagick, of course) from PNG or TIFF or MIFF to JPG.
My usual version of IM is:
%IMG7%magick -version
Version: ImageMagick 7.1.1-20 Q16-HDRI x86 98bb1d4:20231008 https://imagemagick.org Copyright: (C) 1999 ImageMagick Studio LLC License: https://imagemagick.org/script/license.php Features: Cipher DPC HDRI OpenCL OpenMP(2.0) Delegates (built-in): bzlib cairo freetype gslib heic jng jp2 jpeg jxl lcms lqr lzma openexr pangocairo png ps raqm raw rsvg tiff webp xml zip zlib Compiler: Visual Studio 2022 (193532217)
This customised development version is:
%IM7DEV%magick -version
Version: ImageMagick 7.1.1-39 (Beta) Q32-HDRI x86_64 629938332:20240913 https://imagemagick.org Copyright: (C) 1999 ImageMagick Studio LLC License: https://imagemagick.org/script/license.php Features: Cipher DPC HDRI Modules OpenCL OpenMP(4.5) Delegates (built-in): bzlib cairo fftw fontconfig freetype heic jbig jng jpeg lcms ltdl lzma pangocairo png raqm raw rsvg tiff webp wmf x xml zip zlib zstd Compiler: gcc (11.3)
Source file for this web page is customim.h1. To re-create this web page, run "procH1 customim". Also run "procH1 zipbats" to refresh the C files in the zip.
This page, including the images, is my copyright. Anyone is permitted to use or adapt any of my code, scripts or images for any purpose, including commercial use.
My C code borrows heavily from ImageMagick code, and of course copyright for those portions remain with ImageMagick Studio LLC. Any use of my code must also abide by their conditions:
Copyright 1999-2014 ImageMagick Studio LLC, a non-profit organization dedicated to making software imaging solutions freely available.
You may not use this file except in compliance with the License. Obtain a copy of the License at
http://www.imagemagick.org/script/license.phpUnless 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 26-Feb-2025 17:53:22.
Copyright © 2025 Alan Gibson.