2 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
6 % M M OOO RRRR PPPP H H OOO L OOO GGGG Y Y %
7 % MM MM O O R R P P H H O O L O O G Y Y %
8 % M M M O O RRRR PPPP HHHHH O O L O O G GGG Y %
9 % M M O O R R P H H O O L O O G G Y %
10 % M M OOO R R P H H OOO LLLLL OOO GGG Y %
13 % MagickCore Morphology Methods %
20 % Copyright 1999-2014 ImageMagick Studio LLC, a non-profit organization %
21 % dedicated to making software imaging solutions freely available. %
23 % You may not use this file except in compliance with the License. You may %
24 % obtain a copy of the License at %
26 % http://www.imagemagick.org/script/license.php %
28 % Unless required by applicable law or agreed to in writing, software %
29 % distributed under the License is distributed on an "AS IS" BASIS, %
30 % WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %
31 % See the License for the specific language governing permissions and %
32 % limitations under the License. %
34 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
36 % Morpology is the the application of various kernels, of any size and even
37 % shape, to a image in various ways (typically binary, but not always).
39 % Convolution (weighted sum or average) is just one specific type of
40 % morphology. Just one that is very common for image bluring and sharpening
41 % effects. Not only 2D Gaussian blurring, but also 2-pass 1D Blurring.
43 % This module provides not only a general morphology function, and the ability
44 % to apply more advanced or iterative morphologies, but also functions for the
45 % generation of many different types of kernel arrays from user supplied
46 % arguments. Prehaps even the generation of a kernel from a small image.
52 #include "MagickCore/studio.h"
53 #include "MagickCore/artifact.h"
54 #include "MagickCore/cache-view.h"
55 #include "MagickCore/channel.h"
56 #include "MagickCore/color-private.h"
57 #include "MagickCore/enhance.h"
58 #include "MagickCore/exception.h"
59 #include "MagickCore/exception-private.h"
60 #include "MagickCore/gem.h"
61 #include "MagickCore/gem-private.h"
62 #include "MagickCore/hashmap.h"
63 #include "MagickCore/image.h"
64 #include "MagickCore/image-private.h"
65 #include "MagickCore/list.h"
66 #include "MagickCore/magick.h"
67 #include "MagickCore/memory_.h"
68 #include "MagickCore/memory-private.h"
69 #include "MagickCore/monitor-private.h"
70 #include "MagickCore/morphology.h"
71 #include "MagickCore/morphology-private.h"
72 #include "MagickCore/option.h"
73 #include "MagickCore/pixel-accessor.h"
74 #include "MagickCore/pixel-private.h"
75 #include "MagickCore/prepress.h"
76 #include "MagickCore/quantize.h"
77 #include "MagickCore/resource_.h"
78 #include "MagickCore/registry.h"
79 #include "MagickCore/semaphore.h"
80 #include "MagickCore/splay-tree.h"
81 #include "MagickCore/statistic.h"
82 #include "MagickCore/string_.h"
83 #include "MagickCore/string-private.h"
84 #include "MagickCore/thread-private.h"
85 #include "MagickCore/token.h"
86 #include "MagickCore/utility.h"
87 #include "MagickCore/utility-private.h"
90 Other global definitions used by module.
92 static inline double MagickMin(const double x,const double y)
94 return( x < y ? x : y);
96 static inline double MagickMax(const double x,const double y)
98 return( x > y ? x : y);
100 #define Minimize(assign,value) assign=MagickMin(assign,value)
101 #define Maximize(assign,value) assign=MagickMax(assign,value)
103 /* Integer Factorial Function - for a Binomial kernel */
105 static inline size_t fact(size_t n)
108 for(f=1, l=2; l <= n; f=f*l, l++);
111 #elif 1 /* glibc floating point alternatives */
112 #define fact(n) ((size_t)tgamma((double)n+1))
114 #define fact(n) ((size_t)lgamma((double)n+1))
118 /* Currently these are only internal to this module */
120 CalcKernelMetaData(KernelInfo *),
121 ExpandMirrorKernelInfo(KernelInfo *),
122 ExpandRotateKernelInfo(KernelInfo *, const double),
123 RotateKernelInfo(KernelInfo *, double);
126 /* Quick function to find last kernel in a kernel list */
127 static inline KernelInfo *LastKernelInfo(KernelInfo *kernel)
129 while (kernel->next != (KernelInfo *) NULL)
135 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
139 % A c q u i r e K e r n e l I n f o %
143 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
145 % AcquireKernelInfo() takes the given string (generally supplied by the
146 % user) and converts it into a Morphology/Convolution Kernel. This allows
147 % users to specify a kernel from a number of pre-defined kernels, or to fully
148 % specify their own kernel for a specific Convolution or Morphology
151 % The kernel so generated can be any rectangular array of floating point
152 % values (doubles) with the 'control point' or 'pixel being affected'
153 % anywhere within that array of values.
155 % Previously IM was restricted to a square of odd size using the exact
156 % center as origin, this is no longer the case, and any rectangular kernel
157 % with any value being declared the origin. This in turn allows the use of
158 % highly asymmetrical kernels.
160 % The floating point values in the kernel can also include a special value
161 % known as 'nan' or 'not a number' to indicate that this value is not part
162 % of the kernel array. This allows you to shaped the kernel within its
163 % rectangular area. That is 'nan' values provide a 'mask' for the kernel
164 % shape. However at least one non-nan value must be provided for correct
165 % working of a kernel.
167 % The returned kernel should be freed using the DestroyKernelInfo() when you
168 % are finished with it. Do not free this memory yourself.
170 % Input kernel defintion strings can consist of any of three types.
173 % Select from one of the built in kernels, using the name and
174 % geometry arguments supplied. See AcquireKernelBuiltIn()
176 % "WxH[+X+Y][@><]:num, num, num ..."
177 % a kernel of size W by H, with W*H floating point numbers following.
178 % the 'center' can be optionally be defined at +X+Y (such that +0+0
179 % is top left corner). If not defined the pixel in the center, for
180 % odd sizes, or to the immediate top or left of center for even sizes
181 % is automatically selected.
183 % "num, num, num, num, ..."
184 % list of floating point numbers defining an 'old style' odd sized
185 % square kernel. At least 9 values should be provided for a 3x3
186 % square kernel, 25 for a 5x5 square kernel, 49 for 7x7, etc.
187 % Values can be space or comma separated. This is not recommended.
189 % You can define a 'list of kernels' which can be used by some morphology
190 % operators A list is defined as a semi-colon separated list kernels.
192 % " kernel ; kernel ; kernel ; "
194 % Any extra ';' characters, at start, end or between kernel defintions are
197 % The special flags will expand a single kernel, into a list of rotated
198 % kernels. A '@' flag will expand a 3x3 kernel into a list of 45-degree
199 % cyclic rotations, while a '>' will generate a list of 90-degree rotations.
200 % The '<' also exands using 90-degree rotates, but giving a 180-degree
201 % reflected kernel before the +/- 90-degree rotations, which can be important
202 % for Thinning operations.
204 % Note that 'name' kernels will start with an alphabetic character while the
205 % new kernel specification has a ':' character in its specification string.
206 % If neither is the case, it is assumed an old style of a simple list of
207 % numbers generating a odd-sized square kernel has been given.
209 % The format of the AcquireKernal method is:
211 % KernelInfo *AcquireKernelInfo(const char *kernel_string)
213 % A description of each parameter follows:
215 % o kernel_string: the Morphology/Convolution kernel wanted.
219 /* This was separated so that it could be used as a separate
220 ** array input handling function, such as for -color-matrix
222 static KernelInfo *ParseKernelArray(const char *kernel_string)
228 token[MaxTextExtent];
238 nan = sqrt((double)-1.0); /* Special Value : Not A Number */
246 kernel=(KernelInfo *) AcquireQuantumMemory(1,sizeof(*kernel));
247 if (kernel == (KernelInfo *)NULL)
249 (void) ResetMagickMemory(kernel,0,sizeof(*kernel));
250 kernel->minimum = kernel->maximum = kernel->angle = 0.0;
251 kernel->negative_range = kernel->positive_range = 0.0;
252 kernel->type = UserDefinedKernel;
253 kernel->next = (KernelInfo *) NULL;
254 kernel->signature = MagickSignature;
255 if (kernel_string == (const char *) NULL)
258 /* find end of this specific kernel definition string */
259 end = strchr(kernel_string, ';');
260 if ( end == (char *) NULL )
261 end = strchr(kernel_string, '\0');
263 /* clear flags - for Expanding kernel lists thorugh rotations */
266 /* Has a ':' in argument - New user kernel specification
267 FUTURE: this split on ':' could be done by StringToken()
269 p = strchr(kernel_string, ':');
270 if ( p != (char *) NULL && p < end)
272 /* ParseGeometry() needs the geometry separated! -- Arrgghh */
273 memcpy(token, kernel_string, (size_t) (p-kernel_string));
274 token[p-kernel_string] = '\0';
275 SetGeometryInfo(&args);
276 flags = ParseGeometry(token, &args);
278 /* Size handling and checks of geometry settings */
279 if ( (flags & WidthValue) == 0 ) /* if no width then */
280 args.rho = args.sigma; /* then width = height */
281 if ( args.rho < 1.0 ) /* if width too small */
282 args.rho = 1.0; /* then width = 1 */
283 if ( args.sigma < 1.0 ) /* if height too small */
284 args.sigma = args.rho; /* then height = width */
285 kernel->width = (size_t)args.rho;
286 kernel->height = (size_t)args.sigma;
288 /* Offset Handling and Checks */
289 if ( args.xi < 0.0 || args.psi < 0.0 )
290 return(DestroyKernelInfo(kernel));
291 kernel->x = ((flags & XValue)!=0) ? (ssize_t)args.xi
292 : (ssize_t) (kernel->width-1)/2;
293 kernel->y = ((flags & YValue)!=0) ? (ssize_t)args.psi
294 : (ssize_t) (kernel->height-1)/2;
295 if ( kernel->x >= (ssize_t) kernel->width ||
296 kernel->y >= (ssize_t) kernel->height )
297 return(DestroyKernelInfo(kernel));
299 p++; /* advance beyond the ':' */
302 { /* ELSE - Old old specification, forming odd-square kernel */
303 /* count up number of values given */
304 p=(const char *) kernel_string;
305 while ((isspace((int) ((unsigned char) *p)) != 0) || (*p == '\''))
306 p++; /* ignore "'" chars for convolve filter usage - Cristy */
307 for (i=0; p < end; i++)
309 GetMagickToken(p,&p,token);
311 GetMagickToken(p,&p,token);
313 /* set the size of the kernel - old sized square */
314 kernel->width = kernel->height= (size_t) sqrt((double) i+1.0);
315 kernel->x = kernel->y = (ssize_t) (kernel->width-1)/2;
316 p=(const char *) kernel_string;
317 while ((isspace((int) ((unsigned char) *p)) != 0) || (*p == '\''))
318 p++; /* ignore "'" chars for convolve filter usage - Cristy */
321 /* Read in the kernel values from rest of input string argument */
322 kernel->values=(MagickRealType *) MagickAssumeAligned(AcquireAlignedMemory(
323 kernel->width,kernel->height*sizeof(*kernel->values)));
324 if (kernel->values == (MagickRealType *) NULL)
325 return(DestroyKernelInfo(kernel));
326 kernel->minimum=MagickMaximumValue;
327 kernel->maximum=(-MagickMaximumValue);
328 kernel->negative_range = kernel->positive_range = 0.0;
329 for (i=0; (i < (ssize_t) (kernel->width*kernel->height)) && (p < end); i++)
331 GetMagickToken(p,&p,token);
333 GetMagickToken(p,&p,token);
334 if ( LocaleCompare("nan",token) == 0
335 || LocaleCompare("-",token) == 0 ) {
336 kernel->values[i] = nan; /* this value is not part of neighbourhood */
339 kernel->values[i] = StringToDouble(token,(char **) NULL);
340 ( kernel->values[i] < 0)
341 ? ( kernel->negative_range += kernel->values[i] )
342 : ( kernel->positive_range += kernel->values[i] );
343 Minimize(kernel->minimum, kernel->values[i]);
344 Maximize(kernel->maximum, kernel->values[i]);
348 /* sanity check -- no more values in kernel definition */
349 GetMagickToken(p,&p,token);
350 if ( *token != '\0' && *token != ';' && *token != '\'' )
351 return(DestroyKernelInfo(kernel));
354 /* this was the old method of handling a incomplete kernel */
355 if ( i < (ssize_t) (kernel->width*kernel->height) ) {
356 Minimize(kernel->minimum, kernel->values[i]);
357 Maximize(kernel->maximum, kernel->values[i]);
358 for ( ; i < (ssize_t) (kernel->width*kernel->height); i++)
359 kernel->values[i]=0.0;
362 /* Number of values for kernel was not enough - Report Error */
363 if ( i < (ssize_t) (kernel->width*kernel->height) )
364 return(DestroyKernelInfo(kernel));
367 /* check that we recieved at least one real (non-nan) value! */
368 if (kernel->minimum == MagickMaximumValue)
369 return(DestroyKernelInfo(kernel));
371 if ( (flags & AreaValue) != 0 ) /* '@' symbol in kernel size */
372 ExpandRotateKernelInfo(kernel, 45.0); /* cyclic rotate 3x3 kernels */
373 else if ( (flags & GreaterValue) != 0 ) /* '>' symbol in kernel args */
374 ExpandRotateKernelInfo(kernel, 90.0); /* 90 degree rotate of kernel */
375 else if ( (flags & LessValue) != 0 ) /* '<' symbol in kernel args */
376 ExpandMirrorKernelInfo(kernel); /* 90 degree mirror rotate */
381 static KernelInfo *ParseKernelName(const char *kernel_string)
384 token[MaxTextExtent];
402 /* Parse special 'named' kernel */
403 GetMagickToken(kernel_string,&p,token);
404 type=ParseCommandOption(MagickKernelOptions,MagickFalse,token);
405 if ( type < 0 || type == UserDefinedKernel )
406 return((KernelInfo *)NULL); /* not a valid named kernel */
408 while (((isspace((int) ((unsigned char) *p)) != 0) ||
409 (*p == ',') || (*p == ':' )) && (*p != '\0') && (*p != ';'))
412 end = strchr(p, ';'); /* end of this kernel defintion */
413 if ( end == (char *) NULL )
414 end = strchr(p, '\0');
416 /* ParseGeometry() needs the geometry separated! -- Arrgghh */
417 memcpy(token, p, (size_t) (end-p));
419 SetGeometryInfo(&args);
420 flags = ParseGeometry(token, &args);
423 /* For Debugging Geometry Input */
424 (void) FormatLocaleFile(stderr, "Geometry = 0x%04X : %lg x %lg %+lg %+lg\n",
425 flags, args.rho, args.sigma, args.xi, args.psi );
428 /* special handling of missing values in input string */
430 /* Shape Kernel Defaults */
432 if ( (flags & WidthValue) == 0 )
433 args.rho = 1.0; /* Default scale = 1.0, zero is valid */
441 if ( (flags & HeightValue) == 0 )
442 args.sigma = 1.0; /* Default scale = 1.0, zero is valid */
445 if ( (flags & XValue) == 0 )
446 args.xi = 1.0; /* Default scale = 1.0, zero is valid */
448 case RectangleKernel: /* Rectangle - set size defaults */
449 if ( (flags & WidthValue) == 0 ) /* if no width then */
450 args.rho = args.sigma; /* then width = height */
451 if ( args.rho < 1.0 ) /* if width too small */
452 args.rho = 3; /* then width = 3 */
453 if ( args.sigma < 1.0 ) /* if height too small */
454 args.sigma = args.rho; /* then height = width */
455 if ( (flags & XValue) == 0 ) /* center offset if not defined */
456 args.xi = (double)(((ssize_t)args.rho-1)/2);
457 if ( (flags & YValue) == 0 )
458 args.psi = (double)(((ssize_t)args.sigma-1)/2);
460 /* Distance Kernel Defaults */
461 case ChebyshevKernel:
462 case ManhattanKernel:
463 case OctagonalKernel:
464 case EuclideanKernel:
465 if ( (flags & HeightValue) == 0 ) /* no distance scale */
466 args.sigma = 100.0; /* default distance scaling */
467 else if ( (flags & AspectValue ) != 0 ) /* '!' flag */
468 args.sigma = QuantumRange/(args.sigma+1); /* maximum pixel distance */
469 else if ( (flags & PercentValue ) != 0 ) /* '%' flag */
470 args.sigma *= QuantumRange/100.0; /* percentage of color range */
476 kernel = AcquireKernelBuiltIn((KernelInfoType)type, &args);
477 if ( kernel == (KernelInfo *) NULL )
480 /* global expand to rotated kernel list - only for single kernels */
481 if ( kernel->next == (KernelInfo *) NULL ) {
482 if ( (flags & AreaValue) != 0 ) /* '@' symbol in kernel args */
483 ExpandRotateKernelInfo(kernel, 45.0);
484 else if ( (flags & GreaterValue) != 0 ) /* '>' symbol in kernel args */
485 ExpandRotateKernelInfo(kernel, 90.0);
486 else if ( (flags & LessValue) != 0 ) /* '<' symbol in kernel args */
487 ExpandMirrorKernelInfo(kernel);
493 MagickExport KernelInfo *AcquireKernelInfo(const char *kernel_string)
500 token[MaxTextExtent];
508 if (kernel_string == (const char *) NULL)
509 return(ParseKernelArray(kernel_string));
514 while (GetMagickToken(p,NULL,token), *token != '\0')
516 /* ignore extra or multiple ';' kernel separators */
519 /* tokens starting with alpha is a Named kernel */
520 if (isalpha((int) ((unsigned char) *token)) != 0)
521 new_kernel=ParseKernelName(p);
522 else /* otherwise a user defined kernel array */
523 new_kernel=ParseKernelArray(p);
525 /* Error handling -- this is not proper error handling! */
526 if (new_kernel == (KernelInfo *) NULL)
528 if (kernel != (KernelInfo *) NULL)
529 kernel=DestroyKernelInfo(kernel);
530 return((KernelInfo *) NULL);
533 /* initialise or append the kernel list */
534 if (kernel == (KernelInfo *) NULL)
537 LastKernelInfo(kernel)->next=new_kernel;
540 /* look for the next kernel in list */
542 if (p == (char *) NULL)
551 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
555 % A c q u i r e K e r n e l B u i l t I n %
559 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
561 % AcquireKernelBuiltIn() returned one of the 'named' built-in types of
562 % kernels used for special purposes such as gaussian blurring, skeleton
563 % pruning, and edge distance determination.
565 % They take a KernelType, and a set of geometry style arguments, which were
566 % typically decoded from a user supplied string, or from a more complex
567 % Morphology Method that was requested.
569 % The format of the AcquireKernalBuiltIn method is:
571 % KernelInfo *AcquireKernelBuiltIn(const KernelInfoType type,
572 % const GeometryInfo args)
574 % A description of each parameter follows:
576 % o type: the pre-defined type of kernel wanted
578 % o args: arguments defining or modifying the kernel
580 % Convolution Kernels
583 % The a No-Op or Scaling single element kernel.
585 % Gaussian:{radius},{sigma}
586 % Generate a two-dimensional gaussian kernel, as used by -gaussian.
587 % The sigma for the curve is required. The resulting kernel is
590 % If 'sigma' is zero, you get a single pixel on a field of zeros.
592 % NOTE: that the 'radius' is optional, but if provided can limit (clip)
593 % the final size of the resulting kernel to a square 2*radius+1 in size.
594 % The radius should be at least 2 times that of the sigma value, or
595 % sever clipping and aliasing may result. If not given or set to 0 the
596 % radius will be determined so as to produce the best minimal error
597 % result, which is usally much larger than is normally needed.
599 % LoG:{radius},{sigma}
600 % "Laplacian of a Gaussian" or "Mexician Hat" Kernel.
601 % The supposed ideal edge detection, zero-summing kernel.
603 % An alturnative to this kernel is to use a "DoG" with a sigma ratio of
604 % approx 1.6 (according to wikipedia).
606 % DoG:{radius},{sigma1},{sigma2}
607 % "Difference of Gaussians" Kernel.
608 % As "Gaussian" but with a gaussian produced by 'sigma2' subtracted
609 % from the gaussian produced by 'sigma1'. Typically sigma2 > sigma1.
610 % The result is a zero-summing kernel.
612 % Blur:{radius},{sigma}[,{angle}]
613 % Generates a 1 dimensional or linear gaussian blur, at the angle given
614 % (current restricted to orthogonal angles). If a 'radius' is given the
615 % kernel is clipped to a width of 2*radius+1. Kernel can be rotated
616 % by a 90 degree angle.
618 % If 'sigma' is zero, you get a single pixel on a field of zeros.
620 % Note that two convolutions with two "Blur" kernels perpendicular to
621 % each other, is equivalent to a far larger "Gaussian" kernel with the
622 % same sigma value, However it is much faster to apply. This is how the
623 % "-blur" operator actually works.
625 % Comet:{width},{sigma},{angle}
626 % Blur in one direction only, much like how a bright object leaves
627 % a comet like trail. The Kernel is actually half a gaussian curve,
628 % Adding two such blurs in opposite directions produces a Blur Kernel.
629 % Angle can be rotated in multiples of 90 degrees.
631 % Note that the first argument is the width of the kernel and not the
632 % radius of the kernel.
634 % Binomial:[{radius}]
635 % Generate a discrete kernel using a 2 dimentional Pascel's Triangle
636 % of values. Used for special forma of image filters.
638 % # Still to be implemented...
642 % # Set kernel values using a resize filter, and given scale (sigma)
643 % # Cylindrical or Linear. Is this possible with an image?
646 % Named Constant Convolution Kernels
648 % All these are unscaled, zero-summing kernels by default. As such for
649 % non-HDRI version of ImageMagick some form of normalization, user scaling,
650 % and biasing the results is recommended, to prevent the resulting image
653 % The 3x3 kernels (most of these) can be circularly rotated in multiples of
654 % 45 degrees to generate the 8 angled varients of each of the kernels.
657 % Discrete Lapacian Kernels, (without normalization)
658 % Type 0 : 3x3 with center:8 surounded by -1 (8 neighbourhood)
659 % Type 1 : 3x3 with center:4 edge:-1 corner:0 (4 neighbourhood)
660 % Type 2 : 3x3 with center:4 edge:1 corner:-2
661 % Type 3 : 3x3 with center:4 edge:-2 corner:1
662 % Type 5 : 5x5 laplacian
663 % Type 7 : 7x7 laplacian
664 % Type 15 : 5x5 LoG (sigma approx 1.4)
665 % Type 19 : 9x9 LoG (sigma approx 1.4)
668 % Sobel 'Edge' convolution kernel (3x3)
674 % Roberts convolution kernel (3x3)
680 % Prewitt Edge convolution kernel (3x3)
686 % Prewitt's "Compass" convolution kernel (3x3)
692 % Kirsch's "Compass" convolution kernel (3x3)
698 % Frei-Chen Edge Detector is based on a kernel that is similar to
699 % the Sobel Kernel, but is designed to be isotropic. That is it takes
700 % into account the distance of the diagonal in the kernel.
703 % | sqrt(2), 0, -sqrt(2) |
706 % FreiChen:{type},{angle}
708 % Frei-Chen Pre-weighted kernels...
710 % Type 0: default un-nomalized version shown above.
712 % Type 1: Orthogonal Kernel (same as type 11 below)
714 % | sqrt(2), 0, -sqrt(2) | / 2*sqrt(2)
717 % Type 2: Diagonal form of Kernel...
719 % | sqrt(2), 0, -sqrt(2) | / 2*sqrt(2)
722 % However this kernel is als at the heart of the FreiChen Edge Detection
723 % Process which uses a set of 9 specially weighted kernel. These 9
724 % kernels not be normalized, but directly applied to the image. The
725 % results is then added together, to produce the intensity of an edge in
726 % a specific direction. The square root of the pixel value can then be
727 % taken as the cosine of the edge, and at least 2 such runs at 90 degrees
728 % from each other, both the direction and the strength of the edge can be
731 % Type 10: All 9 of the following pre-weighted kernels...
733 % Type 11: | 1, 0, -1 |
734 % | sqrt(2), 0, -sqrt(2) | / 2*sqrt(2)
737 % Type 12: | 1, sqrt(2), 1 |
738 % | 0, 0, 0 | / 2*sqrt(2)
741 % Type 13: | sqrt(2), -1, 0 |
742 % | -1, 0, 1 | / 2*sqrt(2)
745 % Type 14: | 0, 1, -sqrt(2) |
746 % | -1, 0, 1 | / 2*sqrt(2)
749 % Type 15: | 0, -1, 0 |
753 % Type 16: | 1, 0, -1 |
757 % Type 17: | 1, -2, 1 |
761 % Type 18: | -2, 1, -2 |
765 % Type 19: | 1, 1, 1 |
769 % The first 4 are for edge detection, the next 4 are for line detection
770 % and the last is to add a average component to the results.
772 % Using a special type of '-1' will return all 9 pre-weighted kernels
773 % as a multi-kernel list, so that you can use them directly (without
774 % normalization) with the special "-set option:morphology:compose Plus"
775 % setting to apply the full FreiChen Edge Detection Technique.
777 % If 'type' is large it will be taken to be an actual rotation angle for
778 % the default FreiChen (type 0) kernel. As such FreiChen:45 will look
779 % like a Sobel:45 but with 'sqrt(2)' instead of '2' values.
781 % WARNING: The above was layed out as per
782 % http://www.math.tau.ac.il/~turkel/notes/edge_detectors.pdf
783 % But rotated 90 degrees so direction is from left rather than the top.
784 % I have yet to find any secondary confirmation of the above. The only
785 % other source found was actual source code at
786 % http://ltswww.epfl.ch/~courstiv/exos_labos/sol3.pdf
787 % Neigher paper defineds the kernels in a way that looks locical or
788 % correct when taken as a whole.
792 % Diamond:[{radius}[,{scale}]]
793 % Generate a diamond shaped kernel with given radius to the points.
794 % Kernel size will again be radius*2+1 square and defaults to radius 1,
795 % generating a 3x3 kernel that is slightly larger than a square.
797 % Square:[{radius}[,{scale}]]
798 % Generate a square shaped kernel of size radius*2+1, and defaulting
799 % to a 3x3 (radius 1).
801 % Octagon:[{radius}[,{scale}]]
802 % Generate octagonal shaped kernel of given radius and constant scale.
803 % Default radius is 3 producing a 7x7 kernel. A radius of 1 will result
804 % in "Diamond" kernel.
806 % Disk:[{radius}[,{scale}]]
807 % Generate a binary disk, thresholded at the radius given, the radius
808 % may be a float-point value. Final Kernel size is floor(radius)*2+1
809 % square. A radius of 5.3 is the default.
811 % NOTE: That a low radii Disk kernels produce the same results as
812 % many of the previously defined kernels, but differ greatly at larger
813 % radii. Here is a table of equivalences...
814 % "Disk:1" => "Diamond", "Octagon:1", or "Cross:1"
815 % "Disk:1.5" => "Square"
816 % "Disk:2" => "Diamond:2"
817 % "Disk:2.5" => "Octagon"
818 % "Disk:2.9" => "Square:2"
819 % "Disk:3.5" => "Octagon:3"
820 % "Disk:4.5" => "Octagon:4"
821 % "Disk:5.4" => "Octagon:5"
822 % "Disk:6.4" => "Octagon:6"
823 % All other Disk shapes are unique to this kernel, but because a "Disk"
824 % is more circular when using a larger radius, using a larger radius is
825 % preferred over iterating the morphological operation.
827 % Rectangle:{geometry}
828 % Simply generate a rectangle of 1's with the size given. You can also
829 % specify the location of the 'control point', otherwise the closest
830 % pixel to the center of the rectangle is selected.
832 % Properly centered and odd sized rectangles work the best.
834 % Symbol Dilation Kernels
836 % These kernel is not a good general morphological kernel, but is used
837 % more for highlighting and marking any single pixels in an image using,
838 % a "Dilate" method as appropriate.
840 % For the same reasons iterating these kernels does not produce the
841 % same result as using a larger radius for the symbol.
843 % Plus:[{radius}[,{scale}]]
844 % Cross:[{radius}[,{scale}]]
845 % Generate a kernel in the shape of a 'plus' or a 'cross' with
846 % a each arm the length of the given radius (default 2).
848 % NOTE: "plus:1" is equivalent to a "Diamond" kernel.
850 % Ring:{radius1},{radius2}[,{scale}]
851 % A ring of the values given that falls between the two radii.
852 % Defaults to a ring of approximataly 3 radius in a 7x7 kernel.
853 % This is the 'edge' pixels of the default "Disk" kernel,
854 % More specifically, "Ring" -> "Ring:2.5,3.5,1.0"
856 % Hit and Miss Kernels
858 % Peak:radius1,radius2
859 % Find any peak larger than the pixels the fall between the two radii.
860 % The default ring of pixels is as per "Ring".
862 % Find flat orthogonal edges of a binary shape
864 % Find 90 degree corners of a binary shape
866 % A special kernel to thin the 'outside' of diagonals
868 % Find end points of lines (for pruning a skeletion)
869 % Two types of lines ends (default to both) can be searched for
870 % Type 0: All line ends
871 % Type 1: single kernel for 4-conneected line ends
872 % Type 2: single kernel for simple line ends
874 % Find three line junctions (within a skeletion)
875 % Type 0: all line junctions
876 % Type 1: Y Junction kernel
877 % Type 2: Diagonal T Junction kernel
878 % Type 3: Orthogonal T Junction kernel
879 % Type 4: Diagonal X Junction kernel
880 % Type 5: Orthogonal + Junction kernel
882 % Find single pixel ridges or thin lines
883 % Type 1: Fine single pixel thick lines and ridges
884 % Type 2: Find two pixel thick lines and ridges
886 % Octagonal Thickening Kernel, to generate convex hulls of 45 degrees
888 % Traditional skeleton generating kernels.
889 % Type 1: Tradional Skeleton kernel (4 connected skeleton)
890 % Type 2: HIPR2 Skeleton kernel (8 connected skeleton)
891 % Type 3: Thinning skeleton based on a ressearch paper by
892 % Dan S. Bloomberg (Default Type)
894 % A huge variety of Thinning Kernels designed to preserve conectivity.
895 % many other kernel sets use these kernels as source definitions.
896 % Type numbers are 41-49, 81-89, 481, and 482 which are based on
897 % the super and sub notations used in the source research paper.
899 % Distance Measuring Kernels
901 % Different types of distance measuring methods, which are used with the
902 % a 'Distance' morphology method for generating a gradient based on
903 % distance from an edge of a binary shape, though there is a technique
904 % for handling a anti-aliased shape.
906 % See the 'Distance' Morphological Method, for information of how it is
909 % Chebyshev:[{radius}][x{scale}[%!]]
910 % Chebyshev Distance (also known as Tchebychev or Chessboard distance)
911 % is a value of one to any neighbour, orthogonal or diagonal. One why
912 % of thinking of it is the number of squares a 'King' or 'Queen' in
913 % chess needs to traverse reach any other position on a chess board.
914 % It results in a 'square' like distance function, but one where
915 % diagonals are given a value that is closer than expected.
917 % Manhattan:[{radius}][x{scale}[%!]]
918 % Manhattan Distance (also known as Rectilinear, City Block, or the Taxi
919 % Cab distance metric), it is the distance needed when you can only
920 % travel in horizontal or vertical directions only. It is the
921 % distance a 'Rook' in chess would have to travel, and results in a
922 % diamond like distances, where diagonals are further than expected.
924 % Octagonal:[{radius}][x{scale}[%!]]
925 % An interleving of Manhatten and Chebyshev metrics producing an
926 % increasing octagonally shaped distance. Distances matches those of
927 % the "Octagon" shaped kernel of the same radius. The minimum radius
928 % and default is 2, producing a 5x5 kernel.
930 % Euclidean:[{radius}][x{scale}[%!]]
931 % Euclidean distance is the 'direct' or 'as the crow flys' distance.
932 % However by default the kernel size only has a radius of 1, which
933 % limits the distance to 'Knight' like moves, with only orthogonal and
934 % diagonal measurements being correct. As such for the default kernel
935 % you will get octagonal like distance function.
937 % However using a larger radius such as "Euclidean:4" you will get a
938 % much smoother distance gradient from the edge of the shape. Especially
939 % if the image is pre-processed to include any anti-aliasing pixels.
940 % Of course a larger kernel is slower to use, and not always needed.
942 % The first three Distance Measuring Kernels will only generate distances
943 % of exact multiples of {scale} in binary images. As such you can use a
944 % scale of 1 without loosing any information. However you also need some
945 % scaling when handling non-binary anti-aliased shapes.
947 % The "Euclidean" Distance Kernel however does generate a non-integer
948 % fractional results, and as such scaling is vital even for binary shapes.
952 MagickExport KernelInfo *AcquireKernelBuiltIn(const KernelInfoType type,
953 const GeometryInfo *args)
966 nan = sqrt((double)-1.0); /* Special Value : Not A Number */
968 /* Generate a new empty kernel if needed */
969 kernel=(KernelInfo *) NULL;
971 case UndefinedKernel: /* These should not call this function */
972 case UserDefinedKernel:
973 assert("Should not call this function" != (char *)NULL);
975 case LaplacianKernel: /* Named Descrete Convolution Kernels */
976 case SobelKernel: /* these are defined using other kernels */
982 case EdgesKernel: /* Hit and Miss kernels */
984 case DiagonalsKernel:
986 case LineJunctionsKernel:
988 case ConvexHullKernel:
991 break; /* A pre-generated kernel is not needed */
993 /* set to 1 to do a compile-time check that we haven't missed anything */
1000 case BinomialKernel:
1003 case RectangleKernel:
1010 case ChebyshevKernel:
1011 case ManhattanKernel:
1012 case OctangonalKernel:
1013 case EuclideanKernel:
1017 /* Generate the base Kernel Structure */
1018 kernel=(KernelInfo *) AcquireMagickMemory(sizeof(*kernel));
1019 if (kernel == (KernelInfo *) NULL)
1021 (void) ResetMagickMemory(kernel,0,sizeof(*kernel));
1022 kernel->minimum = kernel->maximum = kernel->angle = 0.0;
1023 kernel->negative_range = kernel->positive_range = 0.0;
1024 kernel->type = type;
1025 kernel->next = (KernelInfo *) NULL;
1026 kernel->signature = MagickSignature;
1036 kernel->height = kernel->width = (size_t) 1;
1037 kernel->x = kernel->y = (ssize_t) 0;
1038 kernel->values=(MagickRealType *) MagickAssumeAligned(
1039 AcquireAlignedMemory(1,sizeof(*kernel->values)));
1040 if (kernel->values == (MagickRealType *) NULL)
1041 return(DestroyKernelInfo(kernel));
1042 kernel->maximum = kernel->values[0] = args->rho;
1046 case GaussianKernel:
1050 sigma = fabs(args->sigma),
1051 sigma2 = fabs(args->xi),
1054 if ( args->rho >= 1.0 )
1055 kernel->width = (size_t)args->rho*2+1;
1056 else if ( (type != DoGKernel) || (sigma >= sigma2) )
1057 kernel->width = GetOptimalKernelWidth2D(args->rho,sigma);
1059 kernel->width = GetOptimalKernelWidth2D(args->rho,sigma2);
1060 kernel->height = kernel->width;
1061 kernel->x = kernel->y = (ssize_t) (kernel->width-1)/2;
1062 kernel->values=(MagickRealType *) MagickAssumeAligned(
1063 AcquireAlignedMemory(kernel->width,kernel->height*
1064 sizeof(*kernel->values)));
1065 if (kernel->values == (MagickRealType *) NULL)
1066 return(DestroyKernelInfo(kernel));
1068 /* WARNING: The following generates a 'sampled gaussian' kernel.
1069 * What we really want is a 'discrete gaussian' kernel.
1071 * How to do this is I don't know, but appears to be basied on the
1072 * Error Function 'erf()' (intergral of a gaussian)
1075 if ( type == GaussianKernel || type == DoGKernel )
1076 { /* Calculate a Gaussian, OR positive half of a DoG */
1077 if ( sigma > MagickEpsilon )
1078 { A = 1.0/(2.0*sigma*sigma); /* simplify loop expressions */
1079 B = (double) (1.0/(Magick2PI*sigma*sigma));
1080 for ( i=0, v=-kernel->y; v <= (ssize_t)kernel->y; v++)
1081 for ( u=-kernel->x; u <= (ssize_t)kernel->x; u++, i++)
1082 kernel->values[i] = exp(-((double)(u*u+v*v))*A)*B;
1084 else /* limiting case - a unity (normalized Dirac) kernel */
1085 { (void) ResetMagickMemory(kernel->values,0, (size_t)
1086 kernel->width*kernel->height*sizeof(*kernel->values));
1087 kernel->values[kernel->x+kernel->y*kernel->width] = 1.0;
1091 if ( type == DoGKernel )
1092 { /* Subtract a Negative Gaussian for "Difference of Gaussian" */
1093 if ( sigma2 > MagickEpsilon )
1094 { sigma = sigma2; /* simplify loop expressions */
1095 A = 1.0/(2.0*sigma*sigma);
1096 B = (double) (1.0/(Magick2PI*sigma*sigma));
1097 for ( i=0, v=-kernel->y; v <= (ssize_t)kernel->y; v++)
1098 for ( u=-kernel->x; u <= (ssize_t)kernel->x; u++, i++)
1099 kernel->values[i] -= exp(-((double)(u*u+v*v))*A)*B;
1101 else /* limiting case - a unity (normalized Dirac) kernel */
1102 kernel->values[kernel->x+kernel->y*kernel->width] -= 1.0;
1105 if ( type == LoGKernel )
1106 { /* Calculate a Laplacian of a Gaussian - Or Mexician Hat */
1107 if ( sigma > MagickEpsilon )
1108 { A = 1.0/(2.0*sigma*sigma); /* simplify loop expressions */
1109 B = (double) (1.0/(MagickPI*sigma*sigma*sigma*sigma));
1110 for ( i=0, v=-kernel->y; v <= (ssize_t)kernel->y; v++)
1111 for ( u=-kernel->x; u <= (ssize_t)kernel->x; u++, i++)
1112 { R = ((double)(u*u+v*v))*A;
1113 kernel->values[i] = (1-R)*exp(-R)*B;
1116 else /* special case - generate a unity kernel */
1117 { (void) ResetMagickMemory(kernel->values,0, (size_t)
1118 kernel->width*kernel->height*sizeof(*kernel->values));
1119 kernel->values[kernel->x+kernel->y*kernel->width] = 1.0;
1123 /* Note the above kernels may have been 'clipped' by a user defined
1124 ** radius, producing a smaller (darker) kernel. Also for very small
1125 ** sigma's (> 0.1) the central value becomes larger than one, and thus
1126 ** producing a very bright kernel.
1128 ** Normalization will still be needed.
1131 /* Normalize the 2D Gaussian Kernel
1133 ** NB: a CorrelateNormalize performs a normal Normalize if
1134 ** there are no negative values.
1136 CalcKernelMetaData(kernel); /* the other kernel meta-data */
1137 ScaleKernelInfo(kernel, 1.0, CorrelateNormalizeValue);
1143 sigma = fabs(args->sigma),
1146 if ( args->rho >= 1.0 )
1147 kernel->width = (size_t)args->rho*2+1;
1149 kernel->width = GetOptimalKernelWidth1D(args->rho,sigma);
1151 kernel->x = (ssize_t) (kernel->width-1)/2;
1153 kernel->negative_range = kernel->positive_range = 0.0;
1154 kernel->values=(MagickRealType *) MagickAssumeAligned(
1155 AcquireAlignedMemory(kernel->width,kernel->height*
1156 sizeof(*kernel->values)));
1157 if (kernel->values == (MagickRealType *) NULL)
1158 return(DestroyKernelInfo(kernel));
1161 #define KernelRank 3
1162 /* Formula derived from GetBlurKernel() in "effect.c" (plus bug fix).
1163 ** It generates a gaussian 3 times the width, and compresses it into
1164 ** the expected range. This produces a closer normalization of the
1165 ** resulting kernel, especially for very low sigma values.
1166 ** As such while wierd it is prefered.
1168 ** I am told this method originally came from Photoshop.
1170 ** A properly normalized curve is generated (apart from edge clipping)
1171 ** even though we later normalize the result (for edge clipping)
1172 ** to allow the correct generation of a "Difference of Blurs".
1176 v = (ssize_t) (kernel->width*KernelRank-1)/2; /* start/end points to fit range */
1177 (void) ResetMagickMemory(kernel->values,0, (size_t)
1178 kernel->width*kernel->height*sizeof(*kernel->values));
1179 /* Calculate a Positive 1D Gaussian */
1180 if ( sigma > MagickEpsilon )
1181 { sigma *= KernelRank; /* simplify loop expressions */
1182 alpha = 1.0/(2.0*sigma*sigma);
1183 beta= (double) (1.0/(MagickSQ2PI*sigma ));
1184 for ( u=-v; u <= v; u++) {
1185 kernel->values[(u+v)/KernelRank] +=
1186 exp(-((double)(u*u))*alpha)*beta;
1189 else /* special case - generate a unity kernel */
1190 kernel->values[kernel->x+kernel->y*kernel->width] = 1.0;
1192 /* Direct calculation without curve averaging
1193 This is equivelent to a KernelRank of 1 */
1195 /* Calculate a Positive Gaussian */
1196 if ( sigma > MagickEpsilon )
1197 { alpha = 1.0/(2.0*sigma*sigma); /* simplify loop expressions */
1198 beta = 1.0/(MagickSQ2PI*sigma);
1199 for ( i=0, u=-kernel->x; u <= (ssize_t)kernel->x; u++, i++)
1200 kernel->values[i] = exp(-((double)(u*u))*alpha)*beta;
1202 else /* special case - generate a unity kernel */
1203 { (void) ResetMagickMemory(kernel->values,0, (size_t)
1204 kernel->width*kernel->height*sizeof(*kernel->values));
1205 kernel->values[kernel->x+kernel->y*kernel->width] = 1.0;
1208 /* Note the above kernel may have been 'clipped' by a user defined
1209 ** radius, producing a smaller (darker) kernel. Also for very small
1210 ** sigma's (> 0.1) the central value becomes larger than one, as a
1211 ** result of not generating a actual 'discrete' kernel, and thus
1212 ** producing a very bright 'impulse'.
1214 ** Becuase of these two factors Normalization is required!
1217 /* Normalize the 1D Gaussian Kernel
1219 ** NB: a CorrelateNormalize performs a normal Normalize if
1220 ** there are no negative values.
1222 CalcKernelMetaData(kernel); /* the other kernel meta-data */
1223 ScaleKernelInfo(kernel, 1.0, CorrelateNormalizeValue);
1225 /* rotate the 1D kernel by given angle */
1226 RotateKernelInfo(kernel, args->xi );
1231 sigma = fabs(args->sigma),
1234 if ( args->rho < 1.0 )
1235 kernel->width = (GetOptimalKernelWidth1D(args->rho,sigma)-1)/2+1;
1237 kernel->width = (size_t)args->rho;
1238 kernel->x = kernel->y = 0;
1240 kernel->negative_range = kernel->positive_range = 0.0;
1241 kernel->values=(MagickRealType *) MagickAssumeAligned(
1242 AcquireAlignedMemory(kernel->width,kernel->height*
1243 sizeof(*kernel->values)));
1244 if (kernel->values == (MagickRealType *) NULL)
1245 return(DestroyKernelInfo(kernel));
1247 /* A comet blur is half a 1D gaussian curve, so that the object is
1248 ** blurred in one direction only. This may not be quite the right
1249 ** curve to use so may change in the future. The function must be
1250 ** normalised after generation, which also resolves any clipping.
1252 ** As we are normalizing and not subtracting gaussians,
1253 ** there is no need for a divisor in the gaussian formula
1255 ** It is less comples
1257 if ( sigma > MagickEpsilon )
1260 #define KernelRank 3
1261 v = (ssize_t) kernel->width*KernelRank; /* start/end points */
1262 (void) ResetMagickMemory(kernel->values,0, (size_t)
1263 kernel->width*sizeof(*kernel->values));
1264 sigma *= KernelRank; /* simplify the loop expression */
1265 A = 1.0/(2.0*sigma*sigma);
1266 /* B = 1.0/(MagickSQ2PI*sigma); */
1267 for ( u=0; u < v; u++) {
1268 kernel->values[u/KernelRank] +=
1269 exp(-((double)(u*u))*A);
1270 /* exp(-((double)(i*i))/2.0*sigma*sigma)/(MagickSQ2PI*sigma); */
1272 for (i=0; i < (ssize_t) kernel->width; i++)
1273 kernel->positive_range += kernel->values[i];
1275 A = 1.0/(2.0*sigma*sigma); /* simplify the loop expression */
1276 /* B = 1.0/(MagickSQ2PI*sigma); */
1277 for ( i=0; i < (ssize_t) kernel->width; i++)
1278 kernel->positive_range +=
1279 kernel->values[i] = exp(-((double)(i*i))*A);
1280 /* exp(-((double)(i*i))/2.0*sigma*sigma)/(MagickSQ2PI*sigma); */
1283 else /* special case - generate a unity kernel */
1284 { (void) ResetMagickMemory(kernel->values,0, (size_t)
1285 kernel->width*kernel->height*sizeof(*kernel->values));
1286 kernel->values[kernel->x+kernel->y*kernel->width] = 1.0;
1287 kernel->positive_range = 1.0;
1290 kernel->minimum = 0.0;
1291 kernel->maximum = kernel->values[0];
1292 kernel->negative_range = 0.0;
1294 ScaleKernelInfo(kernel, 1.0, NormalizeValue); /* Normalize */
1295 RotateKernelInfo(kernel, args->xi); /* Rotate by angle */
1298 case BinomialKernel:
1303 if (args->rho < 1.0)
1304 kernel->width = kernel->height = 3; /* default radius = 1 */
1306 kernel->width = kernel->height = ((size_t)args->rho)*2+1;
1307 kernel->x = kernel->y = (ssize_t) (kernel->width-1)/2;
1309 order_f = fact(kernel->width-1);
1311 kernel->values=(MagickRealType *) MagickAssumeAligned(
1312 AcquireAlignedMemory(kernel->width,kernel->height*
1313 sizeof(*kernel->values)));
1314 if (kernel->values == (MagickRealType *) NULL)
1315 return(DestroyKernelInfo(kernel));
1317 /* set all kernel values within diamond area to scale given */
1318 for ( i=0, v=0; v < (ssize_t)kernel->height; v++)
1320 alpha = order_f / ( fact((size_t) v) * fact(kernel->height-v-1) );
1321 for ( u=0; u < (ssize_t)kernel->width; u++, i++)
1322 kernel->positive_range += kernel->values[i] = (double)
1323 (alpha * order_f / ( fact((size_t) u) * fact(kernel->height-u-1) ));
1325 kernel->minimum = 1.0;
1326 kernel->maximum = kernel->values[kernel->x+kernel->y*kernel->width];
1327 kernel->negative_range = 0.0;
1332 Convolution Kernels - Well Known Named Constant Kernels
1334 case LaplacianKernel:
1335 { switch ( (int) args->rho ) {
1337 default: /* laplacian square filter -- default */
1338 kernel=ParseKernelArray("3: -1,-1,-1 -1,8,-1 -1,-1,-1");
1340 case 1: /* laplacian diamond filter */
1341 kernel=ParseKernelArray("3: 0,-1,0 -1,4,-1 0,-1,0");
1344 kernel=ParseKernelArray("3: -2,1,-2 1,4,1 -2,1,-2");
1347 kernel=ParseKernelArray("3: 1,-2,1 -2,4,-2 1,-2,1");
1349 case 5: /* a 5x5 laplacian */
1350 kernel=ParseKernelArray(
1351 "5: -4,-1,0,-1,-4 -1,2,3,2,-1 0,3,4,3,0 -1,2,3,2,-1 -4,-1,0,-1,-4");
1353 case 7: /* a 7x7 laplacian */
1354 kernel=ParseKernelArray(
1355 "7:-10,-5,-2,-1,-2,-5,-10 -5,0,3,4,3,0,-5 -2,3,6,7,6,3,-2 -1,4,7,8,7,4,-1 -2,3,6,7,6,3,-2 -5,0,3,4,3,0,-5 -10,-5,-2,-1,-2,-5,-10" );
1357 case 15: /* a 5x5 LoG (sigma approx 1.4) */
1358 kernel=ParseKernelArray(
1359 "5: 0,0,-1,0,0 0,-1,-2,-1,0 -1,-2,16,-2,-1 0,-1,-2,-1,0 0,0,-1,0,0");
1361 case 19: /* a 9x9 LoG (sigma approx 1.4) */
1362 /* http://www.cscjournals.org/csc/manuscript/Journals/IJIP/volume3/Issue1/IJIP-15.pdf */
1363 kernel=ParseKernelArray(
1364 "9: 0,-1,-1,-2,-2,-2,-1,-1,0 -1,-2,-4,-5,-5,-5,-4,-2,-1 -1,-4,-5,-3,-0,-3,-5,-4,-1 -2,-5,-3,12,24,12,-3,-5,-2 -2,-5,-0,24,40,24,-0,-5,-2 -2,-5,-3,12,24,12,-3,-5,-2 -1,-4,-5,-3,-0,-3,-5,-4,-1 -1,-2,-4,-5,-5,-5,-4,-2,-1 0,-1,-1,-2,-2,-2,-1,-1,0");
1367 if (kernel == (KernelInfo *) NULL)
1369 kernel->type = type;
1373 { /* Simple Sobel Kernel */
1374 kernel=ParseKernelArray("3: 1,0,-1 2,0,-2 1,0,-1");
1375 if (kernel == (KernelInfo *) NULL)
1377 kernel->type = type;
1378 RotateKernelInfo(kernel, args->rho);
1383 kernel=ParseKernelArray("3: 0,0,0 1,-1,0 0,0,0");
1384 if (kernel == (KernelInfo *) NULL)
1386 kernel->type = type;
1387 RotateKernelInfo(kernel, args->rho);
1392 kernel=ParseKernelArray("3: 1,0,-1 1,0,-1 1,0,-1");
1393 if (kernel == (KernelInfo *) NULL)
1395 kernel->type = type;
1396 RotateKernelInfo(kernel, args->rho);
1401 kernel=ParseKernelArray("3: 1,1,-1 1,-2,-1 1,1,-1");
1402 if (kernel == (KernelInfo *) NULL)
1404 kernel->type = type;
1405 RotateKernelInfo(kernel, args->rho);
1410 kernel=ParseKernelArray("3: 5,-3,-3 5,0,-3 5,-3,-3");
1411 if (kernel == (KernelInfo *) NULL)
1413 kernel->type = type;
1414 RotateKernelInfo(kernel, args->rho);
1417 case FreiChenKernel:
1418 /* Direction is set to be left to right positive */
1419 /* http://www.math.tau.ac.il/~turkel/notes/edge_detectors.pdf -- RIGHT? */
1420 /* http://ltswww.epfl.ch/~courstiv/exos_labos/sol3.pdf -- WRONG? */
1421 { switch ( (int) args->rho ) {
1424 kernel=ParseKernelArray("3: 1,0,-1 2,0,-2 1,0,-1");
1425 if (kernel == (KernelInfo *) NULL)
1427 kernel->type = type;
1428 kernel->values[3] = +(MagickRealType) MagickSQ2;
1429 kernel->values[5] = -(MagickRealType) MagickSQ2;
1430 CalcKernelMetaData(kernel); /* recalculate meta-data */
1433 kernel=ParseKernelArray("3: 1,2,0 2,0,-2 0,-2,-1");
1434 if (kernel == (KernelInfo *) NULL)
1436 kernel->type = type;
1437 kernel->values[1] = kernel->values[3]= +(MagickRealType) MagickSQ2;
1438 kernel->values[5] = kernel->values[7]= -(MagickRealType) MagickSQ2;
1439 CalcKernelMetaData(kernel); /* recalculate meta-data */
1440 ScaleKernelInfo(kernel, (double) (1.0/2.0*MagickSQ2), NoValue);
1443 kernel=AcquireKernelInfo("FreiChen:11;FreiChen:12;FreiChen:13;FreiChen:14;FreiChen:15;FreiChen:16;FreiChen:17;FreiChen:18;FreiChen:19");
1444 if (kernel == (KernelInfo *) NULL)
1449 kernel=ParseKernelArray("3: 1,0,-1 2,0,-2 1,0,-1");
1450 if (kernel == (KernelInfo *) NULL)
1452 kernel->type = type;
1453 kernel->values[3] = +(MagickRealType) MagickSQ2;
1454 kernel->values[5] = -(MagickRealType) MagickSQ2;
1455 CalcKernelMetaData(kernel); /* recalculate meta-data */
1456 ScaleKernelInfo(kernel, (double) (1.0/2.0*MagickSQ2), NoValue);
1459 kernel=ParseKernelArray("3: 1,2,1 0,0,0 1,2,1");
1460 if (kernel == (KernelInfo *) NULL)
1462 kernel->type = type;
1463 kernel->values[1] = +(MagickRealType) MagickSQ2;
1464 kernel->values[7] = +(MagickRealType) MagickSQ2;
1465 CalcKernelMetaData(kernel);
1466 ScaleKernelInfo(kernel, (double) (1.0/2.0*MagickSQ2), NoValue);
1469 kernel=ParseKernelArray("3: 2,-1,0 -1,0,1 0,1,-2");
1470 if (kernel == (KernelInfo *) NULL)
1472 kernel->type = type;
1473 kernel->values[0] = +(MagickRealType) MagickSQ2;
1474 kernel->values[8] = -(MagickRealType) MagickSQ2;
1475 CalcKernelMetaData(kernel);
1476 ScaleKernelInfo(kernel, (double) (1.0/2.0*MagickSQ2), NoValue);
1479 kernel=ParseKernelArray("3: 0,1,-2 -1,0,1 2,-1,0");
1480 if (kernel == (KernelInfo *) NULL)
1482 kernel->type = type;
1483 kernel->values[2] = -(MagickRealType) MagickSQ2;
1484 kernel->values[6] = +(MagickRealType) MagickSQ2;
1485 CalcKernelMetaData(kernel);
1486 ScaleKernelInfo(kernel, (double) (1.0/2.0*MagickSQ2), NoValue);
1489 kernel=ParseKernelArray("3: 0,-1,0 1,0,1 0,-1,0");
1490 if (kernel == (KernelInfo *) NULL)
1492 kernel->type = type;
1493 ScaleKernelInfo(kernel, 1.0/2.0, NoValue);
1496 kernel=ParseKernelArray("3: 1,0,-1 0,0,0 -1,0,1");
1497 if (kernel == (KernelInfo *) NULL)
1499 kernel->type = type;
1500 ScaleKernelInfo(kernel, 1.0/2.0, NoValue);
1503 kernel=ParseKernelArray("3: 1,-2,1 -2,4,-2 -1,-2,1");
1504 if (kernel == (KernelInfo *) NULL)
1506 kernel->type = type;
1507 ScaleKernelInfo(kernel, 1.0/6.0, NoValue);
1510 kernel=ParseKernelArray("3: -2,1,-2 1,4,1 -2,1,-2");
1511 if (kernel == (KernelInfo *) NULL)
1513 kernel->type = type;
1514 ScaleKernelInfo(kernel, 1.0/6.0, NoValue);
1517 kernel=ParseKernelArray("3: 1,1,1 1,1,1 1,1,1");
1518 if (kernel == (KernelInfo *) NULL)
1520 kernel->type = type;
1521 ScaleKernelInfo(kernel, 1.0/3.0, NoValue);
1524 if ( fabs(args->sigma) >= MagickEpsilon )
1525 /* Rotate by correctly supplied 'angle' */
1526 RotateKernelInfo(kernel, args->sigma);
1527 else if ( args->rho > 30.0 || args->rho < -30.0 )
1528 /* Rotate by out of bounds 'type' */
1529 RotateKernelInfo(kernel, args->rho);
1534 Boolean or Shaped Kernels
1538 if (args->rho < 1.0)
1539 kernel->width = kernel->height = 3; /* default radius = 1 */
1541 kernel->width = kernel->height = ((size_t)args->rho)*2+1;
1542 kernel->x = kernel->y = (ssize_t) (kernel->width-1)/2;
1544 kernel->values=(MagickRealType *) MagickAssumeAligned(
1545 AcquireAlignedMemory(kernel->width,kernel->height*
1546 sizeof(*kernel->values)));
1547 if (kernel->values == (MagickRealType *) NULL)
1548 return(DestroyKernelInfo(kernel));
1550 /* set all kernel values within diamond area to scale given */
1551 for ( i=0, v=-kernel->y; v <= (ssize_t)kernel->y; v++)
1552 for ( u=-kernel->x; u <= (ssize_t)kernel->x; u++, i++)
1553 if ( (labs((long) u)+labs((long) v)) <= (long) kernel->x)
1554 kernel->positive_range += kernel->values[i] = args->sigma;
1556 kernel->values[i] = nan;
1557 kernel->minimum = kernel->maximum = args->sigma; /* a flat shape */
1561 case RectangleKernel:
1564 if ( type == SquareKernel )
1566 if (args->rho < 1.0)
1567 kernel->width = kernel->height = 3; /* default radius = 1 */
1569 kernel->width = kernel->height = (size_t) (2*args->rho+1);
1570 kernel->x = kernel->y = (ssize_t) (kernel->width-1)/2;
1571 scale = args->sigma;
1574 /* NOTE: user defaults set in "AcquireKernelInfo()" */
1575 if ( args->rho < 1.0 || args->sigma < 1.0 )
1576 return(DestroyKernelInfo(kernel)); /* invalid args given */
1577 kernel->width = (size_t)args->rho;
1578 kernel->height = (size_t)args->sigma;
1579 if ( args->xi < 0.0 || args->xi > (double)kernel->width ||
1580 args->psi < 0.0 || args->psi > (double)kernel->height )
1581 return(DestroyKernelInfo(kernel)); /* invalid args given */
1582 kernel->x = (ssize_t) args->xi;
1583 kernel->y = (ssize_t) args->psi;
1586 kernel->values=(MagickRealType *) MagickAssumeAligned(
1587 AcquireAlignedMemory(kernel->width,kernel->height*
1588 sizeof(*kernel->values)));
1589 if (kernel->values == (MagickRealType *) NULL)
1590 return(DestroyKernelInfo(kernel));
1592 /* set all kernel values to scale given */
1593 u=(ssize_t) (kernel->width*kernel->height);
1594 for ( i=0; i < u; i++)
1595 kernel->values[i] = scale;
1596 kernel->minimum = kernel->maximum = scale; /* a flat shape */
1597 kernel->positive_range = scale*u;
1602 if (args->rho < 1.0)
1603 kernel->width = kernel->height = 5; /* default radius = 2 */
1605 kernel->width = kernel->height = ((size_t)args->rho)*2+1;
1606 kernel->x = kernel->y = (ssize_t) (kernel->width-1)/2;
1608 kernel->values=(MagickRealType *) MagickAssumeAligned(
1609 AcquireAlignedMemory(kernel->width,kernel->height*
1610 sizeof(*kernel->values)));
1611 if (kernel->values == (MagickRealType *) NULL)
1612 return(DestroyKernelInfo(kernel));
1614 for ( i=0, v=-kernel->y; v <= (ssize_t)kernel->y; v++)
1615 for ( u=-kernel->x; u <= (ssize_t)kernel->x; u++, i++)
1616 if ( (labs((long) u)+labs((long) v)) <=
1617 ((long)kernel->x + (long)(kernel->x/2)) )
1618 kernel->positive_range += kernel->values[i] = args->sigma;
1620 kernel->values[i] = nan;
1621 kernel->minimum = kernel->maximum = args->sigma; /* a flat shape */
1627 limit = (ssize_t)(args->rho*args->rho);
1629 if (args->rho < 0.4) /* default radius approx 4.3 */
1630 kernel->width = kernel->height = 9L, limit = 18L;
1632 kernel->width = kernel->height = (size_t)fabs(args->rho)*2+1;
1633 kernel->x = kernel->y = (ssize_t) (kernel->width-1)/2;
1635 kernel->values=(MagickRealType *) MagickAssumeAligned(
1636 AcquireAlignedMemory(kernel->width,kernel->height*
1637 sizeof(*kernel->values)));
1638 if (kernel->values == (MagickRealType *) NULL)
1639 return(DestroyKernelInfo(kernel));
1641 for ( i=0, v=-kernel->y; v <= (ssize_t)kernel->y; v++)
1642 for ( u=-kernel->x; u <= (ssize_t)kernel->x; u++, i++)
1643 if ((u*u+v*v) <= limit)
1644 kernel->positive_range += kernel->values[i] = args->sigma;
1646 kernel->values[i] = nan;
1647 kernel->minimum = kernel->maximum = args->sigma; /* a flat shape */
1652 if (args->rho < 1.0)
1653 kernel->width = kernel->height = 5; /* default radius 2 */
1655 kernel->width = kernel->height = ((size_t)args->rho)*2+1;
1656 kernel->x = kernel->y = (ssize_t) (kernel->width-1)/2;
1658 kernel->values=(MagickRealType *) MagickAssumeAligned(
1659 AcquireAlignedMemory(kernel->width,kernel->height*
1660 sizeof(*kernel->values)));
1661 if (kernel->values == (MagickRealType *) NULL)
1662 return(DestroyKernelInfo(kernel));
1664 /* set all kernel values along axises to given scale */
1665 for ( i=0, v=-kernel->y; v <= (ssize_t)kernel->y; v++)
1666 for ( u=-kernel->x; u <= (ssize_t)kernel->x; u++, i++)
1667 kernel->values[i] = (u == 0 || v == 0) ? args->sigma : nan;
1668 kernel->minimum = kernel->maximum = args->sigma; /* a flat shape */
1669 kernel->positive_range = args->sigma*(kernel->width*2.0 - 1.0);
1674 if (args->rho < 1.0)
1675 kernel->width = kernel->height = 5; /* default radius 2 */
1677 kernel->width = kernel->height = ((size_t)args->rho)*2+1;
1678 kernel->x = kernel->y = (ssize_t) (kernel->width-1)/2;
1680 kernel->values=(MagickRealType *) MagickAssumeAligned(
1681 AcquireAlignedMemory(kernel->width,kernel->height*
1682 sizeof(*kernel->values)));
1683 if (kernel->values == (MagickRealType *) NULL)
1684 return(DestroyKernelInfo(kernel));
1686 /* set all kernel values along axises to given scale */
1687 for ( i=0, v=-kernel->y; v <= (ssize_t)kernel->y; v++)
1688 for ( u=-kernel->x; u <= (ssize_t)kernel->x; u++, i++)
1689 kernel->values[i] = (u == v || u == -v) ? args->sigma : nan;
1690 kernel->minimum = kernel->maximum = args->sigma; /* a flat shape */
1691 kernel->positive_range = args->sigma*(kernel->width*2.0 - 1.0);
1705 if (args->rho < args->sigma)
1707 kernel->width = ((size_t)args->sigma)*2+1;
1708 limit1 = (ssize_t)(args->rho*args->rho);
1709 limit2 = (ssize_t)(args->sigma*args->sigma);
1713 kernel->width = ((size_t)args->rho)*2+1;
1714 limit1 = (ssize_t)(args->sigma*args->sigma);
1715 limit2 = (ssize_t)(args->rho*args->rho);
1718 kernel->width = 7L, limit1 = 7L, limit2 = 11L;
1720 kernel->height = kernel->width;
1721 kernel->x = kernel->y = (ssize_t) (kernel->width-1)/2;
1722 kernel->values=(MagickRealType *) MagickAssumeAligned(
1723 AcquireAlignedMemory(kernel->width,kernel->height*
1724 sizeof(*kernel->values)));
1725 if (kernel->values == (MagickRealType *) NULL)
1726 return(DestroyKernelInfo(kernel));
1728 /* set a ring of points of 'scale' ( 0.0 for PeaksKernel ) */
1729 scale = (ssize_t) (( type == PeaksKernel) ? 0.0 : args->xi);
1730 for ( i=0, v= -kernel->y; v <= (ssize_t)kernel->y; v++)
1731 for ( u=-kernel->x; u <= (ssize_t)kernel->x; u++, i++)
1732 { ssize_t radius=u*u+v*v;
1733 if (limit1 < radius && radius <= limit2)
1734 kernel->positive_range += kernel->values[i] = (double) scale;
1736 kernel->values[i] = nan;
1738 kernel->minimum = kernel->maximum = (double) scale;
1739 if ( type == PeaksKernel ) {
1740 /* set the central point in the middle */
1741 kernel->values[kernel->x+kernel->y*kernel->width] = 1.0;
1742 kernel->positive_range = 1.0;
1743 kernel->maximum = 1.0;
1749 kernel=AcquireKernelInfo("ThinSE:482");
1750 if (kernel == (KernelInfo *) NULL)
1752 kernel->type = type;
1753 ExpandMirrorKernelInfo(kernel); /* mirror expansion of kernels */
1758 kernel=AcquireKernelInfo("ThinSE:87");
1759 if (kernel == (KernelInfo *) NULL)
1761 kernel->type = type;
1762 ExpandRotateKernelInfo(kernel, 90.0); /* Expand 90 degree rotations */
1765 case DiagonalsKernel:
1767 switch ( (int) args->rho ) {
1772 kernel=ParseKernelArray("3: 0,0,0 0,-,1 1,1,-");
1773 if (kernel == (KernelInfo *) NULL)
1775 kernel->type = type;
1776 new_kernel=ParseKernelArray("3: 0,0,1 0,-,1 0,1,-");
1777 if (new_kernel == (KernelInfo *) NULL)
1778 return(DestroyKernelInfo(kernel));
1779 new_kernel->type = type;
1780 LastKernelInfo(kernel)->next = new_kernel;
1781 ExpandMirrorKernelInfo(kernel);
1785 kernel=ParseKernelArray("3: 0,0,0 0,-,1 1,1,-");
1788 kernel=ParseKernelArray("3: 0,0,1 0,-,1 0,1,-");
1791 if (kernel == (KernelInfo *) NULL)
1793 kernel->type = type;
1794 RotateKernelInfo(kernel, args->sigma);
1797 case LineEndsKernel:
1798 { /* Kernels for finding the end of thin lines */
1799 switch ( (int) args->rho ) {
1802 /* set of kernels to find all end of lines */
1803 return(AcquireKernelInfo("LineEnds:1>;LineEnds:2>"));
1805 /* kernel for 4-connected line ends - no rotation */
1806 kernel=ParseKernelArray("3: 0,0,- 0,1,1 0,0,-");
1809 /* kernel to add for 8-connected lines - no rotation */
1810 kernel=ParseKernelArray("3: 0,0,0 0,1,0 0,0,1");
1813 /* kernel to add for orthogonal line ends - does not find corners */
1814 kernel=ParseKernelArray("3: 0,0,0 0,1,1 0,0,0");
1817 /* traditional line end - fails on last T end */
1818 kernel=ParseKernelArray("3: 0,0,0 0,1,- 0,0,-");
1821 if (kernel == (KernelInfo *) NULL)
1823 kernel->type = type;
1824 RotateKernelInfo(kernel, args->sigma);
1827 case LineJunctionsKernel:
1828 { /* kernels for finding the junctions of multiple lines */
1829 switch ( (int) args->rho ) {
1832 /* set of kernels to find all line junctions */
1833 return(AcquireKernelInfo("LineJunctions:1@;LineJunctions:2>"));
1836 kernel=ParseKernelArray("3: 1,-,1 -,1,- -,1,-");
1839 /* Diagonal T Junctions */
1840 kernel=ParseKernelArray("3: 1,-,- -,1,- 1,-,1");
1843 /* Orthogonal T Junctions */
1844 kernel=ParseKernelArray("3: -,-,- 1,1,1 -,1,-");
1847 /* Diagonal X Junctions */
1848 kernel=ParseKernelArray("3: 1,-,1 -,1,- 1,-,1");
1851 /* Orthogonal X Junctions - minimal diamond kernel */
1852 kernel=ParseKernelArray("3: -,1,- 1,1,1 -,1,-");
1855 if (kernel == (KernelInfo *) NULL)
1857 kernel->type = type;
1858 RotateKernelInfo(kernel, args->sigma);
1862 { /* Ridges - Ridge finding kernels */
1865 switch ( (int) args->rho ) {
1868 kernel=ParseKernelArray("3x1:0,1,0");
1869 if (kernel == (KernelInfo *) NULL)
1871 kernel->type = type;
1872 ExpandRotateKernelInfo(kernel, 90.0); /* 2 rotated kernels (symmetrical) */
1875 kernel=ParseKernelArray("4x1:0,1,1,0");
1876 if (kernel == (KernelInfo *) NULL)
1878 kernel->type = type;
1879 ExpandRotateKernelInfo(kernel, 90.0); /* 4 rotated kernels */
1881 /* Kernels to find a stepped 'thick' line, 4 rotates + mirrors */
1882 /* Unfortunatally we can not yet rotate a non-square kernel */
1883 /* But then we can't flip a non-symetrical kernel either */
1884 new_kernel=ParseKernelArray("4x3+1+1:0,1,1,- -,1,1,- -,1,1,0");
1885 if (new_kernel == (KernelInfo *) NULL)
1886 return(DestroyKernelInfo(kernel));
1887 new_kernel->type = type;
1888 LastKernelInfo(kernel)->next = new_kernel;
1889 new_kernel=ParseKernelArray("4x3+2+1:0,1,1,- -,1,1,- -,1,1,0");
1890 if (new_kernel == (KernelInfo *) NULL)
1891 return(DestroyKernelInfo(kernel));
1892 new_kernel->type = type;
1893 LastKernelInfo(kernel)->next = new_kernel;
1894 new_kernel=ParseKernelArray("4x3+1+1:-,1,1,0 -,1,1,- 0,1,1,-");
1895 if (new_kernel == (KernelInfo *) NULL)
1896 return(DestroyKernelInfo(kernel));
1897 new_kernel->type = type;
1898 LastKernelInfo(kernel)->next = new_kernel;
1899 new_kernel=ParseKernelArray("4x3+2+1:-,1,1,0 -,1,1,- 0,1,1,-");
1900 if (new_kernel == (KernelInfo *) NULL)
1901 return(DestroyKernelInfo(kernel));
1902 new_kernel->type = type;
1903 LastKernelInfo(kernel)->next = new_kernel;
1904 new_kernel=ParseKernelArray("3x4+1+1:0,-,- 1,1,1 1,1,1 -,-,0");
1905 if (new_kernel == (KernelInfo *) NULL)
1906 return(DestroyKernelInfo(kernel));
1907 new_kernel->type = type;
1908 LastKernelInfo(kernel)->next = new_kernel;
1909 new_kernel=ParseKernelArray("3x4+1+2:0,-,- 1,1,1 1,1,1 -,-,0");
1910 if (new_kernel == (KernelInfo *) NULL)
1911 return(DestroyKernelInfo(kernel));
1912 new_kernel->type = type;
1913 LastKernelInfo(kernel)->next = new_kernel;
1914 new_kernel=ParseKernelArray("3x4+1+1:-,-,0 1,1,1 1,1,1 0,-,-");
1915 if (new_kernel == (KernelInfo *) NULL)
1916 return(DestroyKernelInfo(kernel));
1917 new_kernel->type = type;
1918 LastKernelInfo(kernel)->next = new_kernel;
1919 new_kernel=ParseKernelArray("3x4+1+2:-,-,0 1,1,1 1,1,1 0,-,-");
1920 if (new_kernel == (KernelInfo *) NULL)
1921 return(DestroyKernelInfo(kernel));
1922 new_kernel->type = type;
1923 LastKernelInfo(kernel)->next = new_kernel;
1928 case ConvexHullKernel:
1932 /* first set of 8 kernels */
1933 kernel=ParseKernelArray("3: 1,1,- 1,0,- 1,-,0");
1934 if (kernel == (KernelInfo *) NULL)
1936 kernel->type = type;
1937 ExpandRotateKernelInfo(kernel, 90.0);
1938 /* append the mirror versions too - no flip function yet */
1939 new_kernel=ParseKernelArray("3: 1,1,1 1,0,- -,-,0");
1940 if (new_kernel == (KernelInfo *) NULL)
1941 return(DestroyKernelInfo(kernel));
1942 new_kernel->type = type;
1943 ExpandRotateKernelInfo(new_kernel, 90.0);
1944 LastKernelInfo(kernel)->next = new_kernel;
1947 case SkeletonKernel:
1949 switch ( (int) args->rho ) {
1952 /* Traditional Skeleton...
1953 ** A cyclically rotated single kernel
1955 kernel=AcquireKernelInfo("ThinSE:482");
1956 if (kernel == (KernelInfo *) NULL)
1958 kernel->type = type;
1959 ExpandRotateKernelInfo(kernel, 45.0); /* 8 rotations */
1962 /* HIPR Variation of the cyclic skeleton
1963 ** Corners of the traditional method made more forgiving,
1964 ** but the retain the same cyclic order.
1966 kernel=AcquireKernelInfo("ThinSE:482; ThinSE:87x90;");
1967 if (kernel == (KernelInfo *) NULL)
1969 if (kernel->next == (KernelInfo *) NULL)
1970 return(DestroyKernelInfo(kernel));
1971 kernel->type = type;
1972 kernel->next->type = type;
1973 ExpandRotateKernelInfo(kernel, 90.0); /* 4 rotations of the 2 kernels */
1976 /* Dan Bloomberg Skeleton, from his paper on 3x3 thinning SE's
1977 ** "Connectivity-Preserving Morphological Image Thransformations"
1978 ** by Dan S. Bloomberg, available on Leptonica, Selected Papers,
1979 ** http://www.leptonica.com/papers/conn.pdf
1981 kernel=AcquireKernelInfo(
1982 "ThinSE:41; ThinSE:42; ThinSE:43");
1983 if (kernel == (KernelInfo *) NULL)
1985 kernel->type = type;
1986 kernel->next->type = type;
1987 kernel->next->next->type = type;
1988 ExpandMirrorKernelInfo(kernel); /* 12 kernels total */
1994 { /* Special kernels for general thinning, while preserving connections
1995 ** "Connectivity-Preserving Morphological Image Thransformations"
1996 ** by Dan S. Bloomberg, available on Leptonica, Selected Papers,
1997 ** http://www.leptonica.com/papers/conn.pdf
1999 ** http://tpgit.github.com/Leptonica/ccthin_8c_source.html
2001 ** Note kernels do not specify the origin pixel, allowing them
2002 ** to be used for both thickening and thinning operations.
2004 switch ( (int) args->rho ) {
2005 /* SE for 4-connected thinning */
2006 case 41: /* SE_4_1 */
2007 kernel=ParseKernelArray("3: -,-,1 0,-,1 -,-,1");
2009 case 42: /* SE_4_2 */
2010 kernel=ParseKernelArray("3: -,-,1 0,-,1 -,0,-");
2012 case 43: /* SE_4_3 */
2013 kernel=ParseKernelArray("3: -,0,- 0,-,1 -,-,1");
2015 case 44: /* SE_4_4 */
2016 kernel=ParseKernelArray("3: -,0,- 0,-,1 -,0,-");
2018 case 45: /* SE_4_5 */
2019 kernel=ParseKernelArray("3: -,0,1 0,-,1 -,0,-");
2021 case 46: /* SE_4_6 */
2022 kernel=ParseKernelArray("3: -,0,- 0,-,1 -,0,1");
2024 case 47: /* SE_4_7 */
2025 kernel=ParseKernelArray("3: -,1,1 0,-,1 -,0,-");
2027 case 48: /* SE_4_8 */
2028 kernel=ParseKernelArray("3: -,-,1 0,-,1 0,-,1");
2030 case 49: /* SE_4_9 */
2031 kernel=ParseKernelArray("3: 0,-,1 0,-,1 -,-,1");
2033 /* SE for 8-connected thinning - negatives of the above */
2034 case 81: /* SE_8_0 */
2035 kernel=ParseKernelArray("3: -,1,- 0,-,1 -,1,-");
2037 case 82: /* SE_8_2 */
2038 kernel=ParseKernelArray("3: -,1,- 0,-,1 0,-,-");
2040 case 83: /* SE_8_3 */
2041 kernel=ParseKernelArray("3: 0,-,- 0,-,1 -,1,-");
2043 case 84: /* SE_8_4 */
2044 kernel=ParseKernelArray("3: 0,-,- 0,-,1 0,-,-");
2046 case 85: /* SE_8_5 */
2047 kernel=ParseKernelArray("3: 0,-,1 0,-,1 0,-,-");
2049 case 86: /* SE_8_6 */
2050 kernel=ParseKernelArray("3: 0,-,- 0,-,1 0,-,1");
2052 case 87: /* SE_8_7 */
2053 kernel=ParseKernelArray("3: -,1,- 0,-,1 0,0,-");
2055 case 88: /* SE_8_8 */
2056 kernel=ParseKernelArray("3: -,1,- 0,-,1 0,1,-");
2058 case 89: /* SE_8_9 */
2059 kernel=ParseKernelArray("3: 0,1,- 0,-,1 -,1,-");
2061 /* Special combined SE kernels */
2062 case 423: /* SE_4_2 , SE_4_3 Combined Kernel */
2063 kernel=ParseKernelArray("3: -,-,1 0,-,- -,0,-");
2065 case 823: /* SE_8_2 , SE_8_3 Combined Kernel */
2066 kernel=ParseKernelArray("3: -,1,- -,-,1 0,-,-");
2068 case 481: /* SE_48_1 - General Connected Corner Kernel */
2069 kernel=ParseKernelArray("3: -,1,1 0,-,1 0,0,-");
2072 case 482: /* SE_48_2 - General Edge Kernel */
2073 kernel=ParseKernelArray("3: 0,-,1 0,-,1 0,-,1");
2076 if (kernel == (KernelInfo *) NULL)
2078 kernel->type = type;
2079 RotateKernelInfo(kernel, args->sigma);
2083 Distance Measuring Kernels
2085 case ChebyshevKernel:
2087 if (args->rho < 1.0)
2088 kernel->width = kernel->height = 3; /* default radius = 1 */
2090 kernel->width = kernel->height = ((size_t)args->rho)*2+1;
2091 kernel->x = kernel->y = (ssize_t) (kernel->width-1)/2;
2093 kernel->values=(MagickRealType *) MagickAssumeAligned(
2094 AcquireAlignedMemory(kernel->width,kernel->height*
2095 sizeof(*kernel->values)));
2096 if (kernel->values == (MagickRealType *) NULL)
2097 return(DestroyKernelInfo(kernel));
2099 for ( i=0, v=-kernel->y; v <= (ssize_t)kernel->y; v++)
2100 for ( u=-kernel->x; u <= (ssize_t)kernel->x; u++, i++)
2101 kernel->positive_range += ( kernel->values[i] =
2102 args->sigma*MagickMax(fabs((double)u),fabs((double)v)) );
2103 kernel->maximum = kernel->values[0];
2106 case ManhattanKernel:
2108 if (args->rho < 1.0)
2109 kernel->width = kernel->height = 3; /* default radius = 1 */
2111 kernel->width = kernel->height = ((size_t)args->rho)*2+1;
2112 kernel->x = kernel->y = (ssize_t) (kernel->width-1)/2;
2114 kernel->values=(MagickRealType *) MagickAssumeAligned(
2115 AcquireAlignedMemory(kernel->width,kernel->height*
2116 sizeof(*kernel->values)));
2117 if (kernel->values == (MagickRealType *) NULL)
2118 return(DestroyKernelInfo(kernel));
2120 for ( i=0, v=-kernel->y; v <= (ssize_t)kernel->y; v++)
2121 for ( u=-kernel->x; u <= (ssize_t)kernel->x; u++, i++)
2122 kernel->positive_range += ( kernel->values[i] =
2123 args->sigma*(labs((long) u)+labs((long) v)) );
2124 kernel->maximum = kernel->values[0];
2127 case OctagonalKernel:
2129 if (args->rho < 2.0)
2130 kernel->width = kernel->height = 5; /* default/minimum radius = 2 */
2132 kernel->width = kernel->height = ((size_t)args->rho)*2+1;
2133 kernel->x = kernel->y = (ssize_t) (kernel->width-1)/2;
2135 kernel->values=(MagickRealType *) MagickAssumeAligned(
2136 AcquireAlignedMemory(kernel->width,kernel->height*
2137 sizeof(*kernel->values)));
2138 if (kernel->values == (MagickRealType *) NULL)
2139 return(DestroyKernelInfo(kernel));
2141 for ( i=0, v=-kernel->y; v <= (ssize_t)kernel->y; v++)
2142 for ( u=-kernel->x; u <= (ssize_t)kernel->x; u++, i++)
2145 r1 = MagickMax(fabs((double)u),fabs((double)v)),
2146 r2 = floor((double)(labs((long)u)+labs((long)v)+1)/1.5);
2147 kernel->positive_range += kernel->values[i] =
2148 args->sigma*MagickMax(r1,r2);
2150 kernel->maximum = kernel->values[0];
2153 case EuclideanKernel:
2155 if (args->rho < 1.0)
2156 kernel->width = kernel->height = 3; /* default radius = 1 */
2158 kernel->width = kernel->height = ((size_t)args->rho)*2+1;
2159 kernel->x = kernel->y = (ssize_t) (kernel->width-1)/2;
2161 kernel->values=(MagickRealType *) MagickAssumeAligned(
2162 AcquireAlignedMemory(kernel->width,kernel->height*
2163 sizeof(*kernel->values)));
2164 if (kernel->values == (MagickRealType *) NULL)
2165 return(DestroyKernelInfo(kernel));
2167 for ( i=0, v=-kernel->y; v <= (ssize_t)kernel->y; v++)
2168 for ( u=-kernel->x; u <= (ssize_t)kernel->x; u++, i++)
2169 kernel->positive_range += ( kernel->values[i] =
2170 args->sigma*sqrt((double)(u*u+v*v)) );
2171 kernel->maximum = kernel->values[0];
2176 /* No-Op Kernel - Basically just a single pixel on its own */
2177 kernel=ParseKernelArray("1:1");
2178 if (kernel == (KernelInfo *) NULL)
2180 kernel->type = UndefinedKernel;
2189 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2193 % C l o n e K e r n e l I n f o %
2197 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2199 % CloneKernelInfo() creates a new clone of the given Kernel List so that its
2200 % can be modified without effecting the original. The cloned kernel should
2201 % be destroyed using DestoryKernelInfo() when no longer needed.
2203 % The format of the CloneKernelInfo method is:
2205 % KernelInfo *CloneKernelInfo(const KernelInfo *kernel)
2207 % A description of each parameter follows:
2209 % o kernel: the Morphology/Convolution kernel to be cloned
2212 MagickExport KernelInfo *CloneKernelInfo(const KernelInfo *kernel)
2220 assert(kernel != (KernelInfo *) NULL);
2221 new_kernel=(KernelInfo *) AcquireMagickMemory(sizeof(*kernel));
2222 if (new_kernel == (KernelInfo *) NULL)
2224 *new_kernel=(*kernel); /* copy values in structure */
2226 /* replace the values with a copy of the values */
2227 new_kernel->values=(MagickRealType *) MagickAssumeAligned(
2228 AcquireAlignedMemory(kernel->width,kernel->height*sizeof(*kernel->values)));
2229 if (new_kernel->values == (MagickRealType *) NULL)
2230 return(DestroyKernelInfo(new_kernel));
2231 for (i=0; i < (ssize_t) (kernel->width*kernel->height); i++)
2232 new_kernel->values[i]=kernel->values[i];
2234 /* Also clone the next kernel in the kernel list */
2235 if ( kernel->next != (KernelInfo *) NULL ) {
2236 new_kernel->next = CloneKernelInfo(kernel->next);
2237 if ( new_kernel->next == (KernelInfo *) NULL )
2238 return(DestroyKernelInfo(new_kernel));
2245 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2249 % D e s t r o y K e r n e l I n f o %
2253 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2255 % DestroyKernelInfo() frees the memory used by a Convolution/Morphology
2258 % The format of the DestroyKernelInfo method is:
2260 % KernelInfo *DestroyKernelInfo(KernelInfo *kernel)
2262 % A description of each parameter follows:
2264 % o kernel: the Morphology/Convolution kernel to be destroyed
2267 MagickExport KernelInfo *DestroyKernelInfo(KernelInfo *kernel)
2269 assert(kernel != (KernelInfo *) NULL);
2270 if (kernel->next != (KernelInfo *) NULL)
2271 kernel->next=DestroyKernelInfo(kernel->next);
2272 kernel->values=(MagickRealType *) RelinquishAlignedMemory(kernel->values);
2273 kernel=(KernelInfo *) RelinquishMagickMemory(kernel);
2278 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2282 + E x p a n d M i r r o r K e r n e l I n f o %
2286 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2288 % ExpandMirrorKernelInfo() takes a single kernel, and expands it into a
2289 % sequence of 90-degree rotated kernels but providing a reflected 180
2290 % rotatation, before the -/+ 90-degree rotations.
2292 % This special rotation order produces a better, more symetrical thinning of
2295 % The format of the ExpandMirrorKernelInfo method is:
2297 % void ExpandMirrorKernelInfo(KernelInfo *kernel)
2299 % A description of each parameter follows:
2301 % o kernel: the Morphology/Convolution kernel
2303 % This function is only internel to this module, as it is not finalized,
2304 % especially with regard to non-orthogonal angles, and rotation of larger
2309 static void FlopKernelInfo(KernelInfo *kernel)
2310 { /* Do a Flop by reversing each row. */
2318 for ( y=0, k=kernel->values; y < kernel->height; y++, k+=kernel->width)
2319 for ( x=0, r=kernel->width-1; x<kernel->width/2; x++, r--)
2320 t=k[x], k[x]=k[r], k[r]=t;
2322 kernel->x = kernel->width - kernel->x - 1;
2323 angle = fmod(angle+180.0, 360.0);
2327 static void ExpandMirrorKernelInfo(KernelInfo *kernel)
2335 clone = CloneKernelInfo(last);
2336 RotateKernelInfo(clone, 180); /* flip */
2337 LastKernelInfo(last)->next = clone;
2340 clone = CloneKernelInfo(last);
2341 RotateKernelInfo(clone, 90); /* transpose */
2342 LastKernelInfo(last)->next = clone;
2345 clone = CloneKernelInfo(last);
2346 RotateKernelInfo(clone, 180); /* flop */
2347 LastKernelInfo(last)->next = clone;
2353 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2357 + E x p a n d R o t a t e K e r n e l I n f o %
2361 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2363 % ExpandRotateKernelInfo() takes a kernel list, and expands it by rotating
2364 % incrementally by the angle given, until the kernel repeats.
2366 % WARNING: 45 degree rotations only works for 3x3 kernels.
2367 % While 90 degree roatations only works for linear and square kernels
2369 % The format of the ExpandRotateKernelInfo method is:
2371 % void ExpandRotateKernelInfo(KernelInfo *kernel, double angle)
2373 % A description of each parameter follows:
2375 % o kernel: the Morphology/Convolution kernel
2377 % o angle: angle to rotate in degrees
2379 % This function is only internel to this module, as it is not finalized,
2380 % especially with regard to non-orthogonal angles, and rotation of larger
2384 /* Internal Routine - Return true if two kernels are the same */
2385 static MagickBooleanType SameKernelInfo(const KernelInfo *kernel1,
2386 const KernelInfo *kernel2)
2391 /* check size and origin location */
2392 if ( kernel1->width != kernel2->width
2393 || kernel1->height != kernel2->height
2394 || kernel1->x != kernel2->x
2395 || kernel1->y != kernel2->y )
2398 /* check actual kernel values */
2399 for (i=0; i < (kernel1->width*kernel1->height); i++) {
2400 /* Test for Nan equivalence */
2401 if ( IfNaN(kernel1->values[i]) && !IfNaN(kernel2->values[i]) )
2403 if ( IfNaN(kernel2->values[i]) && !IfNaN(kernel1->values[i]) )
2405 /* Test actual values are equivalent */
2406 if ( fabs(kernel1->values[i] - kernel2->values[i]) >= MagickEpsilon )
2413 static void ExpandRotateKernelInfo(KernelInfo *kernel, const double angle)
2420 DisableMSCWarning(4127)
2423 clone = CloneKernelInfo(last);
2424 RotateKernelInfo(clone, angle);
2425 if ( SameKernelInfo(kernel, clone) != MagickFalse )
2427 LastKernelInfo(last)->next = clone;
2430 clone = DestroyKernelInfo(clone); /* kernel has repeated - junk the clone */
2435 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2439 + C a l c M e t a K e r n a l I n f o %
2443 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2445 % CalcKernelMetaData() recalculate the KernelInfo meta-data of this kernel only,
2446 % using the kernel values. This should only ne used if it is not possible to
2447 % calculate that meta-data in some easier way.
2449 % It is important that the meta-data is correct before ScaleKernelInfo() is
2450 % used to perform kernel normalization.
2452 % The format of the CalcKernelMetaData method is:
2454 % void CalcKernelMetaData(KernelInfo *kernel, const double scale )
2456 % A description of each parameter follows:
2458 % o kernel: the Morphology/Convolution kernel to modify
2460 % WARNING: Minimum and Maximum values are assumed to include zero, even if
2461 % zero is not part of the kernel (as in Gaussian Derived kernels). This
2462 % however is not true for flat-shaped morphological kernels.
2464 % WARNING: Only the specific kernel pointed to is modified, not a list of
2467 % This is an internal function and not expected to be useful outside this
2468 % module. This could change however.
2470 static void CalcKernelMetaData(KernelInfo *kernel)
2475 kernel->minimum = kernel->maximum = 0.0;
2476 kernel->negative_range = kernel->positive_range = 0.0;
2477 for (i=0; i < (kernel->width*kernel->height); i++)
2479 if ( fabs(kernel->values[i]) < MagickEpsilon )
2480 kernel->values[i] = 0.0;
2481 ( kernel->values[i] < 0)
2482 ? ( kernel->negative_range += kernel->values[i] )
2483 : ( kernel->positive_range += kernel->values[i] );
2484 Minimize(kernel->minimum, kernel->values[i]);
2485 Maximize(kernel->maximum, kernel->values[i]);
2492 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2496 % M o r p h o l o g y A p p l y %
2500 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2502 % MorphologyApply() applies a morphological method, multiple times using
2503 % a list of multiple kernels. This is the method that should be called by
2504 % other 'operators' that internally use morphology operations as part of
2507 % It is basically equivalent to as MorphologyImage() (see below) but without
2508 % any user controls. This allows internel programs to use this method to
2509 % perform a specific task without possible interference by any API user
2510 % supplied settings.
2512 % It is MorphologyImage() task to extract any such user controls, and
2513 % pass them to this function for processing.
2515 % More specifically all given kernels should already be scaled, normalised,
2516 % and blended appropriatally before being parred to this routine. The
2517 % appropriate bias, and compose (typically 'UndefinedComposeOp') given.
2519 % The format of the MorphologyApply method is:
2521 % Image *MorphologyApply(const Image *image,MorphologyMethod method,
2522 % const ssize_t iterations,const KernelInfo *kernel,
2523 % const CompositeMethod compose,const double bias,
2524 % ExceptionInfo *exception)
2526 % A description of each parameter follows:
2528 % o image: the source image
2530 % o method: the morphology method to be applied.
2532 % o iterations: apply the operation this many times (or no change).
2533 % A value of -1 means loop until no change found.
2534 % How this is applied may depend on the morphology method.
2535 % Typically this is a value of 1.
2537 % o channel: the channel type.
2539 % o kernel: An array of double representing the morphology kernel.
2541 % o compose: How to handle or merge multi-kernel results.
2542 % If 'UndefinedCompositeOp' use default for the Morphology method.
2543 % If 'NoCompositeOp' force image to be re-iterated by each kernel.
2544 % Otherwise merge the results using the compose method given.
2546 % o bias: Convolution Output Bias.
2548 % o exception: return any errors or warnings in this structure.
2551 static ssize_t MorphologyPrimitive(const Image *image,Image *morphology_image,
2552 const MorphologyMethod method,const KernelInfo *kernel,const double bias,
2553 ExceptionInfo *exception)
2555 #define MorphologyTag "Morphology/Image"
2581 assert(image != (Image *) NULL);
2582 assert(image->signature == MagickSignature);
2583 assert(morphology_image != (Image *) NULL);
2584 assert(morphology_image->signature == MagickSignature);
2585 assert(kernel != (KernelInfo *) NULL);
2586 assert(kernel->signature == MagickSignature);
2587 assert(exception != (ExceptionInfo *) NULL);
2588 assert(exception->signature == MagickSignature);
2591 image_view=AcquireVirtualCacheView(image,exception);
2592 morphology_view=AcquireAuthenticCacheView(morphology_image,exception);
2593 width=image->columns+kernel->width-1;
2598 case ConvolveMorphology:
2599 case DilateMorphology:
2600 case DilateIntensityMorphology:
2601 case IterativeDistanceMorphology:
2604 Kernel needs to used with reflection about origin.
2606 offset.x=(ssize_t) kernel->width-kernel->x-1;
2607 offset.y=(ssize_t) kernel->height-kernel->y-1;
2610 case ErodeMorphology:
2611 case ErodeIntensityMorphology:
2612 case HitAndMissMorphology:
2613 case ThinningMorphology:
2614 case ThickenMorphology:
2622 assert("Not a Primitive Morphology Method" != (char *) NULL);
2627 changes=(size_t *) AcquireQuantumMemory(GetOpenMPMaximumThreads(),
2629 if (changes == (size_t *) NULL)
2630 ThrowFatalException(ResourceLimitFatalError,"MemoryAllocationFailed");
2631 for (i=0; i < (ssize_t) GetOpenMPMaximumThreads(); i++)
2633 if ((method == ConvolveMorphology) && (kernel->width == 1))
2636 id = GetOpenMPThreadId();
2642 Special handling (for speed) of vertical (blur) kernels. This performs
2643 its handling in columns rather than in rows. This is only done
2644 for convolve as it is the only method that generates very large 1-D
2645 vertical kernels (such as a 'BlurKernel')
2647 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2648 #pragma omp parallel for schedule(static,4) shared(progress,status) \
2649 magick_threads(image,morphology_image,image->columns,1)
2651 for (x=0; x < (ssize_t) image->columns; x++)
2653 register const Quantum
2665 if (status == MagickFalse)
2667 p=GetCacheViewVirtualPixels(image_view,x,-offset.y,1,image->rows+
2668 kernel->height-1,exception);
2669 q=GetCacheViewAuthenticPixels(morphology_view,x,0,1,
2670 morphology_image->rows,exception);
2671 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
2676 center=(ssize_t) GetPixelChannels(image)*offset.y;
2677 for (y=0; y < (ssize_t) image->rows; y++)
2682 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
2696 register const MagickRealType
2699 register const Quantum
2711 channel=GetPixelChannelChannel(image,i);
2712 traits=GetPixelChannelTraits(image,channel);
2713 morphology_traits=GetPixelChannelTraits(morphology_image,channel);
2714 if ((traits == UndefinedPixelTrait) ||
2715 (morphology_traits == UndefinedPixelTrait))
2717 if (((morphology_traits & CopyPixelTrait) != 0) ||
2718 (GetPixelReadMask(image,p+center) == 0))
2720 SetPixelChannel(morphology_image,channel,p[center+i],q);
2723 k=(&kernel->values[kernel->width*kernel->height-1]);
2727 if ((morphology_traits & BlendPixelTrait) == 0)
2732 for (v=0; v < (ssize_t) kernel->height; v++)
2734 for (u=0; u < (ssize_t) kernel->width; u++)
2736 if (IfNaN(*k) == MagickFalse)
2738 pixel+=(*k)*pixels[i];
2742 pixels+=GetPixelChannels(image);
2745 if (fabs(pixel-p[center+i]) > MagickEpsilon)
2747 gamma=(double) kernel->height*kernel->width/count;
2748 SetPixelChannel(morphology_image,channel,ClampToQuantum(gamma*
2756 for (v=0; v < (ssize_t) kernel->height; v++)
2758 for (u=0; u < (ssize_t) kernel->width; u++)
2760 if (IfNaN(*k) == MagickFalse)
2762 alpha=(double) (QuantumScale*GetPixelAlpha(image,pixels));
2763 pixel+=(*k)*alpha*pixels[i];
2768 pixels+=GetPixelChannels(image);
2771 if (fabs(pixel-p[center+i]) > MagickEpsilon)
2773 gamma=PerceptibleReciprocal(gamma);
2774 gamma*=(double) kernel->height*kernel->width/count;
2775 SetPixelChannel(morphology_image,channel,ClampToQuantum(gamma*
2778 p+=GetPixelChannels(image);
2779 q+=GetPixelChannels(morphology_image);
2781 if (SyncCacheViewAuthenticPixels(morphology_view,exception) == MagickFalse)
2783 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2788 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2789 #pragma omp critical (MagickCore_MorphologyPrimitive)
2791 proceed=SetImageProgress(image,MorphologyTag,progress++,
2793 if (proceed == MagickFalse)
2797 morphology_image->type=image->type;
2798 morphology_view=DestroyCacheView(morphology_view);
2799 image_view=DestroyCacheView(image_view);
2800 for (i=0; i < (ssize_t) GetOpenMPMaximumThreads(); i++)
2801 changed+=changes[i];
2802 changes=(size_t *) RelinquishMagickMemory(changes);
2803 return(status ? (ssize_t) changed : 0);
2806 Normal handling of horizontal or rectangular kernels (row by row).
2808 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2809 #pragma omp parallel for schedule(static,4) shared(progress,status) \
2810 magick_threads(image,morphology_image,image->rows,1)
2812 for (y=0; y < (ssize_t) image->rows; y++)
2815 id = GetOpenMPThreadId();
2817 register const Quantum
2829 if (status == MagickFalse)
2831 p=GetCacheViewVirtualPixels(image_view,-offset.x,y-offset.y,width,
2832 kernel->height,exception);
2833 q=GetCacheViewAuthenticPixels(morphology_view,0,y,morphology_image->columns,
2835 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
2840 center=(ssize_t) (GetPixelChannels(image)*width*offset.y+
2841 GetPixelChannels(image)*offset.x);
2842 for (x=0; x < (ssize_t) image->columns; x++)
2847 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
2863 register const MagickRealType
2866 register const Quantum
2878 channel=GetPixelChannelChannel(image,i);
2879 traits=GetPixelChannelTraits(image,channel);
2880 morphology_traits=GetPixelChannelTraits(morphology_image,channel);
2881 if ((traits == UndefinedPixelTrait) ||
2882 (morphology_traits == UndefinedPixelTrait))
2884 if (((morphology_traits & CopyPixelTrait) != 0) ||
2885 (GetPixelReadMask(image,p+center) == 0))
2887 SetPixelChannel(morphology_image,channel,p[center+i],q);
2892 minimum=(double) QuantumRange;
2893 count=kernel->width*kernel->height;
2896 case ConvolveMorphology: pixel=bias; break;
2897 case HitAndMissMorphology: pixel=(double) QuantumRange; break;
2898 case ThinningMorphology: pixel=(double) QuantumRange; break;
2899 case ThickenMorphology: pixel=(double) QuantumRange; break;
2900 case ErodeMorphology: pixel=(double) QuantumRange; break;
2901 case DilateMorphology: pixel=0.0; break;
2902 case ErodeIntensityMorphology:
2903 case DilateIntensityMorphology:
2904 case IterativeDistanceMorphology:
2906 pixel=(double) p[center+i];
2909 default: pixel=0; break;
2914 case ConvolveMorphology:
2917 Weighted Average of pixels using reflected kernel
2919 For correct working of this operation for asymetrical
2920 kernels, the kernel needs to be applied in its reflected form.
2921 That is its values needs to be reversed.
2923 Correlation is actually the same as this but without reflecting
2924 the kernel, and thus 'lower-level' that Convolution. However
2925 as Convolution is the more common method used, and it does not
2926 really cost us much in terms of processing to use a reflected
2927 kernel, so it is Convolution that is implemented.
2929 Correlation will have its kernel reflected before calling
2930 this function to do a Convolve.
2932 For more details of Correlation vs Convolution see
2933 http://www.cs.umd.edu/~djacobs/CMSC426/Convolution.pdf
2935 k=(&kernel->values[kernel->width*kernel->height-1]);
2937 if ((morphology_traits & BlendPixelTrait) == 0)
2942 for (v=0; v < (ssize_t) kernel->height; v++)
2944 for (u=0; u < (ssize_t) kernel->width; u++)
2946 if (IfNaN(*k) == MagickFalse)
2948 pixel+=(*k)*pixels[i];
2952 pixels+=GetPixelChannels(image);
2954 pixels+=(image->columns-1)*GetPixelChannels(image);
2961 for (v=0; v < (ssize_t) kernel->height; v++)
2963 for (u=0; u < (ssize_t) kernel->width; u++)
2965 if (IfNaN(*k) == MagickFalse)
2967 alpha=(double) (QuantumScale*GetPixelAlpha(image,pixels));
2968 pixel+=(*k)*alpha*pixels[i];
2973 pixels+=GetPixelChannels(image);
2975 pixels+=(image->columns-1)*GetPixelChannels(image);
2979 case ErodeMorphology:
2982 Minimum value within kernel neighbourhood.
2984 The kernel is not reflected for this operation. In normal
2985 Greyscale Morphology, the kernel value should be added
2986 to the real value, this is currently not done, due to the
2987 nature of the boolean kernels being used.
2990 for (v=0; v < (ssize_t) kernel->height; v++)
2992 for (u=0; u < (ssize_t) kernel->width; u++)
2994 if ((IfNaN(*k) == MagickFalse) && (*k >= 0.5))
2996 if ((double) pixels[i] < pixel)
2997 pixel=(double) pixels[i];
3000 pixels+=GetPixelChannels(image);
3002 pixels+=(image->columns-1)*GetPixelChannels(image);
3006 case DilateMorphology:
3009 Maximum value within kernel neighbourhood.
3011 For correct working of this operation for asymetrical kernels,
3012 the kernel needs to be applied in its reflected form. That is
3013 its values needs to be reversed.
3015 In normal Greyscale Morphology, the kernel value should be
3016 added to the real value, this is currently not done, due to the
3017 nature of the boolean kernels being used.
3020 k=(&kernel->values[kernel->width*kernel->height-1]);
3021 for (v=0; v < (ssize_t) kernel->height; v++)
3023 for (u=0; u < (ssize_t) kernel->width; u++)
3025 if ((IfNaN(*k) == MagickFalse) && (*k > 0.5))
3027 if ((double) pixels[i] > pixel)
3028 pixel=(double) pixels[i];
3032 pixels+=GetPixelChannels(image);
3034 pixels+=(image->columns-1)*GetPixelChannels(image);
3038 case HitAndMissMorphology:
3039 case ThinningMorphology:
3040 case ThickenMorphology:
3043 Minimum of foreground pixel minus maxumum of background pixels.
3045 The kernel is not reflected for this operation, and consists
3046 of both foreground and background pixel neighbourhoods, 0.0 for
3047 background, and 1.0 for foreground with either Nan or 0.5 values
3050 This never produces a meaningless negative result. Such results
3051 cause Thinning/Thicken to not work correctly when used against a
3056 for (v=0; v < (ssize_t) kernel->height; v++)
3058 for (u=0; u < (ssize_t) kernel->width; u++)
3060 if (IfNaN(*k) == MagickFalse)
3064 if ((double) pixels[i] < pixel)
3065 pixel=(double) pixels[i];
3070 if ((double) pixels[i] > maximum)
3071 maximum=(double) pixels[i];
3076 pixels+=GetPixelChannels(image);
3078 pixels+=(image->columns-1)*GetPixelChannels(image);
3083 if (method == ThinningMorphology)
3084 pixel=(double) p[center+i]-pixel;
3086 if (method == ThickenMorphology)
3087 pixel+=(double) p[center+i]+pixel;
3090 case ErodeIntensityMorphology:
3093 Select pixel with minimum intensity within kernel neighbourhood.
3095 The kernel is not reflected for this operation.
3099 for (v=0; v < (ssize_t) kernel->height; v++)
3101 for (u=0; u < (ssize_t) kernel->width; u++)
3103 if ((IfNaN(*k) == MagickFalse) && (*k >= 0.5))
3105 if (GetPixelIntensity(image,pixels) < minimum)
3107 pixel=(double) pixels[i];
3108 minimum=GetPixelIntensity(image,pixels);
3113 pixels+=GetPixelChannels(image);
3115 pixels+=(image->columns-1)*GetPixelChannels(image);
3119 case DilateIntensityMorphology:
3122 Select pixel with maximum intensity within kernel neighbourhood.
3124 The kernel is not reflected for this operation.
3127 k=(&kernel->values[kernel->width*kernel->height-1]);
3128 for (v=0; v < (ssize_t) kernel->height; v++)
3130 for (u=0; u < (ssize_t) kernel->width; u++)
3132 if ((IfNaN(*k) == MagickFalse) && (*k >= 0.5))
3134 if (GetPixelIntensity(image,pixels) > maximum)
3136 pixel=(double) pixels[i];
3137 maximum=GetPixelIntensity(image,pixels);
3142 pixels+=GetPixelChannels(image);
3144 pixels+=(image->columns-1)*GetPixelChannels(image);
3148 case IterativeDistanceMorphology:
3151 Compute th iterative distance from black edge of a white image
3152 shape. Essentually white values are decreased to the smallest
3153 'distance from edge' it can find.
3155 It works by adding kernel values to the neighbourhood, and and
3156 select the minimum value found. The kernel is rotated before
3157 use, so kernel distances match resulting distances, when a user
3158 provided asymmetric kernel is applied.
3160 This code is nearly identical to True GrayScale Morphology but
3163 GreyDilate Kernel values added, maximum value found Kernel is
3166 GrayErode: Kernel values subtracted and minimum value found No
3167 kernel rotation used.
3169 Note the the Iterative Distance method is essentially a
3170 GrayErode, but with negative kernel values, and kernel rotation
3174 k=(&kernel->values[kernel->width*kernel->height-1]);
3175 for (v=0; v < (ssize_t) kernel->height; v++)
3177 for (u=0; u < (ssize_t) kernel->width; u++)
3179 if (IfNaN(*k) == MagickFalse)
3181 if ((pixels[i]+(*k)) < pixel)
3182 pixel=(double) pixels[i]+(*k);
3186 pixels+=GetPixelChannels(image);
3188 pixels+=(image->columns-1)*GetPixelChannels(image);
3192 case UndefinedMorphology:
3196 if (fabs(pixel-p[center+i]) > MagickEpsilon)
3198 gamma=PerceptibleReciprocal(gamma);
3199 gamma*=(double) kernel->height*kernel->width/count;
3200 SetPixelChannel(morphology_image,channel,ClampToQuantum(gamma*pixel),q);
3202 p+=GetPixelChannels(image);
3203 q+=GetPixelChannels(morphology_image);
3205 if ( SyncCacheViewAuthenticPixels(morphology_view,exception) == MagickFalse)
3207 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3212 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3213 #pragma omp critical (MagickCore_MorphologyPrimitive)
3215 proceed=SetImageProgress(image,MorphologyTag,progress++,image->rows);
3216 if (proceed == MagickFalse)
3220 morphology_view=DestroyCacheView(morphology_view);
3221 image_view=DestroyCacheView(image_view);
3222 for (i=0; i < (ssize_t) GetOpenMPMaximumThreads(); i++)
3223 changed+=changes[i];
3224 changes=(size_t *) RelinquishMagickMemory(changes);
3225 return(status ? (ssize_t) changed : -1);
3229 This is almost identical to the MorphologyPrimative() function above, but
3230 applies the primitive directly to the actual image using two passes, once in
3231 each direction, with the results of the previous (and current) row being
3234 That is after each row is 'Sync'ed' into the image, the next row makes use of
3235 those values as part of the calculation of the next row. It repeats, but
3236 going in the oppisite (bottom-up) direction.
3238 Because of this 're-use of results' this function can not make use of multi-
3239 threaded, parellel processing.
3241 static ssize_t MorphologyPrimitiveDirect(Image *image,
3242 const MorphologyMethod method,const KernelInfo *kernel,
3243 ExceptionInfo *exception)
3265 assert(image != (Image *) NULL);
3266 assert(image->signature == MagickSignature);
3267 assert(kernel != (KernelInfo *) NULL);
3268 assert(kernel->signature == MagickSignature);
3269 assert(exception != (ExceptionInfo *) NULL);
3270 assert(exception->signature == MagickSignature);
3276 case DistanceMorphology:
3277 case VoronoiMorphology:
3280 Kernel reflected about origin.
3282 offset.x=(ssize_t) kernel->width-kernel->x-1;
3283 offset.y=(ssize_t) kernel->height-kernel->y-1;
3294 Two views into same image, do not thread.
3296 image_view=AcquireVirtualCacheView(image,exception);
3297 morphology_view=AcquireAuthenticCacheView(image,exception);
3298 width=image->columns+kernel->width-1;
3299 for (y=0; y < (ssize_t) image->rows; y++)
3301 register const Quantum
3314 Read virtual pixels, and authentic pixels, from the same image! We read
3315 using virtual to get virtual pixel handling, but write back into the same
3318 Only top half of kernel is processed as we do a single pass downward
3319 through the image iterating the distance function as we go.
3321 if (status == MagickFalse)
3323 p=GetCacheViewVirtualPixels(image_view,-offset.x,y-offset.y,width,(size_t)
3324 offset.y+1,exception);
3325 q=GetCacheViewAuthenticPixels(morphology_view,0,y,image->columns,1,
3327 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
3332 center=(ssize_t) (GetPixelChannels(image)*width*offset.y+
3333 GetPixelChannels(image)*offset.x);
3334 for (x=0; x < (ssize_t) image->columns; x++)
3339 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
3347 register const MagickRealType
3350 register const Quantum
3359 traits=GetPixelChannelTraits(image,(PixelChannel) i);
3360 if (traits == UndefinedPixelTrait)
3362 if (((traits & CopyPixelTrait) != 0) ||
3363 (GetPixelReadMask(image,p+center) == 0))
3366 pixel=(double) QuantumRange;
3369 case DistanceMorphology:
3371 k=(&kernel->values[kernel->width*kernel->height-1]);
3372 for (v=0; v <= offset.y; v++)
3374 for (u=0; u < (ssize_t) kernel->width; u++)
3376 if (IfNaN(*k) == MagickFalse)
3378 if ((pixels[i]+(*k)) < pixel)
3379 pixel=(double) pixels[i]+(*k);
3382 pixels+=GetPixelChannels(image);
3384 pixels+=(image->columns-1)*GetPixelChannels(image);
3386 k=(&kernel->values[kernel->width*(kernel->y+1)-1]);
3387 pixels=q-offset.x*GetPixelChannels(image);
3388 for (u=0; u < offset.x; u++)
3390 if ((IfNaN(*k) == MagickFalse) && ((x+u-offset.x) >= 0))
3392 if ((pixels[i]+(*k)) < pixel)
3393 pixel=(double) pixels[i]+(*k);
3396 pixels+=GetPixelChannels(image);
3400 case VoronoiMorphology:
3402 k=(&kernel->values[kernel->width*kernel->height-1]);
3403 for (v=0; v < offset.y; v++)
3405 for (u=0; u < (ssize_t) kernel->width; u++)
3407 if (IfNaN(*k) == MagickFalse)
3409 if ((pixels[i]+(*k)) < pixel)
3410 pixel=(double) pixels[i]+(*k);
3413 pixels+=GetPixelChannels(image);
3415 pixels+=(image->columns-1)*GetPixelChannels(image);
3417 k=(&kernel->values[kernel->width*(kernel->y+1)-1]);
3418 pixels=q-offset.x*GetPixelChannels(image);
3419 for (u=0; u < offset.x; u++)
3421 if ((IfNaN(*k) == MagickFalse) && ((x+u-offset.x) >= 0))
3423 if ((pixels[i]+(*k)) < pixel)
3424 pixel=(double) pixels[i]+(*k);
3427 pixels+=GetPixelChannels(image);
3434 if (fabs(pixel-q[i]) > MagickEpsilon)
3436 q[i]=ClampToQuantum(pixel);
3438 p+=GetPixelChannels(image);
3439 q+=GetPixelChannels(image);
3441 if (SyncCacheViewAuthenticPixels(morphology_view,exception) == MagickFalse)
3443 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3448 proceed=SetImageProgress(image,MorphologyTag,progress++,2*image->rows);
3449 if (proceed == MagickFalse)
3453 morphology_view=DestroyCacheView(morphology_view);
3454 image_view=DestroyCacheView(image_view);
3456 Do the reverse pass through the image.
3458 image_view=AcquireVirtualCacheView(image,exception);
3459 morphology_view=AcquireAuthenticCacheView(image,exception);
3460 for (y=(ssize_t) image->rows-1; y >= 0; y--)
3462 register const Quantum
3475 Read virtual pixels, and authentic pixels, from the same image. We
3476 read using virtual to get virtual pixel handling, but write back
3477 into the same image.
3479 Only the bottom half of the kernel is processed as we up the image.
3481 if (status == MagickFalse)
3483 p=GetCacheViewVirtualPixels(image_view,-offset.x,y,width,(size_t)
3484 kernel->y+1,exception);
3485 q=GetCacheViewAuthenticPixels(morphology_view,0,y,image->columns,1,
3487 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
3492 p+=(image->columns-1)*GetPixelChannels(image);
3493 q+=(image->columns-1)*GetPixelChannels(image);
3494 center=(ssize_t) (offset.x*GetPixelChannels(image));
3495 for (x=(ssize_t) image->columns-1; x >= 0; x--)
3500 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
3508 register const MagickRealType
3511 register const Quantum
3520 traits=GetPixelChannelTraits(image,(PixelChannel) i);
3521 if (traits == UndefinedPixelTrait)
3523 if (((traits & CopyPixelTrait) != 0) ||
3524 (GetPixelReadMask(image,p+center) == 0))
3527 pixel=(double) QuantumRange;
3530 case DistanceMorphology:
3532 k=(&kernel->values[kernel->width*(kernel->y+1)-1]);
3533 for (v=offset.y; v < (ssize_t) kernel->height; v++)
3535 for (u=0; u < (ssize_t) kernel->width; u++)
3537 if (IfNaN(*k) == MagickFalse)
3539 if ((pixels[i]+(*k)) < pixel)
3540 pixel=(double) pixels[i]+(*k);
3543 pixels+=GetPixelChannels(image);
3545 pixels+=(image->columns-1)*GetPixelChannels(image);
3547 k=(&kernel->values[kernel->width*kernel->y+kernel->x-1]);
3548 pixels=q-offset.x*GetPixelChannels(image);
3549 for (u=offset.x+1; u < (ssize_t) kernel->width; u++)
3551 if ((IfNaN(*k) == MagickFalse) &&
3552 ((x+u-offset.x) < (ssize_t) image->columns))
3554 if ((pixels[i]+(*k)) < pixel)
3555 pixel=(double) pixels[i]+(*k);
3558 pixels+=GetPixelChannels(image);
3562 case VoronoiMorphology:
3564 k=(&kernel->values[kernel->width*(kernel->y+1)-1]);
3565 for (v=offset.y; v < (ssize_t) kernel->height; v++)
3567 for (u=0; u < (ssize_t) kernel->width; u++)
3569 if (IfNaN(*k) == MagickFalse)
3571 if ((pixels[i]+(*k)) < pixel)
3572 pixel=(double) pixels[i]+(*k);
3575 pixels+=GetPixelChannels(image);
3577 pixels+=(image->columns-1)*GetPixelChannels(image);
3579 k=(&kernel->values[kernel->width*(kernel->y+1)-1]);
3580 pixels=q-offset.x*GetPixelChannels(image);
3581 for (u=offset.x+1; u < (ssize_t) kernel->width; u++)
3583 if ((IfNaN(*k) == MagickFalse) &&
3584 ((x+u-offset.x) < (ssize_t) image->columns))
3586 if ((pixels[i]+(*k)) < pixel)
3587 pixel=(double) pixels[i]+(*k);
3590 pixels+=GetPixelChannels(image);
3597 if (fabs(pixel-q[i]) > MagickEpsilon)
3599 q[i]=ClampToQuantum(pixel);
3601 p-=GetPixelChannels(image);
3602 q-=GetPixelChannels(image);
3604 if (SyncCacheViewAuthenticPixels(morphology_view,exception) == MagickFalse)
3606 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3611 proceed=SetImageProgress(image,MorphologyTag,progress++,2*image->rows);
3612 if (proceed == MagickFalse)
3616 morphology_view=DestroyCacheView(morphology_view);
3617 image_view=DestroyCacheView(image_view);
3618 return(status ? (ssize_t) changed : -1);
3622 Apply a Morphology by calling one of the above low level primitive
3623 application functions. This function handles any iteration loops,
3624 composition or re-iteration of results, and compound morphology methods that
3625 is based on multiple low-level (staged) morphology methods.
3627 Basically this provides the complex glue between the requested morphology
3628 method and raw low-level implementation (above).
3630 MagickPrivate Image *MorphologyApply(const Image *image,
3631 const MorphologyMethod method, const ssize_t iterations,
3632 const KernelInfo *kernel, const CompositeOperator compose,const double bias,
3633 ExceptionInfo *exception)
3639 *curr_image, /* Image we are working with or iterating */
3640 *work_image, /* secondary image for primitive iteration */
3641 *save_image, /* saved image - for 'edge' method only */
3642 *rslt_image; /* resultant image - after multi-kernel handling */
3645 *reflected_kernel, /* A reflected copy of the kernel (if needed) */
3646 *norm_kernel, /* the current normal un-reflected kernel */
3647 *rflt_kernel, /* the current reflected kernel (if needed) */
3648 *this_kernel; /* the kernel being applied */
3651 primitive; /* the current morphology primitive being applied */
3654 rslt_compose; /* multi-kernel compose method for results to use */
3657 special, /* do we use a direct modify function? */
3658 verbose; /* verbose output of results */
3661 method_loop, /* Loop 1: number of compound method iterations (norm 1) */
3662 method_limit, /* maximum number of compound method iterations */
3663 kernel_number, /* Loop 2: the kernel number being applied */
3664 stage_loop, /* Loop 3: primitive loop for compound morphology */
3665 stage_limit, /* how many primitives are in this compound */
3666 kernel_loop, /* Loop 4: iterate the kernel over image */
3667 kernel_limit, /* number of times to iterate kernel */
3668 count, /* total count of primitive steps applied */
3669 kernel_changed, /* total count of changed using iterated kernel */
3670 method_changed; /* total count of changed over method iteration */
3673 changed; /* number pixels changed by last primitive operation */
3678 assert(image != (Image *) NULL);
3679 assert(image->signature == MagickSignature);
3680 assert(kernel != (KernelInfo *) NULL);
3681 assert(kernel->signature == MagickSignature);
3682 assert(exception != (ExceptionInfo *) NULL);
3683 assert(exception->signature == MagickSignature);
3685 count = 0; /* number of low-level morphology primitives performed */
3686 if ( iterations == 0 )
3687 return((Image *)NULL); /* null operation - nothing to do! */
3689 kernel_limit = (size_t) iterations;
3690 if ( iterations < 0 ) /* negative interations = infinite (well alomst) */
3691 kernel_limit = image->columns>image->rows ? image->columns : image->rows;
3693 verbose = IsStringTrue(GetImageArtifact(image,"debug"));
3695 /* initialise for cleanup */
3696 curr_image = (Image *) image;
3697 curr_compose = image->compose;
3698 (void) curr_compose;
3699 work_image = save_image = rslt_image = (Image *) NULL;
3700 reflected_kernel = (KernelInfo *) NULL;
3702 /* Initialize specific methods
3703 * + which loop should use the given iteratations
3704 * + how many primitives make up the compound morphology
3705 * + multi-kernel compose method to use (by default)
3707 method_limit = 1; /* just do method once, unless otherwise set */
3708 stage_limit = 1; /* assume method is not a compound */
3709 special = MagickFalse; /* assume it is NOT a direct modify primitive */
3710 rslt_compose = compose; /* and we are composing multi-kernels as given */
3712 case SmoothMorphology: /* 4 primitive compound morphology */
3715 case OpenMorphology: /* 2 primitive compound morphology */
3716 case OpenIntensityMorphology:
3717 case TopHatMorphology:
3718 case CloseMorphology:
3719 case CloseIntensityMorphology:
3720 case BottomHatMorphology:
3721 case EdgeMorphology:
3724 case HitAndMissMorphology:
3725 rslt_compose = LightenCompositeOp; /* Union of multi-kernel results */
3727 case ThinningMorphology:
3728 case ThickenMorphology:
3729 method_limit = kernel_limit; /* iterate the whole method */
3730 kernel_limit = 1; /* do not do kernel iteration */
3732 case DistanceMorphology:
3733 case VoronoiMorphology:
3734 special = MagickTrue; /* use special direct primative */
3740 /* Apply special methods with special requirments
3741 ** For example, single run only, or post-processing requirements
3743 if ( special != MagickFalse )
3745 rslt_image=CloneImage(image,0,0,MagickTrue,exception);
3746 if (rslt_image == (Image *) NULL)
3748 if (SetImageStorageClass(rslt_image,DirectClass,exception) == MagickFalse)
3751 changed=MorphologyPrimitiveDirect(rslt_image,method,kernel,exception);
3753 if ( IfMagickTrue(verbose) )
3754 (void) (void) FormatLocaleFile(stderr,
3755 "%s:%.20g.%.20g #%.20g => Changed %.20g\n",
3756 CommandOptionToMnemonic(MagickMorphologyOptions, method),
3757 1.0,0.0,1.0, (double) changed);
3762 if ( method == VoronoiMorphology ) {
3763 /* Preserve the alpha channel of input image - but turned it off */
3764 (void) SetImageAlphaChannel(rslt_image, DeactivateAlphaChannel,
3766 (void) CompositeImage(rslt_image,image,CopyAlphaCompositeOp,
3767 MagickTrue,0,0,exception);
3768 (void) SetImageAlphaChannel(rslt_image, DeactivateAlphaChannel,
3774 /* Handle user (caller) specified multi-kernel composition method */
3775 if ( compose != UndefinedCompositeOp )
3776 rslt_compose = compose; /* override default composition for method */
3777 if ( rslt_compose == UndefinedCompositeOp )
3778 rslt_compose = NoCompositeOp; /* still not defined! Then re-iterate */
3780 /* Some methods require a reflected kernel to use with primitives.
3781 * Create the reflected kernel for those methods. */
3783 case CorrelateMorphology:
3784 case CloseMorphology:
3785 case CloseIntensityMorphology:
3786 case BottomHatMorphology:
3787 case SmoothMorphology:
3788 reflected_kernel = CloneKernelInfo(kernel);
3789 if (reflected_kernel == (KernelInfo *) NULL)
3791 RotateKernelInfo(reflected_kernel,180);
3797 /* Loops around more primitive morpholgy methods
3798 ** erose, dilate, open, close, smooth, edge, etc...
3800 /* Loop 1: iterate the compound method */
3803 while ( method_loop < method_limit && method_changed > 0 ) {
3807 /* Loop 2: iterate over each kernel in a multi-kernel list */
3808 norm_kernel = (KernelInfo *) kernel;
3809 this_kernel = (KernelInfo *) kernel;
3810 rflt_kernel = reflected_kernel;
3813 while ( norm_kernel != NULL ) {
3815 /* Loop 3: Compound Morphology Staging - Select Primative to apply */
3816 stage_loop = 0; /* the compound morphology stage number */
3817 while ( stage_loop < stage_limit ) {
3818 stage_loop++; /* The stage of the compound morphology */
3820 /* Select primitive morphology for this stage of compound method */
3821 this_kernel = norm_kernel; /* default use unreflected kernel */
3822 primitive = method; /* Assume method is a primitive */
3824 case ErodeMorphology: /* just erode */
3825 case EdgeInMorphology: /* erode and image difference */
3826 primitive = ErodeMorphology;
3828 case DilateMorphology: /* just dilate */
3829 case EdgeOutMorphology: /* dilate and image difference */
3830 primitive = DilateMorphology;
3832 case OpenMorphology: /* erode then dialate */
3833 case TopHatMorphology: /* open and image difference */
3834 primitive = ErodeMorphology;
3835 if ( stage_loop == 2 )
3836 primitive = DilateMorphology;
3838 case OpenIntensityMorphology:
3839 primitive = ErodeIntensityMorphology;
3840 if ( stage_loop == 2 )
3841 primitive = DilateIntensityMorphology;
3843 case CloseMorphology: /* dilate, then erode */
3844 case BottomHatMorphology: /* close and image difference */
3845 this_kernel = rflt_kernel; /* use the reflected kernel */
3846 primitive = DilateMorphology;
3847 if ( stage_loop == 2 )
3848 primitive = ErodeMorphology;
3850 case CloseIntensityMorphology:
3851 this_kernel = rflt_kernel; /* use the reflected kernel */
3852 primitive = DilateIntensityMorphology;
3853 if ( stage_loop == 2 )
3854 primitive = ErodeIntensityMorphology;
3856 case SmoothMorphology: /* open, close */
3857 switch ( stage_loop ) {
3858 case 1: /* start an open method, which starts with Erode */
3859 primitive = ErodeMorphology;
3861 case 2: /* now Dilate the Erode */
3862 primitive = DilateMorphology;
3864 case 3: /* Reflect kernel a close */
3865 this_kernel = rflt_kernel; /* use the reflected kernel */
3866 primitive = DilateMorphology;
3868 case 4: /* Finish the Close */
3869 this_kernel = rflt_kernel; /* use the reflected kernel */
3870 primitive = ErodeMorphology;
3874 case EdgeMorphology: /* dilate and erode difference */
3875 primitive = DilateMorphology;
3876 if ( stage_loop == 2 ) {
3877 save_image = curr_image; /* save the image difference */
3878 curr_image = (Image *) image;
3879 primitive = ErodeMorphology;
3882 case CorrelateMorphology:
3883 /* A Correlation is a Convolution with a reflected kernel.
3884 ** However a Convolution is a weighted sum using a reflected
3885 ** kernel. It may seem stange to convert a Correlation into a
3886 ** Convolution as the Correlation is the simplier method, but
3887 ** Convolution is much more commonly used, and it makes sense to
3888 ** implement it directly so as to avoid the need to duplicate the
3889 ** kernel when it is not required (which is typically the
3892 this_kernel = rflt_kernel; /* use the reflected kernel */
3893 primitive = ConvolveMorphology;
3898 assert( this_kernel != (KernelInfo *) NULL );
3900 /* Extra information for debugging compound operations */
3901 if ( IfMagickTrue(verbose) ) {
3902 if ( stage_limit > 1 )
3903 (void) FormatLocaleString(v_info,MaxTextExtent,"%s:%.20g.%.20g -> ",
3904 CommandOptionToMnemonic(MagickMorphologyOptions,method),(double)
3905 method_loop,(double) stage_loop);
3906 else if ( primitive != method )
3907 (void) FormatLocaleString(v_info, MaxTextExtent, "%s:%.20g -> ",
3908 CommandOptionToMnemonic(MagickMorphologyOptions, method),(double)
3914 /* Loop 4: Iterate the kernel with primitive */
3918 while ( kernel_loop < kernel_limit && changed > 0 ) {
3919 kernel_loop++; /* the iteration of this kernel */
3921 /* Create a clone as the destination image, if not yet defined */
3922 if ( work_image == (Image *) NULL )
3924 work_image=CloneImage(image,0,0,MagickTrue,exception);
3925 if (work_image == (Image *) NULL)
3927 if (SetImageStorageClass(work_image,DirectClass,exception) == MagickFalse)
3931 /* APPLY THE MORPHOLOGICAL PRIMITIVE (curr -> work) */
3933 changed = MorphologyPrimitive(curr_image, work_image, primitive,
3934 this_kernel, bias, exception);
3936 if ( IfMagickTrue(verbose) ) {
3937 if ( kernel_loop > 1 )
3938 (void) FormatLocaleFile(stderr, "\n"); /* add end-of-line from previous */
3939 (void) (void) FormatLocaleFile(stderr,
3940 "%s%s%s:%.20g.%.20g #%.20g => Changed %.20g",
3941 v_info,CommandOptionToMnemonic(MagickMorphologyOptions,
3942 primitive),(this_kernel == rflt_kernel ) ? "*" : "",
3943 (double) (method_loop+kernel_loop-1),(double) kernel_number,
3944 (double) count,(double) changed);
3948 kernel_changed += changed;
3949 method_changed += changed;
3951 /* prepare next loop */
3952 { Image *tmp = work_image; /* swap images for iteration */
3953 work_image = curr_image;
3956 if ( work_image == image )
3957 work_image = (Image *) NULL; /* replace input 'image' */
3959 } /* End Loop 4: Iterate the kernel with primitive */
3961 if ( IfMagickTrue(verbose) && kernel_changed != (size_t)changed )
3962 (void) FormatLocaleFile(stderr, " Total %.20g",(double) kernel_changed);
3963 if ( IfMagickTrue(verbose) && stage_loop < stage_limit )
3964 (void) FormatLocaleFile(stderr, "\n"); /* add end-of-line before looping */
3967 (void) FormatLocaleFile(stderr, "--E-- image=0x%lx\n", (unsigned long)image);
3968 (void) FormatLocaleFile(stderr, " curr =0x%lx\n", (unsigned long)curr_image);
3969 (void) FormatLocaleFile(stderr, " work =0x%lx\n", (unsigned long)work_image);
3970 (void) FormatLocaleFile(stderr, " save =0x%lx\n", (unsigned long)save_image);
3971 (void) FormatLocaleFile(stderr, " union=0x%lx\n", (unsigned long)rslt_image);
3974 } /* End Loop 3: Primative (staging) Loop for Coumpound Methods */
3976 /* Final Post-processing for some Compound Methods
3978 ** The removal of any 'Sync' channel flag in the Image Compositon
3979 ** below ensures the methematical compose method is applied in a
3980 ** purely mathematical way, and only to the selected channels.
3981 ** Turn off SVG composition 'alpha blending'.
3984 case EdgeOutMorphology:
3985 case EdgeInMorphology:
3986 case TopHatMorphology:
3987 case BottomHatMorphology:
3988 if ( IfMagickTrue(verbose) )
3989 (void) FormatLocaleFile(stderr,
3990 "\n%s: Difference with original image",CommandOptionToMnemonic(
3991 MagickMorphologyOptions, method) );
3992 (void) CompositeImage(curr_image,image,DifferenceCompositeOp,
3993 MagickTrue,0,0,exception);
3995 case EdgeMorphology:
3996 if ( IfMagickTrue(verbose) )
3997 (void) FormatLocaleFile(stderr,
3998 "\n%s: Difference of Dilate and Erode",CommandOptionToMnemonic(
3999 MagickMorphologyOptions, method) );
4000 (void) CompositeImage(curr_image,save_image,DifferenceCompositeOp,
4001 MagickTrue,0,0,exception);
4002 save_image = DestroyImage(save_image); /* finished with save image */
4008 /* multi-kernel handling: re-iterate, or compose results */
4009 if ( kernel->next == (KernelInfo *) NULL )
4010 rslt_image = curr_image; /* just return the resulting image */
4011 else if ( rslt_compose == NoCompositeOp )
4012 { if ( IfMagickTrue(verbose) ) {
4013 if ( this_kernel->next != (KernelInfo *) NULL )
4014 (void) FormatLocaleFile(stderr, " (re-iterate)");
4016 (void) FormatLocaleFile(stderr, " (done)");
4018 rslt_image = curr_image; /* return result, and re-iterate */
4020 else if ( rslt_image == (Image *) NULL)
4021 { if ( IfMagickTrue(verbose) )
4022 (void) FormatLocaleFile(stderr, " (save for compose)");
4023 rslt_image = curr_image;
4024 curr_image = (Image *) image; /* continue with original image */
4027 { /* Add the new 'current' result to the composition
4029 ** The removal of any 'Sync' channel flag in the Image Compositon
4030 ** below ensures the methematical compose method is applied in a
4031 ** purely mathematical way, and only to the selected channels.
4032 ** IE: Turn off SVG composition 'alpha blending'.
4034 if ( IfMagickTrue(verbose) )
4035 (void) FormatLocaleFile(stderr, " (compose \"%s\")",
4036 CommandOptionToMnemonic(MagickComposeOptions, rslt_compose) );
4037 (void) CompositeImage(rslt_image,curr_image,rslt_compose,MagickTrue,
4039 curr_image = DestroyImage(curr_image);
4040 curr_image = (Image *) image; /* continue with original image */
4042 if ( IfMagickTrue(verbose) )
4043 (void) FormatLocaleFile(stderr, "\n");
4045 /* loop to the next kernel in a multi-kernel list */
4046 norm_kernel = norm_kernel->next;
4047 if ( rflt_kernel != (KernelInfo *) NULL )
4048 rflt_kernel = rflt_kernel->next;
4050 } /* End Loop 2: Loop over each kernel */
4052 } /* End Loop 1: compound method interation */
4056 /* Yes goto's are bad, but it makes cleanup lot more efficient */
4058 if ( curr_image == rslt_image )
4059 curr_image = (Image *) NULL;
4060 if ( rslt_image != (Image *) NULL )
4061 rslt_image = DestroyImage(rslt_image);
4063 if ( curr_image == rslt_image || curr_image == image )
4064 curr_image = (Image *) NULL;
4065 if ( curr_image != (Image *) NULL )
4066 curr_image = DestroyImage(curr_image);
4067 if ( work_image != (Image *) NULL )
4068 work_image = DestroyImage(work_image);
4069 if ( save_image != (Image *) NULL )
4070 save_image = DestroyImage(save_image);
4071 if ( reflected_kernel != (KernelInfo *) NULL )
4072 reflected_kernel = DestroyKernelInfo(reflected_kernel);
4078 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4082 % M o r p h o l o g y I m a g e %
4086 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4088 % MorphologyImage() applies a user supplied kernel to the image according to
4089 % the given mophology method.
4091 % This function applies any and all user defined settings before calling
4092 % the above internal function MorphologyApply().
4094 % User defined settings include...
4095 % * Output Bias for Convolution and correlation ("-define convolve:bias=??")
4096 % * Kernel Scale/normalize settings ("-define convolve:scale=??")
4097 % This can also includes the addition of a scaled unity kernel.
4098 % * Show Kernel being applied ("-define showkernel=1")
4100 % Other operators that do not want user supplied options interfering,
4101 % especially "convolve:bias" and "showkernel" should use MorphologyApply()
4104 % The format of the MorphologyImage method is:
4106 % Image *MorphologyImage(const Image *image,MorphologyMethod method,
4107 % const ssize_t iterations,KernelInfo *kernel,ExceptionInfo *exception)
4109 % A description of each parameter follows:
4111 % o image: the image.
4113 % o method: the morphology method to be applied.
4115 % o iterations: apply the operation this many times (or no change).
4116 % A value of -1 means loop until no change found.
4117 % How this is applied may depend on the morphology method.
4118 % Typically this is a value of 1.
4120 % o kernel: An array of double representing the morphology kernel.
4121 % Warning: kernel may be normalized for the Convolve method.
4123 % o exception: return any errors or warnings in this structure.
4126 MagickExport Image *MorphologyImage(const Image *image,
4127 const MorphologyMethod method,const ssize_t iterations,
4128 const KernelInfo *kernel,ExceptionInfo *exception)
4142 curr_kernel = (KernelInfo *) kernel;
4144 compose = UndefinedCompositeOp; /* use default for method */
4146 /* Apply Convolve/Correlate Normalization and Scaling Factors.
4147 * This is done BEFORE the ShowKernelInfo() function is called so that
4148 * users can see the results of the 'option:convolve:scale' option.
4150 if ( method == ConvolveMorphology || method == CorrelateMorphology ) {
4154 /* Get the bias value as it will be needed */
4155 artifact = GetImageArtifact(image,"convolve:bias");
4156 if ( artifact != (const char *) NULL) {
4157 if (IfMagickFalse(IsGeometry(artifact)))
4158 (void) ThrowMagickException(exception,GetMagickModule(),
4159 OptionWarning,"InvalidSetting","'%s' '%s'",
4160 "convolve:bias",artifact);
4162 bias=StringToDoubleInterval(artifact,(double) QuantumRange+1.0);
4165 /* Scale kernel according to user wishes */
4166 artifact = GetImageArtifact(image,"convolve:scale");
4167 if ( artifact != (const char *)NULL ) {
4168 if (IfMagickFalse(IsGeometry(artifact)))
4169 (void) ThrowMagickException(exception,GetMagickModule(),
4170 OptionWarning,"InvalidSetting","'%s' '%s'",
4171 "convolve:scale",artifact);
4173 if ( curr_kernel == kernel )
4174 curr_kernel = CloneKernelInfo(kernel);
4175 if (curr_kernel == (KernelInfo *) NULL)
4176 return((Image *) NULL);
4177 ScaleGeometryKernelInfo(curr_kernel, artifact);
4182 /* display the (normalized) kernel via stderr */
4183 if ( IfStringTrue(GetImageArtifact(image,"showkernel"))
4184 || IfStringTrue(GetImageArtifact(image,"convolve:showkernel"))
4185 || IfStringTrue(GetImageArtifact(image,"morphology:showkernel")) )
4186 ShowKernelInfo(curr_kernel);
4188 /* Override the default handling of multi-kernel morphology results
4189 * If 'Undefined' use the default method
4190 * If 'None' (default for 'Convolve') re-iterate previous result
4191 * Otherwise merge resulting images using compose method given.
4192 * Default for 'HitAndMiss' is 'Lighten'.
4199 artifact = GetImageArtifact(image,"morphology:compose");
4200 if ( artifact != (const char *) NULL) {
4201 parse=ParseCommandOption(MagickComposeOptions,
4202 MagickFalse,artifact);
4204 (void) ThrowMagickException(exception,GetMagickModule(),
4205 OptionWarning,"UnrecognizedComposeOperator","'%s' '%s'",
4206 "morphology:compose",artifact);
4208 compose=(CompositeOperator)parse;
4211 /* Apply the Morphology */
4212 morphology_image = MorphologyApply(image,method,iterations,
4213 curr_kernel,compose,bias,exception);
4215 /* Cleanup and Exit */
4216 if ( curr_kernel != kernel )
4217 curr_kernel=DestroyKernelInfo(curr_kernel);
4218 return(morphology_image);
4222 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4226 + R o t a t e K e r n e l I n f o %
4230 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4232 % RotateKernelInfo() rotates the kernel by the angle given.
4234 % Currently it is restricted to 90 degree angles, of either 1D kernels
4235 % or square kernels. And 'circular' rotations of 45 degrees for 3x3 kernels.
4236 % It will ignore usless rotations for specific 'named' built-in kernels.
4238 % The format of the RotateKernelInfo method is:
4240 % void RotateKernelInfo(KernelInfo *kernel, double angle)
4242 % A description of each parameter follows:
4244 % o kernel: the Morphology/Convolution kernel
4246 % o angle: angle to rotate in degrees
4248 % This function is currently internal to this module only, but can be exported
4249 % to other modules if needed.
4251 static void RotateKernelInfo(KernelInfo *kernel, double angle)
4253 /* angle the lower kernels first */
4254 if ( kernel->next != (KernelInfo *) NULL)
4255 RotateKernelInfo(kernel->next, angle);
4257 /* WARNING: Currently assumes the kernel (rightly) is horizontally symetrical
4259 ** TODO: expand beyond simple 90 degree rotates, flips and flops
4262 /* Modulus the angle */
4263 angle = fmod(angle, 360.0);
4267 if ( 337.5 < angle || angle <= 22.5 )
4268 return; /* Near zero angle - no change! - At least not at this time */
4270 /* Handle special cases */
4271 switch (kernel->type) {
4272 /* These built-in kernels are cylindrical kernels, rotating is useless */
4273 case GaussianKernel:
4278 case LaplacianKernel:
4279 case ChebyshevKernel:
4280 case ManhattanKernel:
4281 case EuclideanKernel:
4284 /* These may be rotatable at non-90 angles in the future */
4285 /* but simply rotating them in multiples of 90 degrees is useless */
4292 /* These only allows a +/-90 degree rotation (by transpose) */
4293 /* A 180 degree rotation is useless */
4295 if ( 135.0 < angle && angle <= 225.0 )
4297 if ( 225.0 < angle && angle <= 315.0 )
4304 /* Attempt rotations by 45 degrees -- 3x3 kernels only */
4305 if ( 22.5 < fmod(angle,90.0) && fmod(angle,90.0) <= 67.5 )
4307 if ( kernel->width == 3 && kernel->height == 3 )
4308 { /* Rotate a 3x3 square by 45 degree angle */
4309 double t = kernel->values[0];
4310 kernel->values[0] = kernel->values[3];
4311 kernel->values[3] = kernel->values[6];
4312 kernel->values[6] = kernel->values[7];
4313 kernel->values[7] = kernel->values[8];
4314 kernel->values[8] = kernel->values[5];
4315 kernel->values[5] = kernel->values[2];
4316 kernel->values[2] = kernel->values[1];
4317 kernel->values[1] = t;
4318 /* rotate non-centered origin */
4319 if ( kernel->x != 1 || kernel->y != 1 ) {
4321 x = (ssize_t) kernel->x-1;
4322 y = (ssize_t) kernel->y-1;
4323 if ( x == y ) x = 0;
4324 else if ( x == 0 ) x = -y;
4325 else if ( x == -y ) y = 0;
4326 else if ( y == 0 ) y = x;
4327 kernel->x = (ssize_t) x+1;
4328 kernel->y = (ssize_t) y+1;
4330 angle = fmod(angle+315.0, 360.0); /* angle reduced 45 degrees */
4331 kernel->angle = fmod(kernel->angle+45.0, 360.0);
4334 perror("Unable to rotate non-3x3 kernel by 45 degrees");
4336 if ( 45.0 < fmod(angle, 180.0) && fmod(angle,180.0) <= 135.0 )
4338 if ( kernel->width == 1 || kernel->height == 1 )
4339 { /* Do a transpose of a 1 dimensional kernel,
4340 ** which results in a fast 90 degree rotation of some type.
4344 t = (ssize_t) kernel->width;
4345 kernel->width = kernel->height;
4346 kernel->height = (size_t) t;
4348 kernel->x = kernel->y;
4350 if ( kernel->width == 1 ) {
4351 angle = fmod(angle+270.0, 360.0); /* angle reduced 90 degrees */
4352 kernel->angle = fmod(kernel->angle+90.0, 360.0);
4354 angle = fmod(angle+90.0, 360.0); /* angle increased 90 degrees */
4355 kernel->angle = fmod(kernel->angle+270.0, 360.0);
4358 else if ( kernel->width == kernel->height )
4359 { /* Rotate a square array of values by 90 degrees */
4363 register MagickRealType
4367 for( i=0, x=(ssize_t) kernel->width-1; i<=x; i++, x--)
4368 for( j=0, y=(ssize_t) kernel->height-1; j<y; j++, y--)
4369 { t = k[i+j*kernel->width];
4370 k[i+j*kernel->width] = k[j+x*kernel->width];
4371 k[j+x*kernel->width] = k[x+y*kernel->width];
4372 k[x+y*kernel->width] = k[y+i*kernel->width];
4373 k[y+i*kernel->width] = t;
4376 /* rotate the origin - relative to center of array */
4377 { register ssize_t x,y;
4378 x = (ssize_t) (kernel->x*2-kernel->width+1);
4379 y = (ssize_t) (kernel->y*2-kernel->height+1);
4380 kernel->x = (ssize_t) ( -y +(ssize_t) kernel->width-1)/2;
4381 kernel->y = (ssize_t) ( +x +(ssize_t) kernel->height-1)/2;
4383 angle = fmod(angle+270.0, 360.0); /* angle reduced 90 degrees */
4384 kernel->angle = fmod(kernel->angle+90.0, 360.0);
4387 perror("Unable to rotate a non-square, non-linear kernel 90 degrees");
4389 if ( 135.0 < angle && angle <= 225.0 )
4391 /* For a 180 degree rotation - also know as a reflection
4392 * This is actually a very very common operation!
4393 * Basically all that is needed is a reversal of the kernel data!
4394 * And a reflection of the origon
4399 register MagickRealType
4407 j=(ssize_t) (kernel->width*kernel->height-1);
4408 for (i=0; i < j; i++, j--)
4409 t=k[i], k[i]=k[j], k[j]=t;
4411 kernel->x = (ssize_t) kernel->width - kernel->x - 1;
4412 kernel->y = (ssize_t) kernel->height - kernel->y - 1;
4413 angle = fmod(angle-180.0, 360.0); /* angle+180 degrees */
4414 kernel->angle = fmod(kernel->angle+180.0, 360.0);
4416 /* At this point angle should at least between -45 (315) and +45 degrees
4417 * In the future some form of non-orthogonal angled rotates could be
4418 * performed here, posibily with a linear kernel restriction.
4425 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4429 % S c a l e G e o m e t r y K e r n e l I n f o %
4433 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4435 % ScaleGeometryKernelInfo() takes a geometry argument string, typically
4436 % provided as a "-set option:convolve:scale {geometry}" user setting,
4437 % and modifies the kernel according to the parsed arguments of that setting.
4439 % The first argument (and any normalization flags) are passed to
4440 % ScaleKernelInfo() to scale/normalize the kernel. The second argument
4441 % is then passed to UnityAddKernelInfo() to add a scled unity kernel
4442 % into the scaled/normalized kernel.
4444 % The format of the ScaleGeometryKernelInfo method is:
4446 % void ScaleGeometryKernelInfo(KernelInfo *kernel,
4447 % const double scaling_factor,const MagickStatusType normalize_flags)
4449 % A description of each parameter follows:
4451 % o kernel: the Morphology/Convolution kernel to modify
4454 % The geometry string to parse, typically from the user provided
4455 % "-set option:convolve:scale {geometry}" setting.
4458 MagickExport void ScaleGeometryKernelInfo (KernelInfo *kernel,
4459 const char *geometry)
4467 SetGeometryInfo(&args);
4468 flags = ParseGeometry(geometry, &args);
4471 /* For Debugging Geometry Input */
4472 (void) FormatLocaleFile(stderr, "Geometry = 0x%04X : %lg x %lg %+lg %+lg\n",
4473 flags, args.rho, args.sigma, args.xi, args.psi );
4476 if ( (flags & PercentValue) != 0 ) /* Handle Percentage flag*/
4477 args.rho *= 0.01, args.sigma *= 0.01;
4479 if ( (flags & RhoValue) == 0 ) /* Set Defaults for missing args */
4481 if ( (flags & SigmaValue) == 0 )
4484 /* Scale/Normalize the input kernel */
4485 ScaleKernelInfo(kernel, args.rho, (GeometryFlags) flags);
4487 /* Add Unity Kernel, for blending with original */
4488 if ( (flags & SigmaValue) != 0 )
4489 UnityAddKernelInfo(kernel, args.sigma);
4494 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4498 % S c a l e K e r n e l I n f o %
4502 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4504 % ScaleKernelInfo() scales the given kernel list by the given amount, with or
4505 % without normalization of the sum of the kernel values (as per given flags).
4507 % By default (no flags given) the values within the kernel is scaled
4508 % directly using given scaling factor without change.
4510 % If either of the two 'normalize_flags' are given the kernel will first be
4511 % normalized and then further scaled by the scaling factor value given.
4513 % Kernel normalization ('normalize_flags' given) is designed to ensure that
4514 % any use of the kernel scaling factor with 'Convolve' or 'Correlate'
4515 % morphology methods will fall into -1.0 to +1.0 range. Note that for
4516 % non-HDRI versions of IM this may cause images to have any negative results
4517 % clipped, unless some 'bias' is used.
4519 % More specifically. Kernels which only contain positive values (such as a
4520 % 'Gaussian' kernel) will be scaled so that those values sum to +1.0,
4521 % ensuring a 0.0 to +1.0 output range for non-HDRI images.
4523 % For Kernels that contain some negative values, (such as 'Sharpen' kernels)
4524 % the kernel will be scaled by the absolute of the sum of kernel values, so
4525 % that it will generally fall within the +/- 1.0 range.
4527 % For kernels whose values sum to zero, (such as 'Laplician' kernels) kernel
4528 % will be scaled by just the sum of the postive values, so that its output
4529 % range will again fall into the +/- 1.0 range.
4531 % For special kernels designed for locating shapes using 'Correlate', (often
4532 % only containing +1 and -1 values, representing foreground/brackground
4533 % matching) a special normalization method is provided to scale the positive
4534 % values separately to those of the negative values, so the kernel will be
4535 % forced to become a zero-sum kernel better suited to such searches.
4537 % WARNING: Correct normalization of the kernel assumes that the '*_range'
4538 % attributes within the kernel structure have been correctly set during the
4541 % NOTE: The values used for 'normalize_flags' have been selected specifically
4542 % to match the use of geometry options, so that '!' means NormalizeValue, '^'
4543 % means CorrelateNormalizeValue. All other GeometryFlags values are ignored.
4545 % The format of the ScaleKernelInfo method is:
4547 % void ScaleKernelInfo(KernelInfo *kernel, const double scaling_factor,
4548 % const MagickStatusType normalize_flags )
4550 % A description of each parameter follows:
4552 % o kernel: the Morphology/Convolution kernel
4555 % multiply all values (after normalization) by this factor if not
4556 % zero. If the kernel is normalized regardless of any flags.
4558 % o normalize_flags:
4559 % GeometryFlags defining normalization method to use.
4560 % specifically: NormalizeValue, CorrelateNormalizeValue,
4561 % and/or PercentValue
4564 MagickExport void ScaleKernelInfo(KernelInfo *kernel,
4565 const double scaling_factor,const GeometryFlags normalize_flags)
4574 /* do the other kernels in a multi-kernel list first */
4575 if ( kernel->next != (KernelInfo *) NULL)
4576 ScaleKernelInfo(kernel->next, scaling_factor, normalize_flags);
4578 /* Normalization of Kernel */
4580 if ( (normalize_flags&NormalizeValue) != 0 ) {
4581 if ( fabs(kernel->positive_range + kernel->negative_range) >= MagickEpsilon )
4582 /* non-zero-summing kernel (generally positive) */
4583 pos_scale = fabs(kernel->positive_range + kernel->negative_range);
4585 /* zero-summing kernel */
4586 pos_scale = kernel->positive_range;
4588 /* Force kernel into a normalized zero-summing kernel */
4589 if ( (normalize_flags&CorrelateNormalizeValue) != 0 ) {
4590 pos_scale = ( fabs(kernel->positive_range) >= MagickEpsilon )
4591 ? kernel->positive_range : 1.0;
4592 neg_scale = ( fabs(kernel->negative_range) >= MagickEpsilon )
4593 ? -kernel->negative_range : 1.0;
4596 neg_scale = pos_scale;
4598 /* finialize scaling_factor for positive and negative components */
4599 pos_scale = scaling_factor/pos_scale;
4600 neg_scale = scaling_factor/neg_scale;
4602 for (i=0; i < (ssize_t) (kernel->width*kernel->height); i++)
4603 if ( ! IfNaN(kernel->values[i]) )
4604 kernel->values[i] *= (kernel->values[i] >= 0) ? pos_scale : neg_scale;
4606 /* convolution output range */
4607 kernel->positive_range *= pos_scale;
4608 kernel->negative_range *= neg_scale;
4609 /* maximum and minimum values in kernel */
4610 kernel->maximum *= (kernel->maximum >= 0.0) ? pos_scale : neg_scale;
4611 kernel->minimum *= (kernel->minimum >= 0.0) ? pos_scale : neg_scale;
4613 /* swap kernel settings if user's scaling factor is negative */
4614 if ( scaling_factor < MagickEpsilon ) {
4616 t = kernel->positive_range;
4617 kernel->positive_range = kernel->negative_range;
4618 kernel->negative_range = t;
4619 t = kernel->maximum;
4620 kernel->maximum = kernel->minimum;
4621 kernel->minimum = 1;
4628 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4632 % S h o w K e r n e l I n f o %
4636 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4638 % ShowKernelInfo() outputs the details of the given kernel defination to
4639 % standard error, generally due to a users 'showkernel' option request.
4641 % The format of the ShowKernel method is:
4643 % void ShowKernelInfo(const KernelInfo *kernel)
4645 % A description of each parameter follows:
4647 % o kernel: the Morphology/Convolution kernel
4650 MagickPrivate void ShowKernelInfo(const KernelInfo *kernel)
4658 for (c=0, k=kernel; k != (KernelInfo *) NULL; c++, k=k->next ) {
4660 (void) FormatLocaleFile(stderr, "Kernel");
4661 if ( kernel->next != (KernelInfo *) NULL )
4662 (void) FormatLocaleFile(stderr, " #%lu", (unsigned long) c );
4663 (void) FormatLocaleFile(stderr, " \"%s",
4664 CommandOptionToMnemonic(MagickKernelOptions, k->type) );
4665 if ( fabs(k->angle) >= MagickEpsilon )
4666 (void) FormatLocaleFile(stderr, "@%lg", k->angle);
4667 (void) FormatLocaleFile(stderr, "\" of size %lux%lu%+ld%+ld",(unsigned long)
4668 k->width,(unsigned long) k->height,(long) k->x,(long) k->y);
4669 (void) FormatLocaleFile(stderr,
4670 " with values from %.*lg to %.*lg\n",
4671 GetMagickPrecision(), k->minimum,
4672 GetMagickPrecision(), k->maximum);
4673 (void) FormatLocaleFile(stderr, "Forming a output range from %.*lg to %.*lg",
4674 GetMagickPrecision(), k->negative_range,
4675 GetMagickPrecision(), k->positive_range);
4676 if ( fabs(k->positive_range+k->negative_range) < MagickEpsilon )
4677 (void) FormatLocaleFile(stderr, " (Zero-Summing)\n");
4678 else if ( fabs(k->positive_range+k->negative_range-1.0) < MagickEpsilon )
4679 (void) FormatLocaleFile(stderr, " (Normalized)\n");
4681 (void) FormatLocaleFile(stderr, " (Sum %.*lg)\n",
4682 GetMagickPrecision(), k->positive_range+k->negative_range);
4683 for (i=v=0; v < k->height; v++) {
4684 (void) FormatLocaleFile(stderr, "%2lu:", (unsigned long) v );
4685 for (u=0; u < k->width; u++, i++)
4686 if ( IfNaN(k->values[i]) )
4687 (void) FormatLocaleFile(stderr," %*s", GetMagickPrecision()+3, "nan");
4689 (void) FormatLocaleFile(stderr," %*.*lg", GetMagickPrecision()+3,
4690 GetMagickPrecision(), (double) k->values[i]);
4691 (void) FormatLocaleFile(stderr,"\n");
4697 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4701 % U n i t y A d d K e r n a l I n f o %
4705 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4707 % UnityAddKernelInfo() Adds a given amount of the 'Unity' Convolution Kernel
4708 % to the given pre-scaled and normalized Kernel. This in effect adds that
4709 % amount of the original image into the resulting convolution kernel. This
4710 % value is usually provided by the user as a percentage value in the
4711 % 'convolve:scale' setting.
4713 % The resulting effect is to convert the defined kernels into blended
4714 % soft-blurs, unsharp kernels or into sharpening kernels.
4716 % The format of the UnityAdditionKernelInfo method is:
4718 % void UnityAdditionKernelInfo(KernelInfo *kernel, const double scale )
4720 % A description of each parameter follows:
4722 % o kernel: the Morphology/Convolution kernel
4725 % scaling factor for the unity kernel to be added to
4729 MagickExport void UnityAddKernelInfo(KernelInfo *kernel,
4732 /* do the other kernels in a multi-kernel list first */
4733 if ( kernel->next != (KernelInfo *) NULL)
4734 UnityAddKernelInfo(kernel->next, scale);
4736 /* Add the scaled unity kernel to the existing kernel */
4737 kernel->values[kernel->x+kernel->y*kernel->width] += scale;
4738 CalcKernelMetaData(kernel); /* recalculate the meta-data */
4744 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4748 % Z e r o K e r n e l N a n s %
4752 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4754 % ZeroKernelNans() replaces any special 'nan' value that may be present in
4755 % the kernel with a zero value. This is typically done when the kernel will
4756 % be used in special hardware (GPU) convolution processors, to simply
4759 % The format of the ZeroKernelNans method is:
4761 % void ZeroKernelNans (KernelInfo *kernel)
4763 % A description of each parameter follows:
4765 % o kernel: the Morphology/Convolution kernel
4768 MagickPrivate void ZeroKernelNans(KernelInfo *kernel)
4773 /* do the other kernels in a multi-kernel list first */
4774 if ( kernel->next != (KernelInfo *) NULL)
4775 ZeroKernelNans(kernel->next);
4777 for (i=0; i < (kernel->width*kernel->height); i++)
4778 if ( IfNaN(kernel->values[i]) )
4779 kernel->values[i] = 0.0;