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];
505 if (kernel_string == (const char *) NULL)
506 return(ParseKernelArray(kernel_string));
510 while (GetMagickToken(p,NULL,token), *token != '\0')
512 /* ignore extra or multiple ';' kernel separators */
515 /* tokens starting with alpha is a Named kernel */
516 if (isalpha((int) ((unsigned char) *token)) != 0)
517 new_kernel=ParseKernelName(p);
518 else /* otherwise a user defined kernel array */
519 new_kernel=ParseKernelArray(p);
521 /* Error handling -- this is not proper error handling! */
522 if (new_kernel == (KernelInfo *) NULL)
524 if (kernel != (KernelInfo *) NULL)
525 kernel=DestroyKernelInfo(kernel);
526 return((KernelInfo *) NULL);
529 /* initialise or append the kernel list */
530 if (kernel == (KernelInfo *) NULL)
533 LastKernelInfo(kernel)->next=new_kernel;
536 /* look for the next kernel in list */
538 if (p == (char *) NULL)
547 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
551 % A c q u i r e K e r n e l B u i l t I n %
555 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
557 % AcquireKernelBuiltIn() returned one of the 'named' built-in types of
558 % kernels used for special purposes such as gaussian blurring, skeleton
559 % pruning, and edge distance determination.
561 % They take a KernelType, and a set of geometry style arguments, which were
562 % typically decoded from a user supplied string, or from a more complex
563 % Morphology Method that was requested.
565 % The format of the AcquireKernalBuiltIn method is:
567 % KernelInfo *AcquireKernelBuiltIn(const KernelInfoType type,
568 % const GeometryInfo args)
570 % A description of each parameter follows:
572 % o type: the pre-defined type of kernel wanted
574 % o args: arguments defining or modifying the kernel
576 % Convolution Kernels
579 % The a No-Op or Scaling single element kernel.
581 % Gaussian:{radius},{sigma}
582 % Generate a two-dimensional gaussian kernel, as used by -gaussian.
583 % The sigma for the curve is required. The resulting kernel is
586 % If 'sigma' is zero, you get a single pixel on a field of zeros.
588 % NOTE: that the 'radius' is optional, but if provided can limit (clip)
589 % the final size of the resulting kernel to a square 2*radius+1 in size.
590 % The radius should be at least 2 times that of the sigma value, or
591 % sever clipping and aliasing may result. If not given or set to 0 the
592 % radius will be determined so as to produce the best minimal error
593 % result, which is usally much larger than is normally needed.
595 % LoG:{radius},{sigma}
596 % "Laplacian of a Gaussian" or "Mexician Hat" Kernel.
597 % The supposed ideal edge detection, zero-summing kernel.
599 % An alturnative to this kernel is to use a "DoG" with a sigma ratio of
600 % approx 1.6 (according to wikipedia).
602 % DoG:{radius},{sigma1},{sigma2}
603 % "Difference of Gaussians" Kernel.
604 % As "Gaussian" but with a gaussian produced by 'sigma2' subtracted
605 % from the gaussian produced by 'sigma1'. Typically sigma2 > sigma1.
606 % The result is a zero-summing kernel.
608 % Blur:{radius},{sigma}[,{angle}]
609 % Generates a 1 dimensional or linear gaussian blur, at the angle given
610 % (current restricted to orthogonal angles). If a 'radius' is given the
611 % kernel is clipped to a width of 2*radius+1. Kernel can be rotated
612 % by a 90 degree angle.
614 % If 'sigma' is zero, you get a single pixel on a field of zeros.
616 % Note that two convolutions with two "Blur" kernels perpendicular to
617 % each other, is equivalent to a far larger "Gaussian" kernel with the
618 % same sigma value, However it is much faster to apply. This is how the
619 % "-blur" operator actually works.
621 % Comet:{width},{sigma},{angle}
622 % Blur in one direction only, much like how a bright object leaves
623 % a comet like trail. The Kernel is actually half a gaussian curve,
624 % Adding two such blurs in opposite directions produces a Blur Kernel.
625 % Angle can be rotated in multiples of 90 degrees.
627 % Note that the first argument is the width of the kernel and not the
628 % radius of the kernel.
630 % Binomial:[{radius}]
631 % Generate a discrete kernel using a 2 dimentional Pascel's Triangle
632 % of values. Used for special forma of image filters.
634 % # Still to be implemented...
638 % # Set kernel values using a resize filter, and given scale (sigma)
639 % # Cylindrical or Linear. Is this possible with an image?
642 % Named Constant Convolution Kernels
644 % All these are unscaled, zero-summing kernels by default. As such for
645 % non-HDRI version of ImageMagick some form of normalization, user scaling,
646 % and biasing the results is recommended, to prevent the resulting image
649 % The 3x3 kernels (most of these) can be circularly rotated in multiples of
650 % 45 degrees to generate the 8 angled varients of each of the kernels.
653 % Discrete Lapacian Kernels, (without normalization)
654 % Type 0 : 3x3 with center:8 surounded by -1 (8 neighbourhood)
655 % Type 1 : 3x3 with center:4 edge:-1 corner:0 (4 neighbourhood)
656 % Type 2 : 3x3 with center:4 edge:1 corner:-2
657 % Type 3 : 3x3 with center:4 edge:-2 corner:1
658 % Type 5 : 5x5 laplacian
659 % Type 7 : 7x7 laplacian
660 % Type 15 : 5x5 LoG (sigma approx 1.4)
661 % Type 19 : 9x9 LoG (sigma approx 1.4)
664 % Sobel 'Edge' convolution kernel (3x3)
670 % Roberts convolution kernel (3x3)
676 % Prewitt Edge convolution kernel (3x3)
682 % Prewitt's "Compass" convolution kernel (3x3)
688 % Kirsch's "Compass" convolution kernel (3x3)
694 % Frei-Chen Edge Detector is based on a kernel that is similar to
695 % the Sobel Kernel, but is designed to be isotropic. That is it takes
696 % into account the distance of the diagonal in the kernel.
699 % | sqrt(2), 0, -sqrt(2) |
702 % FreiChen:{type},{angle}
704 % Frei-Chen Pre-weighted kernels...
706 % Type 0: default un-nomalized version shown above.
708 % Type 1: Orthogonal Kernel (same as type 11 below)
710 % | sqrt(2), 0, -sqrt(2) | / 2*sqrt(2)
713 % Type 2: Diagonal form of Kernel...
715 % | sqrt(2), 0, -sqrt(2) | / 2*sqrt(2)
718 % However this kernel is als at the heart of the FreiChen Edge Detection
719 % Process which uses a set of 9 specially weighted kernel. These 9
720 % kernels not be normalized, but directly applied to the image. The
721 % results is then added together, to produce the intensity of an edge in
722 % a specific direction. The square root of the pixel value can then be
723 % taken as the cosine of the edge, and at least 2 such runs at 90 degrees
724 % from each other, both the direction and the strength of the edge can be
727 % Type 10: All 9 of the following pre-weighted kernels...
729 % Type 11: | 1, 0, -1 |
730 % | sqrt(2), 0, -sqrt(2) | / 2*sqrt(2)
733 % Type 12: | 1, sqrt(2), 1 |
734 % | 0, 0, 0 | / 2*sqrt(2)
737 % Type 13: | sqrt(2), -1, 0 |
738 % | -1, 0, 1 | / 2*sqrt(2)
741 % Type 14: | 0, 1, -sqrt(2) |
742 % | -1, 0, 1 | / 2*sqrt(2)
745 % Type 15: | 0, -1, 0 |
749 % Type 16: | 1, 0, -1 |
753 % Type 17: | 1, -2, 1 |
757 % Type 18: | -2, 1, -2 |
761 % Type 19: | 1, 1, 1 |
765 % The first 4 are for edge detection, the next 4 are for line detection
766 % and the last is to add a average component to the results.
768 % Using a special type of '-1' will return all 9 pre-weighted kernels
769 % as a multi-kernel list, so that you can use them directly (without
770 % normalization) with the special "-set option:morphology:compose Plus"
771 % setting to apply the full FreiChen Edge Detection Technique.
773 % If 'type' is large it will be taken to be an actual rotation angle for
774 % the default FreiChen (type 0) kernel. As such FreiChen:45 will look
775 % like a Sobel:45 but with 'sqrt(2)' instead of '2' values.
777 % WARNING: The above was layed out as per
778 % http://www.math.tau.ac.il/~turkel/notes/edge_detectors.pdf
779 % But rotated 90 degrees so direction is from left rather than the top.
780 % I have yet to find any secondary confirmation of the above. The only
781 % other source found was actual source code at
782 % http://ltswww.epfl.ch/~courstiv/exos_labos/sol3.pdf
783 % Neigher paper defineds the kernels in a way that looks locical or
784 % correct when taken as a whole.
788 % Diamond:[{radius}[,{scale}]]
789 % Generate a diamond shaped kernel with given radius to the points.
790 % Kernel size will again be radius*2+1 square and defaults to radius 1,
791 % generating a 3x3 kernel that is slightly larger than a square.
793 % Square:[{radius}[,{scale}]]
794 % Generate a square shaped kernel of size radius*2+1, and defaulting
795 % to a 3x3 (radius 1).
797 % Octagon:[{radius}[,{scale}]]
798 % Generate octagonal shaped kernel of given radius and constant scale.
799 % Default radius is 3 producing a 7x7 kernel. A radius of 1 will result
800 % in "Diamond" kernel.
802 % Disk:[{radius}[,{scale}]]
803 % Generate a binary disk, thresholded at the radius given, the radius
804 % may be a float-point value. Final Kernel size is floor(radius)*2+1
805 % square. A radius of 5.3 is the default.
807 % NOTE: That a low radii Disk kernels produce the same results as
808 % many of the previously defined kernels, but differ greatly at larger
809 % radii. Here is a table of equivalences...
810 % "Disk:1" => "Diamond", "Octagon:1", or "Cross:1"
811 % "Disk:1.5" => "Square"
812 % "Disk:2" => "Diamond:2"
813 % "Disk:2.5" => "Octagon"
814 % "Disk:2.9" => "Square:2"
815 % "Disk:3.5" => "Octagon:3"
816 % "Disk:4.5" => "Octagon:4"
817 % "Disk:5.4" => "Octagon:5"
818 % "Disk:6.4" => "Octagon:6"
819 % All other Disk shapes are unique to this kernel, but because a "Disk"
820 % is more circular when using a larger radius, using a larger radius is
821 % preferred over iterating the morphological operation.
823 % Rectangle:{geometry}
824 % Simply generate a rectangle of 1's with the size given. You can also
825 % specify the location of the 'control point', otherwise the closest
826 % pixel to the center of the rectangle is selected.
828 % Properly centered and odd sized rectangles work the best.
830 % Symbol Dilation Kernels
832 % These kernel is not a good general morphological kernel, but is used
833 % more for highlighting and marking any single pixels in an image using,
834 % a "Dilate" method as appropriate.
836 % For the same reasons iterating these kernels does not produce the
837 % same result as using a larger radius for the symbol.
839 % Plus:[{radius}[,{scale}]]
840 % Cross:[{radius}[,{scale}]]
841 % Generate a kernel in the shape of a 'plus' or a 'cross' with
842 % a each arm the length of the given radius (default 2).
844 % NOTE: "plus:1" is equivalent to a "Diamond" kernel.
846 % Ring:{radius1},{radius2}[,{scale}]
847 % A ring of the values given that falls between the two radii.
848 % Defaults to a ring of approximataly 3 radius in a 7x7 kernel.
849 % This is the 'edge' pixels of the default "Disk" kernel,
850 % More specifically, "Ring" -> "Ring:2.5,3.5,1.0"
852 % Hit and Miss Kernels
854 % Peak:radius1,radius2
855 % Find any peak larger than the pixels the fall between the two radii.
856 % The default ring of pixels is as per "Ring".
858 % Find flat orthogonal edges of a binary shape
860 % Find 90 degree corners of a binary shape
862 % A special kernel to thin the 'outside' of diagonals
864 % Find end points of lines (for pruning a skeletion)
865 % Two types of lines ends (default to both) can be searched for
866 % Type 0: All line ends
867 % Type 1: single kernel for 4-conneected line ends
868 % Type 2: single kernel for simple line ends
870 % Find three line junctions (within a skeletion)
871 % Type 0: all line junctions
872 % Type 1: Y Junction kernel
873 % Type 2: Diagonal T Junction kernel
874 % Type 3: Orthogonal T Junction kernel
875 % Type 4: Diagonal X Junction kernel
876 % Type 5: Orthogonal + Junction kernel
878 % Find single pixel ridges or thin lines
879 % Type 1: Fine single pixel thick lines and ridges
880 % Type 2: Find two pixel thick lines and ridges
882 % Octagonal Thickening Kernel, to generate convex hulls of 45 degrees
884 % Traditional skeleton generating kernels.
885 % Type 1: Tradional Skeleton kernel (4 connected skeleton)
886 % Type 2: HIPR2 Skeleton kernel (8 connected skeleton)
887 % Type 3: Thinning skeleton based on a ressearch paper by
888 % Dan S. Bloomberg (Default Type)
890 % A huge variety of Thinning Kernels designed to preserve conectivity.
891 % many other kernel sets use these kernels as source definitions.
892 % Type numbers are 41-49, 81-89, 481, and 482 which are based on
893 % the super and sub notations used in the source research paper.
895 % Distance Measuring Kernels
897 % Different types of distance measuring methods, which are used with the
898 % a 'Distance' morphology method for generating a gradient based on
899 % distance from an edge of a binary shape, though there is a technique
900 % for handling a anti-aliased shape.
902 % See the 'Distance' Morphological Method, for information of how it is
905 % Chebyshev:[{radius}][x{scale}[%!]]
906 % Chebyshev Distance (also known as Tchebychev or Chessboard distance)
907 % is a value of one to any neighbour, orthogonal or diagonal. One why
908 % of thinking of it is the number of squares a 'King' or 'Queen' in
909 % chess needs to traverse reach any other position on a chess board.
910 % It results in a 'square' like distance function, but one where
911 % diagonals are given a value that is closer than expected.
913 % Manhattan:[{radius}][x{scale}[%!]]
914 % Manhattan Distance (also known as Rectilinear, City Block, or the Taxi
915 % Cab distance metric), it is the distance needed when you can only
916 % travel in horizontal or vertical directions only. It is the
917 % distance a 'Rook' in chess would have to travel, and results in a
918 % diamond like distances, where diagonals are further than expected.
920 % Octagonal:[{radius}][x{scale}[%!]]
921 % An interleving of Manhatten and Chebyshev metrics producing an
922 % increasing octagonally shaped distance. Distances matches those of
923 % the "Octagon" shaped kernel of the same radius. The minimum radius
924 % and default is 2, producing a 5x5 kernel.
926 % Euclidean:[{radius}][x{scale}[%!]]
927 % Euclidean distance is the 'direct' or 'as the crow flys' distance.
928 % However by default the kernel size only has a radius of 1, which
929 % limits the distance to 'Knight' like moves, with only orthogonal and
930 % diagonal measurements being correct. As such for the default kernel
931 % you will get octagonal like distance function.
933 % However using a larger radius such as "Euclidean:4" you will get a
934 % much smoother distance gradient from the edge of the shape. Especially
935 % if the image is pre-processed to include any anti-aliasing pixels.
936 % Of course a larger kernel is slower to use, and not always needed.
938 % The first three Distance Measuring Kernels will only generate distances
939 % of exact multiples of {scale} in binary images. As such you can use a
940 % scale of 1 without loosing any information. However you also need some
941 % scaling when handling non-binary anti-aliased shapes.
943 % The "Euclidean" Distance Kernel however does generate a non-integer
944 % fractional results, and as such scaling is vital even for binary shapes.
948 MagickExport KernelInfo *AcquireKernelBuiltIn(const KernelInfoType type,
949 const GeometryInfo *args)
962 nan = sqrt((double)-1.0); /* Special Value : Not A Number */
964 /* Generate a new empty kernel if needed */
965 kernel=(KernelInfo *) NULL;
967 case UndefinedKernel: /* These should not call this function */
968 case UserDefinedKernel:
969 assert("Should not call this function" != (char *)NULL);
971 case LaplacianKernel: /* Named Descrete Convolution Kernels */
972 case SobelKernel: /* these are defined using other kernels */
978 case EdgesKernel: /* Hit and Miss kernels */
980 case DiagonalsKernel:
982 case LineJunctionsKernel:
984 case ConvexHullKernel:
987 break; /* A pre-generated kernel is not needed */
989 /* set to 1 to do a compile-time check that we haven't missed anything */
999 case RectangleKernel:
1006 case ChebyshevKernel:
1007 case ManhattanKernel:
1008 case OctangonalKernel:
1009 case EuclideanKernel:
1013 /* Generate the base Kernel Structure */
1014 kernel=(KernelInfo *) AcquireMagickMemory(sizeof(*kernel));
1015 if (kernel == (KernelInfo *) NULL)
1017 (void) ResetMagickMemory(kernel,0,sizeof(*kernel));
1018 kernel->minimum = kernel->maximum = kernel->angle = 0.0;
1019 kernel->negative_range = kernel->positive_range = 0.0;
1020 kernel->type = type;
1021 kernel->next = (KernelInfo *) NULL;
1022 kernel->signature = MagickSignature;
1032 kernel->height = kernel->width = (size_t) 1;
1033 kernel->x = kernel->y = (ssize_t) 0;
1034 kernel->values=(MagickRealType *) MagickAssumeAligned(
1035 AcquireAlignedMemory(1,sizeof(*kernel->values)));
1036 if (kernel->values == (MagickRealType *) NULL)
1037 return(DestroyKernelInfo(kernel));
1038 kernel->maximum = kernel->values[0] = args->rho;
1042 case GaussianKernel:
1046 sigma = fabs(args->sigma),
1047 sigma2 = fabs(args->xi),
1050 if ( args->rho >= 1.0 )
1051 kernel->width = (size_t)args->rho*2+1;
1052 else if ( (type != DoGKernel) || (sigma >= sigma2) )
1053 kernel->width = GetOptimalKernelWidth2D(args->rho,sigma);
1055 kernel->width = GetOptimalKernelWidth2D(args->rho,sigma2);
1056 kernel->height = kernel->width;
1057 kernel->x = kernel->y = (ssize_t) (kernel->width-1)/2;
1058 kernel->values=(MagickRealType *) MagickAssumeAligned(
1059 AcquireAlignedMemory(kernel->width,kernel->height*
1060 sizeof(*kernel->values)));
1061 if (kernel->values == (MagickRealType *) NULL)
1062 return(DestroyKernelInfo(kernel));
1064 /* WARNING: The following generates a 'sampled gaussian' kernel.
1065 * What we really want is a 'discrete gaussian' kernel.
1067 * How to do this is I don't know, but appears to be basied on the
1068 * Error Function 'erf()' (intergral of a gaussian)
1071 if ( type == GaussianKernel || type == DoGKernel )
1072 { /* Calculate a Gaussian, OR positive half of a DoG */
1073 if ( sigma > MagickEpsilon )
1074 { A = 1.0/(2.0*sigma*sigma); /* simplify loop expressions */
1075 B = (double) (1.0/(Magick2PI*sigma*sigma));
1076 for ( i=0, v=-kernel->y; v <= (ssize_t)kernel->y; v++)
1077 for ( u=-kernel->x; u <= (ssize_t)kernel->x; u++, i++)
1078 kernel->values[i] = exp(-((double)(u*u+v*v))*A)*B;
1080 else /* limiting case - a unity (normalized Dirac) kernel */
1081 { (void) ResetMagickMemory(kernel->values,0, (size_t)
1082 kernel->width*kernel->height*sizeof(*kernel->values));
1083 kernel->values[kernel->x+kernel->y*kernel->width] = 1.0;
1087 if ( type == DoGKernel )
1088 { /* Subtract a Negative Gaussian for "Difference of Gaussian" */
1089 if ( sigma2 > MagickEpsilon )
1090 { sigma = sigma2; /* simplify loop expressions */
1091 A = 1.0/(2.0*sigma*sigma);
1092 B = (double) (1.0/(Magick2PI*sigma*sigma));
1093 for ( i=0, v=-kernel->y; v <= (ssize_t)kernel->y; v++)
1094 for ( u=-kernel->x; u <= (ssize_t)kernel->x; u++, i++)
1095 kernel->values[i] -= exp(-((double)(u*u+v*v))*A)*B;
1097 else /* limiting case - a unity (normalized Dirac) kernel */
1098 kernel->values[kernel->x+kernel->y*kernel->width] -= 1.0;
1101 if ( type == LoGKernel )
1102 { /* Calculate a Laplacian of a Gaussian - Or Mexician Hat */
1103 if ( sigma > MagickEpsilon )
1104 { A = 1.0/(2.0*sigma*sigma); /* simplify loop expressions */
1105 B = (double) (1.0/(MagickPI*sigma*sigma*sigma*sigma));
1106 for ( i=0, v=-kernel->y; v <= (ssize_t)kernel->y; v++)
1107 for ( u=-kernel->x; u <= (ssize_t)kernel->x; u++, i++)
1108 { R = ((double)(u*u+v*v))*A;
1109 kernel->values[i] = (1-R)*exp(-R)*B;
1112 else /* special case - generate a unity kernel */
1113 { (void) ResetMagickMemory(kernel->values,0, (size_t)
1114 kernel->width*kernel->height*sizeof(*kernel->values));
1115 kernel->values[kernel->x+kernel->y*kernel->width] = 1.0;
1119 /* Note the above kernels may have been 'clipped' by a user defined
1120 ** radius, producing a smaller (darker) kernel. Also for very small
1121 ** sigma's (> 0.1) the central value becomes larger than one, and thus
1122 ** producing a very bright kernel.
1124 ** Normalization will still be needed.
1127 /* Normalize the 2D Gaussian Kernel
1129 ** NB: a CorrelateNormalize performs a normal Normalize if
1130 ** there are no negative values.
1132 CalcKernelMetaData(kernel); /* the other kernel meta-data */
1133 ScaleKernelInfo(kernel, 1.0, CorrelateNormalizeValue);
1139 sigma = fabs(args->sigma),
1142 if ( args->rho >= 1.0 )
1143 kernel->width = (size_t)args->rho*2+1;
1145 kernel->width = GetOptimalKernelWidth1D(args->rho,sigma);
1147 kernel->x = (ssize_t) (kernel->width-1)/2;
1149 kernel->negative_range = kernel->positive_range = 0.0;
1150 kernel->values=(MagickRealType *) MagickAssumeAligned(
1151 AcquireAlignedMemory(kernel->width,kernel->height*
1152 sizeof(*kernel->values)));
1153 if (kernel->values == (MagickRealType *) NULL)
1154 return(DestroyKernelInfo(kernel));
1157 #define KernelRank 3
1158 /* Formula derived from GetBlurKernel() in "effect.c" (plus bug fix).
1159 ** It generates a gaussian 3 times the width, and compresses it into
1160 ** the expected range. This produces a closer normalization of the
1161 ** resulting kernel, especially for very low sigma values.
1162 ** As such while wierd it is prefered.
1164 ** I am told this method originally came from Photoshop.
1166 ** A properly normalized curve is generated (apart from edge clipping)
1167 ** even though we later normalize the result (for edge clipping)
1168 ** to allow the correct generation of a "Difference of Blurs".
1172 v = (ssize_t) (kernel->width*KernelRank-1)/2; /* start/end points to fit range */
1173 (void) ResetMagickMemory(kernel->values,0, (size_t)
1174 kernel->width*kernel->height*sizeof(*kernel->values));
1175 /* Calculate a Positive 1D Gaussian */
1176 if ( sigma > MagickEpsilon )
1177 { sigma *= KernelRank; /* simplify loop expressions */
1178 alpha = 1.0/(2.0*sigma*sigma);
1179 beta= (double) (1.0/(MagickSQ2PI*sigma ));
1180 for ( u=-v; u <= v; u++) {
1181 kernel->values[(u+v)/KernelRank] +=
1182 exp(-((double)(u*u))*alpha)*beta;
1185 else /* special case - generate a unity kernel */
1186 kernel->values[kernel->x+kernel->y*kernel->width] = 1.0;
1188 /* Direct calculation without curve averaging
1189 This is equivelent to a KernelRank of 1 */
1191 /* Calculate a Positive Gaussian */
1192 if ( sigma > MagickEpsilon )
1193 { alpha = 1.0/(2.0*sigma*sigma); /* simplify loop expressions */
1194 beta = 1.0/(MagickSQ2PI*sigma);
1195 for ( i=0, u=-kernel->x; u <= (ssize_t)kernel->x; u++, i++)
1196 kernel->values[i] = exp(-((double)(u*u))*alpha)*beta;
1198 else /* special case - generate a unity kernel */
1199 { (void) ResetMagickMemory(kernel->values,0, (size_t)
1200 kernel->width*kernel->height*sizeof(*kernel->values));
1201 kernel->values[kernel->x+kernel->y*kernel->width] = 1.0;
1204 /* Note the above kernel may have been 'clipped' by a user defined
1205 ** radius, producing a smaller (darker) kernel. Also for very small
1206 ** sigma's (> 0.1) the central value becomes larger than one, as a
1207 ** result of not generating a actual 'discrete' kernel, and thus
1208 ** producing a very bright 'impulse'.
1210 ** Becuase of these two factors Normalization is required!
1213 /* Normalize the 1D Gaussian Kernel
1215 ** NB: a CorrelateNormalize performs a normal Normalize if
1216 ** there are no negative values.
1218 CalcKernelMetaData(kernel); /* the other kernel meta-data */
1219 ScaleKernelInfo(kernel, 1.0, CorrelateNormalizeValue);
1221 /* rotate the 1D kernel by given angle */
1222 RotateKernelInfo(kernel, args->xi );
1227 sigma = fabs(args->sigma),
1230 if ( args->rho < 1.0 )
1231 kernel->width = (GetOptimalKernelWidth1D(args->rho,sigma)-1)/2+1;
1233 kernel->width = (size_t)args->rho;
1234 kernel->x = kernel->y = 0;
1236 kernel->negative_range = kernel->positive_range = 0.0;
1237 kernel->values=(MagickRealType *) MagickAssumeAligned(
1238 AcquireAlignedMemory(kernel->width,kernel->height*
1239 sizeof(*kernel->values)));
1240 if (kernel->values == (MagickRealType *) NULL)
1241 return(DestroyKernelInfo(kernel));
1243 /* A comet blur is half a 1D gaussian curve, so that the object is
1244 ** blurred in one direction only. This may not be quite the right
1245 ** curve to use so may change in the future. The function must be
1246 ** normalised after generation, which also resolves any clipping.
1248 ** As we are normalizing and not subtracting gaussians,
1249 ** there is no need for a divisor in the gaussian formula
1251 ** It is less comples
1253 if ( sigma > MagickEpsilon )
1256 #define KernelRank 3
1257 v = (ssize_t) kernel->width*KernelRank; /* start/end points */
1258 (void) ResetMagickMemory(kernel->values,0, (size_t)
1259 kernel->width*sizeof(*kernel->values));
1260 sigma *= KernelRank; /* simplify the loop expression */
1261 A = 1.0/(2.0*sigma*sigma);
1262 /* B = 1.0/(MagickSQ2PI*sigma); */
1263 for ( u=0; u < v; u++) {
1264 kernel->values[u/KernelRank] +=
1265 exp(-((double)(u*u))*A);
1266 /* exp(-((double)(i*i))/2.0*sigma*sigma)/(MagickSQ2PI*sigma); */
1268 for (i=0; i < (ssize_t) kernel->width; i++)
1269 kernel->positive_range += kernel->values[i];
1271 A = 1.0/(2.0*sigma*sigma); /* simplify the loop expression */
1272 /* B = 1.0/(MagickSQ2PI*sigma); */
1273 for ( i=0; i < (ssize_t) kernel->width; i++)
1274 kernel->positive_range +=
1275 kernel->values[i] = exp(-((double)(i*i))*A);
1276 /* exp(-((double)(i*i))/2.0*sigma*sigma)/(MagickSQ2PI*sigma); */
1279 else /* special case - generate a unity kernel */
1280 { (void) ResetMagickMemory(kernel->values,0, (size_t)
1281 kernel->width*kernel->height*sizeof(*kernel->values));
1282 kernel->values[kernel->x+kernel->y*kernel->width] = 1.0;
1283 kernel->positive_range = 1.0;
1286 kernel->minimum = 0.0;
1287 kernel->maximum = kernel->values[0];
1288 kernel->negative_range = 0.0;
1290 ScaleKernelInfo(kernel, 1.0, NormalizeValue); /* Normalize */
1291 RotateKernelInfo(kernel, args->xi); /* Rotate by angle */
1294 case BinomialKernel:
1299 if (args->rho < 1.0)
1300 kernel->width = kernel->height = 3; /* default radius = 1 */
1302 kernel->width = kernel->height = ((size_t)args->rho)*2+1;
1303 kernel->x = kernel->y = (ssize_t) (kernel->width-1)/2;
1305 order_f = fact(kernel->width-1);
1307 kernel->values=(MagickRealType *) MagickAssumeAligned(
1308 AcquireAlignedMemory(kernel->width,kernel->height*
1309 sizeof(*kernel->values)));
1310 if (kernel->values == (MagickRealType *) NULL)
1311 return(DestroyKernelInfo(kernel));
1313 /* set all kernel values within diamond area to scale given */
1314 for ( i=0, v=0; v < (ssize_t)kernel->height; v++)
1316 alpha = order_f / ( fact((size_t) v) * fact(kernel->height-v-1) );
1317 for ( u=0; u < (ssize_t)kernel->width; u++, i++)
1318 kernel->positive_range += kernel->values[i] = (double)
1319 (alpha * order_f / ( fact((size_t) u) * fact(kernel->height-u-1) ));
1321 kernel->minimum = 1.0;
1322 kernel->maximum = kernel->values[kernel->x+kernel->y*kernel->width];
1323 kernel->negative_range = 0.0;
1328 Convolution Kernels - Well Known Named Constant Kernels
1330 case LaplacianKernel:
1331 { switch ( (int) args->rho ) {
1333 default: /* laplacian square filter -- default */
1334 kernel=ParseKernelArray("3: -1,-1,-1 -1,8,-1 -1,-1,-1");
1336 case 1: /* laplacian diamond filter */
1337 kernel=ParseKernelArray("3: 0,-1,0 -1,4,-1 0,-1,0");
1340 kernel=ParseKernelArray("3: -2,1,-2 1,4,1 -2,1,-2");
1343 kernel=ParseKernelArray("3: 1,-2,1 -2,4,-2 1,-2,1");
1345 case 5: /* a 5x5 laplacian */
1346 kernel=ParseKernelArray(
1347 "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");
1349 case 7: /* a 7x7 laplacian */
1350 kernel=ParseKernelArray(
1351 "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" );
1353 case 15: /* a 5x5 LoG (sigma approx 1.4) */
1354 kernel=ParseKernelArray(
1355 "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");
1357 case 19: /* a 9x9 LoG (sigma approx 1.4) */
1358 /* http://www.cscjournals.org/csc/manuscript/Journals/IJIP/volume3/Issue1/IJIP-15.pdf */
1359 kernel=ParseKernelArray(
1360 "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");
1363 if (kernel == (KernelInfo *) NULL)
1365 kernel->type = type;
1369 { /* Simple Sobel Kernel */
1370 kernel=ParseKernelArray("3: 1,0,-1 2,0,-2 1,0,-1");
1371 if (kernel == (KernelInfo *) NULL)
1373 kernel->type = type;
1374 RotateKernelInfo(kernel, args->rho);
1379 kernel=ParseKernelArray("3: 0,0,0 1,-1,0 0,0,0");
1380 if (kernel == (KernelInfo *) NULL)
1382 kernel->type = type;
1383 RotateKernelInfo(kernel, args->rho);
1388 kernel=ParseKernelArray("3: 1,0,-1 1,0,-1 1,0,-1");
1389 if (kernel == (KernelInfo *) NULL)
1391 kernel->type = type;
1392 RotateKernelInfo(kernel, args->rho);
1397 kernel=ParseKernelArray("3: 1,1,-1 1,-2,-1 1,1,-1");
1398 if (kernel == (KernelInfo *) NULL)
1400 kernel->type = type;
1401 RotateKernelInfo(kernel, args->rho);
1406 kernel=ParseKernelArray("3: 5,-3,-3 5,0,-3 5,-3,-3");
1407 if (kernel == (KernelInfo *) NULL)
1409 kernel->type = type;
1410 RotateKernelInfo(kernel, args->rho);
1413 case FreiChenKernel:
1414 /* Direction is set to be left to right positive */
1415 /* http://www.math.tau.ac.il/~turkel/notes/edge_detectors.pdf -- RIGHT? */
1416 /* http://ltswww.epfl.ch/~courstiv/exos_labos/sol3.pdf -- WRONG? */
1417 { switch ( (int) args->rho ) {
1420 kernel=ParseKernelArray("3: 1,0,-1 2,0,-2 1,0,-1");
1421 if (kernel == (KernelInfo *) NULL)
1423 kernel->type = type;
1424 kernel->values[3] = +(MagickRealType) MagickSQ2;
1425 kernel->values[5] = -(MagickRealType) MagickSQ2;
1426 CalcKernelMetaData(kernel); /* recalculate meta-data */
1429 kernel=ParseKernelArray("3: 1,2,0 2,0,-2 0,-2,-1");
1430 if (kernel == (KernelInfo *) NULL)
1432 kernel->type = type;
1433 kernel->values[1] = kernel->values[3]= +(MagickRealType) MagickSQ2;
1434 kernel->values[5] = kernel->values[7]= -(MagickRealType) MagickSQ2;
1435 CalcKernelMetaData(kernel); /* recalculate meta-data */
1436 ScaleKernelInfo(kernel, (double) (1.0/2.0*MagickSQ2), NoValue);
1439 kernel=AcquireKernelInfo("FreiChen:11;FreiChen:12;FreiChen:13;FreiChen:14;FreiChen:15;FreiChen:16;FreiChen:17;FreiChen:18;FreiChen:19");
1440 if (kernel == (KernelInfo *) NULL)
1445 kernel=ParseKernelArray("3: 1,0,-1 2,0,-2 1,0,-1");
1446 if (kernel == (KernelInfo *) NULL)
1448 kernel->type = type;
1449 kernel->values[3] = +(MagickRealType) MagickSQ2;
1450 kernel->values[5] = -(MagickRealType) MagickSQ2;
1451 CalcKernelMetaData(kernel); /* recalculate meta-data */
1452 ScaleKernelInfo(kernel, (double) (1.0/2.0*MagickSQ2), NoValue);
1455 kernel=ParseKernelArray("3: 1,2,1 0,0,0 1,2,1");
1456 if (kernel == (KernelInfo *) NULL)
1458 kernel->type = type;
1459 kernel->values[1] = +(MagickRealType) MagickSQ2;
1460 kernel->values[7] = +(MagickRealType) MagickSQ2;
1461 CalcKernelMetaData(kernel);
1462 ScaleKernelInfo(kernel, (double) (1.0/2.0*MagickSQ2), NoValue);
1465 kernel=ParseKernelArray("3: 2,-1,0 -1,0,1 0,1,-2");
1466 if (kernel == (KernelInfo *) NULL)
1468 kernel->type = type;
1469 kernel->values[0] = +(MagickRealType) MagickSQ2;
1470 kernel->values[8] = -(MagickRealType) MagickSQ2;
1471 CalcKernelMetaData(kernel);
1472 ScaleKernelInfo(kernel, (double) (1.0/2.0*MagickSQ2), NoValue);
1475 kernel=ParseKernelArray("3: 0,1,-2 -1,0,1 2,-1,0");
1476 if (kernel == (KernelInfo *) NULL)
1478 kernel->type = type;
1479 kernel->values[2] = -(MagickRealType) MagickSQ2;
1480 kernel->values[6] = +(MagickRealType) MagickSQ2;
1481 CalcKernelMetaData(kernel);
1482 ScaleKernelInfo(kernel, (double) (1.0/2.0*MagickSQ2), NoValue);
1485 kernel=ParseKernelArray("3: 0,-1,0 1,0,1 0,-1,0");
1486 if (kernel == (KernelInfo *) NULL)
1488 kernel->type = type;
1489 ScaleKernelInfo(kernel, 1.0/2.0, NoValue);
1492 kernel=ParseKernelArray("3: 1,0,-1 0,0,0 -1,0,1");
1493 if (kernel == (KernelInfo *) NULL)
1495 kernel->type = type;
1496 ScaleKernelInfo(kernel, 1.0/2.0, NoValue);
1499 kernel=ParseKernelArray("3: 1,-2,1 -2,4,-2 -1,-2,1");
1500 if (kernel == (KernelInfo *) NULL)
1502 kernel->type = type;
1503 ScaleKernelInfo(kernel, 1.0/6.0, NoValue);
1506 kernel=ParseKernelArray("3: -2,1,-2 1,4,1 -2,1,-2");
1507 if (kernel == (KernelInfo *) NULL)
1509 kernel->type = type;
1510 ScaleKernelInfo(kernel, 1.0/6.0, NoValue);
1513 kernel=ParseKernelArray("3: 1,1,1 1,1,1 1,1,1");
1514 if (kernel == (KernelInfo *) NULL)
1516 kernel->type = type;
1517 ScaleKernelInfo(kernel, 1.0/3.0, NoValue);
1520 if ( fabs(args->sigma) >= MagickEpsilon )
1521 /* Rotate by correctly supplied 'angle' */
1522 RotateKernelInfo(kernel, args->sigma);
1523 else if ( args->rho > 30.0 || args->rho < -30.0 )
1524 /* Rotate by out of bounds 'type' */
1525 RotateKernelInfo(kernel, args->rho);
1530 Boolean or Shaped Kernels
1534 if (args->rho < 1.0)
1535 kernel->width = kernel->height = 3; /* default radius = 1 */
1537 kernel->width = kernel->height = ((size_t)args->rho)*2+1;
1538 kernel->x = kernel->y = (ssize_t) (kernel->width-1)/2;
1540 kernel->values=(MagickRealType *) MagickAssumeAligned(
1541 AcquireAlignedMemory(kernel->width,kernel->height*
1542 sizeof(*kernel->values)));
1543 if (kernel->values == (MagickRealType *) NULL)
1544 return(DestroyKernelInfo(kernel));
1546 /* set all kernel values within diamond area to scale given */
1547 for ( i=0, v=-kernel->y; v <= (ssize_t)kernel->y; v++)
1548 for ( u=-kernel->x; u <= (ssize_t)kernel->x; u++, i++)
1549 if ( (labs((long) u)+labs((long) v)) <= (long) kernel->x)
1550 kernel->positive_range += kernel->values[i] = args->sigma;
1552 kernel->values[i] = nan;
1553 kernel->minimum = kernel->maximum = args->sigma; /* a flat shape */
1557 case RectangleKernel:
1560 if ( type == SquareKernel )
1562 if (args->rho < 1.0)
1563 kernel->width = kernel->height = 3; /* default radius = 1 */
1565 kernel->width = kernel->height = (size_t) (2*args->rho+1);
1566 kernel->x = kernel->y = (ssize_t) (kernel->width-1)/2;
1567 scale = args->sigma;
1570 /* NOTE: user defaults set in "AcquireKernelInfo()" */
1571 if ( args->rho < 1.0 || args->sigma < 1.0 )
1572 return(DestroyKernelInfo(kernel)); /* invalid args given */
1573 kernel->width = (size_t)args->rho;
1574 kernel->height = (size_t)args->sigma;
1575 if ( args->xi < 0.0 || args->xi > (double)kernel->width ||
1576 args->psi < 0.0 || args->psi > (double)kernel->height )
1577 return(DestroyKernelInfo(kernel)); /* invalid args given */
1578 kernel->x = (ssize_t) args->xi;
1579 kernel->y = (ssize_t) args->psi;
1582 kernel->values=(MagickRealType *) MagickAssumeAligned(
1583 AcquireAlignedMemory(kernel->width,kernel->height*
1584 sizeof(*kernel->values)));
1585 if (kernel->values == (MagickRealType *) NULL)
1586 return(DestroyKernelInfo(kernel));
1588 /* set all kernel values to scale given */
1589 u=(ssize_t) (kernel->width*kernel->height);
1590 for ( i=0; i < u; i++)
1591 kernel->values[i] = scale;
1592 kernel->minimum = kernel->maximum = scale; /* a flat shape */
1593 kernel->positive_range = scale*u;
1598 if (args->rho < 1.0)
1599 kernel->width = kernel->height = 5; /* default radius = 2 */
1601 kernel->width = kernel->height = ((size_t)args->rho)*2+1;
1602 kernel->x = kernel->y = (ssize_t) (kernel->width-1)/2;
1604 kernel->values=(MagickRealType *) MagickAssumeAligned(
1605 AcquireAlignedMemory(kernel->width,kernel->height*
1606 sizeof(*kernel->values)));
1607 if (kernel->values == (MagickRealType *) NULL)
1608 return(DestroyKernelInfo(kernel));
1610 for ( i=0, v=-kernel->y; v <= (ssize_t)kernel->y; v++)
1611 for ( u=-kernel->x; u <= (ssize_t)kernel->x; u++, i++)
1612 if ( (labs((long) u)+labs((long) v)) <=
1613 ((long)kernel->x + (long)(kernel->x/2)) )
1614 kernel->positive_range += kernel->values[i] = args->sigma;
1616 kernel->values[i] = nan;
1617 kernel->minimum = kernel->maximum = args->sigma; /* a flat shape */
1623 limit = (ssize_t)(args->rho*args->rho);
1625 if (args->rho < 0.4) /* default radius approx 4.3 */
1626 kernel->width = kernel->height = 9L, limit = 18L;
1628 kernel->width = kernel->height = (size_t)fabs(args->rho)*2+1;
1629 kernel->x = kernel->y = (ssize_t) (kernel->width-1)/2;
1631 kernel->values=(MagickRealType *) MagickAssumeAligned(
1632 AcquireAlignedMemory(kernel->width,kernel->height*
1633 sizeof(*kernel->values)));
1634 if (kernel->values == (MagickRealType *) NULL)
1635 return(DestroyKernelInfo(kernel));
1637 for ( i=0, v=-kernel->y; v <= (ssize_t)kernel->y; v++)
1638 for ( u=-kernel->x; u <= (ssize_t)kernel->x; u++, i++)
1639 if ((u*u+v*v) <= limit)
1640 kernel->positive_range += kernel->values[i] = args->sigma;
1642 kernel->values[i] = nan;
1643 kernel->minimum = kernel->maximum = args->sigma; /* a flat shape */
1648 if (args->rho < 1.0)
1649 kernel->width = kernel->height = 5; /* default radius 2 */
1651 kernel->width = kernel->height = ((size_t)args->rho)*2+1;
1652 kernel->x = kernel->y = (ssize_t) (kernel->width-1)/2;
1654 kernel->values=(MagickRealType *) MagickAssumeAligned(
1655 AcquireAlignedMemory(kernel->width,kernel->height*
1656 sizeof(*kernel->values)));
1657 if (kernel->values == (MagickRealType *) NULL)
1658 return(DestroyKernelInfo(kernel));
1660 /* set all kernel values along axises to given scale */
1661 for ( i=0, v=-kernel->y; v <= (ssize_t)kernel->y; v++)
1662 for ( u=-kernel->x; u <= (ssize_t)kernel->x; u++, i++)
1663 kernel->values[i] = (u == 0 || v == 0) ? args->sigma : nan;
1664 kernel->minimum = kernel->maximum = args->sigma; /* a flat shape */
1665 kernel->positive_range = args->sigma*(kernel->width*2.0 - 1.0);
1670 if (args->rho < 1.0)
1671 kernel->width = kernel->height = 5; /* default radius 2 */
1673 kernel->width = kernel->height = ((size_t)args->rho)*2+1;
1674 kernel->x = kernel->y = (ssize_t) (kernel->width-1)/2;
1676 kernel->values=(MagickRealType *) MagickAssumeAligned(
1677 AcquireAlignedMemory(kernel->width,kernel->height*
1678 sizeof(*kernel->values)));
1679 if (kernel->values == (MagickRealType *) NULL)
1680 return(DestroyKernelInfo(kernel));
1682 /* set all kernel values along axises to given scale */
1683 for ( i=0, v=-kernel->y; v <= (ssize_t)kernel->y; v++)
1684 for ( u=-kernel->x; u <= (ssize_t)kernel->x; u++, i++)
1685 kernel->values[i] = (u == v || u == -v) ? args->sigma : nan;
1686 kernel->minimum = kernel->maximum = args->sigma; /* a flat shape */
1687 kernel->positive_range = args->sigma*(kernel->width*2.0 - 1.0);
1701 if (args->rho < args->sigma)
1703 kernel->width = ((size_t)args->sigma)*2+1;
1704 limit1 = (ssize_t)(args->rho*args->rho);
1705 limit2 = (ssize_t)(args->sigma*args->sigma);
1709 kernel->width = ((size_t)args->rho)*2+1;
1710 limit1 = (ssize_t)(args->sigma*args->sigma);
1711 limit2 = (ssize_t)(args->rho*args->rho);
1714 kernel->width = 7L, limit1 = 7L, limit2 = 11L;
1716 kernel->height = kernel->width;
1717 kernel->x = kernel->y = (ssize_t) (kernel->width-1)/2;
1718 kernel->values=(MagickRealType *) MagickAssumeAligned(
1719 AcquireAlignedMemory(kernel->width,kernel->height*
1720 sizeof(*kernel->values)));
1721 if (kernel->values == (MagickRealType *) NULL)
1722 return(DestroyKernelInfo(kernel));
1724 /* set a ring of points of 'scale' ( 0.0 for PeaksKernel ) */
1725 scale = (ssize_t) (( type == PeaksKernel) ? 0.0 : args->xi);
1726 for ( i=0, v= -kernel->y; v <= (ssize_t)kernel->y; v++)
1727 for ( u=-kernel->x; u <= (ssize_t)kernel->x; u++, i++)
1728 { ssize_t radius=u*u+v*v;
1729 if (limit1 < radius && radius <= limit2)
1730 kernel->positive_range += kernel->values[i] = (double) scale;
1732 kernel->values[i] = nan;
1734 kernel->minimum = kernel->maximum = (double) scale;
1735 if ( type == PeaksKernel ) {
1736 /* set the central point in the middle */
1737 kernel->values[kernel->x+kernel->y*kernel->width] = 1.0;
1738 kernel->positive_range = 1.0;
1739 kernel->maximum = 1.0;
1745 kernel=AcquireKernelInfo("ThinSE:482");
1746 if (kernel == (KernelInfo *) NULL)
1748 kernel->type = type;
1749 ExpandMirrorKernelInfo(kernel); /* mirror expansion of kernels */
1754 kernel=AcquireKernelInfo("ThinSE:87");
1755 if (kernel == (KernelInfo *) NULL)
1757 kernel->type = type;
1758 ExpandRotateKernelInfo(kernel, 90.0); /* Expand 90 degree rotations */
1761 case DiagonalsKernel:
1763 switch ( (int) args->rho ) {
1768 kernel=ParseKernelArray("3: 0,0,0 0,-,1 1,1,-");
1769 if (kernel == (KernelInfo *) NULL)
1771 kernel->type = type;
1772 new_kernel=ParseKernelArray("3: 0,0,1 0,-,1 0,1,-");
1773 if (new_kernel == (KernelInfo *) NULL)
1774 return(DestroyKernelInfo(kernel));
1775 new_kernel->type = type;
1776 LastKernelInfo(kernel)->next = new_kernel;
1777 ExpandMirrorKernelInfo(kernel);
1781 kernel=ParseKernelArray("3: 0,0,0 0,-,1 1,1,-");
1784 kernel=ParseKernelArray("3: 0,0,1 0,-,1 0,1,-");
1787 if (kernel == (KernelInfo *) NULL)
1789 kernel->type = type;
1790 RotateKernelInfo(kernel, args->sigma);
1793 case LineEndsKernel:
1794 { /* Kernels for finding the end of thin lines */
1795 switch ( (int) args->rho ) {
1798 /* set of kernels to find all end of lines */
1799 return(AcquireKernelInfo("LineEnds:1>;LineEnds:2>"));
1801 /* kernel for 4-connected line ends - no rotation */
1802 kernel=ParseKernelArray("3: 0,0,- 0,1,1 0,0,-");
1805 /* kernel to add for 8-connected lines - no rotation */
1806 kernel=ParseKernelArray("3: 0,0,0 0,1,0 0,0,1");
1809 /* kernel to add for orthogonal line ends - does not find corners */
1810 kernel=ParseKernelArray("3: 0,0,0 0,1,1 0,0,0");
1813 /* traditional line end - fails on last T end */
1814 kernel=ParseKernelArray("3: 0,0,0 0,1,- 0,0,-");
1817 if (kernel == (KernelInfo *) NULL)
1819 kernel->type = type;
1820 RotateKernelInfo(kernel, args->sigma);
1823 case LineJunctionsKernel:
1824 { /* kernels for finding the junctions of multiple lines */
1825 switch ( (int) args->rho ) {
1828 /* set of kernels to find all line junctions */
1829 return(AcquireKernelInfo("LineJunctions:1@;LineJunctions:2>"));
1832 kernel=ParseKernelArray("3: 1,-,1 -,1,- -,1,-");
1835 /* Diagonal T Junctions */
1836 kernel=ParseKernelArray("3: 1,-,- -,1,- 1,-,1");
1839 /* Orthogonal T Junctions */
1840 kernel=ParseKernelArray("3: -,-,- 1,1,1 -,1,-");
1843 /* Diagonal X Junctions */
1844 kernel=ParseKernelArray("3: 1,-,1 -,1,- 1,-,1");
1847 /* Orthogonal X Junctions - minimal diamond kernel */
1848 kernel=ParseKernelArray("3: -,1,- 1,1,1 -,1,-");
1851 if (kernel == (KernelInfo *) NULL)
1853 kernel->type = type;
1854 RotateKernelInfo(kernel, args->sigma);
1858 { /* Ridges - Ridge finding kernels */
1861 switch ( (int) args->rho ) {
1864 kernel=ParseKernelArray("3x1:0,1,0");
1865 if (kernel == (KernelInfo *) NULL)
1867 kernel->type = type;
1868 ExpandRotateKernelInfo(kernel, 90.0); /* 2 rotated kernels (symmetrical) */
1871 kernel=ParseKernelArray("4x1:0,1,1,0");
1872 if (kernel == (KernelInfo *) NULL)
1874 kernel->type = type;
1875 ExpandRotateKernelInfo(kernel, 90.0); /* 4 rotated kernels */
1877 /* Kernels to find a stepped 'thick' line, 4 rotates + mirrors */
1878 /* Unfortunatally we can not yet rotate a non-square kernel */
1879 /* But then we can't flip a non-symetrical kernel either */
1880 new_kernel=ParseKernelArray("4x3+1+1:0,1,1,- -,1,1,- -,1,1,0");
1881 if (new_kernel == (KernelInfo *) NULL)
1882 return(DestroyKernelInfo(kernel));
1883 new_kernel->type = type;
1884 LastKernelInfo(kernel)->next = new_kernel;
1885 new_kernel=ParseKernelArray("4x3+2+1:0,1,1,- -,1,1,- -,1,1,0");
1886 if (new_kernel == (KernelInfo *) NULL)
1887 return(DestroyKernelInfo(kernel));
1888 new_kernel->type = type;
1889 LastKernelInfo(kernel)->next = new_kernel;
1890 new_kernel=ParseKernelArray("4x3+1+1:-,1,1,0 -,1,1,- 0,1,1,-");
1891 if (new_kernel == (KernelInfo *) NULL)
1892 return(DestroyKernelInfo(kernel));
1893 new_kernel->type = type;
1894 LastKernelInfo(kernel)->next = new_kernel;
1895 new_kernel=ParseKernelArray("4x3+2+1:-,1,1,0 -,1,1,- 0,1,1,-");
1896 if (new_kernel == (KernelInfo *) NULL)
1897 return(DestroyKernelInfo(kernel));
1898 new_kernel->type = type;
1899 LastKernelInfo(kernel)->next = new_kernel;
1900 new_kernel=ParseKernelArray("3x4+1+1:0,-,- 1,1,1 1,1,1 -,-,0");
1901 if (new_kernel == (KernelInfo *) NULL)
1902 return(DestroyKernelInfo(kernel));
1903 new_kernel->type = type;
1904 LastKernelInfo(kernel)->next = new_kernel;
1905 new_kernel=ParseKernelArray("3x4+1+2:0,-,- 1,1,1 1,1,1 -,-,0");
1906 if (new_kernel == (KernelInfo *) NULL)
1907 return(DestroyKernelInfo(kernel));
1908 new_kernel->type = type;
1909 LastKernelInfo(kernel)->next = new_kernel;
1910 new_kernel=ParseKernelArray("3x4+1+1:-,-,0 1,1,1 1,1,1 0,-,-");
1911 if (new_kernel == (KernelInfo *) NULL)
1912 return(DestroyKernelInfo(kernel));
1913 new_kernel->type = type;
1914 LastKernelInfo(kernel)->next = new_kernel;
1915 new_kernel=ParseKernelArray("3x4+1+2:-,-,0 1,1,1 1,1,1 0,-,-");
1916 if (new_kernel == (KernelInfo *) NULL)
1917 return(DestroyKernelInfo(kernel));
1918 new_kernel->type = type;
1919 LastKernelInfo(kernel)->next = new_kernel;
1924 case ConvexHullKernel:
1928 /* first set of 8 kernels */
1929 kernel=ParseKernelArray("3: 1,1,- 1,0,- 1,-,0");
1930 if (kernel == (KernelInfo *) NULL)
1932 kernel->type = type;
1933 ExpandRotateKernelInfo(kernel, 90.0);
1934 /* append the mirror versions too - no flip function yet */
1935 new_kernel=ParseKernelArray("3: 1,1,1 1,0,- -,-,0");
1936 if (new_kernel == (KernelInfo *) NULL)
1937 return(DestroyKernelInfo(kernel));
1938 new_kernel->type = type;
1939 ExpandRotateKernelInfo(new_kernel, 90.0);
1940 LastKernelInfo(kernel)->next = new_kernel;
1943 case SkeletonKernel:
1945 switch ( (int) args->rho ) {
1948 /* Traditional Skeleton...
1949 ** A cyclically rotated single kernel
1951 kernel=AcquireKernelInfo("ThinSE:482");
1952 if (kernel == (KernelInfo *) NULL)
1954 kernel->type = type;
1955 ExpandRotateKernelInfo(kernel, 45.0); /* 8 rotations */
1958 /* HIPR Variation of the cyclic skeleton
1959 ** Corners of the traditional method made more forgiving,
1960 ** but the retain the same cyclic order.
1962 kernel=AcquireKernelInfo("ThinSE:482; ThinSE:87x90;");
1963 if (kernel == (KernelInfo *) NULL)
1965 if (kernel->next == (KernelInfo *) NULL)
1966 return(DestroyKernelInfo(kernel));
1967 kernel->type = type;
1968 kernel->next->type = type;
1969 ExpandRotateKernelInfo(kernel, 90.0); /* 4 rotations of the 2 kernels */
1972 /* Dan Bloomberg Skeleton, from his paper on 3x3 thinning SE's
1973 ** "Connectivity-Preserving Morphological Image Thransformations"
1974 ** by Dan S. Bloomberg, available on Leptonica, Selected Papers,
1975 ** http://www.leptonica.com/papers/conn.pdf
1977 kernel=AcquireKernelInfo(
1978 "ThinSE:41; ThinSE:42; ThinSE:43");
1979 if (kernel == (KernelInfo *) NULL)
1981 kernel->type = type;
1982 kernel->next->type = type;
1983 kernel->next->next->type = type;
1984 ExpandMirrorKernelInfo(kernel); /* 12 kernels total */
1990 { /* Special kernels for general thinning, while preserving connections
1991 ** "Connectivity-Preserving Morphological Image Thransformations"
1992 ** by Dan S. Bloomberg, available on Leptonica, Selected Papers,
1993 ** http://www.leptonica.com/papers/conn.pdf
1995 ** http://tpgit.github.com/Leptonica/ccthin_8c_source.html
1997 ** Note kernels do not specify the origin pixel, allowing them
1998 ** to be used for both thickening and thinning operations.
2000 switch ( (int) args->rho ) {
2001 /* SE for 4-connected thinning */
2002 case 41: /* SE_4_1 */
2003 kernel=ParseKernelArray("3: -,-,1 0,-,1 -,-,1");
2005 case 42: /* SE_4_2 */
2006 kernel=ParseKernelArray("3: -,-,1 0,-,1 -,0,-");
2008 case 43: /* SE_4_3 */
2009 kernel=ParseKernelArray("3: -,0,- 0,-,1 -,-,1");
2011 case 44: /* SE_4_4 */
2012 kernel=ParseKernelArray("3: -,0,- 0,-,1 -,0,-");
2014 case 45: /* SE_4_5 */
2015 kernel=ParseKernelArray("3: -,0,1 0,-,1 -,0,-");
2017 case 46: /* SE_4_6 */
2018 kernel=ParseKernelArray("3: -,0,- 0,-,1 -,0,1");
2020 case 47: /* SE_4_7 */
2021 kernel=ParseKernelArray("3: -,1,1 0,-,1 -,0,-");
2023 case 48: /* SE_4_8 */
2024 kernel=ParseKernelArray("3: -,-,1 0,-,1 0,-,1");
2026 case 49: /* SE_4_9 */
2027 kernel=ParseKernelArray("3: 0,-,1 0,-,1 -,-,1");
2029 /* SE for 8-connected thinning - negatives of the above */
2030 case 81: /* SE_8_0 */
2031 kernel=ParseKernelArray("3: -,1,- 0,-,1 -,1,-");
2033 case 82: /* SE_8_2 */
2034 kernel=ParseKernelArray("3: -,1,- 0,-,1 0,-,-");
2036 case 83: /* SE_8_3 */
2037 kernel=ParseKernelArray("3: 0,-,- 0,-,1 -,1,-");
2039 case 84: /* SE_8_4 */
2040 kernel=ParseKernelArray("3: 0,-,- 0,-,1 0,-,-");
2042 case 85: /* SE_8_5 */
2043 kernel=ParseKernelArray("3: 0,-,1 0,-,1 0,-,-");
2045 case 86: /* SE_8_6 */
2046 kernel=ParseKernelArray("3: 0,-,- 0,-,1 0,-,1");
2048 case 87: /* SE_8_7 */
2049 kernel=ParseKernelArray("3: -,1,- 0,-,1 0,0,-");
2051 case 88: /* SE_8_8 */
2052 kernel=ParseKernelArray("3: -,1,- 0,-,1 0,1,-");
2054 case 89: /* SE_8_9 */
2055 kernel=ParseKernelArray("3: 0,1,- 0,-,1 -,1,-");
2057 /* Special combined SE kernels */
2058 case 423: /* SE_4_2 , SE_4_3 Combined Kernel */
2059 kernel=ParseKernelArray("3: -,-,1 0,-,- -,0,-");
2061 case 823: /* SE_8_2 , SE_8_3 Combined Kernel */
2062 kernel=ParseKernelArray("3: -,1,- -,-,1 0,-,-");
2064 case 481: /* SE_48_1 - General Connected Corner Kernel */
2065 kernel=ParseKernelArray("3: -,1,1 0,-,1 0,0,-");
2068 case 482: /* SE_48_2 - General Edge Kernel */
2069 kernel=ParseKernelArray("3: 0,-,1 0,-,1 0,-,1");
2072 if (kernel == (KernelInfo *) NULL)
2074 kernel->type = type;
2075 RotateKernelInfo(kernel, args->sigma);
2079 Distance Measuring Kernels
2081 case ChebyshevKernel:
2083 if (args->rho < 1.0)
2084 kernel->width = kernel->height = 3; /* default radius = 1 */
2086 kernel->width = kernel->height = ((size_t)args->rho)*2+1;
2087 kernel->x = kernel->y = (ssize_t) (kernel->width-1)/2;
2089 kernel->values=(MagickRealType *) MagickAssumeAligned(
2090 AcquireAlignedMemory(kernel->width,kernel->height*
2091 sizeof(*kernel->values)));
2092 if (kernel->values == (MagickRealType *) NULL)
2093 return(DestroyKernelInfo(kernel));
2095 for ( i=0, v=-kernel->y; v <= (ssize_t)kernel->y; v++)
2096 for ( u=-kernel->x; u <= (ssize_t)kernel->x; u++, i++)
2097 kernel->positive_range += ( kernel->values[i] =
2098 args->sigma*MagickMax(fabs((double)u),fabs((double)v)) );
2099 kernel->maximum = kernel->values[0];
2102 case ManhattanKernel:
2104 if (args->rho < 1.0)
2105 kernel->width = kernel->height = 3; /* default radius = 1 */
2107 kernel->width = kernel->height = ((size_t)args->rho)*2+1;
2108 kernel->x = kernel->y = (ssize_t) (kernel->width-1)/2;
2110 kernel->values=(MagickRealType *) MagickAssumeAligned(
2111 AcquireAlignedMemory(kernel->width,kernel->height*
2112 sizeof(*kernel->values)));
2113 if (kernel->values == (MagickRealType *) NULL)
2114 return(DestroyKernelInfo(kernel));
2116 for ( i=0, v=-kernel->y; v <= (ssize_t)kernel->y; v++)
2117 for ( u=-kernel->x; u <= (ssize_t)kernel->x; u++, i++)
2118 kernel->positive_range += ( kernel->values[i] =
2119 args->sigma*(labs((long) u)+labs((long) v)) );
2120 kernel->maximum = kernel->values[0];
2123 case OctagonalKernel:
2125 if (args->rho < 2.0)
2126 kernel->width = kernel->height = 5; /* default/minimum radius = 2 */
2128 kernel->width = kernel->height = ((size_t)args->rho)*2+1;
2129 kernel->x = kernel->y = (ssize_t) (kernel->width-1)/2;
2131 kernel->values=(MagickRealType *) MagickAssumeAligned(
2132 AcquireAlignedMemory(kernel->width,kernel->height*
2133 sizeof(*kernel->values)));
2134 if (kernel->values == (MagickRealType *) NULL)
2135 return(DestroyKernelInfo(kernel));
2137 for ( i=0, v=-kernel->y; v <= (ssize_t)kernel->y; v++)
2138 for ( u=-kernel->x; u <= (ssize_t)kernel->x; u++, i++)
2141 r1 = MagickMax(fabs((double)u),fabs((double)v)),
2142 r2 = floor((double)(labs((long)u)+labs((long)v)+1)/1.5);
2143 kernel->positive_range += kernel->values[i] =
2144 args->sigma*MagickMax(r1,r2);
2146 kernel->maximum = kernel->values[0];
2149 case EuclideanKernel:
2151 if (args->rho < 1.0)
2152 kernel->width = kernel->height = 3; /* default radius = 1 */
2154 kernel->width = kernel->height = ((size_t)args->rho)*2+1;
2155 kernel->x = kernel->y = (ssize_t) (kernel->width-1)/2;
2157 kernel->values=(MagickRealType *) MagickAssumeAligned(
2158 AcquireAlignedMemory(kernel->width,kernel->height*
2159 sizeof(*kernel->values)));
2160 if (kernel->values == (MagickRealType *) NULL)
2161 return(DestroyKernelInfo(kernel));
2163 for ( i=0, v=-kernel->y; v <= (ssize_t)kernel->y; v++)
2164 for ( u=-kernel->x; u <= (ssize_t)kernel->x; u++, i++)
2165 kernel->positive_range += ( kernel->values[i] =
2166 args->sigma*sqrt((double)(u*u+v*v)) );
2167 kernel->maximum = kernel->values[0];
2172 /* No-Op Kernel - Basically just a single pixel on its own */
2173 kernel=ParseKernelArray("1:1");
2174 if (kernel == (KernelInfo *) NULL)
2176 kernel->type = UndefinedKernel;
2185 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2189 % C l o n e K e r n e l I n f o %
2193 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2195 % CloneKernelInfo() creates a new clone of the given Kernel List so that its
2196 % can be modified without effecting the original. The cloned kernel should
2197 % be destroyed using DestoryKernelInfo() when no longer needed.
2199 % The format of the CloneKernelInfo method is:
2201 % KernelInfo *CloneKernelInfo(const KernelInfo *kernel)
2203 % A description of each parameter follows:
2205 % o kernel: the Morphology/Convolution kernel to be cloned
2208 MagickExport KernelInfo *CloneKernelInfo(const KernelInfo *kernel)
2216 assert(kernel != (KernelInfo *) NULL);
2217 new_kernel=(KernelInfo *) AcquireMagickMemory(sizeof(*kernel));
2218 if (new_kernel == (KernelInfo *) NULL)
2220 *new_kernel=(*kernel); /* copy values in structure */
2222 /* replace the values with a copy of the values */
2223 new_kernel->values=(MagickRealType *) MagickAssumeAligned(
2224 AcquireAlignedMemory(kernel->width,kernel->height*sizeof(*kernel->values)));
2225 if (new_kernel->values == (MagickRealType *) NULL)
2226 return(DestroyKernelInfo(new_kernel));
2227 for (i=0; i < (ssize_t) (kernel->width*kernel->height); i++)
2228 new_kernel->values[i]=kernel->values[i];
2230 /* Also clone the next kernel in the kernel list */
2231 if ( kernel->next != (KernelInfo *) NULL ) {
2232 new_kernel->next = CloneKernelInfo(kernel->next);
2233 if ( new_kernel->next == (KernelInfo *) NULL )
2234 return(DestroyKernelInfo(new_kernel));
2241 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2245 % D e s t r o y K e r n e l I n f o %
2249 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2251 % DestroyKernelInfo() frees the memory used by a Convolution/Morphology
2254 % The format of the DestroyKernelInfo method is:
2256 % KernelInfo *DestroyKernelInfo(KernelInfo *kernel)
2258 % A description of each parameter follows:
2260 % o kernel: the Morphology/Convolution kernel to be destroyed
2263 MagickExport KernelInfo *DestroyKernelInfo(KernelInfo *kernel)
2265 assert(kernel != (KernelInfo *) NULL);
2266 if (kernel->next != (KernelInfo *) NULL)
2267 kernel->next=DestroyKernelInfo(kernel->next);
2268 kernel->values=(MagickRealType *) RelinquishAlignedMemory(kernel->values);
2269 kernel=(KernelInfo *) RelinquishMagickMemory(kernel);
2274 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2278 + E x p a n d M i r r o r K e r n e l I n f o %
2282 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2284 % ExpandMirrorKernelInfo() takes a single kernel, and expands it into a
2285 % sequence of 90-degree rotated kernels but providing a reflected 180
2286 % rotatation, before the -/+ 90-degree rotations.
2288 % This special rotation order produces a better, more symetrical thinning of
2291 % The format of the ExpandMirrorKernelInfo method is:
2293 % void ExpandMirrorKernelInfo(KernelInfo *kernel)
2295 % A description of each parameter follows:
2297 % o kernel: the Morphology/Convolution kernel
2299 % This function is only internel to this module, as it is not finalized,
2300 % especially with regard to non-orthogonal angles, and rotation of larger
2305 static void FlopKernelInfo(KernelInfo *kernel)
2306 { /* Do a Flop by reversing each row. */
2314 for ( y=0, k=kernel->values; y < kernel->height; y++, k+=kernel->width)
2315 for ( x=0, r=kernel->width-1; x<kernel->width/2; x++, r--)
2316 t=k[x], k[x]=k[r], k[r]=t;
2318 kernel->x = kernel->width - kernel->x - 1;
2319 angle = fmod(angle+180.0, 360.0);
2323 static void ExpandMirrorKernelInfo(KernelInfo *kernel)
2331 clone = CloneKernelInfo(last);
2332 RotateKernelInfo(clone, 180); /* flip */
2333 LastKernelInfo(last)->next = clone;
2336 clone = CloneKernelInfo(last);
2337 RotateKernelInfo(clone, 90); /* transpose */
2338 LastKernelInfo(last)->next = clone;
2341 clone = CloneKernelInfo(last);
2342 RotateKernelInfo(clone, 180); /* flop */
2343 LastKernelInfo(last)->next = clone;
2349 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2353 + E x p a n d R o t a t e K e r n e l I n f o %
2357 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2359 % ExpandRotateKernelInfo() takes a kernel list, and expands it by rotating
2360 % incrementally by the angle given, until the kernel repeats.
2362 % WARNING: 45 degree rotations only works for 3x3 kernels.
2363 % While 90 degree roatations only works for linear and square kernels
2365 % The format of the ExpandRotateKernelInfo method is:
2367 % void ExpandRotateKernelInfo(KernelInfo *kernel, double angle)
2369 % A description of each parameter follows:
2371 % o kernel: the Morphology/Convolution kernel
2373 % o angle: angle to rotate in degrees
2375 % This function is only internel to this module, as it is not finalized,
2376 % especially with regard to non-orthogonal angles, and rotation of larger
2380 /* Internal Routine - Return true if two kernels are the same */
2381 static MagickBooleanType SameKernelInfo(const KernelInfo *kernel1,
2382 const KernelInfo *kernel2)
2387 /* check size and origin location */
2388 if ( kernel1->width != kernel2->width
2389 || kernel1->height != kernel2->height
2390 || kernel1->x != kernel2->x
2391 || kernel1->y != kernel2->y )
2394 /* check actual kernel values */
2395 for (i=0; i < (kernel1->width*kernel1->height); i++) {
2396 /* Test for Nan equivalence */
2397 if ( IfNaN(kernel1->values[i]) && !IfNaN(kernel2->values[i]) )
2399 if ( IfNaN(kernel2->values[i]) && !IfNaN(kernel1->values[i]) )
2401 /* Test actual values are equivalent */
2402 if ( fabs(kernel1->values[i] - kernel2->values[i]) >= MagickEpsilon )
2409 static void ExpandRotateKernelInfo(KernelInfo *kernel, const double angle)
2416 DisableMSCWarning(4127)
2419 clone = CloneKernelInfo(last);
2420 RotateKernelInfo(clone, angle);
2421 if ( SameKernelInfo(kernel, clone) != MagickFalse )
2423 LastKernelInfo(last)->next = clone;
2426 clone = DestroyKernelInfo(clone); /* kernel has repeated - junk the clone */
2431 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2435 + C a l c M e t a K e r n a l I n f o %
2439 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2441 % CalcKernelMetaData() recalculate the KernelInfo meta-data of this kernel only,
2442 % using the kernel values. This should only ne used if it is not possible to
2443 % calculate that meta-data in some easier way.
2445 % It is important that the meta-data is correct before ScaleKernelInfo() is
2446 % used to perform kernel normalization.
2448 % The format of the CalcKernelMetaData method is:
2450 % void CalcKernelMetaData(KernelInfo *kernel, const double scale )
2452 % A description of each parameter follows:
2454 % o kernel: the Morphology/Convolution kernel to modify
2456 % WARNING: Minimum and Maximum values are assumed to include zero, even if
2457 % zero is not part of the kernel (as in Gaussian Derived kernels). This
2458 % however is not true for flat-shaped morphological kernels.
2460 % WARNING: Only the specific kernel pointed to is modified, not a list of
2463 % This is an internal function and not expected to be useful outside this
2464 % module. This could change however.
2466 static void CalcKernelMetaData(KernelInfo *kernel)
2471 kernel->minimum = kernel->maximum = 0.0;
2472 kernel->negative_range = kernel->positive_range = 0.0;
2473 for (i=0; i < (kernel->width*kernel->height); i++)
2475 if ( fabs(kernel->values[i]) < MagickEpsilon )
2476 kernel->values[i] = 0.0;
2477 ( kernel->values[i] < 0)
2478 ? ( kernel->negative_range += kernel->values[i] )
2479 : ( kernel->positive_range += kernel->values[i] );
2480 Minimize(kernel->minimum, kernel->values[i]);
2481 Maximize(kernel->maximum, kernel->values[i]);
2488 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2492 % M o r p h o l o g y A p p l y %
2496 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2498 % MorphologyApply() applies a morphological method, multiple times using
2499 % a list of multiple kernels. This is the method that should be called by
2500 % other 'operators' that internally use morphology operations as part of
2503 % It is basically equivalent to as MorphologyImage() (see below) but without
2504 % any user controls. This allows internel programs to use this method to
2505 % perform a specific task without possible interference by any API user
2506 % supplied settings.
2508 % It is MorphologyImage() task to extract any such user controls, and
2509 % pass them to this function for processing.
2511 % More specifically all given kernels should already be scaled, normalised,
2512 % and blended appropriatally before being parred to this routine. The
2513 % appropriate bias, and compose (typically 'UndefinedComposeOp') given.
2515 % The format of the MorphologyApply method is:
2517 % Image *MorphologyApply(const Image *image,MorphologyMethod method,
2518 % const ssize_t iterations,const KernelInfo *kernel,
2519 % const CompositeMethod compose,const double bias,
2520 % ExceptionInfo *exception)
2522 % A description of each parameter follows:
2524 % o image: the source image
2526 % o method: the morphology method to be applied.
2528 % o iterations: apply the operation this many times (or no change).
2529 % A value of -1 means loop until no change found.
2530 % How this is applied may depend on the morphology method.
2531 % Typically this is a value of 1.
2533 % o channel: the channel type.
2535 % o kernel: An array of double representing the morphology kernel.
2537 % o compose: How to handle or merge multi-kernel results.
2538 % If 'UndefinedCompositeOp' use default for the Morphology method.
2539 % If 'NoCompositeOp' force image to be re-iterated by each kernel.
2540 % Otherwise merge the results using the compose method given.
2542 % o bias: Convolution Output Bias.
2544 % o exception: return any errors or warnings in this structure.
2547 static ssize_t MorphologyPrimitive(const Image *image,Image *morphology_image,
2548 const MorphologyMethod method,const KernelInfo *kernel,const double bias,
2549 ExceptionInfo *exception)
2551 #define MorphologyTag "Morphology/Image"
2577 assert(image != (Image *) NULL);
2578 assert(image->signature == MagickSignature);
2579 assert(morphology_image != (Image *) NULL);
2580 assert(morphology_image->signature == MagickSignature);
2581 assert(kernel != (KernelInfo *) NULL);
2582 assert(kernel->signature == MagickSignature);
2583 assert(exception != (ExceptionInfo *) NULL);
2584 assert(exception->signature == MagickSignature);
2587 image_view=AcquireVirtualCacheView(image,exception);
2588 morphology_view=AcquireAuthenticCacheView(morphology_image,exception);
2589 width=image->columns+kernel->width-1;
2594 case ConvolveMorphology:
2595 case DilateMorphology:
2596 case DilateIntensityMorphology:
2597 case IterativeDistanceMorphology:
2600 Kernel needs to used with reflection about origin.
2602 offset.x=(ssize_t) kernel->width-kernel->x-1;
2603 offset.y=(ssize_t) kernel->height-kernel->y-1;
2606 case ErodeMorphology:
2607 case ErodeIntensityMorphology:
2608 case HitAndMissMorphology:
2609 case ThinningMorphology:
2610 case ThickenMorphology:
2618 assert("Not a Primitive Morphology Method" != (char *) NULL);
2623 changes=(size_t *) AcquireQuantumMemory(GetOpenMPMaximumThreads(),
2625 if (changes == (size_t *) NULL)
2626 ThrowFatalException(ResourceLimitFatalError,"MemoryAllocationFailed");
2627 for (i=0; i < (ssize_t) GetOpenMPMaximumThreads(); i++)
2629 if ((method == ConvolveMorphology) && (kernel->width == 1))
2632 id = GetOpenMPThreadId();
2638 Special handling (for speed) of vertical (blur) kernels. This performs
2639 its handling in columns rather than in rows. This is only done
2640 for convolve as it is the only method that generates very large 1-D
2641 vertical kernels (such as a 'BlurKernel')
2643 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2644 #pragma omp parallel for schedule(static,4) shared(progress,status) \
2645 magick_threads(image,morphology_image,image->columns,1)
2647 for (x=0; x < (ssize_t) image->columns; x++)
2649 register const Quantum
2661 if (status == MagickFalse)
2663 p=GetCacheViewVirtualPixels(image_view,x,-offset.y,1,image->rows+
2664 kernel->height-1,exception);
2665 q=GetCacheViewAuthenticPixels(morphology_view,x,0,1,
2666 morphology_image->rows,exception);
2667 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
2672 center=(ssize_t) GetPixelChannels(image)*offset.y;
2673 for (y=0; y < (ssize_t) image->rows; y++)
2676 gamma[MaxPixelChannels],
2677 pixel[MaxPixelChannels];
2686 register const MagickRealType
2689 register const Quantum
2696 count[MaxPixelChannels];
2701 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
2708 k=(&kernel->values[kernel->width*kernel->height-1]);
2709 for (v=0; v < (ssize_t) kernel->height; v++)
2714 for (u=0; u < (ssize_t) kernel->width; u++)
2716 if (IfNaN(*k) == MagickFalse)
2721 alpha=(double) (QuantumScale*GetPixelAlpha(image,pixels));
2722 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
2724 channel=GetPixelChannelChannel(image,i);
2725 traits=GetPixelChannelTraits(image,channel);
2726 if ((traits & BlendPixelTrait) == 0)
2728 pixel[i]+=(*k)*pixels[i];
2733 pixel[i]+=alpha*(*k)*pixels[i];
2734 gamma[i]+=alpha*(*k);
2740 pixels+=GetPixelChannels(image);
2743 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
2745 channel=GetPixelChannelChannel(image,i);
2746 traits=GetPixelChannelTraits(image,channel);
2747 morphology_traits=GetPixelChannelTraits(morphology_image,channel);
2748 if ((traits == UndefinedPixelTrait) ||
2749 (morphology_traits == UndefinedPixelTrait))
2751 if (GetPixelReadMask(image,p+center) == 0)
2753 SetPixelChannel(morphology_image,channel,p[center+i],q);
2756 if (fabs(pixel[i]-p[center+i]) > MagickEpsilon)
2758 gamma[i]=PerceptibleReciprocal(gamma[i]);
2760 gamma[i]*=(double) kernel->height*kernel->width/count[i];
2761 SetPixelChannel(morphology_image,channel,ClampToQuantum(gamma[i]*
2764 p+=GetPixelChannels(image);
2765 q+=GetPixelChannels(morphology_image);
2767 if (SyncCacheViewAuthenticPixels(morphology_view,exception) == MagickFalse)
2769 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2774 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2775 #pragma omp critical (MagickCore_MorphologyPrimitive)
2777 proceed=SetImageProgress(image,MorphologyTag,progress++,
2779 if (proceed == MagickFalse)
2783 morphology_image->type=image->type;
2784 morphology_view=DestroyCacheView(morphology_view);
2785 image_view=DestroyCacheView(image_view);
2786 for (i=0; i < (ssize_t) GetOpenMPMaximumThreads(); i++)
2787 changed+=changes[i];
2788 changes=(size_t *) RelinquishMagickMemory(changes);
2789 return(status ? (ssize_t) changed : 0);
2792 Normal handling of horizontal or rectangular kernels (row by row).
2794 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2795 #pragma omp parallel for schedule(static,4) shared(progress,status) \
2796 magick_threads(image,morphology_image,image->rows,1)
2798 for (y=0; y < (ssize_t) image->rows; y++)
2801 id = GetOpenMPThreadId();
2803 register const Quantum
2815 if (status == MagickFalse)
2817 p=GetCacheViewVirtualPixels(image_view,-offset.x,y-offset.y,width,
2818 kernel->height,exception);
2819 q=GetCacheViewAuthenticPixels(morphology_view,0,y,morphology_image->columns,
2821 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
2826 center=(ssize_t) (GetPixelChannels(image)*width*offset.y+
2827 GetPixelChannels(image)*offset.x);
2828 for (x=0; x < (ssize_t) image->columns; x++)
2833 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
2849 register const MagickRealType
2852 register const Quantum
2864 channel=GetPixelChannelChannel(image,i);
2865 traits=GetPixelChannelTraits(image,channel);
2866 morphology_traits=GetPixelChannelTraits(morphology_image,channel);
2867 if ((traits == UndefinedPixelTrait) ||
2868 (morphology_traits == UndefinedPixelTrait))
2870 if (GetPixelReadMask(image,p+center) == 0)
2872 SetPixelChannel(morphology_image,channel,p[center+i],q);
2877 minimum=(double) QuantumRange;
2878 count=kernel->width*kernel->height;
2881 case ConvolveMorphology: pixel=bias; break;
2882 case HitAndMissMorphology: pixel=(double) QuantumRange; break;
2883 case ThinningMorphology: pixel=(double) QuantumRange; break;
2884 case ThickenMorphology: pixel=(double) QuantumRange; break;
2885 case ErodeMorphology: pixel=(double) QuantumRange; break;
2886 case DilateMorphology: pixel=0.0; break;
2887 case ErodeIntensityMorphology:
2888 case DilateIntensityMorphology:
2889 case IterativeDistanceMorphology:
2891 pixel=(double) p[center+i];
2894 default: pixel=0; break;
2899 case ConvolveMorphology:
2902 Weighted Average of pixels using reflected kernel
2904 For correct working of this operation for asymetrical
2905 kernels, the kernel needs to be applied in its reflected form.
2906 That is its values needs to be reversed.
2908 Correlation is actually the same as this but without reflecting
2909 the kernel, and thus 'lower-level' that Convolution. However
2910 as Convolution is the more common method used, and it does not
2911 really cost us much in terms of processing to use a reflected
2912 kernel, so it is Convolution that is implemented.
2914 Correlation will have its kernel reflected before calling
2915 this function to do a Convolve.
2917 For more details of Correlation vs Convolution see
2918 http://www.cs.umd.edu/~djacobs/CMSC426/Convolution.pdf
2920 k=(&kernel->values[kernel->width*kernel->height-1]);
2921 if ((morphology_traits & BlendPixelTrait) == 0)
2926 for (v=0; v < (ssize_t) kernel->height; v++)
2928 for (u=0; u < (ssize_t) kernel->width; u++)
2930 if (IfNaN(*k) == MagickFalse)
2931 pixel+=(*k)*pixels[i];
2933 pixels+=GetPixelChannels(image);
2935 pixels+=(image->columns-1)*GetPixelChannels(image);
2944 for (v=0; v < (ssize_t) kernel->height; v++)
2946 for (u=0; u < (ssize_t) kernel->width; u++)
2948 if (IfNaN(*k) == MagickFalse)
2950 alpha=(double) (QuantumScale*GetPixelAlpha(image,pixels));
2951 pixel+=alpha*(*k)*pixels[i];
2956 pixels+=GetPixelChannels(image);
2958 pixels+=(image->columns-1)*GetPixelChannels(image);
2962 case ErodeMorphology:
2965 Minimum value within kernel neighbourhood.
2967 The kernel is not reflected for this operation. In normal
2968 Greyscale Morphology, the kernel value should be added
2969 to the real value, this is currently not done, due to the
2970 nature of the boolean kernels being used.
2973 for (v=0; v < (ssize_t) kernel->height; v++)
2975 for (u=0; u < (ssize_t) kernel->width; u++)
2977 if ((IfNaN(*k) == MagickFalse) && (*k >= 0.5))
2979 if ((double) pixels[i] < pixel)
2980 pixel=(double) pixels[i];
2983 pixels+=GetPixelChannels(image);
2985 pixels+=(image->columns-1)*GetPixelChannels(image);
2989 case DilateMorphology:
2992 Maximum value within kernel neighbourhood.
2994 For correct working of this operation for asymetrical kernels,
2995 the kernel needs to be applied in its reflected form. That is
2996 its values needs to be reversed.
2998 In normal Greyscale Morphology, the kernel value should be
2999 added to the real value, this is currently not done, due to the
3000 nature of the boolean kernels being used.
3003 k=(&kernel->values[kernel->width*kernel->height-1]);
3004 for (v=0; v < (ssize_t) kernel->height; v++)
3006 for (u=0; u < (ssize_t) kernel->width; u++)
3008 if ((IfNaN(*k) == MagickFalse) && (*k > 0.5))
3010 if ((double) pixels[i] > pixel)
3011 pixel=(double) pixels[i];
3015 pixels+=GetPixelChannels(image);
3017 pixels+=(image->columns-1)*GetPixelChannels(image);
3021 case HitAndMissMorphology:
3022 case ThinningMorphology:
3023 case ThickenMorphology:
3026 Minimum of foreground pixel minus maxumum of background pixels.
3028 The kernel is not reflected for this operation, and consists
3029 of both foreground and background pixel neighbourhoods, 0.0 for
3030 background, and 1.0 for foreground with either Nan or 0.5 values
3033 This never produces a meaningless negative result. Such results
3034 cause Thinning/Thicken to not work correctly when used against a
3039 for (v=0; v < (ssize_t) kernel->height; v++)
3041 for (u=0; u < (ssize_t) kernel->width; u++)
3043 if (IfNaN(*k) == MagickFalse)
3047 if ((double) pixels[i] < pixel)
3048 pixel=(double) pixels[i];
3053 if ((double) pixels[i] > maximum)
3054 maximum=(double) pixels[i];
3059 pixels+=GetPixelChannels(image);
3061 pixels+=(image->columns-1)*GetPixelChannels(image);
3066 if (method == ThinningMorphology)
3067 pixel=(double) p[center+i]-pixel;
3069 if (method == ThickenMorphology)
3070 pixel+=(double) p[center+i]+pixel;
3073 case ErodeIntensityMorphology:
3076 Select pixel with minimum intensity within kernel neighbourhood.
3078 The kernel is not reflected for this operation.
3082 for (v=0; v < (ssize_t) kernel->height; v++)
3084 for (u=0; u < (ssize_t) kernel->width; u++)
3086 if ((IfNaN(*k) == MagickFalse) && (*k >= 0.5))
3088 if (GetPixelIntensity(image,pixels) < minimum)
3090 pixel=(double) pixels[i];
3091 minimum=GetPixelIntensity(image,pixels);
3096 pixels+=GetPixelChannels(image);
3098 pixels+=(image->columns-1)*GetPixelChannels(image);
3102 case DilateIntensityMorphology:
3105 Select pixel with maximum intensity within kernel neighbourhood.
3107 The kernel is not reflected for this operation.
3110 k=(&kernel->values[kernel->width*kernel->height-1]);
3111 for (v=0; v < (ssize_t) kernel->height; v++)
3113 for (u=0; u < (ssize_t) kernel->width; u++)
3115 if ((IfNaN(*k) == MagickFalse) && (*k >= 0.5))
3117 if (GetPixelIntensity(image,pixels) > maximum)
3119 pixel=(double) pixels[i];
3120 maximum=GetPixelIntensity(image,pixels);
3125 pixels+=GetPixelChannels(image);
3127 pixels+=(image->columns-1)*GetPixelChannels(image);
3131 case IterativeDistanceMorphology:
3134 Compute th iterative distance from black edge of a white image
3135 shape. Essentually white values are decreased to the smallest
3136 'distance from edge' it can find.
3138 It works by adding kernel values to the neighbourhood, and and
3139 select the minimum value found. The kernel is rotated before
3140 use, so kernel distances match resulting distances, when a user
3141 provided asymmetric kernel is applied.
3143 This code is nearly identical to True GrayScale Morphology but
3146 GreyDilate Kernel values added, maximum value found Kernel is
3149 GrayErode: Kernel values subtracted and minimum value found No
3150 kernel rotation used.
3152 Note the the Iterative Distance method is essentially a
3153 GrayErode, but with negative kernel values, and kernel rotation
3157 k=(&kernel->values[kernel->width*kernel->height-1]);
3158 for (v=0; v < (ssize_t) kernel->height; v++)
3160 for (u=0; u < (ssize_t) kernel->width; u++)
3162 if (IfNaN(*k) == MagickFalse)
3164 if ((pixels[i]+(*k)) < pixel)
3165 pixel=(double) pixels[i]+(*k);
3169 pixels+=GetPixelChannels(image);
3171 pixels+=(image->columns-1)*GetPixelChannels(image);
3175 case UndefinedMorphology:
3179 if (fabs(pixel-p[center+i]) > MagickEpsilon)
3181 gamma=PerceptibleReciprocal(gamma);
3183 gamma*=(double) kernel->height*kernel->width/count;
3184 SetPixelChannel(morphology_image,channel,ClampToQuantum(gamma*pixel),q);
3186 p+=GetPixelChannels(image);
3187 q+=GetPixelChannels(morphology_image);
3189 if (SyncCacheViewAuthenticPixels(morphology_view,exception) == MagickFalse)
3191 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3196 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3197 #pragma omp critical (MagickCore_MorphologyPrimitive)
3199 proceed=SetImageProgress(image,MorphologyTag,progress++,image->rows);
3200 if (proceed == MagickFalse)
3204 morphology_view=DestroyCacheView(morphology_view);
3205 image_view=DestroyCacheView(image_view);
3206 for (i=0; i < (ssize_t) GetOpenMPMaximumThreads(); i++)
3207 changed+=changes[i];
3208 changes=(size_t *) RelinquishMagickMemory(changes);
3209 return(status ? (ssize_t) changed : -1);
3213 This is almost identical to the MorphologyPrimative() function above, but
3214 applies the primitive directly to the actual image using two passes, once in
3215 each direction, with the results of the previous (and current) row being
3218 That is after each row is 'Sync'ed' into the image, the next row makes use of
3219 those values as part of the calculation of the next row. It repeats, but
3220 going in the oppisite (bottom-up) direction.
3222 Because of this 're-use of results' this function can not make use of multi-
3223 threaded, parellel processing.
3225 static ssize_t MorphologyPrimitiveDirect(Image *image,
3226 const MorphologyMethod method,const KernelInfo *kernel,
3227 ExceptionInfo *exception)
3249 assert(image != (Image *) NULL);
3250 assert(image->signature == MagickSignature);
3251 assert(kernel != (KernelInfo *) NULL);
3252 assert(kernel->signature == MagickSignature);
3253 assert(exception != (ExceptionInfo *) NULL);
3254 assert(exception->signature == MagickSignature);
3260 case DistanceMorphology:
3261 case VoronoiMorphology:
3264 Kernel reflected about origin.
3266 offset.x=(ssize_t) kernel->width-kernel->x-1;
3267 offset.y=(ssize_t) kernel->height-kernel->y-1;
3278 Two views into same image, do not thread.
3280 image_view=AcquireVirtualCacheView(image,exception);
3281 morphology_view=AcquireAuthenticCacheView(image,exception);
3282 width=image->columns+kernel->width-1;
3283 for (y=0; y < (ssize_t) image->rows; y++)
3285 register const Quantum
3298 Read virtual pixels, and authentic pixels, from the same image! We read
3299 using virtual to get virtual pixel handling, but write back into the same
3302 Only top half of kernel is processed as we do a single pass downward
3303 through the image iterating the distance function as we go.
3305 if (status == MagickFalse)
3307 p=GetCacheViewVirtualPixels(image_view,-offset.x,y-offset.y,width,(size_t)
3308 offset.y+1,exception);
3309 q=GetCacheViewAuthenticPixels(morphology_view,0,y,image->columns,1,
3311 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
3316 center=(ssize_t) (GetPixelChannels(image)*width*offset.y+
3317 GetPixelChannels(image)*offset.x);
3318 for (x=0; x < (ssize_t) image->columns; x++)
3323 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
3331 register const MagickRealType
3334 register const Quantum
3343 traits=GetPixelChannelTraits(image,(PixelChannel) i);
3344 if (traits == UndefinedPixelTrait)
3346 if (((traits & CopyPixelTrait) != 0) ||
3347 (GetPixelReadMask(image,p+center) == 0))
3350 pixel=(double) QuantumRange;
3353 case DistanceMorphology:
3355 k=(&kernel->values[kernel->width*kernel->height-1]);
3356 for (v=0; v <= offset.y; v++)
3358 for (u=0; u < (ssize_t) kernel->width; u++)
3360 if (IfNaN(*k) == MagickFalse)
3362 if ((pixels[i]+(*k)) < pixel)
3363 pixel=(double) pixels[i]+(*k);
3366 pixels+=GetPixelChannels(image);
3368 pixels+=(image->columns-1)*GetPixelChannels(image);
3370 k=(&kernel->values[kernel->width*(kernel->y+1)-1]);
3371 pixels=q-offset.x*GetPixelChannels(image);
3372 for (u=0; u < offset.x; u++)
3374 if ((IfNaN(*k) == MagickFalse) && ((x+u-offset.x) >= 0))
3376 if ((pixels[i]+(*k)) < pixel)
3377 pixel=(double) pixels[i]+(*k);
3380 pixels+=GetPixelChannels(image);
3384 case VoronoiMorphology:
3386 k=(&kernel->values[kernel->width*kernel->height-1]);
3387 for (v=0; v < offset.y; v++)
3389 for (u=0; u < (ssize_t) kernel->width; u++)
3391 if (IfNaN(*k) == MagickFalse)
3393 if ((pixels[i]+(*k)) < pixel)
3394 pixel=(double) pixels[i]+(*k);
3397 pixels+=GetPixelChannels(image);
3399 pixels+=(image->columns-1)*GetPixelChannels(image);
3401 k=(&kernel->values[kernel->width*(kernel->y+1)-1]);
3402 pixels=q-offset.x*GetPixelChannels(image);
3403 for (u=0; u < offset.x; u++)
3405 if ((IfNaN(*k) == MagickFalse) && ((x+u-offset.x) >= 0))
3407 if ((pixels[i]+(*k)) < pixel)
3408 pixel=(double) pixels[i]+(*k);
3411 pixels+=GetPixelChannels(image);
3418 if (fabs(pixel-q[i]) > MagickEpsilon)
3420 q[i]=ClampToQuantum(pixel);
3422 p+=GetPixelChannels(image);
3423 q+=GetPixelChannels(image);
3425 if (SyncCacheViewAuthenticPixels(morphology_view,exception) == MagickFalse)
3427 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3432 proceed=SetImageProgress(image,MorphologyTag,progress++,2*image->rows);
3433 if (proceed == MagickFalse)
3437 morphology_view=DestroyCacheView(morphology_view);
3438 image_view=DestroyCacheView(image_view);
3440 Do the reverse pass through the image.
3442 image_view=AcquireVirtualCacheView(image,exception);
3443 morphology_view=AcquireAuthenticCacheView(image,exception);
3444 for (y=(ssize_t) image->rows-1; y >= 0; y--)
3446 register const Quantum
3459 Read virtual pixels, and authentic pixels, from the same image. We
3460 read using virtual to get virtual pixel handling, but write back
3461 into the same image.
3463 Only the bottom half of the kernel is processed as we up the image.
3465 if (status == MagickFalse)
3467 p=GetCacheViewVirtualPixels(image_view,-offset.x,y,width,(size_t)
3468 kernel->y+1,exception);
3469 q=GetCacheViewAuthenticPixels(morphology_view,0,y,image->columns,1,
3471 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
3476 p+=(image->columns-1)*GetPixelChannels(image);
3477 q+=(image->columns-1)*GetPixelChannels(image);
3478 center=(ssize_t) (offset.x*GetPixelChannels(image));
3479 for (x=(ssize_t) image->columns-1; x >= 0; x--)
3484 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
3492 register const MagickRealType
3495 register const Quantum
3504 traits=GetPixelChannelTraits(image,(PixelChannel) i);
3505 if (traits == UndefinedPixelTrait)
3507 if (((traits & CopyPixelTrait) != 0) ||
3508 (GetPixelReadMask(image,p+center) == 0))
3511 pixel=(double) QuantumRange;
3514 case DistanceMorphology:
3516 k=(&kernel->values[kernel->width*(kernel->y+1)-1]);
3517 for (v=offset.y; v < (ssize_t) kernel->height; v++)
3519 for (u=0; u < (ssize_t) kernel->width; u++)
3521 if (IfNaN(*k) == MagickFalse)
3523 if ((pixels[i]+(*k)) < pixel)
3524 pixel=(double) pixels[i]+(*k);
3527 pixels+=GetPixelChannels(image);
3529 pixels+=(image->columns-1)*GetPixelChannels(image);
3531 k=(&kernel->values[kernel->width*kernel->y+kernel->x-1]);
3532 pixels=q-offset.x*GetPixelChannels(image);
3533 for (u=offset.x+1; u < (ssize_t) kernel->width; u++)
3535 if ((IfNaN(*k) == MagickFalse) &&
3536 ((x+u-offset.x) < (ssize_t) image->columns))
3538 if ((pixels[i]+(*k)) < pixel)
3539 pixel=(double) pixels[i]+(*k);
3542 pixels+=GetPixelChannels(image);
3546 case VoronoiMorphology:
3548 k=(&kernel->values[kernel->width*(kernel->y+1)-1]);
3549 for (v=offset.y; v < (ssize_t) kernel->height; v++)
3551 for (u=0; u < (ssize_t) kernel->width; u++)
3553 if (IfNaN(*k) == MagickFalse)
3555 if ((pixels[i]+(*k)) < pixel)
3556 pixel=(double) pixels[i]+(*k);
3559 pixels+=GetPixelChannels(image);
3561 pixels+=(image->columns-1)*GetPixelChannels(image);
3563 k=(&kernel->values[kernel->width*(kernel->y+1)-1]);
3564 pixels=q-offset.x*GetPixelChannels(image);
3565 for (u=offset.x+1; u < (ssize_t) kernel->width; u++)
3567 if ((IfNaN(*k) == MagickFalse) &&
3568 ((x+u-offset.x) < (ssize_t) image->columns))
3570 if ((pixels[i]+(*k)) < pixel)
3571 pixel=(double) pixels[i]+(*k);
3574 pixels+=GetPixelChannels(image);
3581 if (fabs(pixel-q[i]) > MagickEpsilon)
3583 q[i]=ClampToQuantum(pixel);
3585 p-=GetPixelChannels(image);
3586 q-=GetPixelChannels(image);
3588 if (SyncCacheViewAuthenticPixels(morphology_view,exception) == MagickFalse)
3590 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3595 proceed=SetImageProgress(image,MorphologyTag,progress++,2*image->rows);
3596 if (proceed == MagickFalse)
3600 morphology_view=DestroyCacheView(morphology_view);
3601 image_view=DestroyCacheView(image_view);
3602 return(status ? (ssize_t) changed : -1);
3606 Apply a Morphology by calling one of the above low level primitive
3607 application functions. This function handles any iteration loops,
3608 composition or re-iteration of results, and compound morphology methods that
3609 is based on multiple low-level (staged) morphology methods.
3611 Basically this provides the complex glue between the requested morphology
3612 method and raw low-level implementation (above).
3614 MagickPrivate Image *MorphologyApply(const Image *image,
3615 const MorphologyMethod method, const ssize_t iterations,
3616 const KernelInfo *kernel, const CompositeOperator compose,const double bias,
3617 ExceptionInfo *exception)
3623 *curr_image, /* Image we are working with or iterating */
3624 *work_image, /* secondary image for primitive iteration */
3625 *save_image, /* saved image - for 'edge' method only */
3626 *rslt_image; /* resultant image - after multi-kernel handling */
3629 *reflected_kernel, /* A reflected copy of the kernel (if needed) */
3630 *norm_kernel, /* the current normal un-reflected kernel */
3631 *rflt_kernel, /* the current reflected kernel (if needed) */
3632 *this_kernel; /* the kernel being applied */
3635 primitive; /* the current morphology primitive being applied */
3638 rslt_compose; /* multi-kernel compose method for results to use */
3641 special, /* do we use a direct modify function? */
3642 verbose; /* verbose output of results */
3645 method_loop, /* Loop 1: number of compound method iterations (norm 1) */
3646 method_limit, /* maximum number of compound method iterations */
3647 kernel_number, /* Loop 2: the kernel number being applied */
3648 stage_loop, /* Loop 3: primitive loop for compound morphology */
3649 stage_limit, /* how many primitives are in this compound */
3650 kernel_loop, /* Loop 4: iterate the kernel over image */
3651 kernel_limit, /* number of times to iterate kernel */
3652 count, /* total count of primitive steps applied */
3653 kernel_changed, /* total count of changed using iterated kernel */
3654 method_changed; /* total count of changed over method iteration */
3657 changed; /* number pixels changed by last primitive operation */
3660 v_info[MaxTextExtent];
3662 assert(image != (Image *) NULL);
3663 assert(image->signature == MagickSignature);
3664 assert(kernel != (KernelInfo *) NULL);
3665 assert(kernel->signature == MagickSignature);
3666 assert(exception != (ExceptionInfo *) NULL);
3667 assert(exception->signature == MagickSignature);
3669 count = 0; /* number of low-level morphology primitives performed */
3670 if ( iterations == 0 )
3671 return((Image *)NULL); /* null operation - nothing to do! */
3673 kernel_limit = (size_t) iterations;
3674 if ( iterations < 0 ) /* negative interations = infinite (well alomst) */
3675 kernel_limit = image->columns>image->rows ? image->columns : image->rows;
3677 verbose = IsStringTrue(GetImageArtifact(image,"debug"));
3679 /* initialise for cleanup */
3680 curr_image = (Image *) image;
3681 curr_compose = image->compose;
3682 (void) curr_compose;
3683 work_image = save_image = rslt_image = (Image *) NULL;
3684 reflected_kernel = (KernelInfo *) NULL;
3686 /* Initialize specific methods
3687 * + which loop should use the given iteratations
3688 * + how many primitives make up the compound morphology
3689 * + multi-kernel compose method to use (by default)
3691 method_limit = 1; /* just do method once, unless otherwise set */
3692 stage_limit = 1; /* assume method is not a compound */
3693 special = MagickFalse; /* assume it is NOT a direct modify primitive */
3694 rslt_compose = compose; /* and we are composing multi-kernels as given */
3696 case SmoothMorphology: /* 4 primitive compound morphology */
3699 case OpenMorphology: /* 2 primitive compound morphology */
3700 case OpenIntensityMorphology:
3701 case TopHatMorphology:
3702 case CloseMorphology:
3703 case CloseIntensityMorphology:
3704 case BottomHatMorphology:
3705 case EdgeMorphology:
3708 case HitAndMissMorphology:
3709 rslt_compose = LightenCompositeOp; /* Union of multi-kernel results */
3711 case ThinningMorphology:
3712 case ThickenMorphology:
3713 method_limit = kernel_limit; /* iterate the whole method */
3714 kernel_limit = 1; /* do not do kernel iteration */
3716 case DistanceMorphology:
3717 case VoronoiMorphology:
3718 special = MagickTrue; /* use special direct primative */
3724 /* Apply special methods with special requirments
3725 ** For example, single run only, or post-processing requirements
3727 if ( special != MagickFalse )
3729 rslt_image=CloneImage(image,0,0,MagickTrue,exception);
3730 if (rslt_image == (Image *) NULL)
3732 if (SetImageStorageClass(rslt_image,DirectClass,exception) == MagickFalse)
3735 changed=MorphologyPrimitiveDirect(rslt_image,method,kernel,exception);
3737 if ( IfMagickTrue(verbose) )
3738 (void) (void) FormatLocaleFile(stderr,
3739 "%s:%.20g.%.20g #%.20g => Changed %.20g\n",
3740 CommandOptionToMnemonic(MagickMorphologyOptions, method),
3741 1.0,0.0,1.0, (double) changed);
3746 if ( method == VoronoiMorphology ) {
3747 /* Preserve the alpha channel of input image - but turned it off */
3748 (void) SetImageAlphaChannel(rslt_image, DeactivateAlphaChannel,
3750 (void) CompositeImage(rslt_image,image,CopyAlphaCompositeOp,
3751 MagickTrue,0,0,exception);
3752 (void) SetImageAlphaChannel(rslt_image, DeactivateAlphaChannel,
3758 /* Handle user (caller) specified multi-kernel composition method */
3759 if ( compose != UndefinedCompositeOp )
3760 rslt_compose = compose; /* override default composition for method */
3761 if ( rslt_compose == UndefinedCompositeOp )
3762 rslt_compose = NoCompositeOp; /* still not defined! Then re-iterate */
3764 /* Some methods require a reflected kernel to use with primitives.
3765 * Create the reflected kernel for those methods. */
3767 case CorrelateMorphology:
3768 case CloseMorphology:
3769 case CloseIntensityMorphology:
3770 case BottomHatMorphology:
3771 case SmoothMorphology:
3772 reflected_kernel = CloneKernelInfo(kernel);
3773 if (reflected_kernel == (KernelInfo *) NULL)
3775 RotateKernelInfo(reflected_kernel,180);
3781 /* Loops around more primitive morpholgy methods
3782 ** erose, dilate, open, close, smooth, edge, etc...
3784 /* Loop 1: iterate the compound method */
3787 while ( method_loop < method_limit && method_changed > 0 ) {
3791 /* Loop 2: iterate over each kernel in a multi-kernel list */
3792 norm_kernel = (KernelInfo *) kernel;
3793 this_kernel = (KernelInfo *) kernel;
3794 rflt_kernel = reflected_kernel;
3797 while ( norm_kernel != NULL ) {
3799 /* Loop 3: Compound Morphology Staging - Select Primative to apply */
3800 stage_loop = 0; /* the compound morphology stage number */
3801 while ( stage_loop < stage_limit ) {
3802 stage_loop++; /* The stage of the compound morphology */
3804 /* Select primitive morphology for this stage of compound method */
3805 this_kernel = norm_kernel; /* default use unreflected kernel */
3806 primitive = method; /* Assume method is a primitive */
3808 case ErodeMorphology: /* just erode */
3809 case EdgeInMorphology: /* erode and image difference */
3810 primitive = ErodeMorphology;
3812 case DilateMorphology: /* just dilate */
3813 case EdgeOutMorphology: /* dilate and image difference */
3814 primitive = DilateMorphology;
3816 case OpenMorphology: /* erode then dialate */
3817 case TopHatMorphology: /* open and image difference */
3818 primitive = ErodeMorphology;
3819 if ( stage_loop == 2 )
3820 primitive = DilateMorphology;
3822 case OpenIntensityMorphology:
3823 primitive = ErodeIntensityMorphology;
3824 if ( stage_loop == 2 )
3825 primitive = DilateIntensityMorphology;
3827 case CloseMorphology: /* dilate, then erode */
3828 case BottomHatMorphology: /* close and image difference */
3829 this_kernel = rflt_kernel; /* use the reflected kernel */
3830 primitive = DilateMorphology;
3831 if ( stage_loop == 2 )
3832 primitive = ErodeMorphology;
3834 case CloseIntensityMorphology:
3835 this_kernel = rflt_kernel; /* use the reflected kernel */
3836 primitive = DilateIntensityMorphology;
3837 if ( stage_loop == 2 )
3838 primitive = ErodeIntensityMorphology;
3840 case SmoothMorphology: /* open, close */
3841 switch ( stage_loop ) {
3842 case 1: /* start an open method, which starts with Erode */
3843 primitive = ErodeMorphology;
3845 case 2: /* now Dilate the Erode */
3846 primitive = DilateMorphology;
3848 case 3: /* Reflect kernel a close */
3849 this_kernel = rflt_kernel; /* use the reflected kernel */
3850 primitive = DilateMorphology;
3852 case 4: /* Finish the Close */
3853 this_kernel = rflt_kernel; /* use the reflected kernel */
3854 primitive = ErodeMorphology;
3858 case EdgeMorphology: /* dilate and erode difference */
3859 primitive = DilateMorphology;
3860 if ( stage_loop == 2 ) {
3861 save_image = curr_image; /* save the image difference */
3862 curr_image = (Image *) image;
3863 primitive = ErodeMorphology;
3866 case CorrelateMorphology:
3867 /* A Correlation is a Convolution with a reflected kernel.
3868 ** However a Convolution is a weighted sum using a reflected
3869 ** kernel. It may seem stange to convert a Correlation into a
3870 ** Convolution as the Correlation is the simplier method, but
3871 ** Convolution is much more commonly used, and it makes sense to
3872 ** implement it directly so as to avoid the need to duplicate the
3873 ** kernel when it is not required (which is typically the
3876 this_kernel = rflt_kernel; /* use the reflected kernel */
3877 primitive = ConvolveMorphology;
3882 assert( this_kernel != (KernelInfo *) NULL );
3884 /* Extra information for debugging compound operations */
3885 if ( IfMagickTrue(verbose) ) {
3886 if ( stage_limit > 1 )
3887 (void) FormatLocaleString(v_info,MaxTextExtent,"%s:%.20g.%.20g -> ",
3888 CommandOptionToMnemonic(MagickMorphologyOptions,method),(double)
3889 method_loop,(double) stage_loop);
3890 else if ( primitive != method )
3891 (void) FormatLocaleString(v_info, MaxTextExtent, "%s:%.20g -> ",
3892 CommandOptionToMnemonic(MagickMorphologyOptions, method),(double)
3898 /* Loop 4: Iterate the kernel with primitive */
3902 while ( kernel_loop < kernel_limit && changed > 0 ) {
3903 kernel_loop++; /* the iteration of this kernel */
3905 /* Create a clone as the destination image, if not yet defined */
3906 if ( work_image == (Image *) NULL )
3908 work_image=CloneImage(image,0,0,MagickTrue,exception);
3909 if (work_image == (Image *) NULL)
3911 if (SetImageStorageClass(work_image,DirectClass,exception) == MagickFalse)
3915 /* APPLY THE MORPHOLOGICAL PRIMITIVE (curr -> work) */
3917 changed = MorphologyPrimitive(curr_image, work_image, primitive,
3918 this_kernel, bias, exception);
3919 if ( IfMagickTrue(verbose) ) {
3920 if ( kernel_loop > 1 )
3921 (void) FormatLocaleFile(stderr, "\n"); /* add end-of-line from previous */
3922 (void) (void) FormatLocaleFile(stderr,
3923 "%s%s%s:%.20g.%.20g #%.20g => Changed %.20g",
3924 v_info,CommandOptionToMnemonic(MagickMorphologyOptions,
3925 primitive),(this_kernel == rflt_kernel ) ? "*" : "",
3926 (double) (method_loop+kernel_loop-1),(double) kernel_number,
3927 (double) count,(double) changed);
3931 kernel_changed += changed;
3932 method_changed += changed;
3934 /* prepare next loop */
3935 { Image *tmp = work_image; /* swap images for iteration */
3936 work_image = curr_image;
3939 if ( work_image == image )
3940 work_image = (Image *) NULL; /* replace input 'image' */
3942 } /* End Loop 4: Iterate the kernel with primitive */
3944 if ( IfMagickTrue(verbose) && kernel_changed != (size_t)changed )
3945 (void) FormatLocaleFile(stderr, " Total %.20g",(double) kernel_changed);
3946 if ( IfMagickTrue(verbose) && stage_loop < stage_limit )
3947 (void) FormatLocaleFile(stderr, "\n"); /* add end-of-line before looping */
3950 (void) FormatLocaleFile(stderr, "--E-- image=0x%lx\n", (unsigned long)image);
3951 (void) FormatLocaleFile(stderr, " curr =0x%lx\n", (unsigned long)curr_image);
3952 (void) FormatLocaleFile(stderr, " work =0x%lx\n", (unsigned long)work_image);
3953 (void) FormatLocaleFile(stderr, " save =0x%lx\n", (unsigned long)save_image);
3954 (void) FormatLocaleFile(stderr, " union=0x%lx\n", (unsigned long)rslt_image);
3957 } /* End Loop 3: Primative (staging) Loop for Coumpound Methods */
3959 /* Final Post-processing for some Compound Methods
3961 ** The removal of any 'Sync' channel flag in the Image Compositon
3962 ** below ensures the methematical compose method is applied in a
3963 ** purely mathematical way, and only to the selected channels.
3964 ** Turn off SVG composition 'alpha blending'.
3967 case EdgeOutMorphology:
3968 case EdgeInMorphology:
3969 case TopHatMorphology:
3970 case BottomHatMorphology:
3971 if ( IfMagickTrue(verbose) )
3972 (void) FormatLocaleFile(stderr,
3973 "\n%s: Difference with original image",CommandOptionToMnemonic(
3974 MagickMorphologyOptions, method) );
3975 (void) CompositeImage(curr_image,image,DifferenceCompositeOp,
3976 MagickTrue,0,0,exception);
3978 case EdgeMorphology:
3979 if ( IfMagickTrue(verbose) )
3980 (void) FormatLocaleFile(stderr,
3981 "\n%s: Difference of Dilate and Erode",CommandOptionToMnemonic(
3982 MagickMorphologyOptions, method) );
3983 (void) CompositeImage(curr_image,save_image,DifferenceCompositeOp,
3984 MagickTrue,0,0,exception);
3985 save_image = DestroyImage(save_image); /* finished with save image */
3991 /* multi-kernel handling: re-iterate, or compose results */
3992 if ( kernel->next == (KernelInfo *) NULL )
3993 rslt_image = curr_image; /* just return the resulting image */
3994 else if ( rslt_compose == NoCompositeOp )
3995 { if ( IfMagickTrue(verbose) ) {
3996 if ( this_kernel->next != (KernelInfo *) NULL )
3997 (void) FormatLocaleFile(stderr, " (re-iterate)");
3999 (void) FormatLocaleFile(stderr, " (done)");
4001 rslt_image = curr_image; /* return result, and re-iterate */
4003 else if ( rslt_image == (Image *) NULL)
4004 { if ( IfMagickTrue(verbose) )
4005 (void) FormatLocaleFile(stderr, " (save for compose)");
4006 rslt_image = curr_image;
4007 curr_image = (Image *) image; /* continue with original image */
4010 { /* Add the new 'current' result to the composition
4012 ** The removal of any 'Sync' channel flag in the Image Compositon
4013 ** below ensures the methematical compose method is applied in a
4014 ** purely mathematical way, and only to the selected channels.
4015 ** IE: Turn off SVG composition 'alpha blending'.
4017 if ( IfMagickTrue(verbose) )
4018 (void) FormatLocaleFile(stderr, " (compose \"%s\")",
4019 CommandOptionToMnemonic(MagickComposeOptions, rslt_compose) );
4020 (void) CompositeImage(rslt_image,curr_image,rslt_compose,MagickTrue,
4022 curr_image = DestroyImage(curr_image);
4023 curr_image = (Image *) image; /* continue with original image */
4025 if ( IfMagickTrue(verbose) )
4026 (void) FormatLocaleFile(stderr, "\n");
4028 /* loop to the next kernel in a multi-kernel list */
4029 norm_kernel = norm_kernel->next;
4030 if ( rflt_kernel != (KernelInfo *) NULL )
4031 rflt_kernel = rflt_kernel->next;
4033 } /* End Loop 2: Loop over each kernel */
4035 } /* End Loop 1: compound method interation */
4039 /* Yes goto's are bad, but it makes cleanup lot more efficient */
4041 if ( curr_image == rslt_image )
4042 curr_image = (Image *) NULL;
4043 if ( rslt_image != (Image *) NULL )
4044 rslt_image = DestroyImage(rslt_image);
4046 if ( curr_image == rslt_image || curr_image == image )
4047 curr_image = (Image *) NULL;
4048 if ( curr_image != (Image *) NULL )
4049 curr_image = DestroyImage(curr_image);
4050 if ( work_image != (Image *) NULL )
4051 work_image = DestroyImage(work_image);
4052 if ( save_image != (Image *) NULL )
4053 save_image = DestroyImage(save_image);
4054 if ( reflected_kernel != (KernelInfo *) NULL )
4055 reflected_kernel = DestroyKernelInfo(reflected_kernel);
4061 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4065 % M o r p h o l o g y I m a g e %
4069 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4071 % MorphologyImage() applies a user supplied kernel to the image according to
4072 % the given mophology method.
4074 % This function applies any and all user defined settings before calling
4075 % the above internal function MorphologyApply().
4077 % User defined settings include...
4078 % * Output Bias for Convolution and correlation ("-define convolve:bias=??")
4079 % * Kernel Scale/normalize settings ("-define convolve:scale=??")
4080 % This can also includes the addition of a scaled unity kernel.
4081 % * Show Kernel being applied ("-define showkernel=1")
4083 % Other operators that do not want user supplied options interfering,
4084 % especially "convolve:bias" and "showkernel" should use MorphologyApply()
4087 % The format of the MorphologyImage method is:
4089 % Image *MorphologyImage(const Image *image,MorphologyMethod method,
4090 % const ssize_t iterations,KernelInfo *kernel,ExceptionInfo *exception)
4092 % A description of each parameter follows:
4094 % o image: the image.
4096 % o method: the morphology method to be applied.
4098 % o iterations: apply the operation this many times (or no change).
4099 % A value of -1 means loop until no change found.
4100 % How this is applied may depend on the morphology method.
4101 % Typically this is a value of 1.
4103 % o kernel: An array of double representing the morphology kernel.
4104 % Warning: kernel may be normalized for the Convolve method.
4106 % o exception: return any errors or warnings in this structure.
4109 MagickExport Image *MorphologyImage(const Image *image,
4110 const MorphologyMethod method,const ssize_t iterations,
4111 const KernelInfo *kernel,ExceptionInfo *exception)
4125 curr_kernel = (KernelInfo *) kernel;
4127 compose = UndefinedCompositeOp; /* use default for method */
4129 /* Apply Convolve/Correlate Normalization and Scaling Factors.
4130 * This is done BEFORE the ShowKernelInfo() function is called so that
4131 * users can see the results of the 'option:convolve:scale' option.
4133 if ((method == ConvolveMorphology) || (method == CorrelateMorphology)) {
4137 /* Get the bias value as it will be needed */
4138 artifact = GetImageArtifact(image,"convolve:bias");
4139 if ( artifact != (const char *) NULL) {
4140 if (IfMagickFalse(IsGeometry(artifact)))
4141 (void) ThrowMagickException(exception,GetMagickModule(),
4142 OptionWarning,"InvalidSetting","'%s' '%s'",
4143 "convolve:bias",artifact);
4145 bias=StringToDoubleInterval(artifact,(double) QuantumRange+1.0);
4148 /* Scale kernel according to user wishes */
4149 artifact = GetImageArtifact(image,"convolve:scale");
4150 if ( artifact != (const char *)NULL ) {
4151 if (IfMagickFalse(IsGeometry(artifact)))
4152 (void) ThrowMagickException(exception,GetMagickModule(),
4153 OptionWarning,"InvalidSetting","'%s' '%s'",
4154 "convolve:scale",artifact);
4156 if ( curr_kernel == kernel )
4157 curr_kernel = CloneKernelInfo(kernel);
4158 if (curr_kernel == (KernelInfo *) NULL)
4159 return((Image *) NULL);
4160 ScaleGeometryKernelInfo(curr_kernel, artifact);
4165 /* display the (normalized) kernel via stderr */
4166 if ( IfStringTrue(GetImageArtifact(image,"showkernel"))
4167 || IfStringTrue(GetImageArtifact(image,"convolve:showkernel"))
4168 || IfStringTrue(GetImageArtifact(image,"morphology:showkernel")) )
4169 ShowKernelInfo(curr_kernel);
4171 /* Override the default handling of multi-kernel morphology results
4172 * If 'Undefined' use the default method
4173 * If 'None' (default for 'Convolve') re-iterate previous result
4174 * Otherwise merge resulting images using compose method given.
4175 * Default for 'HitAndMiss' is 'Lighten'.
4182 artifact = GetImageArtifact(image,"morphology:compose");
4183 if ( artifact != (const char *) NULL) {
4184 parse=ParseCommandOption(MagickComposeOptions,
4185 MagickFalse,artifact);
4187 (void) ThrowMagickException(exception,GetMagickModule(),
4188 OptionWarning,"UnrecognizedComposeOperator","'%s' '%s'",
4189 "morphology:compose",artifact);
4191 compose=(CompositeOperator)parse;
4194 /* Apply the Morphology */
4195 morphology_image = MorphologyApply(image,method,iterations,
4196 curr_kernel,compose,bias,exception);
4198 /* Cleanup and Exit */
4199 if ( curr_kernel != kernel )
4200 curr_kernel=DestroyKernelInfo(curr_kernel);
4201 return(morphology_image);
4205 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4209 + R o t a t e K e r n e l I n f o %
4213 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4215 % RotateKernelInfo() rotates the kernel by the angle given.
4217 % Currently it is restricted to 90 degree angles, of either 1D kernels
4218 % or square kernels. And 'circular' rotations of 45 degrees for 3x3 kernels.
4219 % It will ignore usless rotations for specific 'named' built-in kernels.
4221 % The format of the RotateKernelInfo method is:
4223 % void RotateKernelInfo(KernelInfo *kernel, double angle)
4225 % A description of each parameter follows:
4227 % o kernel: the Morphology/Convolution kernel
4229 % o angle: angle to rotate in degrees
4231 % This function is currently internal to this module only, but can be exported
4232 % to other modules if needed.
4234 static void RotateKernelInfo(KernelInfo *kernel, double angle)
4236 /* angle the lower kernels first */
4237 if ( kernel->next != (KernelInfo *) NULL)
4238 RotateKernelInfo(kernel->next, angle);
4240 /* WARNING: Currently assumes the kernel (rightly) is horizontally symetrical
4242 ** TODO: expand beyond simple 90 degree rotates, flips and flops
4245 /* Modulus the angle */
4246 angle = fmod(angle, 360.0);
4250 if ( 337.5 < angle || angle <= 22.5 )
4251 return; /* Near zero angle - no change! - At least not at this time */
4253 /* Handle special cases */
4254 switch (kernel->type) {
4255 /* These built-in kernels are cylindrical kernels, rotating is useless */
4256 case GaussianKernel:
4261 case LaplacianKernel:
4262 case ChebyshevKernel:
4263 case ManhattanKernel:
4264 case EuclideanKernel:
4267 /* These may be rotatable at non-90 angles in the future */
4268 /* but simply rotating them in multiples of 90 degrees is useless */
4275 /* These only allows a +/-90 degree rotation (by transpose) */
4276 /* A 180 degree rotation is useless */
4278 if ( 135.0 < angle && angle <= 225.0 )
4280 if ( 225.0 < angle && angle <= 315.0 )
4287 /* Attempt rotations by 45 degrees -- 3x3 kernels only */
4288 if ( 22.5 < fmod(angle,90.0) && fmod(angle,90.0) <= 67.5 )
4290 if ( kernel->width == 3 && kernel->height == 3 )
4291 { /* Rotate a 3x3 square by 45 degree angle */
4292 double t = kernel->values[0];
4293 kernel->values[0] = kernel->values[3];
4294 kernel->values[3] = kernel->values[6];
4295 kernel->values[6] = kernel->values[7];
4296 kernel->values[7] = kernel->values[8];
4297 kernel->values[8] = kernel->values[5];
4298 kernel->values[5] = kernel->values[2];
4299 kernel->values[2] = kernel->values[1];
4300 kernel->values[1] = t;
4301 /* rotate non-centered origin */
4302 if ( kernel->x != 1 || kernel->y != 1 ) {
4304 x = (ssize_t) kernel->x-1;
4305 y = (ssize_t) kernel->y-1;
4306 if ( x == y ) x = 0;
4307 else if ( x == 0 ) x = -y;
4308 else if ( x == -y ) y = 0;
4309 else if ( y == 0 ) y = x;
4310 kernel->x = (ssize_t) x+1;
4311 kernel->y = (ssize_t) y+1;
4313 angle = fmod(angle+315.0, 360.0); /* angle reduced 45 degrees */
4314 kernel->angle = fmod(kernel->angle+45.0, 360.0);
4317 perror("Unable to rotate non-3x3 kernel by 45 degrees");
4319 if ( 45.0 < fmod(angle, 180.0) && fmod(angle,180.0) <= 135.0 )
4321 if ( kernel->width == 1 || kernel->height == 1 )
4322 { /* Do a transpose of a 1 dimensional kernel,
4323 ** which results in a fast 90 degree rotation of some type.
4327 t = (ssize_t) kernel->width;
4328 kernel->width = kernel->height;
4329 kernel->height = (size_t) t;
4331 kernel->x = kernel->y;
4333 if ( kernel->width == 1 ) {
4334 angle = fmod(angle+270.0, 360.0); /* angle reduced 90 degrees */
4335 kernel->angle = fmod(kernel->angle+90.0, 360.0);
4337 angle = fmod(angle+90.0, 360.0); /* angle increased 90 degrees */
4338 kernel->angle = fmod(kernel->angle+270.0, 360.0);
4341 else if ( kernel->width == kernel->height )
4342 { /* Rotate a square array of values by 90 degrees */
4346 register MagickRealType
4350 for( i=0, x=(ssize_t) kernel->width-1; i<=x; i++, x--)
4351 for( j=0, y=(ssize_t) kernel->height-1; j<y; j++, y--)
4352 { t = k[i+j*kernel->width];
4353 k[i+j*kernel->width] = k[j+x*kernel->width];
4354 k[j+x*kernel->width] = k[x+y*kernel->width];
4355 k[x+y*kernel->width] = k[y+i*kernel->width];
4356 k[y+i*kernel->width] = t;
4359 /* rotate the origin - relative to center of array */
4360 { register ssize_t x,y;
4361 x = (ssize_t) (kernel->x*2-kernel->width+1);
4362 y = (ssize_t) (kernel->y*2-kernel->height+1);
4363 kernel->x = (ssize_t) ( -y +(ssize_t) kernel->width-1)/2;
4364 kernel->y = (ssize_t) ( +x +(ssize_t) kernel->height-1)/2;
4366 angle = fmod(angle+270.0, 360.0); /* angle reduced 90 degrees */
4367 kernel->angle = fmod(kernel->angle+90.0, 360.0);
4370 perror("Unable to rotate a non-square, non-linear kernel 90 degrees");
4372 if ( 135.0 < angle && angle <= 225.0 )
4374 /* For a 180 degree rotation - also know as a reflection
4375 * This is actually a very very common operation!
4376 * Basically all that is needed is a reversal of the kernel data!
4377 * And a reflection of the origon
4382 register MagickRealType
4390 j=(ssize_t) (kernel->width*kernel->height-1);
4391 for (i=0; i < j; i++, j--)
4392 t=k[i], k[i]=k[j], k[j]=t;
4394 kernel->x = (ssize_t) kernel->width - kernel->x - 1;
4395 kernel->y = (ssize_t) kernel->height - kernel->y - 1;
4396 angle = fmod(angle-180.0, 360.0); /* angle+180 degrees */
4397 kernel->angle = fmod(kernel->angle+180.0, 360.0);
4399 /* At this point angle should at least between -45 (315) and +45 degrees
4400 * In the future some form of non-orthogonal angled rotates could be
4401 * performed here, posibily with a linear kernel restriction.
4408 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4412 % S c a l e G e o m e t r y K e r n e l I n f o %
4416 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4418 % ScaleGeometryKernelInfo() takes a geometry argument string, typically
4419 % provided as a "-set option:convolve:scale {geometry}" user setting,
4420 % and modifies the kernel according to the parsed arguments of that setting.
4422 % The first argument (and any normalization flags) are passed to
4423 % ScaleKernelInfo() to scale/normalize the kernel. The second argument
4424 % is then passed to UnityAddKernelInfo() to add a scled unity kernel
4425 % into the scaled/normalized kernel.
4427 % The format of the ScaleGeometryKernelInfo method is:
4429 % void ScaleGeometryKernelInfo(KernelInfo *kernel,
4430 % const double scaling_factor,const MagickStatusType normalize_flags)
4432 % A description of each parameter follows:
4434 % o kernel: the Morphology/Convolution kernel to modify
4437 % The geometry string to parse, typically from the user provided
4438 % "-set option:convolve:scale {geometry}" setting.
4441 MagickExport void ScaleGeometryKernelInfo (KernelInfo *kernel,
4442 const char *geometry)
4450 SetGeometryInfo(&args);
4451 flags = ParseGeometry(geometry, &args);
4454 /* For Debugging Geometry Input */
4455 (void) FormatLocaleFile(stderr, "Geometry = 0x%04X : %lg x %lg %+lg %+lg\n",
4456 flags, args.rho, args.sigma, args.xi, args.psi );
4459 if ( (flags & PercentValue) != 0 ) /* Handle Percentage flag*/
4460 args.rho *= 0.01, args.sigma *= 0.01;
4462 if ( (flags & RhoValue) == 0 ) /* Set Defaults for missing args */
4464 if ( (flags & SigmaValue) == 0 )
4467 /* Scale/Normalize the input kernel */
4468 ScaleKernelInfo(kernel, args.rho, (GeometryFlags) flags);
4470 /* Add Unity Kernel, for blending with original */
4471 if ( (flags & SigmaValue) != 0 )
4472 UnityAddKernelInfo(kernel, args.sigma);
4477 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4481 % S c a l e K e r n e l I n f o %
4485 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4487 % ScaleKernelInfo() scales the given kernel list by the given amount, with or
4488 % without normalization of the sum of the kernel values (as per given flags).
4490 % By default (no flags given) the values within the kernel is scaled
4491 % directly using given scaling factor without change.
4493 % If either of the two 'normalize_flags' are given the kernel will first be
4494 % normalized and then further scaled by the scaling factor value given.
4496 % Kernel normalization ('normalize_flags' given) is designed to ensure that
4497 % any use of the kernel scaling factor with 'Convolve' or 'Correlate'
4498 % morphology methods will fall into -1.0 to +1.0 range. Note that for
4499 % non-HDRI versions of IM this may cause images to have any negative results
4500 % clipped, unless some 'bias' is used.
4502 % More specifically. Kernels which only contain positive values (such as a
4503 % 'Gaussian' kernel) will be scaled so that those values sum to +1.0,
4504 % ensuring a 0.0 to +1.0 output range for non-HDRI images.
4506 % For Kernels that contain some negative values, (such as 'Sharpen' kernels)
4507 % the kernel will be scaled by the absolute of the sum of kernel values, so
4508 % that it will generally fall within the +/- 1.0 range.
4510 % For kernels whose values sum to zero, (such as 'Laplician' kernels) kernel
4511 % will be scaled by just the sum of the postive values, so that its output
4512 % range will again fall into the +/- 1.0 range.
4514 % For special kernels designed for locating shapes using 'Correlate', (often
4515 % only containing +1 and -1 values, representing foreground/brackground
4516 % matching) a special normalization method is provided to scale the positive
4517 % values separately to those of the negative values, so the kernel will be
4518 % forced to become a zero-sum kernel better suited to such searches.
4520 % WARNING: Correct normalization of the kernel assumes that the '*_range'
4521 % attributes within the kernel structure have been correctly set during the
4524 % NOTE: The values used for 'normalize_flags' have been selected specifically
4525 % to match the use of geometry options, so that '!' means NormalizeValue, '^'
4526 % means CorrelateNormalizeValue. All other GeometryFlags values are ignored.
4528 % The format of the ScaleKernelInfo method is:
4530 % void ScaleKernelInfo(KernelInfo *kernel, const double scaling_factor,
4531 % const MagickStatusType normalize_flags )
4533 % A description of each parameter follows:
4535 % o kernel: the Morphology/Convolution kernel
4538 % multiply all values (after normalization) by this factor if not
4539 % zero. If the kernel is normalized regardless of any flags.
4541 % o normalize_flags:
4542 % GeometryFlags defining normalization method to use.
4543 % specifically: NormalizeValue, CorrelateNormalizeValue,
4544 % and/or PercentValue
4547 MagickExport void ScaleKernelInfo(KernelInfo *kernel,
4548 const double scaling_factor,const GeometryFlags normalize_flags)
4557 /* do the other kernels in a multi-kernel list first */
4558 if ( kernel->next != (KernelInfo *) NULL)
4559 ScaleKernelInfo(kernel->next, scaling_factor, normalize_flags);
4561 /* Normalization of Kernel */
4563 if ( (normalize_flags&NormalizeValue) != 0 ) {
4564 if ( fabs(kernel->positive_range + kernel->negative_range) >= MagickEpsilon )
4565 /* non-zero-summing kernel (generally positive) */
4566 pos_scale = fabs(kernel->positive_range + kernel->negative_range);
4568 /* zero-summing kernel */
4569 pos_scale = kernel->positive_range;
4571 /* Force kernel into a normalized zero-summing kernel */
4572 if ( (normalize_flags&CorrelateNormalizeValue) != 0 ) {
4573 pos_scale = ( fabs(kernel->positive_range) >= MagickEpsilon )
4574 ? kernel->positive_range : 1.0;
4575 neg_scale = ( fabs(kernel->negative_range) >= MagickEpsilon )
4576 ? -kernel->negative_range : 1.0;
4579 neg_scale = pos_scale;
4581 /* finialize scaling_factor for positive and negative components */
4582 pos_scale = scaling_factor/pos_scale;
4583 neg_scale = scaling_factor/neg_scale;
4585 for (i=0; i < (ssize_t) (kernel->width*kernel->height); i++)
4586 if ( ! IfNaN(kernel->values[i]) )
4587 kernel->values[i] *= (kernel->values[i] >= 0) ? pos_scale : neg_scale;
4589 /* convolution output range */
4590 kernel->positive_range *= pos_scale;
4591 kernel->negative_range *= neg_scale;
4592 /* maximum and minimum values in kernel */
4593 kernel->maximum *= (kernel->maximum >= 0.0) ? pos_scale : neg_scale;
4594 kernel->minimum *= (kernel->minimum >= 0.0) ? pos_scale : neg_scale;
4596 /* swap kernel settings if user's scaling factor is negative */
4597 if ( scaling_factor < MagickEpsilon ) {
4599 t = kernel->positive_range;
4600 kernel->positive_range = kernel->negative_range;
4601 kernel->negative_range = t;
4602 t = kernel->maximum;
4603 kernel->maximum = kernel->minimum;
4604 kernel->minimum = 1;
4611 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4615 % S h o w K e r n e l I n f o %
4619 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4621 % ShowKernelInfo() outputs the details of the given kernel defination to
4622 % standard error, generally due to a users 'showkernel' option request.
4624 % The format of the ShowKernel method is:
4626 % void ShowKernelInfo(const KernelInfo *kernel)
4628 % A description of each parameter follows:
4630 % o kernel: the Morphology/Convolution kernel
4633 MagickPrivate void ShowKernelInfo(const KernelInfo *kernel)
4641 for (c=0, k=kernel; k != (KernelInfo *) NULL; c++, k=k->next ) {
4643 (void) FormatLocaleFile(stderr, "Kernel");
4644 if ( kernel->next != (KernelInfo *) NULL )
4645 (void) FormatLocaleFile(stderr, " #%lu", (unsigned long) c );
4646 (void) FormatLocaleFile(stderr, " \"%s",
4647 CommandOptionToMnemonic(MagickKernelOptions, k->type) );
4648 if ( fabs(k->angle) >= MagickEpsilon )
4649 (void) FormatLocaleFile(stderr, "@%lg", k->angle);
4650 (void) FormatLocaleFile(stderr, "\" of size %lux%lu%+ld%+ld",(unsigned long)
4651 k->width,(unsigned long) k->height,(long) k->x,(long) k->y);
4652 (void) FormatLocaleFile(stderr,
4653 " with values from %.*lg to %.*lg\n",
4654 GetMagickPrecision(), k->minimum,
4655 GetMagickPrecision(), k->maximum);
4656 (void) FormatLocaleFile(stderr, "Forming a output range from %.*lg to %.*lg",
4657 GetMagickPrecision(), k->negative_range,
4658 GetMagickPrecision(), k->positive_range);
4659 if ( fabs(k->positive_range+k->negative_range) < MagickEpsilon )
4660 (void) FormatLocaleFile(stderr, " (Zero-Summing)\n");
4661 else if ( fabs(k->positive_range+k->negative_range-1.0) < MagickEpsilon )
4662 (void) FormatLocaleFile(stderr, " (Normalized)\n");
4664 (void) FormatLocaleFile(stderr, " (Sum %.*lg)\n",
4665 GetMagickPrecision(), k->positive_range+k->negative_range);
4666 for (i=v=0; v < k->height; v++) {
4667 (void) FormatLocaleFile(stderr, "%2lu:", (unsigned long) v );
4668 for (u=0; u < k->width; u++, i++)
4669 if ( IfNaN(k->values[i]) )
4670 (void) FormatLocaleFile(stderr," %*s", GetMagickPrecision()+3, "nan");
4672 (void) FormatLocaleFile(stderr," %*.*lg", GetMagickPrecision()+3,
4673 GetMagickPrecision(), (double) k->values[i]);
4674 (void) FormatLocaleFile(stderr,"\n");
4680 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4684 % U n i t y A d d K e r n a l I n f o %
4688 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4690 % UnityAddKernelInfo() Adds a given amount of the 'Unity' Convolution Kernel
4691 % to the given pre-scaled and normalized Kernel. This in effect adds that
4692 % amount of the original image into the resulting convolution kernel. This
4693 % value is usually provided by the user as a percentage value in the
4694 % 'convolve:scale' setting.
4696 % The resulting effect is to convert the defined kernels into blended
4697 % soft-blurs, unsharp kernels or into sharpening kernels.
4699 % The format of the UnityAdditionKernelInfo method is:
4701 % void UnityAdditionKernelInfo(KernelInfo *kernel, const double scale )
4703 % A description of each parameter follows:
4705 % o kernel: the Morphology/Convolution kernel
4708 % scaling factor for the unity kernel to be added to
4712 MagickExport void UnityAddKernelInfo(KernelInfo *kernel,
4715 /* do the other kernels in a multi-kernel list first */
4716 if ( kernel->next != (KernelInfo *) NULL)
4717 UnityAddKernelInfo(kernel->next, scale);
4719 /* Add the scaled unity kernel to the existing kernel */
4720 kernel->values[kernel->x+kernel->y*kernel->width] += scale;
4721 CalcKernelMetaData(kernel); /* recalculate the meta-data */
4727 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4731 % Z e r o K e r n e l N a n s %
4735 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4737 % ZeroKernelNans() replaces any special 'nan' value that may be present in
4738 % the kernel with a zero value. This is typically done when the kernel will
4739 % be used in special hardware (GPU) convolution processors, to simply
4742 % The format of the ZeroKernelNans method is:
4744 % void ZeroKernelNans (KernelInfo *kernel)
4746 % A description of each parameter follows:
4748 % o kernel: the Morphology/Convolution kernel
4751 MagickPrivate void ZeroKernelNans(KernelInfo *kernel)
4756 /* do the other kernels in a multi-kernel list first */
4757 if ( kernel->next != (KernelInfo *) NULL)
4758 ZeroKernelNans(kernel->next);
4760 for (i=0; i < (kernel->width*kernel->height); i++)
4761 if ( IfNaN(kernel->values[i]) )
4762 kernel->values[i] = 0.0;