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 | subSrchPnts.bat srchImgCring.bat salMapCrop.bat chainSrch.bat 24cardXX.bat 24cardX2.bat |
srt3d | rotCubeAnim.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 )
When we have arguments, we need to bracket the module name and arguments with quotes.
%IM7DEV%magick ^ xc:red ^ -size 1x10 gradient:blue-green ^ -process "echostuff hello world" ^ NULL:
echostuff: 2 arguments: [hello] [world] mc=black echostuff: Input image [0] [red] depth 32 size 1x1 Virtual pixel method 0 [(null)] mattecolor: (null) fuzz 0 alpha is on: no channels 3 meta_channels 0 ( MaxPixelChannels = 64 ) GetPixelChannels() = 3 Number of channels with update trait = 3 Image channels: i, channel, name, traits 0, 0, R, traits: 2=(update ) 1, 1, G, traits: 2=(update ) 2, 2, B, traits: 2=(update ) channel_map: i, channel, offset, traits 0, 0, 0 traits: 2=(update ) 1, 1, 1 traits: 2=(update ) 2, 2, 2 traits: 2=(update ) 3, 0, 0 traits: 0=(undefined ) 4, 0, 0 traits: 0=(undefined ) 5, 0, 0 traits: 0=(undefined ) 6, 0, 0 traits: 0=(undefined ) 7, 0, 0 traits: 0=(undefined ) 8, 0, 0 traits: 0=(undefined ) 9, 0, 0 traits: 0=(undefined ) 10, 0, 0 traits: 0=(undefined ) 11, 0, 0 traits: 0=(undefined ) 12, 0, 0 traits: 0=(undefined ) 13, 0, 0 traits: 0=(undefined ) 14, 0, 0 traits: 0=(undefined ) 15, 0, 0 traits: 0=(undefined ) 16, 0, 0 traits: 0=(undefined ) 17, 0, 0 traits: 0=(undefined ) 18, 0, 0 traits: 0=(undefined ) 19, 0, 0 traits: 0=(undefined ) 20, 0, 0 traits: 0=(undefined ) 21, 0, 0 traits: 0=(undefined ) 22, 0, 0 traits: 0=(undefined ) 23, 0, 0 traits: 0=(undefined ) 24, 0, 0 traits: 0=(undefined ) 25, 0, 0 traits: 0=(undefined ) 26, 0, 0 traits: 0=(undefined ) 27, 0, 0 traits: 0=(undefined ) 28, 0, 0 traits: 0=(undefined ) 29, 0, 0 traits: 0=(undefined ) 30, 0, 0 traits: 0=(undefined ) 31, 0, 0 traits: 0=(undefined ) 32, 0, 0 traits: 0=(undefined ) 33, 0, 0 traits: 0=(undefined ) 34, 0, 0 traits: 0=(undefined ) 35, 0, 0 traits: 0=(undefined ) 36, 0, 0 traits: 0=(undefined ) 37, 0, 0 traits: 0=(undefined ) 38, 0, 0 traits: 0=(undefined ) 39, 0, 0 traits: 0=(undefined ) 40, 0, 0 traits: 0=(undefined ) 41, 0, 0 traits: 0=(undefined ) 42, 0, 0 traits: 0=(undefined ) 43, 0, 0 traits: 0=(undefined ) 44, 0, 0 traits: 0=(undefined ) 45, 0, 0 traits: 0=(undefined ) 46, 0, 0 traits: 0=(undefined ) 47, 0, 0 traits: 0=(undefined ) 48, 0, 0 traits: 0=(undefined ) 49, 0, 0 traits: 0=(undefined ) 50, 0, 0 traits: 0=(undefined ) 51, 0, 0 traits: 0=(undefined ) 52, 0, 0 traits: 0=(undefined ) 53, 0, 0 traits: 0=(undefined ) 54, 0, 0 traits: 0=(undefined ) 55, 0, 0 traits: 0=(undefined ) 56, 0, 0 traits: 0=(undefined ) 57, 0, 0 traits: 0=(undefined ) 58, 0, 0 traits: 0=(undefined ) 59, 0, 0 traits: 0=(undefined ) 60, 0, 0 traits: 0=(undefined ) 61, 0, 0 traits: 0=(undefined ) 62, 0, 0 traits: 0=(undefined ) 63, 0, 0 traits: 0=(undefined ) mc=black echostuff: Input image [1] [blue] depth 32 size 1x10 Virtual pixel method 0 [(null)] mattecolor: (null) fuzz 0 alpha is on: no channels 3 meta_channels 0 ( MaxPixelChannels = 64 ) GetPixelChannels() = 3 Number of channels with update trait = 3 Image channels: i, channel, name, traits 0, 0, R, traits: 2=(update ) 1, 1, G, traits: 2=(update ) 2, 2, B, traits: 2=(update ) channel_map: i, channel, offset, traits 0, 0, 0 traits: 2=(update ) 1, 1, 1 traits: 2=(update ) 2, 2, 2 traits: 2=(update ) 3, 0, 0 traits: 0=(undefined ) 4, 0, 0 traits: 0=(undefined ) 5, 0, 0 traits: 0=(undefined ) 6, 0, 0 traits: 0=(undefined ) 7, 0, 0 traits: 0=(undefined ) 8, 0, 0 traits: 0=(undefined ) 9, 0, 0 traits: 0=(undefined ) 10, 0, 0 traits: 0=(undefined ) 11, 0, 0 traits: 0=(undefined ) 12, 0, 0 traits: 0=(undefined ) 13, 0, 0 traits: 0=(undefined ) 14, 0, 0 traits: 0=(undefined ) 15, 0, 0 traits: 0=(undefined ) 16, 0, 0 traits: 0=(undefined ) 17, 0, 0 traits: 0=(undefined ) 18, 0, 0 traits: 0=(undefined ) 19, 0, 0 traits: 0=(undefined ) 20, 0, 0 traits: 0=(undefined ) 21, 0, 0 traits: 0=(undefined ) 22, 0, 0 traits: 0=(undefined ) 23, 0, 0 traits: 0=(undefined ) 24, 0, 0 traits: 0=(undefined ) 25, 0, 0 traits: 0=(undefined ) 26, 0, 0 traits: 0=(undefined ) 27, 0, 0 traits: 0=(undefined ) 28, 0, 0 traits: 0=(undefined ) 29, 0, 0 traits: 0=(undefined ) 30, 0, 0 traits: 0=(undefined ) 31, 0, 0 traits: 0=(undefined ) 32, 0, 0 traits: 0=(undefined ) 33, 0, 0 traits: 0=(undefined ) 34, 0, 0 traits: 0=(undefined ) 35, 0, 0 traits: 0=(undefined ) 36, 0, 0 traits: 0=(undefined ) 37, 0, 0 traits: 0=(undefined ) 38, 0, 0 traits: 0=(undefined ) 39, 0, 0 traits: 0=(undefined ) 40, 0, 0 traits: 0=(undefined ) 41, 0, 0 traits: 0=(undefined ) 42, 0, 0 traits: 0=(undefined ) 43, 0, 0 traits: 0=(undefined ) 44, 0, 0 traits: 0=(undefined ) 45, 0, 0 traits: 0=(undefined ) 46, 0, 0 traits: 0=(undefined ) 47, 0, 0 traits: 0=(undefined ) 48, 0, 0 traits: 0=(undefined ) 49, 0, 0 traits: 0=(undefined ) 50, 0, 0 traits: 0=(undefined ) 51, 0, 0 traits: 0=(undefined ) 52, 0, 0 traits: 0=(undefined ) 53, 0, 0 traits: 0=(undefined ) 54, 0, 0 traits: 0=(undefined ) 55, 0, 0 traits: 0=(undefined ) 56, 0, 0 traits: 0=(undefined ) 57, 0, 0 traits: 0=(undefined ) 58, 0, 0 traits: 0=(undefined ) 59, 0, 0 traits: 0=(undefined ) 60, 0, 0 traits: 0=(undefined ) 61, 0, 0 traits: 0=(undefined ) 62, 0, 0 traits: 0=(undefined ) 63, 0, 0 traits: 0=(undefined )
Single quotes also work. The "wizard" image occurs after -process, so echostuff can't see it.
%IM7DEV%magick ^ xc:red ^ -size 1x10 gradient:blue-green ^ rose: ^ -process 'echostuff hello world' ^ wizard: ^ NULL:
echostuff: 2 arguments: [hello] [world] mc=black echostuff: Input image [0] [red] depth 32 size 1x1 Virtual pixel method 0 [(null)] mattecolor: (null) fuzz 0 alpha is on: no channels 3 meta_channels 0 ( MaxPixelChannels = 64 ) GetPixelChannels() = 3 Number of channels with update trait = 3 Image channels: i, channel, name, traits 0, 0, R, traits: 2=(update ) 1, 1, G, traits: 2=(update ) 2, 2, B, traits: 2=(update ) channel_map: i, channel, offset, traits 0, 0, 0 traits: 2=(update ) 1, 1, 1 traits: 2=(update ) 2, 2, 2 traits: 2=(update ) 3, 0, 0 traits: 0=(undefined ) 4, 0, 0 traits: 0=(undefined ) 5, 0, 0 traits: 0=(undefined ) 6, 0, 0 traits: 0=(undefined ) 7, 0, 0 traits: 0=(undefined ) 8, 0, 0 traits: 0=(undefined ) 9, 0, 0 traits: 0=(undefined ) 10, 0, 0 traits: 0=(undefined ) 11, 0, 0 traits: 0=(undefined ) 12, 0, 0 traits: 0=(undefined ) 13, 0, 0 traits: 0=(undefined ) 14, 0, 0 traits: 0=(undefined ) 15, 0, 0 traits: 0=(undefined ) 16, 0, 0 traits: 0=(undefined ) 17, 0, 0 traits: 0=(undefined ) 18, 0, 0 traits: 0=(undefined ) 19, 0, 0 traits: 0=(undefined ) 20, 0, 0 traits: 0=(undefined ) 21, 0, 0 traits: 0=(undefined ) 22, 0, 0 traits: 0=(undefined ) 23, 0, 0 traits: 0=(undefined ) 24, 0, 0 traits: 0=(undefined ) 25, 0, 0 traits: 0=(undefined ) 26, 0, 0 traits: 0=(undefined ) 27, 0, 0 traits: 0=(undefined ) 28, 0, 0 traits: 0=(undefined ) 29, 0, 0 traits: 0=(undefined ) 30, 0, 0 traits: 0=(undefined ) 31, 0, 0 traits: 0=(undefined ) 32, 0, 0 traits: 0=(undefined ) 33, 0, 0 traits: 0=(undefined ) 34, 0, 0 traits: 0=(undefined ) 35, 0, 0 traits: 0=(undefined ) 36, 0, 0 traits: 0=(undefined ) 37, 0, 0 traits: 0=(undefined ) 38, 0, 0 traits: 0=(undefined ) 39, 0, 0 traits: 0=(undefined ) 40, 0, 0 traits: 0=(undefined ) 41, 0, 0 traits: 0=(undefined ) 42, 0, 0 traits: 0=(undefined ) 43, 0, 0 traits: 0=(undefined ) 44, 0, 0 traits: 0=(undefined ) 45, 0, 0 traits: 0=(undefined ) 46, 0, 0 traits: 0=(undefined ) 47, 0, 0 traits: 0=(undefined ) 48, 0, 0 traits: 0=(undefined ) 49, 0, 0 traits: 0=(undefined ) 50, 0, 0 traits: 0=(undefined ) 51, 0, 0 traits: 0=(undefined ) 52, 0, 0 traits: 0=(undefined ) 53, 0, 0 traits: 0=(undefined ) 54, 0, 0 traits: 0=(undefined ) 55, 0, 0 traits: 0=(undefined ) 56, 0, 0 traits: 0=(undefined ) 57, 0, 0 traits: 0=(undefined ) 58, 0, 0 traits: 0=(undefined ) 59, 0, 0 traits: 0=(undefined ) 60, 0, 0 traits: 0=(undefined ) 61, 0, 0 traits: 0=(undefined ) 62, 0, 0 traits: 0=(undefined ) 63, 0, 0 traits: 0=(undefined ) mc=black echostuff: Input image [1] [blue] depth 32 size 1x10 Virtual pixel method 0 [(null)] mattecolor: (null) fuzz 0 alpha is on: no channels 3 meta_channels 0 ( MaxPixelChannels = 64 ) GetPixelChannels() = 3 Number of channels with update trait = 3 Image channels: i, channel, name, traits 0, 0, R, traits: 2=(update ) 1, 1, G, traits: 2=(update ) 2, 2, B, traits: 2=(update ) channel_map: i, channel, offset, traits 0, 0, 0 traits: 2=(update ) 1, 1, 1 traits: 2=(update ) 2, 2, 2 traits: 2=(update ) 3, 0, 0 traits: 0=(undefined ) 4, 0, 0 traits: 0=(undefined ) 5, 0, 0 traits: 0=(undefined ) 6, 0, 0 traits: 0=(undefined ) 7, 0, 0 traits: 0=(undefined ) 8, 0, 0 traits: 0=(undefined ) 9, 0, 0 traits: 0=(undefined ) 10, 0, 0 traits: 0=(undefined ) 11, 0, 0 traits: 0=(undefined ) 12, 0, 0 traits: 0=(undefined ) 13, 0, 0 traits: 0=(undefined ) 14, 0, 0 traits: 0=(undefined ) 15, 0, 0 traits: 0=(undefined ) 16, 0, 0 traits: 0=(undefined ) 17, 0, 0 traits: 0=(undefined ) 18, 0, 0 traits: 0=(undefined ) 19, 0, 0 traits: 0=(undefined ) 20, 0, 0 traits: 0=(undefined ) 21, 0, 0 traits: 0=(undefined ) 22, 0, 0 traits: 0=(undefined ) 23, 0, 0 traits: 0=(undefined ) 24, 0, 0 traits: 0=(undefined ) 25, 0, 0 traits: 0=(undefined ) 26, 0, 0 traits: 0=(undefined ) 27, 0, 0 traits: 0=(undefined ) 28, 0, 0 traits: 0=(undefined ) 29, 0, 0 traits: 0=(undefined ) 30, 0, 0 traits: 0=(undefined ) 31, 0, 0 traits: 0=(undefined ) 32, 0, 0 traits: 0=(undefined ) 33, 0, 0 traits: 0=(undefined ) 34, 0, 0 traits: 0=(undefined ) 35, 0, 0 traits: 0=(undefined ) 36, 0, 0 traits: 0=(undefined ) 37, 0, 0 traits: 0=(undefined ) 38, 0, 0 traits: 0=(undefined ) 39, 0, 0 traits: 0=(undefined ) 40, 0, 0 traits: 0=(undefined ) 41, 0, 0 traits: 0=(undefined ) 42, 0, 0 traits: 0=(undefined ) 43, 0, 0 traits: 0=(undefined ) 44, 0, 0 traits: 0=(undefined ) 45, 0, 0 traits: 0=(undefined ) 46, 0, 0 traits: 0=(undefined ) 47, 0, 0 traits: 0=(undefined ) 48, 0, 0 traits: 0=(undefined ) 49, 0, 0 traits: 0=(undefined ) 50, 0, 0 traits: 0=(undefined ) 51, 0, 0 traits: 0=(undefined ) 52, 0, 0 traits: 0=(undefined ) 53, 0, 0 traits: 0=(undefined ) 54, 0, 0 traits: 0=(undefined ) 55, 0, 0 traits: 0=(undefined ) 56, 0, 0 traits: 0=(undefined ) 57, 0, 0 traits: 0=(undefined ) 58, 0, 0 traits: 0=(undefined ) 59, 0, 0 traits: 0=(undefined ) 60, 0, 0 traits: 0=(undefined ) 61, 0, 0 traits: 0=(undefined ) 62, 0, 0 traits: 0=(undefined ) 63, 0, 0 traits: 0=(undefined ) mc=black echostuff: Input image [2] [ROSE] depth 8 size 70x46 Virtual pixel method 0 [(null)] mattecolor: (null) fuzz 0 alpha is on: no channels 3 meta_channels 0 ( MaxPixelChannels = 64 ) GetPixelChannels() = 3 Number of channels with update trait = 3 Image channels: i, channel, name, traits 0, 0, R, traits: 2=(update ) 1, 1, G, traits: 2=(update ) 2, 2, B, traits: 2=(update ) channel_map: i, channel, offset, traits 0, 0, 0 traits: 2=(update ) 1, 1, 1 traits: 2=(update ) 2, 2, 2 traits: 2=(update ) 3, 0, 0 traits: 0=(undefined ) 4, 0, 0 traits: 0=(undefined ) 5, 0, 0 traits: 0=(undefined ) 6, 0, 0 traits: 0=(undefined ) 7, 0, 0 traits: 0=(undefined ) 8, 0, 0 traits: 0=(undefined ) 9, 0, 0 traits: 0=(undefined ) 10, 0, 0 traits: 0=(undefined ) 11, 0, 0 traits: 0=(undefined ) 12, 0, 0 traits: 0=(undefined ) 13, 0, 0 traits: 0=(undefined ) 14, 0, 0 traits: 0=(undefined ) 15, 0, 0 traits: 0=(undefined ) 16, 0, 0 traits: 0=(undefined ) 17, 0, 0 traits: 0=(undefined ) 18, 0, 0 traits: 0=(undefined ) 19, 0, 0 traits: 0=(undefined ) 20, 0, 0 traits: 0=(undefined ) 21, 0, 0 traits: 0=(undefined ) 22, 0, 0 traits: 0=(undefined ) 23, 0, 0 traits: 0=(undefined ) 24, 0, 0 traits: 0=(undefined ) 25, 0, 0 traits: 0=(undefined ) 26, 0, 0 traits: 0=(undefined ) 27, 0, 0 traits: 0=(undefined ) 28, 0, 0 traits: 0=(undefined ) 29, 0, 0 traits: 0=(undefined ) 30, 0, 0 traits: 0=(undefined ) 31, 0, 0 traits: 0=(undefined ) 32, 0, 0 traits: 0=(undefined ) 33, 0, 0 traits: 0=(undefined ) 34, 0, 0 traits: 0=(undefined ) 35, 0, 0 traits: 0=(undefined ) 36, 0, 0 traits: 0=(undefined ) 37, 0, 0 traits: 0=(undefined ) 38, 0, 0 traits: 0=(undefined ) 39, 0, 0 traits: 0=(undefined ) 40, 0, 0 traits: 0=(undefined ) 41, 0, 0 traits: 0=(undefined ) 42, 0, 0 traits: 0=(undefined ) 43, 0, 0 traits: 0=(undefined ) 44, 0, 0 traits: 0=(undefined ) 45, 0, 0 traits: 0=(undefined ) 46, 0, 0 traits: 0=(undefined ) 47, 0, 0 traits: 0=(undefined ) 48, 0, 0 traits: 0=(undefined ) 49, 0, 0 traits: 0=(undefined ) 50, 0, 0 traits: 0=(undefined ) 51, 0, 0 traits: 0=(undefined ) 52, 0, 0 traits: 0=(undefined ) 53, 0, 0 traits: 0=(undefined ) 54, 0, 0 traits: 0=(undefined ) 55, 0, 0 traits: 0=(undefined ) 56, 0, 0 traits: 0=(undefined ) 57, 0, 0 traits: 0=(undefined ) 58, 0, 0 traits: 0=(undefined ) 59, 0, 0 traits: 0=(undefined ) 60, 0, 0 traits: 0=(undefined ) 61, 0, 0 traits: 0=(undefined ) 62, 0, 0 traits: 0=(undefined ) 63, 0, 0 traits: 0=(undefined ) AcquireImageColormap: image->colors=256 sizeof(*image->colormap)=88
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 ) AcquireImageColormap: image->colors=256 sizeof(*image->colormap)=88
What happens if we don't clone?
%IM7DEV%magick ^ xc:red ^ -size 1x10 gradient:blue-green ^ rose: ^ ( ^ -process 'echostuff hello world' ^ ) ^ wizard: ^ NULL: cmd /c exit /B 0
magick: no images found for operation `-process' at CLI arg 7 @ error/operation.c/CLIOption/5479.
IM v7 correctly reports an error: we start a new list with "(", and then try to "-process" an image, but that are no images in the list.
From v6, we get a weird result. The process module is given a list of four images: the three that come before it, and also the one that comes after it. This may be a legacy from old syntax when arguments were given before the images. Open parenthesis should normally be immediately followed by an image (not an operation) so I would regard the visibility of wizard to the module as an "undocumented feature".
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 ) mc=black echostuff: Input image [1] [toes.png] depth 16 size 200x175 Virtual pixel method 0 [(null)] mattecolor: (null) fuzz 0 alpha is on: no channels 3 meta_channels 0 ( MaxPixelChannels = 64 ) GetPixelChannels() = 3 Number of channels with update trait = 3 Image channels: i, channel, name, traits 0, 0, R, traits: 2=(update ) 1, 1, G, traits: 2=(update ) 2, 2, B, traits: 2=(update ) channel_map: i, channel, offset, traits 0, 0, 0 traits: 2=(update ) 1, 1, 1 traits: 2=(update ) 2, 2, 2 traits: 2=(update ) 3, 0, 0 traits: 0=(undefined ) 4, 0, 0 traits: 0=(undefined ) 5, 0, 0 traits: 0=(undefined ) 6, 0, 0 traits: 0=(undefined ) 7, 0, 0 traits: 0=(undefined ) 8, 0, 0 traits: 0=(undefined ) 9, 0, 0 traits: 0=(undefined ) 10, 0, 0 traits: 0=(undefined ) 11, 0, 0 traits: 0=(undefined ) 12, 0, 0 traits: 0=(undefined ) 13, 0, 0 traits: 0=(undefined ) 14, 0, 0 traits: 0=(undefined ) 15, 0, 0 traits: 0=(undefined ) 16, 0, 0 traits: 0=(undefined ) 17, 0, 0 traits: 0=(undefined ) 18, 0, 0 traits: 0=(undefined ) 19, 0, 0 traits: 0=(undefined ) 20, 0, 0 traits: 0=(undefined ) 21, 0, 0 traits: 0=(undefined ) 22, 0, 0 traits: 0=(undefined ) 23, 0, 0 traits: 0=(undefined ) 24, 0, 0 traits: 0=(undefined ) 25, 0, 0 traits: 0=(undefined ) 26, 0, 0 traits: 0=(undefined ) 27, 0, 0 traits: 0=(undefined ) 28, 0, 0 traits: 0=(undefined ) 29, 0, 0 traits: 0=(undefined ) 30, 0, 0 traits: 0=(undefined ) 31, 0, 0 traits: 0=(undefined ) 32, 0, 0 traits: 0=(undefined ) 33, 0, 0 traits: 0=(undefined ) 34, 0, 0 traits: 0=(undefined ) 35, 0, 0 traits: 0=(undefined ) 36, 0, 0 traits: 0=(undefined ) 37, 0, 0 traits: 0=(undefined ) 38, 0, 0 traits: 0=(undefined ) 39, 0, 0 traits: 0=(undefined ) 40, 0, 0 traits: 0=(undefined ) 41, 0, 0 traits: 0=(undefined ) 42, 0, 0 traits: 0=(undefined ) 43, 0, 0 traits: 0=(undefined ) 44, 0, 0 traits: 0=(undefined ) 45, 0, 0 traits: 0=(undefined ) 46, 0, 0 traits: 0=(undefined ) 47, 0, 0 traits: 0=(undefined ) 48, 0, 0 traits: 0=(undefined ) 49, 0, 0 traits: 0=(undefined ) 50, 0, 0 traits: 0=(undefined ) 51, 0, 0 traits: 0=(undefined ) 52, 0, 0 traits: 0=(undefined ) 53, 0, 0 traits: 0=(undefined ) 54, 0, 0 traits: 0=(undefined ) 55, 0, 0 traits: 0=(undefined ) 56, 0, 0 traits: 0=(undefined ) 57, 0, 0 traits: 0=(undefined ) 58, 0, 0 traits: 0=(undefined ) 59, 0, 0 traits: 0=(undefined ) 60, 0, 0 traits: 0=(undefined ) 61, 0, 0 traits: 0=(undefined ) 62, 0, 0 traits: 0=(undefined ) 63, 0, 0 traits: 0=(undefined ) mc=black echostuff: Input image [2] [toes.png] depth 16 size 200x175 Virtual pixel method 0 [(null)] mattecolor: (null) fuzz 0 alpha is on: no channels 3 meta_channels 0 ( MaxPixelChannels = 64 ) GetPixelChannels() = 3 Number of channels with update trait = 3 Image channels: i, channel, name, traits 0, 0, R, traits: 2=(update ) 1, 1, G, traits: 2=(update ) 2, 2, B, traits: 2=(update ) channel_map: i, channel, offset, traits 0, 0, 0 traits: 2=(update ) 1, 1, 1 traits: 2=(update ) 2, 2, 2 traits: 2=(update ) 3, 0, 0 traits: 0=(undefined ) 4, 0, 0 traits: 0=(undefined ) 5, 0, 0 traits: 0=(undefined ) 6, 0, 0 traits: 0=(undefined ) 7, 0, 0 traits: 0=(undefined ) 8, 0, 0 traits: 0=(undefined ) 9, 0, 0 traits: 0=(undefined ) 10, 0, 0 traits: 0=(undefined ) 11, 0, 0 traits: 0=(undefined ) 12, 0, 0 traits: 0=(undefined ) 13, 0, 0 traits: 0=(undefined ) 14, 0, 0 traits: 0=(undefined ) 15, 0, 0 traits: 0=(undefined ) 16, 0, 0 traits: 0=(undefined ) 17, 0, 0 traits: 0=(undefined ) 18, 0, 0 traits: 0=(undefined ) 19, 0, 0 traits: 0=(undefined ) 20, 0, 0 traits: 0=(undefined ) 21, 0, 0 traits: 0=(undefined ) 22, 0, 0 traits: 0=(undefined ) 23, 0, 0 traits: 0=(undefined ) 24, 0, 0 traits: 0=(undefined ) 25, 0, 0 traits: 0=(undefined ) 26, 0, 0 traits: 0=(undefined ) 27, 0, 0 traits: 0=(undefined ) 28, 0, 0 traits: 0=(undefined ) 29, 0, 0 traits: 0=(undefined ) 30, 0, 0 traits: 0=(undefined ) 31, 0, 0 traits: 0=(undefined ) 32, 0, 0 traits: 0=(undefined ) 33, 0, 0 traits: 0=(undefined ) 34, 0, 0 traits: 0=(undefined ) 35, 0, 0 traits: 0=(undefined ) 36, 0, 0 traits: 0=(undefined ) 37, 0, 0 traits: 0=(undefined ) 38, 0, 0 traits: 0=(undefined ) 39, 0, 0 traits: 0=(undefined ) 40, 0, 0 traits: 0=(undefined ) 41, 0, 0 traits: 0=(undefined ) 42, 0, 0 traits: 0=(undefined ) 43, 0, 0 traits: 0=(undefined ) 44, 0, 0 traits: 0=(undefined ) 45, 0, 0 traits: 0=(undefined ) 46, 0, 0 traits: 0=(undefined ) 47, 0, 0 traits: 0=(undefined ) 48, 0, 0 traits: 0=(undefined ) 49, 0, 0 traits: 0=(undefined ) 50, 0, 0 traits: 0=(undefined ) 51, 0, 0 traits: 0=(undefined ) 52, 0, 0 traits: 0=(undefined ) 53, 0, 0 traits: 0=(undefined ) 54, 0, 0 traits: 0=(undefined ) 55, 0, 0 traits: 0=(undefined ) 56, 0, 0 traits: 0=(undefined ) 57, 0, 0 traits: 0=(undefined ) 58, 0, 0 traits: 0=(undefined ) 59, 0, 0 traits: 0=(undefined ) 60, 0, 0 traits: 0=(undefined ) 61, 0, 0 traits: 0=(undefined ) 62, 0, 0 traits: 0=(undefined ) 63, 0, 0 traits: 0=(undefined ) toes.png[0] PNG 267x233 267x233+0+0 16-bit TrueColor sRGB 320268B 0.109u 0:00.123 toes.png[1] PNG 200x175 200x175+0+0 16-bit sRGB 320268B 0.109u 0:00.123 toes.png[2] PNG 200x175 200x175+0+0 16-bit sRGB 320268B 0.109u 0:00.123 |
replacelast.c replaces the last image in the list with a green image.
-process replacelast is equivalent to ( -repect-parentheses +clone -fill green -colorize 100 ) -delete -2.
%IM7DEV%magick ^ toes.png ^ ( +clone -fill khaki -colorize 50 -resize 75%% ) ^ -process replacelast ^ -process echostuff ^ -write info: ^ +append +repage ^ cu_repl.png echostuff: no arguments mc=black echostuff: Input image [0] [toes.png] depth 16 size 267x233 Virtual pixel method 0 [(null)] mattecolor: (null) fuzz 0 alpha is on: no channels 3 meta_channels 0 ( MaxPixelChannels = 64 ) GetPixelChannels() = 3 Number of channels with update trait = 3 Image channels: i, channel, name, traits 0, 0, R, traits: 2=(update ) 1, 1, G, traits: 2=(update ) 2, 2, B, traits: 2=(update ) channel_map: i, channel, offset, traits 0, 0, 0 traits: 2=(update ) 1, 1, 1 traits: 2=(update ) 2, 2, 2 traits: 2=(update ) 3, 0, 0 traits: 0=(undefined ) 4, 0, 0 traits: 0=(undefined ) 5, 0, 0 traits: 0=(undefined ) 6, 0, 0 traits: 0=(undefined ) 7, 0, 0 traits: 0=(undefined ) 8, 0, 0 traits: 0=(undefined ) 9, 0, 0 traits: 0=(undefined ) 10, 0, 0 traits: 0=(undefined ) 11, 0, 0 traits: 0=(undefined ) 12, 0, 0 traits: 0=(undefined ) 13, 0, 0 traits: 0=(undefined ) 14, 0, 0 traits: 0=(undefined ) 15, 0, 0 traits: 0=(undefined ) 16, 0, 0 traits: 0=(undefined ) 17, 0, 0 traits: 0=(undefined ) 18, 0, 0 traits: 0=(undefined ) 19, 0, 0 traits: 0=(undefined ) 20, 0, 0 traits: 0=(undefined ) 21, 0, 0 traits: 0=(undefined ) 22, 0, 0 traits: 0=(undefined ) 23, 0, 0 traits: 0=(undefined ) 24, 0, 0 traits: 0=(undefined ) 25, 0, 0 traits: 0=(undefined ) 26, 0, 0 traits: 0=(undefined ) 27, 0, 0 traits: 0=(undefined ) 28, 0, 0 traits: 0=(undefined ) 29, 0, 0 traits: 0=(undefined ) 30, 0, 0 traits: 0=(undefined ) 31, 0, 0 traits: 0=(undefined ) 32, 0, 0 traits: 0=(undefined ) 33, 0, 0 traits: 0=(undefined ) 34, 0, 0 traits: 0=(undefined ) 35, 0, 0 traits: 0=(undefined ) 36, 0, 0 traits: 0=(undefined ) 37, 0, 0 traits: 0=(undefined ) 38, 0, 0 traits: 0=(undefined ) 39, 0, 0 traits: 0=(undefined ) 40, 0, 0 traits: 0=(undefined ) 41, 0, 0 traits: 0=(undefined ) 42, 0, 0 traits: 0=(undefined ) 43, 0, 0 traits: 0=(undefined ) 44, 0, 0 traits: 0=(undefined ) 45, 0, 0 traits: 0=(undefined ) 46, 0, 0 traits: 0=(undefined ) 47, 0, 0 traits: 0=(undefined ) 48, 0, 0 traits: 0=(undefined ) 49, 0, 0 traits: 0=(undefined ) 50, 0, 0 traits: 0=(undefined ) 51, 0, 0 traits: 0=(undefined ) 52, 0, 0 traits: 0=(undefined ) 53, 0, 0 traits: 0=(undefined ) 54, 0, 0 traits: 0=(undefined ) 55, 0, 0 traits: 0=(undefined ) 56, 0, 0 traits: 0=(undefined ) 57, 0, 0 traits: 0=(undefined ) 58, 0, 0 traits: 0=(undefined ) 59, 0, 0 traits: 0=(undefined ) 60, 0, 0 traits: 0=(undefined ) 61, 0, 0 traits: 0=(undefined ) 62, 0, 0 traits: 0=(undefined ) 63, 0, 0 traits: 0=(undefined ) mc=black echostuff: Input image [1] [toes.png] depth 8 size 200x175 Virtual pixel method 0 [(null)] mattecolor: (null) fuzz 0 alpha is on: no channels 3 meta_channels 0 ( MaxPixelChannels = 64 ) GetPixelChannels() = 3 Number of channels with update trait = 3 Image channels: i, channel, name, traits 0, 0, R, traits: 2=(update ) 1, 1, G, traits: 2=(update ) 2, 2, B, traits: 2=(update ) channel_map: i, channel, offset, traits 0, 0, 0 traits: 2=(update ) 1, 1, 1 traits: 2=(update ) 2, 2, 2 traits: 2=(update ) 3, 0, 0 traits: 0=(undefined ) 4, 0, 0 traits: 0=(undefined ) 5, 0, 0 traits: 0=(undefined ) 6, 0, 0 traits: 0=(undefined ) 7, 0, 0 traits: 0=(undefined ) 8, 0, 0 traits: 0=(undefined ) 9, 0, 0 traits: 0=(undefined ) 10, 0, 0 traits: 0=(undefined ) 11, 0, 0 traits: 0=(undefined ) 12, 0, 0 traits: 0=(undefined ) 13, 0, 0 traits: 0=(undefined ) 14, 0, 0 traits: 0=(undefined ) 15, 0, 0 traits: 0=(undefined ) 16, 0, 0 traits: 0=(undefined ) 17, 0, 0 traits: 0=(undefined ) 18, 0, 0 traits: 0=(undefined ) 19, 0, 0 traits: 0=(undefined ) 20, 0, 0 traits: 0=(undefined ) 21, 0, 0 traits: 0=(undefined ) 22, 0, 0 traits: 0=(undefined ) 23, 0, 0 traits: 0=(undefined ) 24, 0, 0 traits: 0=(undefined ) 25, 0, 0 traits: 0=(undefined ) 26, 0, 0 traits: 0=(undefined ) 27, 0, 0 traits: 0=(undefined ) 28, 0, 0 traits: 0=(undefined ) 29, 0, 0 traits: 0=(undefined ) 30, 0, 0 traits: 0=(undefined ) 31, 0, 0 traits: 0=(undefined ) 32, 0, 0 traits: 0=(undefined ) 33, 0, 0 traits: 0=(undefined ) 34, 0, 0 traits: 0=(undefined ) 35, 0, 0 traits: 0=(undefined ) 36, 0, 0 traits: 0=(undefined ) 37, 0, 0 traits: 0=(undefined ) 38, 0, 0 traits: 0=(undefined ) 39, 0, 0 traits: 0=(undefined ) 40, 0, 0 traits: 0=(undefined ) 41, 0, 0 traits: 0=(undefined ) 42, 0, 0 traits: 0=(undefined ) 43, 0, 0 traits: 0=(undefined ) 44, 0, 0 traits: 0=(undefined ) 45, 0, 0 traits: 0=(undefined ) 46, 0, 0 traits: 0=(undefined ) 47, 0, 0 traits: 0=(undefined ) 48, 0, 0 traits: 0=(undefined ) 49, 0, 0 traits: 0=(undefined ) 50, 0, 0 traits: 0=(undefined ) 51, 0, 0 traits: 0=(undefined ) 52, 0, 0 traits: 0=(undefined ) 53, 0, 0 traits: 0=(undefined ) 54, 0, 0 traits: 0=(undefined ) 55, 0, 0 traits: 0=(undefined ) 56, 0, 0 traits: 0=(undefined ) 57, 0, 0 traits: 0=(undefined ) 58, 0, 0 traits: 0=(undefined ) 59, 0, 0 traits: 0=(undefined ) 60, 0, 0 traits: 0=(undefined ) 61, 0, 0 traits: 0=(undefined ) 62, 0, 0 traits: 0=(undefined ) 63, 0, 0 traits: 0=(undefined ) toes.png[0] PNG 267x233 267x233+0+0 16-bit TrueColor sRGB 320268B 0.095u 0:00.095 toes.png[1] PNG 200x175 200x175+0+0 8-bit sRGB 320268B 0.095u 0:00.095 |
replacefirst.c replaces the first image in the list with a red image the same size as the last image.
-process replacefirst is equivalent to ( -repect-parentheses +clone -fill red -colorize 100 ) -swap 0,-1 +delete.
%IM7DEV%magick ^ toes.png ^ ( +clone -fill khaki -colorize 50 -resize 75%% ) ^ -process replacefirst ^ -process echostuff ^ -write info: ^ +append +repage ^ cu_repf.png echostuff: no arguments mc=black echostuff: Input image [0] [toes.png] depth 8 size 200x175 Virtual pixel method 0 [(null)] mattecolor: (null) fuzz 0 alpha is on: no channels 3 meta_channels 0 ( MaxPixelChannels = 64 ) GetPixelChannels() = 3 Number of channels with update trait = 3 Image channels: i, channel, name, traits 0, 0, R, traits: 2=(update ) 1, 1, G, traits: 2=(update ) 2, 2, B, traits: 2=(update ) channel_map: i, channel, offset, traits 0, 0, 0 traits: 2=(update ) 1, 1, 1 traits: 2=(update ) 2, 2, 2 traits: 2=(update ) 3, 0, 0 traits: 0=(undefined ) 4, 0, 0 traits: 0=(undefined ) 5, 0, 0 traits: 0=(undefined ) 6, 0, 0 traits: 0=(undefined ) 7, 0, 0 traits: 0=(undefined ) 8, 0, 0 traits: 0=(undefined ) 9, 0, 0 traits: 0=(undefined ) 10, 0, 0 traits: 0=(undefined ) 11, 0, 0 traits: 0=(undefined ) 12, 0, 0 traits: 0=(undefined ) 13, 0, 0 traits: 0=(undefined ) 14, 0, 0 traits: 0=(undefined ) 15, 0, 0 traits: 0=(undefined ) 16, 0, 0 traits: 0=(undefined ) 17, 0, 0 traits: 0=(undefined ) 18, 0, 0 traits: 0=(undefined ) 19, 0, 0 traits: 0=(undefined ) 20, 0, 0 traits: 0=(undefined ) 21, 0, 0 traits: 0=(undefined ) 22, 0, 0 traits: 0=(undefined ) 23, 0, 0 traits: 0=(undefined ) 24, 0, 0 traits: 0=(undefined ) 25, 0, 0 traits: 0=(undefined ) 26, 0, 0 traits: 0=(undefined ) 27, 0, 0 traits: 0=(undefined ) 28, 0, 0 traits: 0=(undefined ) 29, 0, 0 traits: 0=(undefined ) 30, 0, 0 traits: 0=(undefined ) 31, 0, 0 traits: 0=(undefined ) 32, 0, 0 traits: 0=(undefined ) 33, 0, 0 traits: 0=(undefined ) 34, 0, 0 traits: 0=(undefined ) 35, 0, 0 traits: 0=(undefined ) 36, 0, 0 traits: 0=(undefined ) 37, 0, 0 traits: 0=(undefined ) 38, 0, 0 traits: 0=(undefined ) 39, 0, 0 traits: 0=(undefined ) 40, 0, 0 traits: 0=(undefined ) 41, 0, 0 traits: 0=(undefined ) 42, 0, 0 traits: 0=(undefined ) 43, 0, 0 traits: 0=(undefined ) 44, 0, 0 traits: 0=(undefined ) 45, 0, 0 traits: 0=(undefined ) 46, 0, 0 traits: 0=(undefined ) 47, 0, 0 traits: 0=(undefined ) 48, 0, 0 traits: 0=(undefined ) 49, 0, 0 traits: 0=(undefined ) 50, 0, 0 traits: 0=(undefined ) 51, 0, 0 traits: 0=(undefined ) 52, 0, 0 traits: 0=(undefined ) 53, 0, 0 traits: 0=(undefined ) 54, 0, 0 traits: 0=(undefined ) 55, 0, 0 traits: 0=(undefined ) 56, 0, 0 traits: 0=(undefined ) 57, 0, 0 traits: 0=(undefined ) 58, 0, 0 traits: 0=(undefined ) 59, 0, 0 traits: 0=(undefined ) 60, 0, 0 traits: 0=(undefined ) 61, 0, 0 traits: 0=(undefined ) 62, 0, 0 traits: 0=(undefined ) 63, 0, 0 traits: 0=(undefined ) mc=black echostuff: Input image [1] [toes.png] depth 16 size 200x175 Virtual pixel method 0 [(null)] mattecolor: (null) fuzz 0 alpha is on: no channels 3 meta_channels 0 ( MaxPixelChannels = 64 ) GetPixelChannels() = 3 Number of channels with update trait = 3 Image channels: i, channel, name, traits 0, 0, R, traits: 2=(update ) 1, 1, G, traits: 2=(update ) 2, 2, B, traits: 2=(update ) channel_map: i, channel, offset, traits 0, 0, 0 traits: 2=(update ) 1, 1, 1 traits: 2=(update ) 2, 2, 2 traits: 2=(update ) 3, 0, 0 traits: 0=(undefined ) 4, 0, 0 traits: 0=(undefined ) 5, 0, 0 traits: 0=(undefined ) 6, 0, 0 traits: 0=(undefined ) 7, 0, 0 traits: 0=(undefined ) 8, 0, 0 traits: 0=(undefined ) 9, 0, 0 traits: 0=(undefined ) 10, 0, 0 traits: 0=(undefined ) 11, 0, 0 traits: 0=(undefined ) 12, 0, 0 traits: 0=(undefined ) 13, 0, 0 traits: 0=(undefined ) 14, 0, 0 traits: 0=(undefined ) 15, 0, 0 traits: 0=(undefined ) 16, 0, 0 traits: 0=(undefined ) 17, 0, 0 traits: 0=(undefined ) 18, 0, 0 traits: 0=(undefined ) 19, 0, 0 traits: 0=(undefined ) 20, 0, 0 traits: 0=(undefined ) 21, 0, 0 traits: 0=(undefined ) 22, 0, 0 traits: 0=(undefined ) 23, 0, 0 traits: 0=(undefined ) 24, 0, 0 traits: 0=(undefined ) 25, 0, 0 traits: 0=(undefined ) 26, 0, 0 traits: 0=(undefined ) 27, 0, 0 traits: 0=(undefined ) 28, 0, 0 traits: 0=(undefined ) 29, 0, 0 traits: 0=(undefined ) 30, 0, 0 traits: 0=(undefined ) 31, 0, 0 traits: 0=(undefined ) 32, 0, 0 traits: 0=(undefined ) 33, 0, 0 traits: 0=(undefined ) 34, 0, 0 traits: 0=(undefined ) 35, 0, 0 traits: 0=(undefined ) 36, 0, 0 traits: 0=(undefined ) 37, 0, 0 traits: 0=(undefined ) 38, 0, 0 traits: 0=(undefined ) 39, 0, 0 traits: 0=(undefined ) 40, 0, 0 traits: 0=(undefined ) 41, 0, 0 traits: 0=(undefined ) 42, 0, 0 traits: 0=(undefined ) 43, 0, 0 traits: 0=(undefined ) 44, 0, 0 traits: 0=(undefined ) 45, 0, 0 traits: 0=(undefined ) 46, 0, 0 traits: 0=(undefined ) 47, 0, 0 traits: 0=(undefined ) 48, 0, 0 traits: 0=(undefined ) 49, 0, 0 traits: 0=(undefined ) 50, 0, 0 traits: 0=(undefined ) 51, 0, 0 traits: 0=(undefined ) 52, 0, 0 traits: 0=(undefined ) 53, 0, 0 traits: 0=(undefined ) 54, 0, 0 traits: 0=(undefined ) 55, 0, 0 traits: 0=(undefined ) 56, 0, 0 traits: 0=(undefined ) 57, 0, 0 traits: 0=(undefined ) 58, 0, 0 traits: 0=(undefined ) 59, 0, 0 traits: 0=(undefined ) 60, 0, 0 traits: 0=(undefined ) 61, 0, 0 traits: 0=(undefined ) 62, 0, 0 traits: 0=(undefined ) 63, 0, 0 traits: 0=(undefined ) toes.png[0] PNG 200x175 200x175+0+0 8-bit sRGB 320268B 0.110u 0:00.096 toes.png[1] PNG 200x175 200x175+0+0 16-bit sRGB 320268B 0.110u 0:00.096 |
replaceall.c replaces the all images in the list with a single blue image the same size as the last image.
-process replacefirst is equivalent to ( -repect-parentheses +clone -fill blue -colorize 100 ) -delete 0--2.
%IM7DEV%magick ^ toes.png ^ ( +clone -fill khaki -colorize 50 -resize 75%% ) ^ -process replaceall ^ -process echostuff ^ -write info: ^ +append +repage ^ cu_repa.png echostuff: no arguments mc=black echostuff: Input image [0] [toes.png] depth 8 size 200x175 Virtual pixel method 0 [(null)] mattecolor: (null) fuzz 0 alpha is on: no channels 3 meta_channels 0 ( MaxPixelChannels = 64 ) GetPixelChannels() = 3 Number of channels with update trait = 3 Image channels: i, channel, name, traits 0, 0, R, traits: 2=(update ) 1, 1, G, traits: 2=(update ) 2, 2, B, traits: 2=(update ) channel_map: i, channel, offset, traits 0, 0, 0 traits: 2=(update ) 1, 1, 1 traits: 2=(update ) 2, 2, 2 traits: 2=(update ) 3, 0, 0 traits: 0=(undefined ) 4, 0, 0 traits: 0=(undefined ) 5, 0, 0 traits: 0=(undefined ) 6, 0, 0 traits: 0=(undefined ) 7, 0, 0 traits: 0=(undefined ) 8, 0, 0 traits: 0=(undefined ) 9, 0, 0 traits: 0=(undefined ) 10, 0, 0 traits: 0=(undefined ) 11, 0, 0 traits: 0=(undefined ) 12, 0, 0 traits: 0=(undefined ) 13, 0, 0 traits: 0=(undefined ) 14, 0, 0 traits: 0=(undefined ) 15, 0, 0 traits: 0=(undefined ) 16, 0, 0 traits: 0=(undefined ) 17, 0, 0 traits: 0=(undefined ) 18, 0, 0 traits: 0=(undefined ) 19, 0, 0 traits: 0=(undefined ) 20, 0, 0 traits: 0=(undefined ) 21, 0, 0 traits: 0=(undefined ) 22, 0, 0 traits: 0=(undefined ) 23, 0, 0 traits: 0=(undefined ) 24, 0, 0 traits: 0=(undefined ) 25, 0, 0 traits: 0=(undefined ) 26, 0, 0 traits: 0=(undefined ) 27, 0, 0 traits: 0=(undefined ) 28, 0, 0 traits: 0=(undefined ) 29, 0, 0 traits: 0=(undefined ) 30, 0, 0 traits: 0=(undefined ) 31, 0, 0 traits: 0=(undefined ) 32, 0, 0 traits: 0=(undefined ) 33, 0, 0 traits: 0=(undefined ) 34, 0, 0 traits: 0=(undefined ) 35, 0, 0 traits: 0=(undefined ) 36, 0, 0 traits: 0=(undefined ) 37, 0, 0 traits: 0=(undefined ) 38, 0, 0 traits: 0=(undefined ) 39, 0, 0 traits: 0=(undefined ) 40, 0, 0 traits: 0=(undefined ) 41, 0, 0 traits: 0=(undefined ) 42, 0, 0 traits: 0=(undefined ) 43, 0, 0 traits: 0=(undefined ) 44, 0, 0 traits: 0=(undefined ) 45, 0, 0 traits: 0=(undefined ) 46, 0, 0 traits: 0=(undefined ) 47, 0, 0 traits: 0=(undefined ) 48, 0, 0 traits: 0=(undefined ) 49, 0, 0 traits: 0=(undefined ) 50, 0, 0 traits: 0=(undefined ) 51, 0, 0 traits: 0=(undefined ) 52, 0, 0 traits: 0=(undefined ) 53, 0, 0 traits: 0=(undefined ) 54, 0, 0 traits: 0=(undefined ) 55, 0, 0 traits: 0=(undefined ) 56, 0, 0 traits: 0=(undefined ) 57, 0, 0 traits: 0=(undefined ) 58, 0, 0 traits: 0=(undefined ) 59, 0, 0 traits: 0=(undefined ) 60, 0, 0 traits: 0=(undefined ) 61, 0, 0 traits: 0=(undefined ) 62, 0, 0 traits: 0=(undefined ) 63, 0, 0 traits: 0=(undefined ) toes.png PNG 200x175 200x175+0+0 8-bit sRGB 320268B 0.078u 0:00.096 AcquireImageColormap: image->colors=2 sizeof(*image->colormap)=88 |
replaceeach.c replaces each of the images in the list with a magenta image the same size as the corresponding input image.
-process replaceeach is equivalent to -fill magenta -colorize 100.
%IM7DEV%magick ^ toes.png ^ ( +clone -fill khaki -colorize 50 -resize 75%% ) ^ -process replaceeach ^ -process echostuff ^ -write info: ^ +append +repage ^ cu_repe.png echostuff: no arguments mc=black echostuff: Input image [0] [toes.png] depth 8 size 267x233 Virtual pixel method 0 [(null)] mattecolor: (null) fuzz 0 alpha is on: no channels 3 meta_channels 0 ( MaxPixelChannels = 64 ) GetPixelChannels() = 3 Number of channels with update trait = 3 Image channels: i, channel, name, traits 0, 0, R, traits: 2=(update ) 1, 1, G, traits: 2=(update ) 2, 2, B, traits: 2=(update ) channel_map: i, channel, offset, traits 0, 0, 0 traits: 2=(update ) 1, 1, 1 traits: 2=(update ) 2, 2, 2 traits: 2=(update ) 3, 0, 0 traits: 0=(undefined ) 4, 0, 0 traits: 0=(undefined ) 5, 0, 0 traits: 0=(undefined ) 6, 0, 0 traits: 0=(undefined ) 7, 0, 0 traits: 0=(undefined ) 8, 0, 0 traits: 0=(undefined ) 9, 0, 0 traits: 0=(undefined ) 10, 0, 0 traits: 0=(undefined ) 11, 0, 0 traits: 0=(undefined ) 12, 0, 0 traits: 0=(undefined ) 13, 0, 0 traits: 0=(undefined ) 14, 0, 0 traits: 0=(undefined ) 15, 0, 0 traits: 0=(undefined ) 16, 0, 0 traits: 0=(undefined ) 17, 0, 0 traits: 0=(undefined ) 18, 0, 0 traits: 0=(undefined ) 19, 0, 0 traits: 0=(undefined ) 20, 0, 0 traits: 0=(undefined ) 21, 0, 0 traits: 0=(undefined ) 22, 0, 0 traits: 0=(undefined ) 23, 0, 0 traits: 0=(undefined ) 24, 0, 0 traits: 0=(undefined ) 25, 0, 0 traits: 0=(undefined ) 26, 0, 0 traits: 0=(undefined ) 27, 0, 0 traits: 0=(undefined ) 28, 0, 0 traits: 0=(undefined ) 29, 0, 0 traits: 0=(undefined ) 30, 0, 0 traits: 0=(undefined ) 31, 0, 0 traits: 0=(undefined ) 32, 0, 0 traits: 0=(undefined ) 33, 0, 0 traits: 0=(undefined ) 34, 0, 0 traits: 0=(undefined ) 35, 0, 0 traits: 0=(undefined ) 36, 0, 0 traits: 0=(undefined ) 37, 0, 0 traits: 0=(undefined ) 38, 0, 0 traits: 0=(undefined ) 39, 0, 0 traits: 0=(undefined ) 40, 0, 0 traits: 0=(undefined ) 41, 0, 0 traits: 0=(undefined ) 42, 0, 0 traits: 0=(undefined ) 43, 0, 0 traits: 0=(undefined ) 44, 0, 0 traits: 0=(undefined ) 45, 0, 0 traits: 0=(undefined ) 46, 0, 0 traits: 0=(undefined ) 47, 0, 0 traits: 0=(undefined ) 48, 0, 0 traits: 0=(undefined ) 49, 0, 0 traits: 0=(undefined ) 50, 0, 0 traits: 0=(undefined ) 51, 0, 0 traits: 0=(undefined ) 52, 0, 0 traits: 0=(undefined ) 53, 0, 0 traits: 0=(undefined ) 54, 0, 0 traits: 0=(undefined ) 55, 0, 0 traits: 0=(undefined ) 56, 0, 0 traits: 0=(undefined ) 57, 0, 0 traits: 0=(undefined ) 58, 0, 0 traits: 0=(undefined ) 59, 0, 0 traits: 0=(undefined ) 60, 0, 0 traits: 0=(undefined ) 61, 0, 0 traits: 0=(undefined ) 62, 0, 0 traits: 0=(undefined ) 63, 0, 0 traits: 0=(undefined ) mc=black echostuff: Input image [1] [toes.png] depth 8 size 200x175 Virtual pixel method 0 [(null)] mattecolor: (null) fuzz 0 alpha is on: no channels 3 meta_channels 0 ( MaxPixelChannels = 64 ) GetPixelChannels() = 3 Number of channels with update trait = 3 Image channels: i, channel, name, traits 0, 0, R, traits: 2=(update ) 1, 1, G, traits: 2=(update ) 2, 2, B, traits: 2=(update ) channel_map: i, channel, offset, traits 0, 0, 0 traits: 2=(update ) 1, 1, 1 traits: 2=(update ) 2, 2, 2 traits: 2=(update ) 3, 0, 0 traits: 0=(undefined ) 4, 0, 0 traits: 0=(undefined ) 5, 0, 0 traits: 0=(undefined ) 6, 0, 0 traits: 0=(undefined ) 7, 0, 0 traits: 0=(undefined ) 8, 0, 0 traits: 0=(undefined ) 9, 0, 0 traits: 0=(undefined ) 10, 0, 0 traits: 0=(undefined ) 11, 0, 0 traits: 0=(undefined ) 12, 0, 0 traits: 0=(undefined ) 13, 0, 0 traits: 0=(undefined ) 14, 0, 0 traits: 0=(undefined ) 15, 0, 0 traits: 0=(undefined ) 16, 0, 0 traits: 0=(undefined ) 17, 0, 0 traits: 0=(undefined ) 18, 0, 0 traits: 0=(undefined ) 19, 0, 0 traits: 0=(undefined ) 20, 0, 0 traits: 0=(undefined ) 21, 0, 0 traits: 0=(undefined ) 22, 0, 0 traits: 0=(undefined ) 23, 0, 0 traits: 0=(undefined ) 24, 0, 0 traits: 0=(undefined ) 25, 0, 0 traits: 0=(undefined ) 26, 0, 0 traits: 0=(undefined ) 27, 0, 0 traits: 0=(undefined ) 28, 0, 0 traits: 0=(undefined ) 29, 0, 0 traits: 0=(undefined ) 30, 0, 0 traits: 0=(undefined ) 31, 0, 0 traits: 0=(undefined ) 32, 0, 0 traits: 0=(undefined ) 33, 0, 0 traits: 0=(undefined ) 34, 0, 0 traits: 0=(undefined ) 35, 0, 0 traits: 0=(undefined ) 36, 0, 0 traits: 0=(undefined ) 37, 0, 0 traits: 0=(undefined ) 38, 0, 0 traits: 0=(undefined ) 39, 0, 0 traits: 0=(undefined ) 40, 0, 0 traits: 0=(undefined ) 41, 0, 0 traits: 0=(undefined ) 42, 0, 0 traits: 0=(undefined ) 43, 0, 0 traits: 0=(undefined ) 44, 0, 0 traits: 0=(undefined ) 45, 0, 0 traits: 0=(undefined ) 46, 0, 0 traits: 0=(undefined ) 47, 0, 0 traits: 0=(undefined ) 48, 0, 0 traits: 0=(undefined ) 49, 0, 0 traits: 0=(undefined ) 50, 0, 0 traits: 0=(undefined ) 51, 0, 0 traits: 0=(undefined ) 52, 0, 0 traits: 0=(undefined ) 53, 0, 0 traits: 0=(undefined ) 54, 0, 0 traits: 0=(undefined ) 55, 0, 0 traits: 0=(undefined ) 56, 0, 0 traits: 0=(undefined ) 57, 0, 0 traits: 0=(undefined ) 58, 0, 0 traits: 0=(undefined ) 59, 0, 0 traits: 0=(undefined ) 60, 0, 0 traits: 0=(undefined ) 61, 0, 0 traits: 0=(undefined ) 62, 0, 0 traits: 0=(undefined ) 63, 0, 0 traits: 0=(undefined ) toes.png[0] PNG 267x233 267x233+0+0 8-bit sRGB 320268B 0.109u 0:00.095 toes.png[1] PNG 200x175 200x175+0+0 8-bit sRGB 320268B 0.109u 0:00.095 AcquireImageColormap: image->colors=2 sizeof(*image->colormap)=88 |
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 grad2 is equivalent to -draw "fill Blue circle CX,CY CX,CY+RAD" where (CX,CY) is the centre and RAD is min(CX,CY).
%IM7DEV%magick ^ toes.png ^ ( +clone -fill khaki -colorize 50 -resize 75%% ) ^ -process drawcirc ^ +append +repage ^ cu_drawcirc.png |
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.093u 0:00.097
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.0392266806 @ 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 14528 Console 1 42,384 K magick.exe 14528 Console 1 73,700 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 AcquireImageColormap: image->colors=61 sizeof(*image->colormap)=88
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 AcquireImageColormap: image->colors=42 sizeof(*image->colormap)=88
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.
This pixelates an image into polygons or other shapes.
See Polygonal pixelation for details and source code.
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 |
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 "vsn_defines.h" ModuleExport size_t hellowImage(Image **images,const int argc, const char **argv,ExceptionInfo *exception) { int precision; // Prevent compilers warning about unused parameters: (void) images; (void) argc; (void) argv; (void) exception; precision = GetMagickPrecision(); printf ("Greetings to wizards of the ImageMagick world!\n\n"); printf ("MAGICKCORE_QUANTUM_DEPTH %.*g\n", precision, (double)MAGICKCORE_QUANTUM_DEPTH); printf ("QuantumRange %.*g\n", precision, (double)QuantumRange); printf (" V/V-1 %.*g\n", precision, (double)QuantumRange/(double)QuantumRange-1.0); printf ("QuantumScale %.*g\n", precision, QuantumScale); printf ("MagickEpsilon %.*g\n", precision, MagickEpsilon); printf ("Quantum %s\n", MagickStringify(Quantum)); printf ("sizeof:\n"); printf (" MagickRealType %i\n", (int)sizeof(MagickRealType)); printf (" MagickSizeType %i\n", (int)sizeof(MagickSizeType)); printf (" int %i\n", (int)sizeof(int)); printf (" long int %i\n", (int)sizeof(long int)); printf (" long long int %i\n", (int)sizeof(long long int)); printf (" float %i\n", (int)sizeof(float)); printf (" double %i\n", (int)sizeof(double)); printf (" long double %i\n", (int)sizeof(long double)); printf (" _Float128 %i\n", (int)sizeof(_Float128)); printf (" Image %i\n", (int)sizeof(Image)); printf (" ImageInfo %i\n", (int)sizeof(ImageInfo)); printf (" Quantum %i\n", (int)sizeof(Quantum)); printf ("MagickSizeType [%s]\n", MagickStringify(MagickSizeType)); printf ("MagickSizeFormat [%s]\n", MagickSizeFormat); printf ("QuantumFormat: [%s]\n", QuantumFormat); printf ("MaxMap %i\n", (int)MaxMap); printf ("MaxTextExtent %i\n", (int)MaxTextExtent); #if IMV6OR7==7 printf ("MagickPathExtent %i\n", (int)MagickPathExtent); #endif #if defined(MAGICKCORE_HDRI_SUPPORT) printf ("Includes HDRI\n"); #else printf ("Does not includes HDRI\n"); #endif #if defined(MAGICKCORE_WINDOWS_SUPPORT) printf ("MAGICKCORE_WINDOWS_SUPPORT defined\n"); #endif #if defined(WIN32) printf ("WIN32 defined\n"); #endif #if defined(WIN64) printf ("WIN64 defined\n"); #endif #if defined(MAGICKCORE_OPENMP_SUPPORT) printf ("MAGICKCORE_OPENMP_SUPPORT defined\n"); #endif return(MagickImageFilterSignature); }
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <assert.h> #include "vsn_defines.h" // The next function corresponds in style to functions in enhance.c // It takes one image, and returns a status. // static MagickBooleanType EchoImage(const Image *image, int count, ExceptionInfo *exception) { VirtualPixelMethod vpm; const char *mattecolor = GetImageArtifact (image, "mattecolor"); if (!mattecolor) mattecolor = "black"; printf ("mc=%s\n", mattecolor); assert(image != (Image *) NULL); assert(image->signature == MAGICK_CORE_SIG); if (image->debug != MagickFalse) (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename); printf ("echostuff: Input image [%i] [%s] depth %i size %ix%i\n", count, image->filename, (int)image->depth, (int)image->columns, (int)image->rows); vpm = GetImageVirtualPixelMethod(image); printf (" Virtual pixel method %i [%s]\n", (int)vpm, GetImageArtifact(image, "virtual-pixel")); printf (" mattecolor: %s\n", GetImageArtifact (image, "mattecolor")); printf (" fuzz %g\n", image->fuzz); // printf (" channels %li\n", image->channels); printf (" alpha is on: %s\n", IS_ALPHA_CH(image) ? "yes" : "no"); ResetImageArtifactIterator(image); const char * p = GetNextImageArtifact(image); while (p && *p) { printf ("Artifact=[%s] value=[%s]\n", p, GetImageArtifact(image, p)); p = GetNextImageArtifact(image); } return MagickTrue; } ModuleExport size_t echostuffImage(Image **images,const int argc, const char **argv,ExceptionInfo *exception) { Image *image; int i; MagickBooleanType status; (void) argc; (void) argv; assert(images != (Image **) NULL); assert(*images != (Image *) NULL); assert((*images)->signature == MAGICK_CORE_SIG); if (argc) { printf ("echostuff: %i arguments:", argc); for (i=0; i < argc; i++) { printf (" [%s]", argv[i]); } printf ("\n"); } else { printf ("echostuff: no arguments\n"); } i = 0; for (image=(*images); image != (Image *) NULL; image=GetNextImageInList(image)) { status = EchoImage(image, i, exception); if (status == MagickFalse) continue; i++; } assert (status == MagickTrue); return(MagickImageFilterSignature); }
#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-1); if (sscanf (prs->sSize, "%lux%lu", &prs->width, &prs->height) == 0) { if (sscanf (prs->sSize, "x%lu", &prs->height) != 1) { fprintf (stderr, "replacespec: invalid 'size' argument [%s]\n", prs->sSize); status = MagickFalse; } } } else if (IsArg (pa, "c", "color")==MagickTrue) { i++; strncpy (prs->sColor, argv[i], MaxTextExtent-1); } else if (IsArg (pa, "v", "verbose")==MagickTrue) { prs->do_verbose = MagickTrue; } else { fprintf (stderr, "replacespec: ERROR: unknown option [%s]\n", pa); status = MagickFalse; } } if (prs->do_verbose) { fprintf (stderr, "replacespec options:"); if (*prs->sSize) fprintf (stderr, " size %s ", prs->sSize); if (*prs->sColor) fprintf (stderr, " color %s ", prs->sColor); if (prs->do_verbose) fprintf (stderr, " verbose"); fprintf (stderr, "\n"); } if (status == MagickFalse) usage (); return (status); } // The next function corresponds in style to functions in transform.c // It takes one image, and returns an image. // static Image *CopyColor(const Image *image, replspecT *prs, ExceptionInfo *exception) { Image *new_image; PIX_INFO mpp; size_t width, height; assert(image != (Image *) NULL); assert(image->signature == MAGICK_CORE_SIG); if (image->debug != MagickFalse) (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename); if (prs->do_verbose) { fprintf (stderr, " input image size %lux%lu\n", image->columns, image->rows); if (*prs->sSize) { fprintf (stderr, " requested size %lux%lu\n", prs->width, prs->height); } } //width = image->columns; //height = image->rows; //if (*prs->sSize) { // width = prs->width; // height = prs->height; //} width = (prs->width) ? prs->width : image->columns; height = (prs->height) ? prs->height : image->rows; new_image=CloneImage(image, width, height, MagickTrue, exception); if (new_image == (Image *) NULL) return(new_image); if (SetNoPalette (new_image, exception) == MagickFalse) return (Image *)NULL; #if IMV6OR7==6 // Make a "colour", which will initially be black. GetMagickPixelPacket (new_image, &mpp); // We set it to any colour: (void)QueryMagickColor( *prs->sColor ? prs->sColor : "brown", &mpp, exception); // Set all the pixels to this colour. (void)SetImageColor(new_image, &mpp); #else GetPixelInfo (new_image, &mpp); QueryColorCompliance ( *prs->sColor ? prs->sColor : "brown", AllCompliance, &mpp, exception); (void)SetImageColor(new_image, &mpp, exception); #endif return (new_image); } /* We replace each image as we come across it. An alternative technique is to create a new list, and add new images to it; at the end, replace the original list with the new one. That alternative is slightly more complex and needs more memory as it would hold all input and output images in memory. */ ModuleExport size_t replacespecImage(Image **images,const int argc, const char **argv,ExceptionInfo *exception) { Image *image, *new_image; MagickBooleanType status; replspecT replace_spec; assert(images != (Image **) NULL); assert(*images != (Image *) NULL); assert((*images)->signature == MAGICK_CORE_SIG); status = menu (argc, argv, &replace_spec); if (status == MagickFalse) return (-1); for (image=(*images); image != (Image *) NULL; image=GetNextImageInList(image)) { new_image = CopyColor(image, &replace_spec, exception); if (new_image == (Image *) NULL) return(-1); ReplaceImageInList(&image,new_image); // Replace messes up the images pointer. Make it good: *images=GetFirstImageInList(image); } return(MagickImageFilterSignature); }
/* 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 */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <time.h> #include <assert.h> #include <math.h> #include "vsn_defines.h" #if IMV6OR7==7 #include "MagickCore/gem-private.h" #endif #include "chklist.h" static void usage (void) { printf ("Usage: -process 'geodist [OPTION]...'\n"); printf ("Distort image.\n"); printf ("\n"); printf (" s, style N style N: 0 to 3\n"); } static MagickBooleanType IsArg (char * pa, char * ShtOpt, char * LongOpt) { if ((LocaleCompare(pa, ShtOpt)==0) || (LocaleCompare(pa, LongOpt)==0)) return MagickTrue; return MagickFalse; } static MagickBooleanType menu( const int argc, const char **argv, int *pstyle) // Returns MagickTrue if okay. { int i; MagickBooleanType status; *pstyle = 0; status = MagickTrue; for (i=0; i < argc; i++) { char * pa = (char *)argv[i]; //printf ("Arg %i [%s]\n", i, pa); if (IsArg (pa, "s", "style")==MagickTrue) { i++; *pstyle = atoi (argv[i]); } else { fprintf (stderr, "ERROR\n"); status = MagickFalse; } } if (status == MagickFalse) usage (); return (status); } // The next function corresponds in style to functions in transform.c // It takes one image, and returns an image. // static Image *GeoDImage(const Image *image, int style, ExceptionInfo *exception) { Image *new_image; long double twoPi; CacheView *image_view, *out_view; ssize_t y; MagickBooleanType status; double dx, dy; assert(image != (Image *) NULL); assert(image->signature == MAGICK_CORE_SIG); if (image->debug != MagickFalse) (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename); twoPi = 2 * M_PI; dx = image->columns * 0.10; dy = image->rows * 0.10; // Clone, and we must initialise. new_image=CloneImage(image, 0, 0, MagickTrue, exception); if (new_image == (Image *) NULL) return(new_image); if (SetNoPalette (new_image, exception) == MagickFalse) return (Image *)NULL; out_view = AcquireAuthenticCacheView(new_image,exception); image_view = AcquireVirtualCacheView(image,exception); status = MagickTrue; #if defined(MAGICKCORE_OPENMP_SUPPORT) #pragma omp parallel for schedule(static,4) shared(status) \ MAGICK_THREADS(image,image,image->rows,1) #endif for (y=0; y < (ssize_t) image->rows; y++) { VIEW_PIX_PTR *q_out; register ssize_t x; if (status == MagickFalse) continue; q_out=GetCacheViewAuthenticPixels(out_view,0,y,new_image->columns,1,exception); if (q_out == (const VIEW_PIX_PTR *) NULL) status=MagickFalse; for (x=0; x < (ssize_t) image->columns; x++) { PIX_INFO src_pixel; switch (style) { case 0: default: { break; } case 1: { double hue, saturation, brightness; // For v7, use: // static MagickBooleanType sRGBTransformImage(Image *image, // const ColorspaceType colorspace,ExceptionInfo *exception) /*++ { double r=(double) GET_PIXEL_RED(image,q_out); double g=(double) GET_PIXEL_GREEN(image,q_out); double b=(double) GET_PIXEL_BLUE(image,q_out); double min = r < g ? r : g; if (b < min) min = b; double max = r > g ? r : g; if (b > max) max = b; if (max > 0.0) { delta = max-min; saturation = delta/max; brightness = QuantumScale*max; if (delta == 0.0) return; if (r == max) hue = (g-b)/delta; else { if (g == max) hue = 2.0+(b-r)/delta; else hue = 4.0+(r-g)/delta; } hue /= 6.0; if (hue < 0.0) hue += 1.0; } else { saturation = 0.0; brightness = 0.0; hue = 0.0; } } ++*/ ConvertRGBToHSB( GET_PIXEL_RED(image,q_out), GET_PIXEL_GREEN(image,q_out), GET_PIXEL_BLUE(image,q_out), &hue, &saturation, &brightness); // hsb are 0.0 to 1.0 dx = (saturation - 0.5) * image->columns; dy = (brightness - 0.5) * image->rows; break; } case 2: { double hue, saturation, brightness, rads; ConvertRGBToHSB( GET_PIXEL_RED(image,q_out), GET_PIXEL_GREEN(image,q_out), GET_PIXEL_BLUE(image,q_out), &hue, &saturation, &brightness); rads = twoPi * hue; dx = sin(rads) * brightness * image->columns; dy = sin(rads) * brightness * image->rows; break; } } #if IMV6OR7==6 InterpolateMagickPixelPacket ( image, image_view, image->interpolate, x + dx, y + dy, &src_pixel, exception); #else InterpolatePixelInfo ( image, image_view, image->interpolate, x + dx, y + dy, &src_pixel, exception); #endif SET_PIXEL_RED (new_image, src_pixel.red, q_out); SET_PIXEL_GREEN (new_image, src_pixel.green, q_out); SET_PIXEL_BLUE (new_image, src_pixel.blue, q_out); #if IMV6OR7==6 SET_PIXEL_ALPHA (new_image, QuantumRange - src_pixel.opacity, q_out); #else SET_PIXEL_ALPHA (new_image, src_pixel.alpha, q_out); #endif q_out += Inc_ViewPixPtr (new_image); } if (SyncCacheViewAuthenticPixels(out_view,exception) == MagickFalse) status=MagickFalse; } image_view=DestroyCacheView(image_view); out_view=DestroyCacheView(out_view); //chklist("end GeoDImage new_image",&new_image); return (new_image); } ModuleExport size_t geodistImage(Image **images,const int argc, const char **argv,ExceptionInfo *exception) { Image *image, *new_image; int style; MagickBooleanType Error; assert(images != (Image **) NULL); assert(*images != (Image *) NULL); assert((*images)->signature == MAGICK_CORE_SIG); Error = !menu (argc, argv, &style); if (Error == MagickTrue) { return (-1); } for (image=(*images); image != (Image *) NULL; image=GetNextImageInList(image)) { new_image = GeoDImage (image, style, exception); if (new_image == (Image *) NULL) return(-1); //chklist("after GeoDImage: &new_image",&new_image); //chklist("after GeoDImage: images",images); //chklist("after GeoDImage: images",images); ReplaceImageInList(&image,new_image); //chkentry("after replace: &image",&image); *images=GetFirstImageInList(image); //chklist("after replace: images",images); } //chklist("end geodistImage: images",images); return(MagickImageFilterSignature); }
#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 */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <time.h> #include <assert.h> #include <math.h> #include <ctype.h> #include "vsn_defines.h" #define DEFAULT_CAP 65536 static void usage (void) { printf ("Usage: -process 'invclut [OPTION]...'\n"); printf ("Invert a histogram image.\n"); printf ("\n"); printf (" v, verbose write text information to stdout\n"); } static MagickBooleanType IsArg (char * pa, char * ShtOpt, char * LongOpt) { if ((LocaleCompare(pa, ShtOpt)==0) || (LocaleCompare(pa, LongOpt)==0)) return MagickTrue; return MagickFalse; } #if defined(MAGICKCORE_HDRI_SUPPORT) #define ADD_HALF #else #define ADD_HALF +0.5 #endif ModuleExport size_t invclutImage(Image **images,const int argc, const char **argv,ExceptionInfo *exception) { Image *image; Image *new_image, *transform_images; int i; MagickBooleanType Error, do_verbose; assert(images != (Image **) NULL); assert(*images != (Image *) NULL); assert((*images)->signature == MAGICK_CORE_SIG); Error = MagickFalse; do_verbose = MagickFalse; for (i=0; i < argc; i++) { char * pa = (char *)argv[i]; if (IsArg (pa, "v", "verbose")==MagickTrue) { do_verbose = MagickTrue; } else { fprintf (stderr, "invclut: ERROR: unknown option\n"); Error = MagickTrue; } } if (Error == MagickTrue) { usage (); return (-1); } if (do_verbose) { printf ("invclut options: "); if (do_verbose) printf ("verbose "); printf ("\n"); } transform_images=NewImageList(); image=(*images); for ( ; image != (Image *) NULL; image=GetNextImageInList(image)) { CacheView *image_view, *out_view; ssize_t y, inXmult; MagickBooleanType status; long double quant_over_width; if (do_verbose) { printf ("invclut: Input image [%s] depth is %i\n", image->filename, (int)image->depth); } quant_over_width = QuantumRange/(long double)image->columns; new_image=CloneImage(image, 0, 0, MagickTrue, exception); if (new_image == (Image *) NULL) return(-1); if (SetNoPalette (new_image, exception) == MagickFalse) return (-1); // FIXME: set new_image depth to Quantum? // new_image->depth=MAGICKCORE_QUANTUM_DEPTH; SetAllBlack (new_image, exception); inXmult = Inc_ViewPixPtr (image); out_view=AcquireAuthenticCacheView(new_image,exception); status=MagickTrue; image_view=AcquireVirtualCacheView(image,exception); #if defined(MAGICKCORE_OPENMP_SUPPORT) #pragma omp parallel for schedule(static,4) shared(status) \ MAGICK_THREADS(image,image,image->rows,1) #endif for (y=0; y < (ssize_t) image->rows; y++) { register const VIEW_PIX_PTR *p; VIEW_PIX_PTR *q_out; ssize_t in_x_red, in_x_green, in_x_blue, out_x; in_x_red = in_x_green = in_x_blue = 0; if (status == MagickFalse) continue; p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception); if (p == (const VIEW_PIX_PTR *) NULL) { fprintf (stderr, "bad GetCacheViewAuthenticPixels image_view\n"); status=MagickFalse; continue; } q_out=GetCacheViewAuthenticPixels(out_view,0,y,new_image->columns,1,exception); if (q_out == (const VIEW_PIX_PTR *) NULL) { fprintf (stderr, "invclut: bad GetCacheViewAuthenticPixels out_view\n"); status=MagickFalse; } for (out_x=0; out_x < (ssize_t) image->columns; out_x++) { // Find the next input x with value (possibly scaled) >= out_x // This input x is the new value, possibly scaled. long double limit; limit = out_x * quant_over_width; // "+0.5" because we need the centre of the bucket. // 12/10/14 Remove +0.5 while (in_x_red < image->columns && GET_PIXEL_RED(image,p + in_x_red * inXmult) < limit) in_x_red++; SET_PIXEL_RED (new_image, in_x_red * quant_over_width ADD_HALF, q_out); while (in_x_green < image->columns && GET_PIXEL_GREEN(image,p + in_x_green * inXmult) < limit) in_x_green++; SET_PIXEL_GREEN (new_image, in_x_green * quant_over_width ADD_HALF, q_out); while (in_x_blue < image->columns && GET_PIXEL_BLUE(image,p + in_x_blue * inXmult) < limit) in_x_blue++; SET_PIXEL_BLUE (new_image, in_x_blue * quant_over_width ADD_HALF, q_out); q_out += Inc_ViewPixPtr (new_image); } if (SyncCacheViewAuthenticPixels(out_view,exception) == MagickFalse) { fprintf (stderr, "bad sync\n"); status=MagickFalse; } } if (do_verbose) { } image_view=DestroyCacheView(image_view); out_view=DestroyCacheView(out_view); AppendImageToList(&transform_images,new_image); } DestroyImageList(*images); *images=transform_images; return(MagickImageFilterSignature); }
#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 */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <assert.h> #include "vsn_defines.h" typedef enum { mtAbsolute, mtRelative } MapTypeT; typedef struct { MagickBooleanType do_verbose; MapTypeT MapType; } invDispMapT; static void usage (void) { printf ("Usage: -process 'invdispmap [OPTION]...'\n"); printf ("Invert a displacement map.\n"); printf ("\n"); printf (" t, type STRING STRING is Relative or Absolute\n"); printf (" v, verbose write text information to stdout\n"); printf ("\n"); } static MagickBooleanType IsArg (char * pa, char * ShtOpt, char * LongOpt) { if ((LocaleCompare(pa, ShtOpt)==0) || (LocaleCompare(pa, LongOpt)==0)) return MagickTrue; return MagickFalse; } static MagickBooleanType menu( const int argc, const char **argv, invDispMapT * idm ) // Returns MagickTrue if okay. { int i; MagickBooleanType status; status = MagickTrue; idm->do_verbose = MagickFalse; idm->MapType = mtAbsolute; for (i=0; i < argc; i++) { char * pa = (char *)argv[i]; if (IsArg (pa, "t", "type")==MagickTrue) { i++; char * pa = (char *)argv[i]; if (LocaleCompare(pa, "Absolute")==0) idm->MapType = mtAbsolute; else if (LocaleCompare(pa, "Relative")==0) idm->MapType = mtRelative; else { fprintf (stderr, "invdispmap: ERROR: unknown map type\n"); status = MagickFalse; } } else if (IsArg (pa, "v", "verbose")==MagickTrue) { idm->do_verbose = MagickTrue; } else { fprintf (stderr, "invdispmap: ERROR: unknown option\n"); status = MagickFalse; } } if (idm->do_verbose) { fprintf (stderr, "invdispmap options: "); fprintf (stderr, "type "); fprintf (stderr, idm->MapType==mtAbsolute?"Absolute ":"Relative "); if (idm->do_verbose) fprintf (stderr, "verbose "); fprintf (stderr, "\n"); } if (status == MagickFalse) usage (); return (status); } #if defined(MAGICKCORE_HDRI_SUPPORT) #define ADD_HALF #else #define ADD_HALF +0.5 #endif // The next function corresponds in style to functions in transform.c // It takes one image, and returns an image. // static Image *invDispMap(const Image *image, invDispMapT * idm, ExceptionInfo *exception) { Image *new_image; CacheView *image_view, *out_view; ssize_t y; MagickBooleanType status; PIX_INFO mppBlack; long double QoverCols, QoverRows, relHalf; if (image->debug != MagickFalse) (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename); if (idm->do_verbose) { fprintf (stderr, "invdispmap: Input image [%s] depth is %i\n", image->filename, (int)image->depth); } new_image=CloneImage(image, 0, 0, MagickTrue, exception); if (new_image == (Image *) NULL) return(new_image); // Just in case image had a palette. if (SetNoPalette (new_image, exception) == MagickFalse) return (Image *)NULL; #if IMV6OR7==6 GetMagickPixelPacket (new_image, &mppBlack); mppBlack.matte=MagickTrue; mppBlack.opacity=(MagickRealType) TransparentOpacity; SetImageColor (new_image, &mppBlack); #else GetPixelInfo (new_image, &mppBlack); mppBlack.alpha = 0; SetImageColor (new_image, &mppBlack, exception); #endif status=MagickTrue; image_view=AcquireVirtualCacheView(image,exception); out_view=AcquireAuthenticCacheView(new_image,exception); QoverCols = QuantumRange / (long double)(image->columns-1); QoverRows = QuantumRange / (long double)(image->rows-1); if (idm->MapType == mtRelative) { relHalf = QuantumRange / 2.0; } else { relHalf = 0.0; } // Unlike most processes, // this reads pixels sequentially // and writes them randomly. // Two threads might want to update the same row, // so don't allow multiple threads. // #if defined(MAGICKCORE_OPENMP_SUPPORT) // #pragma omp parallel for schedule(static,4) shared(status) // MAGICK_THREADS(image,image,image->rows,1) // #endif for (y=0; y < (ssize_t) image->rows; y++) { const VIEW_PIX_PTR *p; VIEW_PIX_PTR *q_out; ssize_t x, red, grn, xa, ya; long double x_norm, y_norm; if (status == MagickFalse) continue; p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception); if (p == (const VIEW_PIX_PTR *) NULL) { fprintf (stderr, "bad GetCacheViewVirtualPixels image_view\n"); status=MagickFalse; continue; } for (x=0; x < (ssize_t) image->columns; x++) { if (GET_PIXEL_ALPHA (image, p) > 0) { red = floor((GET_PIXEL_RED (image, p) - relHalf) / QoverCols + 0.5); grn = floor((GET_PIXEL_GREEN (image, p) - relHalf) / QoverRows + 0.5); if (idm->MapType == mtRelative) { red += x; grn += y; xa = x - red; ya = y - grn; } else { xa = x; ya = y; } if (red >= 0 && red < image->columns && grn >= 0 && grn < image->rows) { q_out=GetCacheViewAuthenticPixels(out_view,red,grn,1,1,exception); if (q_out == (const VIEW_PIX_PTR *) NULL) { fprintf (stderr, "invdispmap: bad GetCacheViewAuthenticPixels out_view (%lu %lu)\n", red, grn); status=MagickFalse; } x_norm = xa * QoverCols + relHalf ADD_HALF; y_norm = ya * QoverRows + relHalf ADD_HALF; SET_PIXEL_RED (new_image, x_norm, q_out); SET_PIXEL_GREEN (new_image, y_norm, q_out); SET_PIXEL_BLUE (new_image, 0, q_out); SET_PIXEL_ALPHA (new_image, QuantumRange, q_out); if (SyncCacheViewAuthenticPixels(out_view,exception) == MagickFalse) { fprintf (stderr, "bad sync\n"); status=MagickFalse; } } else { fprintf (stderr, "ignored: red=%li grn=%li\n", red, grn); } } p += Inc_ViewPixPtr (image); } } image_view=DestroyCacheView(image_view); out_view=DestroyCacheView(out_view); return (new_image); } ModuleExport size_t invdispmapImage(Image **images,const int argc, const char **argv,ExceptionInfo *exception) { Image *image, *new_image; MagickBooleanType status; invDispMapT idm; assert(images != (Image **) NULL); assert(*images != (Image *) NULL); assert((*images)->signature == MAGICK_CORE_SIG); status = menu (argc, argv, &idm); if (status == MagickFalse) return (-1); for (image=(*images); image != (Image *) NULL; image=GetNextImageInList(image)) { new_image = invDispMap (image, &idm, exception); ReplaceImageInList(&image,new_image); *images=GetFirstImageInList(image); } return(MagickImageFilterSignature); }
/* 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 */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <assert.h> #include <ctype.h> #include "vsn_defines.h" // FUTURE: We could weight diagonal steps by sqrt(2). typedef struct { int max_iter, precision; MagickBooleanType autoIter, cumOnly, edgeLeft, edgeBottom, edgeRight, endIsGiven, do_verbose, do_verbose2; ssize_t endX, endY; } darkestmeanderT; static void usage (void) { printf ("Usage: -process 'darkestmeander [OPTION]...'\n"); printf ("Finds darkest meander from top to bottom.\n"); printf ("\n"); printf (" m, maxiter N maximum number of iterations [10]\n"); printf (" a, autoiter stop iterating when path is stable\n"); printf (" c, cumerr result is cumulative instead of meander\n"); printf (" s, side str str contains any of lbr for left, bottom and right\n"); printf (" e, end_at X,Y end the path at (integer) coordinates instead of a side\n"); printf (" v, verbose write text information to stdout\n"); printf (" v2, verbose2 write more text information to stdout\n"); printf ("\n"); } static MagickBooleanType IsArg (char * pa, char * ShtOpt, char * LongOpt) { if ((LocaleCompare(pa, ShtOpt)==0) || (LocaleCompare(pa, LongOpt)==0)) return MagickTrue; return MagickFalse; } static MagickBooleanType menu( const int argc, const char **argv, darkestmeanderT * pch ) // Returns MagickTrue if okay. { int i; MagickBooleanType status; status = MagickTrue; pch->autoIter = pch->cumOnly = pch->do_verbose = pch->do_verbose2 = MagickFalse; pch->edgeLeft = pch->edgeRight = MagickFalse; pch->edgeBottom = MagickTrue; pch->endIsGiven = MagickFalse; pch->endX = pch->endY = 0; pch->max_iter = 10; for (i=0; i < argc; i++) { char * pa = (char *)argv[i]; if (IsArg (pa, "c", "cumerr")==MagickTrue) { pch->cumOnly = MagickTrue; } else if (IsArg (pa, "m", "maxiter")==MagickTrue) { i++; if (!isdigit ((int)*argv[i])) { fprintf (stderr, "Non-numeric argument to 'maxiter'.\n"); status = MagickFalse; } else pch->max_iter = atoi (argv[i]); } else if (IsArg (pa, "a", "autoiter")==MagickTrue) { pch->autoIter = MagickTrue; } else if (IsArg (pa, "e", "end_at")==MagickTrue) { pch->endIsGiven = MagickTrue; i++; int n = sscanf (argv[i],"%zd,%zd",&pch->endX,&pch->endY); if (n != 2) { fprintf (stderr, "'end_at' needs X,Y.\n"); status = MagickFalse; } if (pch->endX < 0 || pch->endY < 0) { fprintf (stderr, "'end_at' needs positive X,Y.\n"); status = MagickFalse; } } else if (IsArg (pa, "s", "side")==MagickTrue) { pch->edgeLeft = pch->edgeBottom = pch->edgeRight = MagickFalse; i++; const char * p = argv[i]; while (*p) { if (*p=='l' || *p=='L') pch->edgeLeft = MagickTrue; else if (*p=='b' || *p=='B') pch->edgeBottom = MagickTrue; else if (*p=='r' || *p=='R') pch->edgeRight = MagickTrue; else { fprintf (stderr, "Argument to 'side' should contain lbr only.\n"); status = MagickFalse; } p++; } } else if (IsArg (pa, "v", "verbose")==MagickTrue) { pch->do_verbose = MagickTrue; } else if (IsArg (pa, "v2", "verbose2")==MagickTrue) { pch->do_verbose2 = MagickTrue; pch->do_verbose = MagickTrue; } else { fprintf (stderr, "darkestmeander: ERROR: unknown option\n"); status = MagickFalse; } } if (!pch->edgeLeft && !pch->edgeBottom && !pch->edgeRight) { fprintf (stderr, "side: must contain any of lbr\n"); status = MagickFalse; } if (pch->do_verbose) { fprintf (stderr, "darkestmeander options:"); if (pch->cumOnly) fprintf (stderr, " cumerr"); fprintf (stderr, " maxiter %i", pch->max_iter); if (pch->autoIter) fprintf (stderr, " autoiter"); fprintf (stderr, " side "); if (pch->edgeLeft) fprintf (stderr, "l"); if (pch->edgeBottom) fprintf (stderr, "b"); if (pch->edgeRight) fprintf (stderr, "r"); if (pch->endIsGiven) fprintf (stderr, " end_at %li,%li", pch->endX, pch->endY); if (pch->do_verbose2) fprintf (stderr, " verbose2"); else if (pch->do_verbose) fprintf (stderr, " verbose"); fprintf (stderr, "\n"); } if (status == MagickFalse) usage (); return (status); } static MagickBooleanType FindMinAtEdge (const Image *image, darkestmeanderT * pch, CacheView *out_view, ssize_t * patX, ssize_t * patY, ExceptionInfo *exception) { // FIXME? If we have a span of equally good minimums, take the middle one. MagickBooleanType status = MagickTrue, FoundOne; ssize_t x, y, atYL, atXB, atYR, atX=0, atY=0; long double minVal, minValL, minValB, minValR; VIEW_PIX_PTR *q_out; FoundOne = MagickFalse; minVal = 999; if (pch->do_verbose2) printf ("FindMinAtEdge\n"); if (pch->edgeBottom) { if (pch->do_verbose2) printf ("Find minimum value on the bottom row.\n"); q_out=GetCacheViewAuthenticPixels(out_view,0,image->rows-1,image->columns,1,exception); if (q_out == (const VIEW_PIX_PTR *) NULL) { fprintf (stderr, "darkestmeander: bad q_out\n"); status=MagickFalse; } minValB = GET_PIXEL_RED(image,q_out); atXB = 0; for (x=0; x < (ssize_t) image->columns; x++) { long double thisVal; thisVal = GET_PIXEL_RED(image,q_out); if (minValB > thisVal) { minValB = thisVal; atXB = x; } q_out += Inc_ViewPixPtr (image); } if (FoundOne == MagickFalse) { FoundOne = MagickTrue; minVal = minValB; atX = atXB; atY = image->rows-1; } if (pch->do_verbose) printf ("Min value on bottom row %.*g at x=%i.\n", pch->precision, (double)minValB, (int)atXB); } if (pch->edgeLeft) { if (pch->do_verbose2) { printf ("Find minimum value on the left edge.\n"); } q_out=GetCacheViewAuthenticPixels(out_view,0,0,1,image->rows,exception); if (q_out == (const VIEW_PIX_PTR *) NULL) { fprintf (stderr, "darkestmeander: bad q_out\n"); status=MagickFalse; } minValL = GET_PIXEL_RED(image,q_out); atYL = 0; for (y=0; y < (ssize_t) image->rows; y++) { long double thisVal; thisVal = GET_PIXEL_RED(image,q_out); if (minValL > thisVal) { minValL = thisVal; atYL = y; } q_out += Inc_ViewPixPtr (image); } if (FoundOne == MagickFalse || minVal > minValL) { FoundOne = MagickTrue; minVal = minValL; atX = 0; atY = atYL; } if (pch->do_verbose) printf ("Min value on left %.*g at y=%i.\n", pch->precision, (double)minValL, (int)atYL); } if (pch->edgeRight) { if (pch->do_verbose2) { printf ("Find minimum value on the right edge.\n"); } q_out=GetCacheViewAuthenticPixels(out_view,image->columns-1,0,1,image->rows,exception); if (q_out == (const VIEW_PIX_PTR *) NULL) { fprintf (stderr, "darkestmeander: bad q_out\n"); status=MagickFalse; } minValR = GET_PIXEL_RED(image,q_out); atYR = 0; for (y=0; y < (ssize_t) image->rows; y++) { long double thisVal; thisVal = GET_PIXEL_RED(image,q_out); if (minValR > thisVal) { minValR = thisVal; atYR = y; } q_out += Inc_ViewPixPtr (image); } if (FoundOne == MagickFalse || minVal > minValR) { FoundOne = MagickTrue; minVal = minValR; atX = image->columns-1; atY = atYR; } if (pch->do_verbose) printf ("Min value on right %.*g at y=%i.\n", pch->precision, (double)minValR, (int)atYR); } *patX = atX; *patY = atY; if (FoundOne) { if (pch->do_verbose) printf ("Min value at %i,%i\n", (int)atX, (int)atY); } else { status = MagickFalse; } return (status); } static MagickBooleanType CalcPathCumul ( const Image *image, const Image *new_image, darkestmeanderT * pch, CacheView *out_view, ExceptionInfo *exception) { // Calculates cumulation, as for darkestpath, more or less. ssize_t y; MagickBooleanType status = MagickTrue; if (pch->do_verbose2) printf ("CalcPathCumul: Get minimum from adjacent pixels on previous row.\n"); // FIXME: green and blue values in row 0 are junk? // In q_out, we use blue and green channels to record delta x,y of parent of each pixel. // Values in these channels are 0, 50% or 100% representing delta of -1, 0, +1. // This simplifies finding the path after q_out is populated and stable. for (y=1; y < (ssize_t) image->rows; y++) { ssize_t x; long double minVal, prevVal; VIEW_PIX_PTR *q_out; //printf ("%i ", (int)y); const VIEW_PIX_PTR *p; if (status == MagickFalse) continue; p=GetCacheViewVirtualPixels(out_view,0,y-1,image->columns,1,exception); if (p == (const VIEW_PIX_PTR *) NULL) { fprintf (stderr, "bad GetCacheViewVirtualPixels out_view\n"); status=MagickFalse; continue; } q_out=GetCacheViewAuthenticPixels(out_view,0,y,image->columns,1,exception); if (q_out == (const VIEW_PIX_PTR *) NULL) { fprintf (stderr, "darkestmeander: bad GetCacheViewAuthenticPixels out_view\n"); status=MagickFalse; } for (x=0; x < (ssize_t) image->columns; x++) { signed int pv = 0; // Get minimum from adjacent pixels on previous row. minVal = GET_PIXEL_RED(image,p); if (x > 0) { prevVal = GET_PIXEL_RED(image,p-1); if (minVal > prevVal) { minVal = prevVal; pv = -1; } } if (x < image->columns-1) { prevVal = GET_PIXEL_RED(image,p+1); if (minVal > prevVal) { minVal = prevVal; pv = +1; } } if (minVal==0) printf ("minval==0 at %i,%i\n", (int)x,(int)y); SET_PIXEL_RED (new_image, GET_PIXEL_RED(new_image,q_out)+minVal, q_out); SET_PIXEL_GREEN (new_image, 0, q_out); SET_PIXEL_BLUE (new_image, (pv+1)/2.0*QuantumRange, q_out); p += Inc_ViewPixPtr (image); q_out += Inc_ViewPixPtr (new_image); } if (SyncCacheViewAuthenticPixels(out_view,exception) == MagickFalse) { fprintf (stderr, "bad sync\n"); status=MagickFalse; } } return (status); } static MagickBooleanType Calc4Cumul ( const Image *image, const Image *new_image, darkestmeanderT * pch, CacheView *image_view, CacheView *out_view, int * pnChanged, ExceptionInfo *exception) { // Calculates cumulation in four directions. // Returns the number of pixels updated. MagickBooleanType status = MagickTrue; VIEW_PIX_PTR *q_out; int nIter; *pnChanged = 0; for (nIter=1; nIter <= 4; nIter++) { ssize_t startU, endU, dU, startV, endV, dV, u, v, cacheWi, cacheHt; int nCh = 0; switch (nIter % 4) { case 0: if (pch->do_verbose2) printf ("Down "); startU = 1; endU = (ssize_t)image->rows; dU = +1; startV = 0; endV = (ssize_t)image->columns; dV = +1; cacheWi = (ssize_t)image->columns; cacheHt = 1; break; case 1: if (pch->do_verbose2) printf ("Right "); startU = 1; endU = (ssize_t)image->columns; dU = +1; startV = 0; endV = (ssize_t)image->rows; dV = +1; cacheWi = 1; cacheHt = (ssize_t)image->rows; break; case 2: if (pch->do_verbose2) printf ("Up "); startU = (ssize_t)image->rows-2; endU = -1; dU = -1; startV = (ssize_t)image->columns-1; endV = -1; dV = -1; cacheWi = (ssize_t)image->columns; cacheHt = 1; break; case 3: default: if (pch->do_verbose2) printf ("Left "); startU = (ssize_t)image->columns-2; endU = -1; dU = -1; startV = (ssize_t)image->rows-1; endV = -1; dV = -1; cacheWi = 1; cacheHt = (ssize_t)image->rows; break; } // u and v are proxies for y and x respectively. for (u=startU; u != endU; u+=dU) { ssize_t cacheT, cacheL, cacheTm1, cacheLm1; const VIEW_PIX_PTR *p, *qm1; switch (nIter % 4) { case 0: cacheT = u; cacheL = 0; cacheTm1 = u-1; cacheLm1 = 0; break; case 1: cacheT = 0; cacheL = u; cacheTm1 = 0; cacheLm1 = u-1; break; case 2: cacheT = u; cacheL = 0; cacheTm1 = u+1; cacheLm1 = 0; break; case 3: default: cacheT = 0; cacheL = u; cacheTm1 = 0; cacheLm1 = u+1; break; } p=GetCacheViewVirtualPixels(image_view,cacheL,cacheT,cacheWi,cacheHt,exception); if (p == (const VIEW_PIX_PTR *) NULL) { fprintf (stderr, "bad GetCacheViewVirtualPixels image_view\n"); status=MagickFalse; continue; } qm1=GetCacheViewVirtualPixels(out_view,cacheLm1,cacheTm1,cacheWi,cacheHt,exception); if (qm1 == (const VIEW_PIX_PTR *) NULL) { fprintf (stderr, "bad GetCacheViewVirtualPixels qm1\n"); status=MagickFalse; continue; } q_out=GetCacheViewAuthenticPixels(out_view,cacheL,cacheT,cacheWi,cacheHt,exception); if (q_out == (const VIEW_PIX_PTR *) NULL) { fprintf (stderr, "bad GetCacheViewAthenticPixels q_out\n"); status=MagickFalse; continue; } for (v=startV; v != endV; v+=dV) { long double minVal, prevVal; signed int pu=0, pv=0; minVal = (GET_PIXEL_RED(image,q_out)-GET_PIXEL_RED(image,p)) * 0.99999; prevVal = GET_PIXEL_RED(image,qm1); if (minVal > prevVal) { minVal = prevVal; pu=-1; pv=0; } // FIXME: for v7, -1 or +1 should be Inc_ViewPixPtr(image); if (v > startV) { prevVal = GET_PIXEL_RED(image,qm1-Inc_ViewPixPtr(image)); if (minVal > prevVal) { minVal = prevVal; pu=-1; pv=-1; } } if (v < endV-1) { prevVal = GET_PIXEL_RED(image,qm1+Inc_ViewPixPtr(image)); if (minVal > prevVal) { minVal = prevVal; pu=-1; pv=+1; } } if (v > startV) { prevVal = GET_PIXEL_RED(image,q_out-Inc_ViewPixPtr(image)); if (minVal > prevVal) { minVal = prevVal; pu=0; pv=-1; } } if (v < endV-1) { prevVal = GET_PIXEL_RED(image,q_out+Inc_ViewPixPtr(image)); if (minVal > prevVal) { minVal = prevVal; pu=0; pv=+1; } } if (pu != 0 || pv != 0) { pu *= dU; pv *= dV; if (minVal >= 0 || GET_PIXEL_RED(image,p) >= 0) { SET_PIXEL_RED (new_image, GET_PIXEL_RED(image,p)+minVal, q_out); if (nIter % 2 == 1) { signed int t = pu; pu = pv; pv = t; } SET_PIXEL_GREEN (new_image, (pu+1)/2.0*QuantumRange, q_out); SET_PIXEL_BLUE (new_image, (pv+1)/2.0*QuantumRange, q_out); nCh++; } } p += Inc_ViewPixPtr (image); qm1 += Inc_ViewPixPtr (image); q_out += Inc_ViewPixPtr (image); } if (SyncCacheViewAuthenticPixels(out_view,exception) == MagickFalse) { fprintf (stderr, "bad sync\n"); status=MagickFalse; } } if (pch->do_verbose2) printf ("%i nCh=%i\n", nIter, nCh); *pnChanged += nCh; } if (pch->do_verbose2) printf ("Calc4Cumul pnChanged=%i\n", *pnChanged); return (status); } static MagickBooleanType PaintPath ( const Image *current_path_image, const Image *new_path_image, darkestmeanderT * pch, CacheView *out_view, CacheView *path_view, ssize_t atX, ssize_t atY, MagickBooleanType *Cyclic, ExceptionInfo *exception) { ssize_t y, x; VIEW_PIX_PTR *q_path; MagickBooleanType status = MagickTrue; *Cyclic = MagickFalse; if (pch->do_verbose2) printf ("PaintPath: Set path end\n"); // Get just the one pixel we need. q_path=GetCacheViewAuthenticPixels(path_view,atX,atY,1,1,exception); if (q_path == (const VIEW_PIX_PTR *) NULL) { fprintf (stderr, "darkestmeander: bad GetCacheViewAuthenticPixels q_path\n"); status=MagickFalse; } SET_PIXEL_RED (new_path_image, QuantumRange, q_path); SET_PIXEL_GREEN (new_path_image, QuantumRange, q_path); SET_PIXEL_BLUE (new_path_image, QuantumRange, q_path); if (SyncCacheViewAuthenticPixels(path_view,exception) == MagickFalse) { fprintf (stderr, "bad sync\n"); status=MagickFalse; } if (pch->do_verbose2) printf ("PaintPath: Walk up the image, painting the path white.\n"); // Walk back through the meander, painting the path white. // At each point, find the parent. x = atX; y = atY; while (y > 0 && *Cyclic == MagickFalse && status == MagickTrue) { const VIEW_PIX_PTR *p; signed int di, dj; //if (pch->do_verbose2) printf ("PaintPath: xy=%i,%i ", (int)x, (int)y); p=GetCacheViewVirtualPixels(out_view,x,y,1,1,exception); if (p == (const VIEW_PIX_PTR *) NULL) { fprintf (stderr, "darkestmeander: bad GetCacheViewVirtualPixels p\n"); status=MagickFalse; } di = (signed int)(GET_PIXEL_BLUE (current_path_image, p) / QuantumRange * 2.0 + 0.5) -1; dj = (signed int)(GET_PIXEL_GREEN (current_path_image, p) / QuantumRange * 2.0 + 0.5) -1; x += di; y += dj; //if (pch->do_verbose2) printf (" dij=%i,%i W%i,%i\n", di, dj, (int)x, (int)y); // Get just the one pixel we need. q_path=GetCacheViewAuthenticPixels(path_view,x,y,1,1,exception); if (q_path == (const VIEW_PIX_PTR *) NULL) { fprintf (stderr, "PaintPath: bad GetCacheViewAuthenticPixels path_view\n"); status=MagickFalse; } if (GET_PIXEL_RED (new_path_image, q_path) > 0) { if (pch->do_verbose) fprintf (stderr, "PaintPath: Path not black at %i,%i: cyclic\n", (int)x, (int)y); y = 0; *Cyclic = MagickTrue; } SET_PIXEL_RED (new_path_image, QuantumRange, q_path); SET_PIXEL_GREEN (new_path_image, QuantumRange, q_path); SET_PIXEL_BLUE (new_path_image, QuantumRange, q_path); if (SyncCacheViewAuthenticPixels(path_view,exception) == MagickFalse) { fprintf (stderr, "bad sync\n"); status=MagickFalse; } } if (pch->do_verbose2) printf ("PaintPath: done\n"); return (status); } static Image *TryPath(Image *current_path_image, darkestmeanderT * pch, ssize_t atX, ssize_t atY, CacheView *out_view, MagickBooleanType *IsEqual, ExceptionInfo *exception) // Makes a new path, and compare it to current_path_image. // Destroys current_path_image, and returns the new path image (or NULL if failure). { Image *new_path_image; CacheView *new_path_view; MagickBooleanType Cyclic; if (pch->do_verbose2) printf ("TryPath\n"); // Create a new black image for the path. // Make a clone of this image, same size but undefined pixel values: // new_path_image = CloneImage ( current_path_image, current_path_image->columns, current_path_image->rows, MagickTrue, exception); if (new_path_image == (Image *) NULL) return(new_path_image); if (SetNoPalette (new_path_image, exception) == MagickFalse) return (Image *)NULL; SetAllBlack (new_path_image, exception); new_path_view=AcquireAuthenticCacheView(new_path_image,exception); if (PaintPath ( current_path_image, new_path_image, pch, out_view, new_path_view, atX, atY, &Cyclic, exception )==MagickFalse) { return (Image *)NULL; } *IsEqual = (Cyclic == MagickFalse) && #if IMV6OR7==6 IsImagesEqual (new_path_image, current_path_image); #else IsImagesEqual (new_path_image, current_path_image, exception); #endif new_path_view=DestroyCacheView(new_path_view); DestroyImageList(current_path_image); return (new_path_image); } static MagickBooleanType CalcCumul ( const Image *image, const Image *new_image, Image **current_path_image, darkestmeanderT * pch, CacheView *image_view, CacheView *out_view, ExceptionInfo *exception) { MagickBooleanType status = MagickTrue; ssize_t prevAtX=-1, prevAtY=-1; status = CalcPathCumul (image, new_image, pch, out_view, exception); if (status == MagickFalse) return MagickFalse; if (pch->do_verbose2) printf ("CalcCumul: Get meander minimums.\n"); int nIterCyc = 0; int nChanged = 0; MagickBooleanType IsEqual = MagickFalse; do { status = Calc4Cumul (image, new_image, pch, image_view, out_view, &nChanged, exception); nIterCyc++; if (pch->autoIter) { ssize_t atX, atY; if (pch->endIsGiven) { atX = pch->endX; atY = pch->endY; } else { status = FindMinAtEdge (image, pch, out_view, &atX, &atY, exception); if (status == MagickFalse) return MagickFalse; } //status = FindMinAtEdge (image, pch, out_view, &atX, &atY, exception); //if (status == MagickFalse) return MagickFalse; if (prevAtX == atX && prevAtY == atY) { *current_path_image = TryPath(*current_path_image, pch, atX, atY, out_view, &IsEqual, exception); } if (pch->do_verbose) printf ("CalcCumul: nIterCyc=%i at %i,%i IsEqual=%i\n", nIterCyc, (int)atX, (int)atY, (int)IsEqual); prevAtX = atX; prevAtY = atY; } } while ( nChanged > 0 && IsEqual==MagickFalse && (pch->max_iter == 0 || nIterCyc < pch->max_iter)); if (pch->do_verbose) { printf ("darkestmeander: nIterCyc=%i\n", nIterCyc); if (nChanged == 0) { printf ("darkestmeander: completed\n"); } else if (pch->max_iter > 0 && nIterCyc >= pch->max_iter) { printf ("darkestmeander: nIterCyc bust: %i\n", nIterCyc); } else if (IsEqual) { printf ("darkestmeander: path stable\n"); } } return (status); } static MagickBooleanType EnsureNonZero (Image *image, darkestmeanderT * pch, ExceptionInfo *exception) { CacheView *image_view; ssize_t y; MagickBooleanType status = MagickTrue; #if defined(MAGICKCORE_HDRI_SUPPORT) #define NON_ZERO_VALUE 1.0 #else #define NON_ZERO_VALUE 1 #endif if (pch->do_verbose2) { printf ("EnsureNonZero: %g\n", NON_ZERO_VALUE); } image_view=AcquireAuthenticCacheView(image,exception); for (y=0; y < (ssize_t) image->rows; y++) { ssize_t x; VIEW_PIX_PTR *p; if (status == MagickFalse) continue; p=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception); if (p == (const VIEW_PIX_PTR *) NULL) { fprintf (stderr, "EnsureNonZero: bad GetCacheViewAuthenticPixels p\n"); status=MagickFalse; } for (x=0; x < (ssize_t) image->columns; x++) { if (GET_PIXEL_RED(image,p) == 0) SET_PIXEL_RED(image, NON_ZERO_VALUE, p); p += Inc_ViewPixPtr (image); } if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse) { fprintf (stderr, "bad sync\n"); status=MagickFalse; } } image_view=DestroyCacheView(image_view); return (status); } // The next function corresponds in style to functions in transform.c // It takes one image, and returns an image. // static Image *darkestmeander(Image *image, darkestmeanderT * pch, ExceptionInfo *exception) { Image *new_image, *path_image; CacheView *image_view, *out_view; ssize_t atX, atY; MagickBooleanType status = MagickTrue; pch->precision = GetMagickPrecision(); if (image->debug != MagickFalse) (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename); if (!Has3Channels (image)) { fprintf (stderr, "Image has less than three channels.\n"); return (Image *)NULL; } // No good, because it knocks out negative values. // We must change _only_ where Red is exactly zero. // status = EvaluateImageChannel(image, RedChannel, MaxEvaluateOperator, 1, exception); // if (status==MagickFalse) return (Image *) NULL; EnsureNonZero (image, pch, exception); //const ChannelType channel,const MagickEvaluateOperator op,const double value, //ExceptionInfo *exception) // Copy the current image. new_image=CloneImage(image, 0, 0, MagickTrue, exception); if (new_image == (Image *) NULL) return(new_image); if (SetNoPalette (new_image, exception) == MagickFalse) return (Image *)NULL; //GetMagickVIEW_PIX_PTR(new_image, &mppBlack); //(void)SetImageColor(new_image, &mppBlack); status=MagickTrue; out_view=AcquireAuthenticCacheView(new_image,exception); image_view=AcquireVirtualCacheView(image,exception); if (pch->do_verbose2) printf ("Create a new black image for the path.\n"); // Create a new black image for the path. // Make a clone of this image, same size but undefined pixel values: // path_image=CloneImage(image, image->columns, image->rows, MagickTrue, exception); if (path_image == (Image *) NULL) return(path_image); if (SetNoPalette (path_image, exception) == MagickFalse) return (Image *)NULL; SetAllBlack (path_image, exception); status = CalcCumul (image, new_image, &path_image, pch, image_view, out_view, exception); if (status == MagickFalse) return (Image *)NULL; if (pch->cumOnly) { if (pch->do_verbose2) printf ("End.\n"); image_view=DestroyCacheView(image_view); out_view=DestroyCacheView(out_view); DestroyImageList(path_image); return (new_image); } else { CacheView *path_view; MagickBooleanType Cyclic; if (pch->endIsGiven) { atX = pch->endX; atY = pch->endY; } else { status = FindMinAtEdge (image, pch, out_view, &atX, &atY, exception); if (status == MagickFalse) return (Image *)NULL; } // FIXME: maybe we have a good path already. SetAllBlack (path_image, exception); path_view=AcquireAuthenticCacheView(path_image,exception); status = PaintPath (image, path_image, pch, out_view, path_view, atX, atY, &Cyclic, exception); if (status == MagickFalse) return (Image *)NULL; if (Cyclic == MagickTrue) { fprintf (stderr, "darkestmeander: Path is cyclic at %i,%i\n", (int)atX, (int)atY); } path_view=DestroyCacheView(path_view); image_view=DestroyCacheView(image_view); out_view=DestroyCacheView(out_view); DestroyImageList(new_image); return (path_image); } } ModuleExport size_t darkestmeanderImage(Image **images,const int argc, const char **argv,ExceptionInfo *exception) { Image *image, *new_image; MagickBooleanType status; darkestmeanderT dm; assert(images != (Image **) NULL); assert(*images != (Image *) NULL); assert((*images)->signature == MAGICK_CORE_SIG); #if defined(MAGICKCORE_HDRI_SUPPORT) #else fprintf (stderr, "No HDRI. darkestmeander may not work correctly."); #endif status = menu (argc, argv, &dm); if (status == MagickFalse) return (-1); for (image=(*images); image != (Image *) NULL; image=GetNextImageInList(image)) { if (dm.endIsGiven) { if (dm.endX >= image->columns || dm.endY >= image->rows) { fprintf (stderr, "Given end is outside image.\n"); return (-1); } } new_image = darkestmeander (image, &dm, exception); if (new_image == (Image *)NULL) return -1; ReplaceImageInList(&image,new_image); *images=GetFirstImageInList(image); } return(MagickImageFilterSignature); }
/* 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 onePow = log(psss->goal_mn) / log(psss->fnd_mean/(double)QuantumRange); psss->pow *= onePow; if (fabs(onePow-1) < psss->error) break; if (psss->do_verbose > 1) fprintf (psss->fh_data, "before pow: onePow=%.*g pow=%.*g: mean=%.*g sd=%.*g ", psss->precision, onePow, psss->precision, psss->pow, psss->precision, psss->fnd_mean / (double)QuantumRange, psss->precision, psss->fnd_sd / (double)QuantumRange); psss->nIter++; if (!EvaluateImage (temp_image, PowEvaluateOperator, onePow, exception)) return (Image *)NULL; if (!GetImageMean (temp_image, &psss->fnd_mean, &psss->fnd_sd, exception)) return (Image *)NULL; if (isnan(psss->fnd_sd)) psss->fnd_sd = 0; } } } if (psss->do_verbose > 1) fprintf (psss->fh_data, "contrast=%.*g: mean=%.*g sd=%.*g ", psss->precision, contrast, psss->precision, psss->fnd_mean / (double)QuantumRange, psss->precision, psss->fnd_sd / (double)QuantumRange); double err = fabs (psss->fnd_sd/(double)QuantumRange - psss->goal_sd); psss->error = (err > psss->tolerance) ? err : psss->tolerance; return (temp_image); } static void AnnounceResult ( Image *image, setmnsdT * psss, double contrast, ExceptionInfo *exception) { if (psss->do_verbose) { fprintf (psss->fh_data, "nIter=%i\n", psss->nIter); fprintf (psss->fh_data, "result: mean=%.*g sd=%.*g\n", psss->precision, psss->fnd_mean / (double)QuantumRange, psss->precision, psss->fnd_sd / (double)QuantumRange); } char msg[200] = ""; if (contrast != 0) { sprintf (msg, "%csigmoidal-contrast %.*g,%.*g%%", psss->sharpen ? '-':'+', psss->precision, contrast, psss->precision, psss->mid_pt_q * 100.0 / (double)QuantumRange); } if (psss->has_mn_goal) { char msg2[100]; sprintf (msg2, " -evaluate pow %.*g", psss->precision, psss->pow); strcat (msg, msg2); } // If no change, make a "no-op" operation. if (!*msg) strcpy (msg, "-evaluate Add 0"); if (psss->do_verbose) { fprintf (psss->fh_data, "setmnsd command: %s\n", msg); } #if IMV6OR7==6 SetImageProperty (image, "filter:setmnsd", msg); #else SetImageProperty (image, "filter:setmnsd", msg, exception); #endif } // The next function corresponds in style to functions in transform.c // It takes one image, and returns an image (or the input image, unchanged). // static Image *setmnsd ( Image *image, setmnsdT * psss, ExceptionInfo *exception) { assert(image != (Image *) NULL); assert(image->signature == MAGICK_CORE_SIG); if (image->debug != MagickFalse) (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename); // if (SetNoPalette (image, exception) == MagickFalse) // return (Image *)NULL; psss->nIter = 0; double tol_q = psss->tolerance * QuantumRange; psss->mid_pt_q = psss->mid_pt/100.0 * QuantumRange; { double goal_sd_q = psss->goal_sd * QuantumRange; psss->upper_goal_sd = goal_sd_q + tol_q; psss->lower_goal_sd = goal_sd_q - tol_q; } double contrast0 = psss->initCon0; double contrast1; double contrast2 = psss->initCon2; if (psss->do_verbose > 1) fprintf (psss->fh_data, "setmnsd: contrasts %.*g %.*g\n", psss->precision, contrast0, psss->precision, contrast2); double mean, stddev; if (!GetImageMean (image, &mean, &stddev, exception)) return (Image *)NULL; if (isnan(stddev)) stddev = 0; if (psss->do_verbose) fprintf (psss->fh_data, "input: mean=%.*g sd=%.*g\n", psss->precision, mean / (double)QuantumRange, psss->precision, stddev / (double)QuantumRange); if (stddev==0 && psss->has_sd_goal) { if (psss->do_verbose) fprintf (psss->fh_data, "setmnsd Warning: Input SD is zero, so ignoring SD goal\n"); psss->has_sd_goal = MagickFalse; psss->pin_sd = MagickFalse; } if ((mean<=0 || mean==QuantumRange) && psss->has_mn_goal) { if (psss->do_verbose) fprintf (psss->fh_data, "setmnsd Warning: Input mean is <=zero or 100%%, so ignoring mean goal\n"); psss->has_mn_goal = MagickFalse; psss->pin_mn = MagickFalse; } if (psss->pin_sd) { psss->goal_sd = stddev / (double)QuantumRange; double goal_sd_q = psss->goal_sd * QuantumRange; psss->upper_goal_sd = goal_sd_q + tol_q; psss->lower_goal_sd = goal_sd_q - tol_q; } if (psss->pin_mn) psss->goal_mn = mean / (double)QuantumRange; if (psss->mid_is_mean) { psss->mid_pt_q = mean; } Image * iInit = TryContrast (image, psss, 0, exception); if (iInit == (Image *)NULL) return (iInit); if (!psss->has_sd_goal) { // That's all we have to do. AnnounceResult (iInit, psss, 0, exception); return (iInit); } iInit = DestroyImage (iInit); mean = psss->fnd_mean; stddev = psss->fnd_sd; psss->sharpen = MagickTrue; // whether to increase contrast in middle if (stddev > psss->upper_goal_sd) { if (psss->do_verbose > 1) fprintf (psss->fh_data, "above upper\n"); psss->sharpen = MagickFalse; if (psss->direction == dIncOnly) return (image); double t = contrast0; contrast0 = contrast2; contrast2 = t; } else if (stddev < psss->lower_goal_sd) { if (psss->do_verbose > 1) fprintf (psss->fh_data, "below lower\n"); if (psss->direction == dDecOnly) return (image); psss->sharpen = MagickTrue; } else { if (psss->do_verbose > 1) fprintf (psss->fh_data, "within tolerance\n"); AnnounceResult (image, psss, 0, exception); return (image); } Image * i0 = TryContrast (image, psss, contrast0, exception); if (i0 == (Image *)NULL) return (i0); if (psss->fnd_sd > psss->upper_goal_sd) { if (psss->do_verbose) fprintf (psss->fh_data, "i0 above upper -- fail\n"); fprintf (stderr, "i0 C=%.*g stddev too high: %.*g\n", psss->precision, contrast0, psss->precision, psss->fnd_sd / (double)QuantumRange); i0 = DestroyImage (i0); return (i0); } else if (psss->fnd_sd < psss->lower_goal_sd) { if (psss->do_verbose > 1) fprintf (psss->fh_data, "i0 below lower -- good\n"); } else { if (psss->do_verbose > 1) fprintf (psss->fh_data, "i0 within tolerance\n"); AnnounceResult (i0, psss, contrast0, exception); return (i0); } i0 = DestroyImage (i0); Image * i2 = TryContrast (image, psss, contrast2, exception); if (i2 == (Image *)NULL) return (i2); if (psss->fnd_sd > psss->upper_goal_sd) { if (psss->do_verbose > 1) fprintf (psss->fh_data, "i2 above upper -- good\n"); } else if (psss->fnd_sd < psss->lower_goal_sd) { if (psss->do_verbose) fprintf (psss->fh_data, "i2 below lower -- fail\n"); fprintf (stderr, "i2 C=%.*g stddev too low: %.*g\n", psss->precision, contrast2, psss->precision, psss->fnd_sd / (double)QuantumRange); i2 = DestroyImage (i2); return (i2); } else { if (psss->do_verbose > 1) fprintf (psss->fh_data, "i2 within tolerance\n"); AnnounceResult (i2, psss, contrast2, exception); return (i2); } i2 = DestroyImage (i2); // Limit the number of iterations, just in case... int i; for (i=0; i < 100; i++) { double prod = contrast0 * contrast2; if (prod > 0) contrast1 = sqrt (contrast0 * contrast2); else contrast1 = (contrast0 + contrast2) / 2.0; Image * i1 = TryContrast (image, psss, contrast1, exception); if (i1 == (Image *)NULL) return (i1); if (psss->fnd_sd > psss->upper_goal_sd) { if (psss->do_verbose > 1) fprintf (psss->fh_data, "i1 above upper\n"); contrast2 = contrast1; } else if (psss->fnd_sd < psss->lower_goal_sd) { if (psss->do_verbose > 1) fprintf (psss->fh_data, "i1 below lower\n"); contrast0 = contrast1; } else { if (psss->do_verbose > 1) fprintf (psss->fh_data, "i1 within tolerance\n"); // If mean is a goal, do it again with tighter tolerance on mean. if (psss->has_mn_goal) { i1 = DestroyImage (i1); i1 = TryContrast (image, psss, contrast1, exception); if (i1 == (Image *)NULL) return (i1); } AnnounceResult (i1, psss, contrast1, exception); return (i1); } i1 = DestroyImage (i1); } if (psss->do_verbose) fprintf (psss->fh_data, "Bust. Using contrast1 = %.*g\n", psss->precision, contrast1); Image * i1 = TryContrast (image, psss, contrast1, exception); if (i1 == (Image *)NULL) return (i1); AnnounceResult (i1, psss, contrast1, exception); return (i1); } ModuleExport size_t setmnsdImage(Image **images,const int argc, const char **argv,ExceptionInfo *exception) { Image *image, *new_image; MagickBooleanType status; setmnsdT sss; assert(images != (Image **) NULL); assert(*images != (Image *) NULL); assert((*images)->signature == MAGICK_CORE_SIG); sss.precision = GetMagickPrecision(); status = menu (argc, argv, &sss); if (status == MagickFalse) return (-1); for (image=(*images); image != (Image *) NULL; image=GetNextImageInList(image)) { new_image = setmnsd (image, &sss, exception); if (new_image == (Image *) NULL) return(-1); if (new_image != image) ReplaceImageInList(&image,new_image); *images=GetFirstImageInList(image); } return(MagickImageFilterSignature); }
/* Reference: http://im.snibgo.com/customim.htm http://im.snibgo.com/integim.htm Latest update: 24-July-2017 */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <assert.h> #include "vsn_defines.h" /* Latest update: */ typedef enum { aaAuto, aaDisregard, aaRegard } alphaActionT; typedef struct { FILE * fh_data; MagickBooleanType do_regardalpha, do_verbose; alphaActionT alphaAction; } integimT; static void usage (void) { printf ("Usage: -process 'integim [OPTION]...'\n"); printf ("Create an HDRI integral image (summed area table).\n"); printf ("\n"); printf (" pm, premult 'yes' or 'no' or 'auto'\n"); printf (" f, file string Write verbose text to stderr or stdout\n"); printf (" v, verbose write text information to stdout\n"); printf ("\n"); } static MagickBooleanType IsArg (char * pa, char * ShtOpt, char * LongOpt) { if ((LocaleCompare(pa, ShtOpt)==0) || (LocaleCompare(pa, LongOpt)==0)) return MagickTrue; return MagickFalse; } static MagickBooleanType menu( const int argc, const char **argv, integimT * pch ) // Returns MagickTrue if okay. { int i; MagickBooleanType status; status = MagickTrue; pch->fh_data = stderr; pch->do_regardalpha = pch->do_verbose = MagickFalse; pch->alphaAction = aaRegard; for (i=0; i < argc; i++) { char * pa = (char *)argv[i]; if (IsArg (pa, "pm", "premult")==MagickTrue) { i++; if (strcasecmp (argv[i], "auto")==0) pch->alphaAction = aaAuto; else if (strcasecmp (argv[i], "yes")==0) pch->alphaAction = aaRegard; else if (strcasecmp (argv[i], "no")==0) pch->alphaAction = aaDisregard; else status = MagickFalse; } else if (IsArg (pa, "f", "file")==MagickTrue) { i++; if (strcasecmp (argv[i], "stdout")==0) pch->fh_data = stdout; else if (strcasecmp (argv[i], "stderr")==0) pch->fh_data = stderr; else status = MagickFalse; } else if (IsArg (pa, "v", "verbose")==MagickTrue) { pch->do_verbose = MagickTrue; } else { fprintf (stderr, "integim: ERROR: unknown option [%s]\n", pa); status = MagickFalse; } } if (pch->do_verbose) { fprintf (stderr, "integim options:"); // if (pch->do_regardalpha) fprintf (stderr, " regardalpha"); if (pch->alphaAction != aaAuto) { fprintf (stderr, " premult "); if (pch->alphaAction==aaRegard) fprintf (stderr, "yes"); else if (pch->alphaAction==aaDisregard) fprintf (stderr, "no"); else fprintf (stderr, "??"); } if (pch->fh_data == stdout) fprintf (stderr, " file stdout"); if (pch->do_verbose) fprintf (stderr, " verbose"); fprintf (stderr, "\n"); } if (status == MagickFalse) usage (); return (status); } #if defined(MAGICKCORE_HDRI_SUPPORT) #define ADD_HALF #else #define ADD_HALF +0.5 #endif // The next function corresponds in style to functions in transform.c // It takes one image, and returns an image. // static Image * integim ( const Image *image, integimT * pch, ExceptionInfo *exception) { Image *new_image; CacheView *image_view, *out_view; ssize_t y, inXmult, outXmult; MagickBooleanType IsAlphaOn = IS_ALPHA_CH(image), status; if (image->debug != MagickFalse) (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename); if (pch->do_verbose) { fprintf (pch->fh_data, "integim: Input image [%s] depth is %i IsAlpha %s\n", image->filename, (int)image->depth, IsAlphaOn ? "ON":"OFF" ); } switch (pch->alphaAction) { case aaAuto: pch->do_regardalpha = IsAlphaOn; if (pch->do_verbose) fprintf (pch->fh_data, "integim: alphaaction auto: regardalpha %s\n", pch->do_regardalpha ? "TRUE" : "FALSE"); break; case aaRegard: pch->do_regardalpha = MagickTrue; break; case aaDisregard: pch->do_regardalpha = MagickFalse; break; } MagickBooleanType UseAlpha = (IsAlphaOn && pch->do_regardalpha); // FIXME: Can we do this in-place? new_image=CloneImage(image, image->columns, image->rows, MagickTrue, exception); if (new_image == (Image *) NULL) return(new_image); // Just in case image had a palette. if (SetNoPalette (new_image, exception) == MagickFalse) return (Image *)NULL; inXmult = Inc_ViewPixPtr (image); outXmult = Inc_ViewPixPtr (new_image); status=MagickTrue; out_view=AcquireAuthenticCacheView(new_image,exception); image_view=AcquireVirtualCacheView(image,exception); // We can't easily parallelise // because each output row needs the previous row. for (y=0; y < (ssize_t) image->rows; y++) { const VIEW_PIX_PTR *p, *q_prev = NULL; VIEW_PIX_PTR *q_out; ssize_t x; long double alpha; if (status==MagickFalse) continue; p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception); if (p == (const VIEW_PIX_PTR *) NULL) { fprintf (stderr, "bad GetCacheViewVirtualPixels image_view\n"); status=MagickFalse; } q_out=GetCacheViewAuthenticPixels(out_view,0,y,new_image->columns,1,exception); if (q_out == (const VIEW_PIX_PTR *) NULL) { fprintf (stderr, "integim: bad GetCacheViewAuthenticPixels out_view\n"); status=MagickFalse; } if (y > 0) { q_prev=GetCacheViewVirtualPixels(out_view,0,y-1,new_image->columns,1,exception); if (q_prev == (const VIEW_PIX_PTR *) NULL) { fprintf (stderr, "integim: bad GetCacheViewAuthenticPixels q_prev\n"); status=MagickFalse; } } alpha = 1.0; if (status==MagickFalse) continue; long double left_r = 0, up_r = 0, upleft_r = 0, left_g = 0, up_g = 0, upleft_g = 0, left_b = 0, up_b = 0, upleft_b = 0, left_a = 0, up_a = 0, upleft_a = 0; for (x=0; x < (ssize_t) image->columns; x++) { if (x > 0) { left_r = GET_PIXEL_RED(new_image, q_out-outXmult); left_g = GET_PIXEL_GREEN(new_image, q_out-outXmult); left_b = GET_PIXEL_BLUE(new_image, q_out-outXmult); left_a = GET_PIXEL_ALPHA(new_image, q_out-outXmult); } if (y > 0) { up_r = GET_PIXEL_RED(new_image, q_prev); up_g = GET_PIXEL_GREEN(new_image, q_prev); up_b = GET_PIXEL_BLUE(new_image, q_prev); up_a = GET_PIXEL_ALPHA(new_image, q_prev); if (x > 0) { upleft_r = GET_PIXEL_RED(new_image, q_prev-outXmult); upleft_g = GET_PIXEL_GREEN(new_image, q_prev-outXmult); upleft_b = GET_PIXEL_BLUE(new_image, q_prev-outXmult); upleft_a = GET_PIXEL_ALPHA(new_image, q_prev-outXmult); } } if (UseAlpha) { alpha = GET_PIXEL_ALPHA(image,p) / (long double)QuantumRange; } SET_PIXEL_RED (new_image, alpha * GET_PIXEL_RED(image,p) + left_r + up_r - upleft_r ADD_HALF, q_out); SET_PIXEL_GREEN (new_image, alpha * GET_PIXEL_GREEN(image,p) + left_g + up_g - upleft_g ADD_HALF, q_out); SET_PIXEL_BLUE (new_image, alpha * GET_PIXEL_BLUE(image,p) + left_b + up_b - upleft_b ADD_HALF, q_out); if (IsAlphaOn) { SET_PIXEL_ALPHA (new_image, GET_PIXEL_ALPHA(image,p) + left_a + up_a - upleft_a ADD_HALF, q_out); } p += inXmult; q_out += outXmult; q_prev += outXmult; } if (SyncCacheViewAuthenticPixels(out_view,exception) == MagickFalse) { fprintf (stderr, "bad sync\n"); status=MagickFalse; } } image_view=DestroyCacheView(image_view); out_view=DestroyCacheView(out_view); return (new_image); } ModuleExport size_t integimImage(Image **images,const int argc, const char **argv,ExceptionInfo *exception) { Image *image, *new_image; MagickBooleanType status; integimT cumul_histo; assert(images != (Image **) NULL); assert(*images != (Image *) NULL); assert((*images)->signature == MAGICK_CORE_SIG); status = menu (argc, argv, &cumul_histo); if (status == MagickFalse) return (-1); for (image=(*images); image != (Image *) NULL; image=GetNextImageInList(image)) { new_image = integim (image, &cumul_histo, exception); if (!new_image) return(-1); ReplaceImageInList(&image,new_image); *images=GetFirstImageInList(image); } return(MagickImageFilterSignature); }
/* Reference: http://im.snibgo.com/customim.htm http://im.snibgo.com/integim.htm Latest update: 24-July-2017 Fixed bug on top line and left column. (x1 and y1 can now be -1.) 3-April-2018 for v7.0.7-28 */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <assert.h> #include "vsn_defines.h" typedef enum { aaAuto, aaDisregard, aaRegard } alphaActionT; typedef struct { FILE * fh_data; char *sWindow; MagickBooleanType do_regardalpha, do_verbose, dontDivide; alphaActionT alphaAction; ssize_t offsetx, offsety, winWidth, winHeight; } deintegimT; static void usage (void) { printf ("Usage: -process 'deintegim [OPTION]...'\n"); printf ("De-integrate an HDRI integral image (summed area table).\n"); printf ("\n"); printf (" w, window string window size, WxH\n"); printf (" pd, postdiv 'yes' or 'no' or 'auto'\n"); printf (" dd, dontdivide don't divide colour channels\n"); printf (" ox, offsetX int offset for window centre in X-direction\n"); printf (" oy, offsetY int offset for window centre in Y-direction\n"); printf (" f, file string Write verbose text to stderr or stdout\n"); printf (" v, verbose write text information to stdout\n"); printf ("\n"); } static MagickBooleanType IsArg (char * pa, char * ShtOpt, char * LongOpt) { if ((LocaleCompare(pa, ShtOpt)==0) || (LocaleCompare(pa, LongOpt)==0)) return MagickTrue; return MagickFalse; } static MagickBooleanType menu( const int argc, const char **argv, deintegimT * pch ) // Returns MagickTrue if okay. { int i; MagickBooleanType status; status = MagickTrue; pch->fh_data = stderr; pch->sWindow = NULL; pch->alphaAction = aaRegard; pch->do_regardalpha = pch->do_verbose = MagickFalse; pch->dontDivide = MagickFalse; pch->offsetx = 0; pch->offsety = 0; for (i=0; i < argc; i++) { char * pa = (char *)argv[i]; if (IsArg (pa, "w", "window")==MagickTrue) { i++; pch->sWindow = (char *)argv[i]; } else if (IsArg (pa, "pd", "postdiv")==MagickTrue) { i++; if (strcasecmp (argv[i], "auto")==0) pch->alphaAction = aaAuto; else if (strcasecmp (argv[i], "yes")==0) pch->alphaAction = aaRegard; else if (strcasecmp (argv[i], "no")==0) pch->alphaAction = aaDisregard; else status = MagickFalse; } else if (IsArg (pa, "f", "file")==MagickTrue) { i++; if (strcasecmp (argv[i], "stdout")==0) pch->fh_data = stdout; else if (strcasecmp (argv[i], "stderr")==0) pch->fh_data = stderr; else status = MagickFalse; } else if (IsArg (pa, "ox", "offsetX")==MagickTrue) { i++; pch->offsetx = atoi(argv[i]); } else if (IsArg (pa, "oy", "offsetY")==MagickTrue) { i++; pch->offsety = atoi(argv[i]); } else if (IsArg (pa, "dd", "dontdivide")==MagickTrue) { pch->dontDivide = MagickTrue; } else if (IsArg (pa, "v", "verbose")==MagickTrue) { pch->do_verbose = MagickTrue; } else { fprintf (stderr, "deintegim: ERROR: unknown option [%s]\n", pa); status = MagickFalse; } } if (pch->do_verbose) { fprintf (stderr, "deintegim options:"); if (pch->sWindow) fprintf (stderr, " window %s", pch->sWindow); if (pch->offsetx) fprintf (stderr, " offsetX %li", pch->offsetx); if (pch->offsety) fprintf (stderr, " offsetY %li", pch->offsety); if (pch->alphaAction != aaAuto) { fprintf (stderr, " postdiv "); if (pch->alphaAction==aaRegard) fprintf (stderr, "yes"); else if (pch->alphaAction==aaDisregard) fprintf (stderr, "no"); else fprintf (stderr, "??"); } if (pch->fh_data == stdout) fprintf (stderr, " file stdout"); if (pch->dontDivide) fprintf (stderr, " dontdivide"); if (pch->do_verbose) fprintf (stderr, " verbose"); fprintf (stderr, "\n"); } if (status == MagickFalse) usage (); return (status); } static MagickBooleanType GetNumber ( char ** p, double *pValue, ssize_t dim) // Returns whether valid. { int len; if (sscanf (*p, "%lf%n", pValue, &len) != 1) return MagickFalse; *p += len; char c = **p; switch (c) { case 'c': case 'C': case '%': *pValue = floor (*pValue/100.0 * dim + 0.5); if (*pValue < 1) *pValue = 1; (*p)++; break; case 'p': case 'P': *pValue = floor (*pValue * dim + 0.5); if (*pValue < 1) *pValue = 1; (*p)++; break; case '\0': case 'x': case 'X': /* nothing */ ; break; default: printf ("Bad terminator? [%c]\n", c); } return MagickTrue; } static MagickBooleanType ParseWindow ( deintegimT * pch, ssize_t imgWidth, ssize_t imgHeight ) // Expected format: WxH // where W and H are numbers optionally followed by '%' or 'c' or 'p'. // Returns whether okay. { double w=0, h=0; char * p = pch->sWindow; if (!*p) return MagickFalse; if (!GetNumber (&p, &w, imgWidth)) { fprintf (stderr, "deintegim error: Window needs two dimensions.\n"); return MagickFalse; } if (*p!='x') { fprintf (stderr, "deintegim error: Window needs 'x' betweem two dimensions.\n"); return MagickFalse; } p++; if (!GetNumber (&p, &h, imgHeight)) { fprintf (stderr, "deintegim error: Window needs two dimensions.\n"); return MagickFalse; } if (w < 1 || h < 1) { fprintf (stderr, "deintegim error: Window dimensions must be >=1.\n"); return MagickFalse; } if (*p) { fprintf (stderr, "deintegim error: Window dimensions have extra characters [%s].\n", p); return MagickFalse; } pch->winWidth = w; pch->winHeight = h; return MagickTrue; } #if defined(MAGICKCORE_HDRI_SUPPORT) #define ADD_HALF #else #define ADD_HALF +0.5 #endif // The next function corresponds in style to functions in transform.c // It takes one image, and returns an image. // static Image * deintegim ( const Image *image, deintegimT * pch, ExceptionInfo *exception) { Image *new_image; CacheView *image_view, *out_view; ssize_t y, inXmult, outXmult; MagickBooleanType IsAlphaOn = IS_ALPHA_CH(image), status; if (image->debug != MagickFalse) (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename); if (pch->do_verbose) { fprintf (pch->fh_data, "deintegim: Input image [%s] depth is %i IsAlpha %s\n", image->filename, (int)image->depth, IsAlphaOn ? "ON":"OFF" ); } pch->winWidth = pch->winHeight = 1; if (pch->sWindow) { if (! ParseWindow (pch, image->columns, image->rows)) return (Image *)NULL; } if (pch->do_verbose) fprintf (pch->fh_data, "deintegim Window: %lix%li\n", pch->winWidth, pch->winHeight); ssize_t winSemiHt = (pch->winHeight+1) / 2; ssize_t winSemiWi = (pch->winWidth+1) / 2; switch (pch->alphaAction) { case aaAuto: pch->do_regardalpha = IsAlphaOn; if (pch->do_verbose) fprintf (pch->fh_data, "deintegim: alphaaction auto: regardalpha %s\n", pch->do_regardalpha ? "TRUE" : "FALSE"); break; case aaRegard: pch->do_regardalpha = MagickTrue; break; case aaDisregard: pch->do_regardalpha = MagickFalse; break; } MagickBooleanType UseAlpha = (IsAlphaOn && pch->do_regardalpha); // Note: We can't do this in-place, // because an output pixel generally depends on a _later_ input pixel. new_image=CloneImage(image, image->columns, image->rows, MagickTrue, exception); if (new_image == (Image *) NULL) return(new_image); // Just in case image had a palette. if (SetNoPalette (new_image, exception) == MagickFalse) return (Image *)NULL; inXmult = Inc_ViewPixPtr (image); outXmult = Inc_ViewPixPtr (new_image); status=MagickTrue; image_view=AcquireVirtualCacheView(image,exception); out_view=AcquireAuthenticCacheView(new_image,exception); #if defined(MAGICKCORE_OPENMP_SUPPORT) #pragma omp parallel for schedule(static,4) shared(status) \ MAGICK_THREADS(new_image,new_image,new_image->rows,1) #endif for (y=0; y < (ssize_t) new_image->rows; y++) { const VIEW_PIX_PTR *p, *p_prev = NULL; VIEW_PIX_PTR *q_out; ssize_t y1, y2, x; long double alpha; y1 = y - winSemiHt + pch->offsety; y2 = y1 + pch->winHeight; if (y1 < 0) y1 = -1; if (y2 > image->rows-1) y2 = image->rows-1; ssize_t winHt = y2 - y1; if (winHt==0) winHt = 1; if (status==MagickFalse) continue; p=GetCacheViewVirtualPixels(image_view,0,y2,image->columns,1,exception); if (p == (const VIEW_PIX_PTR *) NULL) { fprintf (stderr, "bad GetCacheViewVirtualPixels image_view\n"); status=MagickFalse; } if (y1 >= 0) { p_prev=GetCacheViewVirtualPixels(image_view,0,y1,image->columns,1,exception); if (p_prev == (const VIEW_PIX_PTR *) NULL) { fprintf (stderr, "deintegim: bad GetCacheViewAuthenticPixels p_prev\n"); status=MagickFalse; } } else { p_prev = NULL; } q_out=GetCacheViewAuthenticPixels(out_view,0,y,new_image->columns,1,exception); if (q_out == (const VIEW_PIX_PTR *) NULL) { fprintf (stderr, "deintegim: bad GetCacheViewAuthenticPixels out_view\n"); status=MagickFalse; } alpha = 1.0; if (status==MagickFalse) continue; long double left_r = 0, up_r = 0, upleft_r = 0, left_g = 0, up_g = 0, upleft_g = 0, left_b = 0, up_b = 0, upleft_b = 0, left_a = 0, up_a = 0, upleft_a = 0; ssize_t x1, x2; for (x=0; x < (ssize_t) new_image->columns; x++) { x1 = x - winSemiWi + pch->offsetx; x2 = x1 + pch->winWidth; if (x1 < 0) x1 = -1; if (x2 > image->columns-1) x2 = image->columns-1; ssize_t winWi = x2 - x1; if (winWi==0) winWi = 1; int winArea = winHt * winWi; if (x1 >= 0) { const VIEW_PIX_PTR * pv = p+x1*inXmult; left_r = GET_PIXEL_RED(image, pv); left_g = GET_PIXEL_GREEN(image, pv); left_b = GET_PIXEL_BLUE(image, pv); left_a = GET_PIXEL_ALPHA(image, pv); } if (p_prev) { const VIEW_PIX_PTR * pv = p_prev+x2*inXmult; up_r = GET_PIXEL_RED(image, pv); up_g = GET_PIXEL_GREEN(image, pv); up_b = GET_PIXEL_BLUE(image, pv); up_a = GET_PIXEL_ALPHA(image, pv); if (x1 >= 0) { const VIEW_PIX_PTR * pv = p_prev + x1*inXmult; upleft_r = GET_PIXEL_RED(image, pv); upleft_g = GET_PIXEL_GREEN(image, pv); upleft_b = GET_PIXEL_BLUE(image, pv); upleft_a = GET_PIXEL_ALPHA(image, pv); } } // point to bottom-right of window const VIEW_PIX_PTR * pbr = p + x2*inXmult; if (IsAlphaOn) { // If alpha isn't on, image alpha values are 100%. alpha = GET_PIXEL_ALPHA(image,pbr) - left_a - up_a + upleft_a; SET_PIXEL_ALPHA (new_image, alpha / winArea ADD_HALF, q_out); } if (UseAlpha && alpha != 0) { alpha /= (long double)QuantumRange; } else { alpha = winArea; } if (pch->dontDivide) alpha = 1.0; // temp hack if (alpha == 0) { SET_PIXEL_RED (new_image, 0, q_out); SET_PIXEL_GREEN (new_image, 0, q_out); SET_PIXEL_BLUE (new_image, 0, q_out); } else { SET_PIXEL_RED (new_image, (GET_PIXEL_RED(image,pbr) - left_r - up_r + upleft_r)/alpha ADD_HALF, q_out); SET_PIXEL_GREEN (new_image, (GET_PIXEL_GREEN(image,pbr) - left_g - up_g + upleft_g)/alpha ADD_HALF, q_out); SET_PIXEL_BLUE (new_image, (GET_PIXEL_BLUE(image,pbr) - left_b - up_b + upleft_b)/alpha ADD_HALF, q_out); } q_out += outXmult; } if (SyncCacheViewAuthenticPixels(out_view,exception) == MagickFalse) { fprintf (stderr, "bad sync\n"); status=MagickFalse; } } image_view=DestroyCacheView(image_view); out_view=DestroyCacheView(out_view); return (new_image); } ModuleExport size_t deintegimImage(Image **images,const int argc, const char **argv,ExceptionInfo *exception) { Image *image, *new_image; MagickBooleanType status; deintegimT di; assert(images != (Image **) NULL); assert(*images != (Image *) NULL); assert((*images)->signature == MAGICK_CORE_SIG); status = menu (argc, argv, &di); if (status == MagickFalse) return (-1); for (image=(*images); image != (Image *) NULL; image=GetNextImageInList(image)) { new_image = deintegim (image, &di, exception); if (!new_image) return(-1); ReplaceImageInList(&image,new_image); *images=GetFirstImageInList(image); } return(MagickImageFilterSignature); }
/* 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 (stder