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-2011 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/color-private.h"
56 #include "MagickCore/enhance.h"
57 #include "MagickCore/exception.h"
58 #include "MagickCore/exception-private.h"
59 #include "MagickCore/gem.h"
60 #include "MagickCore/hashmap.h"
61 #include "MagickCore/image.h"
62 #include "MagickCore/image-private.h"
63 #include "MagickCore/list.h"
64 #include "MagickCore/magick.h"
65 #include "MagickCore/memory_.h"
66 #include "MagickCore/monitor-private.h"
67 #include "MagickCore/morphology.h"
68 #include "MagickCore/morphology-private.h"
69 #include "MagickCore/option.h"
70 #include "MagickCore/pixel-accessor.h"
71 #include "MagickCore/prepress.h"
72 #include "MagickCore/quantize.h"
73 #include "MagickCore/registry.h"
74 #include "MagickCore/semaphore.h"
75 #include "MagickCore/splay-tree.h"
76 #include "MagickCore/statistic.h"
77 #include "MagickCore/string_.h"
78 #include "MagickCore/string-private.h"
79 #include "MagickCore/token.h"
80 #include "MagickCore/utility.h"
84 ** The following test is for special floating point numbers of value NaN (not
85 ** a number), that may be used within a Kernel Definition. NaN's are defined
86 ** as part of the IEEE standard for floating point number representation.
88 ** These are used as a Kernel value to mean that this kernel position is not
89 ** part of the kernel neighbourhood for convolution or morphology processing,
90 ** and thus should be ignored. This allows the use of 'shaped' kernels.
92 ** The special properity that two NaN's are never equal, even if they are from
93 ** the same variable allow you to test if a value is special NaN value.
95 ** This macro IsNaN() is thus is only true if the value given is NaN.
97 #define IsNan(a) ((a)!=(a))
100 Other global definitions used by module.
102 static inline double MagickMin(const double x,const double y)
104 return( x < y ? x : y);
106 static inline double MagickMax(const double x,const double y)
108 return( x > y ? x : y);
110 #define Minimize(assign,value) assign=MagickMin(assign,value)
111 #define Maximize(assign,value) assign=MagickMax(assign,value)
113 /* Currently these are only internal to this module */
115 CalcKernelMetaData(KernelInfo *),
116 ExpandMirrorKernelInfo(KernelInfo *),
117 ExpandRotateKernelInfo(KernelInfo *, const double),
118 RotateKernelInfo(KernelInfo *, double);
121 /* Quick function to find last kernel in a kernel list */
122 static inline KernelInfo *LastKernelInfo(KernelInfo *kernel)
124 while (kernel->next != (KernelInfo *) NULL)
125 kernel = kernel->next;
130 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
134 % A c q u i r e K e r n e l I n f o %
138 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
140 % AcquireKernelInfo() takes the given string (generally supplied by the
141 % user) and converts it into a Morphology/Convolution Kernel. This allows
142 % users to specify a kernel from a number of pre-defined kernels, or to fully
143 % specify their own kernel for a specific Convolution or Morphology
146 % The kernel so generated can be any rectangular array of floating point
147 % values (doubles) with the 'control point' or 'pixel being affected'
148 % anywhere within that array of values.
150 % Previously IM was restricted to a square of odd size using the exact
151 % center as origin, this is no longer the case, and any rectangular kernel
152 % with any value being declared the origin. This in turn allows the use of
153 % highly asymmetrical kernels.
155 % The floating point values in the kernel can also include a special value
156 % known as 'nan' or 'not a number' to indicate that this value is not part
157 % of the kernel array. This allows you to shaped the kernel within its
158 % rectangular area. That is 'nan' values provide a 'mask' for the kernel
159 % shape. However at least one non-nan value must be provided for correct
160 % working of a kernel.
162 % The returned kernel should be freed using the DestroyKernelInfo() when you
163 % are finished with it. Do not free this memory yourself.
165 % Input kernel defintion strings can consist of any of three types.
168 % Select from one of the built in kernels, using the name and
169 % geometry arguments supplied. See AcquireKernelBuiltIn()
171 % "WxH[+X+Y][@><]:num, num, num ..."
172 % a kernel of size W by H, with W*H floating point numbers following.
173 % the 'center' can be optionally be defined at +X+Y (such that +0+0
174 % is top left corner). If not defined the pixel in the center, for
175 % odd sizes, or to the immediate top or left of center for even sizes
176 % is automatically selected.
178 % "num, num, num, num, ..."
179 % list of floating point numbers defining an 'old style' odd sized
180 % square kernel. At least 9 values should be provided for a 3x3
181 % square kernel, 25 for a 5x5 square kernel, 49 for 7x7, etc.
182 % Values can be space or comma separated. This is not recommended.
184 % You can define a 'list of kernels' which can be used by some morphology
185 % operators A list is defined as a semi-colon separated list kernels.
187 % " kernel ; kernel ; kernel ; "
189 % Any extra ';' characters, at start, end or between kernel defintions are
192 % The special flags will expand a single kernel, into a list of rotated
193 % kernels. A '@' flag will expand a 3x3 kernel into a list of 45-degree
194 % cyclic rotations, while a '>' will generate a list of 90-degree rotations.
195 % The '<' also exands using 90-degree rotates, but giving a 180-degree
196 % reflected kernel before the +/- 90-degree rotations, which can be important
197 % for Thinning operations.
199 % Note that 'name' kernels will start with an alphabetic character while the
200 % new kernel specification has a ':' character in its specification string.
201 % If neither is the case, it is assumed an old style of a simple list of
202 % numbers generating a odd-sized square kernel has been given.
204 % The format of the AcquireKernal method is:
206 % KernelInfo *AcquireKernelInfo(const char *kernel_string)
208 % A description of each parameter follows:
210 % o kernel_string: the Morphology/Convolution kernel wanted.
214 /* This was separated so that it could be used as a separate
215 ** array input handling function, such as for -color-matrix
217 static KernelInfo *ParseKernelArray(const char *kernel_string)
223 token[MaxTextExtent];
233 nan = sqrt((double)-1.0); /* Special Value : Not A Number */
241 kernel=(KernelInfo *) AcquireMagickMemory(sizeof(*kernel));
242 if (kernel == (KernelInfo *)NULL)
244 (void) ResetMagickMemory(kernel,0,sizeof(*kernel));
245 kernel->minimum = kernel->maximum = kernel->angle = 0.0;
246 kernel->negative_range = kernel->positive_range = 0.0;
247 kernel->type = UserDefinedKernel;
248 kernel->next = (KernelInfo *) NULL;
249 kernel->signature = MagickSignature;
250 if (kernel_string == (const char *) NULL)
253 /* find end of this specific kernel definition string */
254 end = strchr(kernel_string, ';');
255 if ( end == (char *) NULL )
256 end = strchr(kernel_string, '\0');
258 /* clear flags - for Expanding kernel lists thorugh rotations */
261 /* Has a ':' in argument - New user kernel specification */
262 p = strchr(kernel_string, ':');
263 if ( p != (char *) NULL && p < end)
265 /* ParseGeometry() needs the geometry separated! -- Arrgghh */
266 memcpy(token, kernel_string, (size_t) (p-kernel_string));
267 token[p-kernel_string] = '\0';
268 SetGeometryInfo(&args);
269 flags = ParseGeometry(token, &args);
271 /* Size handling and checks of geometry settings */
272 if ( (flags & WidthValue) == 0 ) /* if no width then */
273 args.rho = args.sigma; /* then width = height */
274 if ( args.rho < 1.0 ) /* if width too small */
275 args.rho = 1.0; /* then width = 1 */
276 if ( args.sigma < 1.0 ) /* if height too small */
277 args.sigma = args.rho; /* then height = width */
278 kernel->width = (size_t)args.rho;
279 kernel->height = (size_t)args.sigma;
281 /* Offset Handling and Checks */
282 if ( args.xi < 0.0 || args.psi < 0.0 )
283 return(DestroyKernelInfo(kernel));
284 kernel->x = ((flags & XValue)!=0) ? (ssize_t)args.xi
285 : (ssize_t) (kernel->width-1)/2;
286 kernel->y = ((flags & YValue)!=0) ? (ssize_t)args.psi
287 : (ssize_t) (kernel->height-1)/2;
288 if ( kernel->x >= (ssize_t) kernel->width ||
289 kernel->y >= (ssize_t) kernel->height )
290 return(DestroyKernelInfo(kernel));
292 p++; /* advance beyond the ':' */
295 { /* ELSE - Old old specification, forming odd-square kernel */
296 /* count up number of values given */
297 p=(const char *) kernel_string;
298 while ((isspace((int) ((unsigned char) *p)) != 0) || (*p == '\''))
299 p++; /* ignore "'" chars for convolve filter usage - Cristy */
300 for (i=0; p < end; i++)
302 GetMagickToken(p,&p,token);
304 GetMagickToken(p,&p,token);
306 /* set the size of the kernel - old sized square */
307 kernel->width = kernel->height= (size_t) sqrt((double) i+1.0);
308 kernel->x = kernel->y = (ssize_t) (kernel->width-1)/2;
309 p=(const char *) kernel_string;
310 while ((isspace((int) ((unsigned char) *p)) != 0) || (*p == '\''))
311 p++; /* ignore "'" chars for convolve filter usage - Cristy */
314 /* Read in the kernel values from rest of input string argument */
315 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
316 kernel->height*sizeof(double));
317 if (kernel->values == (double *) NULL)
318 return(DestroyKernelInfo(kernel));
320 kernel->minimum = +MagickHuge;
321 kernel->maximum = -MagickHuge;
322 kernel->negative_range = kernel->positive_range = 0.0;
324 for (i=0; (i < (ssize_t) (kernel->width*kernel->height)) && (p < end); i++)
326 GetMagickToken(p,&p,token);
328 GetMagickToken(p,&p,token);
329 if ( LocaleCompare("nan",token) == 0
330 || LocaleCompare("-",token) == 0 ) {
331 kernel->values[i] = nan; /* do not include this value in kernel */
334 kernel->values[i] = InterpretLocaleValue(token,(char **) NULL);
335 ( kernel->values[i] < 0)
336 ? ( kernel->negative_range += kernel->values[i] )
337 : ( kernel->positive_range += kernel->values[i] );
338 Minimize(kernel->minimum, kernel->values[i]);
339 Maximize(kernel->maximum, kernel->values[i]);
343 /* sanity check -- no more values in kernel definition */
344 GetMagickToken(p,&p,token);
345 if ( *token != '\0' && *token != ';' && *token != '\'' )
346 return(DestroyKernelInfo(kernel));
349 /* this was the old method of handling a incomplete kernel */
350 if ( i < (ssize_t) (kernel->width*kernel->height) ) {
351 Minimize(kernel->minimum, kernel->values[i]);
352 Maximize(kernel->maximum, kernel->values[i]);
353 for ( ; i < (ssize_t) (kernel->width*kernel->height); i++)
354 kernel->values[i]=0.0;
357 /* Number of values for kernel was not enough - Report Error */
358 if ( i < (ssize_t) (kernel->width*kernel->height) )
359 return(DestroyKernelInfo(kernel));
362 /* check that we recieved at least one real (non-nan) value! */
363 if ( kernel->minimum == MagickHuge )
364 return(DestroyKernelInfo(kernel));
366 if ( (flags & AreaValue) != 0 ) /* '@' symbol in kernel size */
367 ExpandRotateKernelInfo(kernel, 45.0); /* cyclic rotate 3x3 kernels */
368 else if ( (flags & GreaterValue) != 0 ) /* '>' symbol in kernel args */
369 ExpandRotateKernelInfo(kernel, 90.0); /* 90 degree rotate of kernel */
370 else if ( (flags & LessValue) != 0 ) /* '<' symbol in kernel args */
371 ExpandMirrorKernelInfo(kernel); /* 90 degree mirror rotate */
376 static KernelInfo *ParseKernelName(const char *kernel_string)
379 token[MaxTextExtent];
397 /* Parse special 'named' kernel */
398 GetMagickToken(kernel_string,&p,token);
399 type=ParseCommandOption(MagickKernelOptions,MagickFalse,token);
400 if ( type < 0 || type == UserDefinedKernel )
401 return((KernelInfo *)NULL); /* not a valid named kernel */
403 while (((isspace((int) ((unsigned char) *p)) != 0) ||
404 (*p == ',') || (*p == ':' )) && (*p != '\0') && (*p != ';'))
407 end = strchr(p, ';'); /* end of this kernel defintion */
408 if ( end == (char *) NULL )
409 end = strchr(p, '\0');
411 /* ParseGeometry() needs the geometry separated! -- Arrgghh */
412 memcpy(token, p, (size_t) (end-p));
414 SetGeometryInfo(&args);
415 flags = ParseGeometry(token, &args);
418 /* For Debugging Geometry Input */
419 (void) FormatLocaleFile(stderr, "Geometry = 0x%04X : %lg x %lg %+lg %+lg\n",
420 flags, args.rho, args.sigma, args.xi, args.psi );
423 /* special handling of missing values in input string */
425 /* Shape Kernel Defaults */
427 if ( (flags & WidthValue) == 0 )
428 args.rho = 1.0; /* Default scale = 1.0, zero is valid */
436 if ( (flags & HeightValue) == 0 )
437 args.sigma = 1.0; /* Default scale = 1.0, zero is valid */
440 if ( (flags & XValue) == 0 )
441 args.xi = 1.0; /* Default scale = 1.0, zero is valid */
443 case RectangleKernel: /* Rectangle - set size defaults */
444 if ( (flags & WidthValue) == 0 ) /* if no width then */
445 args.rho = args.sigma; /* then width = height */
446 if ( args.rho < 1.0 ) /* if width too small */
447 args.rho = 3; /* then width = 3 */
448 if ( args.sigma < 1.0 ) /* if height too small */
449 args.sigma = args.rho; /* then height = width */
450 if ( (flags & XValue) == 0 ) /* center offset if not defined */
451 args.xi = (double)(((ssize_t)args.rho-1)/2);
452 if ( (flags & YValue) == 0 )
453 args.psi = (double)(((ssize_t)args.sigma-1)/2);
455 /* Distance Kernel Defaults */
456 case ChebyshevKernel:
457 case ManhattanKernel:
458 case OctagonalKernel:
459 case EuclideanKernel:
460 if ( (flags & HeightValue) == 0 ) /* no distance scale */
461 args.sigma = 100.0; /* default distance scaling */
462 else if ( (flags & AspectValue ) != 0 ) /* '!' flag */
463 args.sigma = QuantumRange/(args.sigma+1); /* maximum pixel distance */
464 else if ( (flags & PercentValue ) != 0 ) /* '%' flag */
465 args.sigma *= QuantumRange/100.0; /* percentage of color range */
471 kernel = AcquireKernelBuiltIn((KernelInfoType)type, &args);
472 if ( kernel == (KernelInfo *) NULL )
475 /* global expand to rotated kernel list - only for single kernels */
476 if ( kernel->next == (KernelInfo *) NULL ) {
477 if ( (flags & AreaValue) != 0 ) /* '@' symbol in kernel args */
478 ExpandRotateKernelInfo(kernel, 45.0);
479 else if ( (flags & GreaterValue) != 0 ) /* '>' symbol in kernel args */
480 ExpandRotateKernelInfo(kernel, 90.0);
481 else if ( (flags & LessValue) != 0 ) /* '<' symbol in kernel args */
482 ExpandMirrorKernelInfo(kernel);
488 MagickExport KernelInfo *AcquireKernelInfo(const char *kernel_string)
496 token[MaxTextExtent];
504 if (kernel_string == (const char *) NULL)
505 return(ParseKernelArray(kernel_string));
510 while ( GetMagickToken(p,NULL,token), *token != '\0' ) {
512 /* ignore extra or multiple ';' kernel separators */
513 if ( *token != ';' ) {
515 /* tokens starting with alpha is a Named kernel */
516 if (isalpha((int) *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 ) {
523 (void) FormatLocaleFile(stderr, "Failed to parse kernel number #%.20g\n",
524 (double) kernel_number);
525 if ( kernel != (KernelInfo *) NULL )
526 kernel=DestroyKernelInfo(kernel);
527 return((KernelInfo *) NULL);
530 /* initialise or append the kernel list */
531 if ( kernel == (KernelInfo *) NULL )
534 LastKernelInfo(kernel)->next = new_kernel;
537 /* look for the next kernel in list */
539 if ( p == (char *) NULL )
549 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
553 % A c q u i r e K e r n e l B u i l t I n %
557 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
559 % AcquireKernelBuiltIn() returned one of the 'named' built-in types of
560 % kernels used for special purposes such as gaussian blurring, skeleton
561 % pruning, and edge distance determination.
563 % They take a KernelType, and a set of geometry style arguments, which were
564 % typically decoded from a user supplied string, or from a more complex
565 % Morphology Method that was requested.
567 % The format of the AcquireKernalBuiltIn method is:
569 % KernelInfo *AcquireKernelBuiltIn(const KernelInfoType type,
570 % const GeometryInfo args)
572 % A description of each parameter follows:
574 % o type: the pre-defined type of kernel wanted
576 % o args: arguments defining or modifying the kernel
578 % Convolution Kernels
581 % The a No-Op or Scaling single element kernel.
583 % Gaussian:{radius},{sigma}
584 % Generate a two-dimensional gaussian kernel, as used by -gaussian.
585 % The sigma for the curve is required. The resulting kernel is
588 % If 'sigma' is zero, you get a single pixel on a field of zeros.
590 % NOTE: that the 'radius' is optional, but if provided can limit (clip)
591 % the final size of the resulting kernel to a square 2*radius+1 in size.
592 % The radius should be at least 2 times that of the sigma value, or
593 % sever clipping and aliasing may result. If not given or set to 0 the
594 % radius will be determined so as to produce the best minimal error
595 % result, which is usally much larger than is normally needed.
597 % LoG:{radius},{sigma}
598 % "Laplacian of a Gaussian" or "Mexician Hat" Kernel.
599 % The supposed ideal edge detection, zero-summing kernel.
601 % An alturnative to this kernel is to use a "DoG" with a sigma ratio of
602 % approx 1.6 (according to wikipedia).
604 % DoG:{radius},{sigma1},{sigma2}
605 % "Difference of Gaussians" Kernel.
606 % As "Gaussian" but with a gaussian produced by 'sigma2' subtracted
607 % from the gaussian produced by 'sigma1'. Typically sigma2 > sigma1.
608 % The result is a zero-summing kernel.
610 % Blur:{radius},{sigma}[,{angle}]
611 % Generates a 1 dimensional or linear gaussian blur, at the angle given
612 % (current restricted to orthogonal angles). If a 'radius' is given the
613 % kernel is clipped to a width of 2*radius+1. Kernel can be rotated
614 % by a 90 degree angle.
616 % If 'sigma' is zero, you get a single pixel on a field of zeros.
618 % Note that two convolutions with two "Blur" kernels perpendicular to
619 % each other, is equivalent to a far larger "Gaussian" kernel with the
620 % same sigma value, However it is much faster to apply. This is how the
621 % "-blur" operator actually works.
623 % Comet:{width},{sigma},{angle}
624 % Blur in one direction only, much like how a bright object leaves
625 % a comet like trail. The Kernel is actually half a gaussian curve,
626 % Adding two such blurs in opposite directions produces a Blur Kernel.
627 % Angle can be rotated in multiples of 90 degrees.
629 % Note that the first argument is the width of the kernel and not the
630 % radius of the kernel.
632 % # Still to be implemented...
636 % # Set kernel values using a resize filter, and given scale (sigma)
637 % # Cylindrical or Linear. Is this possible with an image?
640 % Named Constant Convolution Kernels
642 % All these are unscaled, zero-summing kernels by default. As such for
643 % non-HDRI version of ImageMagick some form of normalization, user scaling,
644 % and biasing the results is recommended, to prevent the resulting image
647 % The 3x3 kernels (most of these) can be circularly rotated in multiples of
648 % 45 degrees to generate the 8 angled varients of each of the kernels.
651 % Discrete Lapacian Kernels, (without normalization)
652 % Type 0 : 3x3 with center:8 surounded by -1 (8 neighbourhood)
653 % Type 1 : 3x3 with center:4 edge:-1 corner:0 (4 neighbourhood)
654 % Type 2 : 3x3 with center:4 edge:1 corner:-2
655 % Type 3 : 3x3 with center:4 edge:-2 corner:1
656 % Type 5 : 5x5 laplacian
657 % Type 7 : 7x7 laplacian
658 % Type 15 : 5x5 LoG (sigma approx 1.4)
659 % Type 19 : 9x9 LoG (sigma approx 1.4)
662 % Sobel 'Edge' convolution kernel (3x3)
668 % Roberts convolution kernel (3x3)
674 % Prewitt Edge convolution kernel (3x3)
680 % Prewitt's "Compass" convolution kernel (3x3)
686 % Kirsch's "Compass" convolution kernel (3x3)
692 % Frei-Chen Edge Detector is based on a kernel that is similar to
693 % the Sobel Kernel, but is designed to be isotropic. That is it takes
694 % into account the distance of the diagonal in the kernel.
697 % | sqrt(2), 0, -sqrt(2) |
700 % FreiChen:{type},{angle}
702 % Frei-Chen Pre-weighted kernels...
704 % Type 0: default un-nomalized version shown above.
706 % Type 1: Orthogonal Kernel (same as type 11 below)
708 % | sqrt(2), 0, -sqrt(2) | / 2*sqrt(2)
711 % Type 2: Diagonal form of Kernel...
713 % | sqrt(2), 0, -sqrt(2) | / 2*sqrt(2)
716 % However this kernel is als at the heart of the FreiChen Edge Detection
717 % Process which uses a set of 9 specially weighted kernel. These 9
718 % kernels not be normalized, but directly applied to the image. The
719 % results is then added together, to produce the intensity of an edge in
720 % a specific direction. The square root of the pixel value can then be
721 % taken as the cosine of the edge, and at least 2 such runs at 90 degrees
722 % from each other, both the direction and the strength of the edge can be
725 % Type 10: All 9 of the following pre-weighted kernels...
727 % Type 11: | 1, 0, -1 |
728 % | sqrt(2), 0, -sqrt(2) | / 2*sqrt(2)
731 % Type 12: | 1, sqrt(2), 1 |
732 % | 0, 0, 0 | / 2*sqrt(2)
735 % Type 13: | sqrt(2), -1, 0 |
736 % | -1, 0, 1 | / 2*sqrt(2)
739 % Type 14: | 0, 1, -sqrt(2) |
740 % | -1, 0, 1 | / 2*sqrt(2)
743 % Type 15: | 0, -1, 0 |
747 % Type 16: | 1, 0, -1 |
751 % Type 17: | 1, -2, 1 |
755 % Type 18: | -2, 1, -2 |
759 % Type 19: | 1, 1, 1 |
763 % The first 4 are for edge detection, the next 4 are for line detection
764 % and the last is to add a average component to the results.
766 % Using a special type of '-1' will return all 9 pre-weighted kernels
767 % as a multi-kernel list, so that you can use them directly (without
768 % normalization) with the special "-set option:morphology:compose Plus"
769 % setting to apply the full FreiChen Edge Detection Technique.
771 % If 'type' is large it will be taken to be an actual rotation angle for
772 % the default FreiChen (type 0) kernel. As such FreiChen:45 will look
773 % like a Sobel:45 but with 'sqrt(2)' instead of '2' values.
775 % WARNING: The above was layed out as per
776 % http://www.math.tau.ac.il/~turkel/notes/edge_detectors.pdf
777 % But rotated 90 degrees so direction is from left rather than the top.
778 % I have yet to find any secondary confirmation of the above. The only
779 % other source found was actual source code at
780 % http://ltswww.epfl.ch/~courstiv/exos_labos/sol3.pdf
781 % Neigher paper defineds the kernels in a way that looks locical or
782 % correct when taken as a whole.
786 % Diamond:[{radius}[,{scale}]]
787 % Generate a diamond shaped kernel with given radius to the points.
788 % Kernel size will again be radius*2+1 square and defaults to radius 1,
789 % generating a 3x3 kernel that is slightly larger than a square.
791 % Square:[{radius}[,{scale}]]
792 % Generate a square shaped kernel of size radius*2+1, and defaulting
793 % to a 3x3 (radius 1).
795 % Octagon:[{radius}[,{scale}]]
796 % Generate octagonal shaped kernel of given radius and constant scale.
797 % Default radius is 3 producing a 7x7 kernel. A radius of 1 will result
798 % in "Diamond" kernel.
800 % Disk:[{radius}[,{scale}]]
801 % Generate a binary disk, thresholded at the radius given, the radius
802 % may be a float-point value. Final Kernel size is floor(radius)*2+1
803 % square. A radius of 5.3 is the default.
805 % NOTE: That a low radii Disk kernels produce the same results as
806 % many of the previously defined kernels, but differ greatly at larger
807 % radii. Here is a table of equivalences...
808 % "Disk:1" => "Diamond", "Octagon:1", or "Cross:1"
809 % "Disk:1.5" => "Square"
810 % "Disk:2" => "Diamond:2"
811 % "Disk:2.5" => "Octagon"
812 % "Disk:2.9" => "Square:2"
813 % "Disk:3.5" => "Octagon:3"
814 % "Disk:4.5" => "Octagon:4"
815 % "Disk:5.4" => "Octagon:5"
816 % "Disk:6.4" => "Octagon:6"
817 % All other Disk shapes are unique to this kernel, but because a "Disk"
818 % is more circular when using a larger radius, using a larger radius is
819 % preferred over iterating the morphological operation.
821 % Rectangle:{geometry}
822 % Simply generate a rectangle of 1's with the size given. You can also
823 % specify the location of the 'control point', otherwise the closest
824 % pixel to the center of the rectangle is selected.
826 % Properly centered and odd sized rectangles work the best.
828 % Symbol Dilation Kernels
830 % These kernel is not a good general morphological kernel, but is used
831 % more for highlighting and marking any single pixels in an image using,
832 % a "Dilate" method as appropriate.
834 % For the same reasons iterating these kernels does not produce the
835 % same result as using a larger radius for the symbol.
837 % Plus:[{radius}[,{scale}]]
838 % Cross:[{radius}[,{scale}]]
839 % Generate a kernel in the shape of a 'plus' or a 'cross' with
840 % a each arm the length of the given radius (default 2).
842 % NOTE: "plus:1" is equivalent to a "Diamond" kernel.
844 % Ring:{radius1},{radius2}[,{scale}]
845 % A ring of the values given that falls between the two radii.
846 % Defaults to a ring of approximataly 3 radius in a 7x7 kernel.
847 % This is the 'edge' pixels of the default "Disk" kernel,
848 % More specifically, "Ring" -> "Ring:2.5,3.5,1.0"
850 % Hit and Miss Kernels
852 % Peak:radius1,radius2
853 % Find any peak larger than the pixels the fall between the two radii.
854 % The default ring of pixels is as per "Ring".
856 % Find flat orthogonal edges of a binary shape
858 % Find 90 degree corners of a binary shape
860 % A special kernel to thin the 'outside' of diagonals
862 % Find end points of lines (for pruning a skeletion)
863 % Two types of lines ends (default to both) can be searched for
864 % Type 0: All line ends
865 % Type 1: single kernel for 4-conneected line ends
866 % Type 2: single kernel for simple line ends
868 % Find three line junctions (within a skeletion)
869 % Type 0: all line junctions
870 % Type 1: Y Junction kernel
871 % Type 2: Diagonal T Junction kernel
872 % Type 3: Orthogonal T Junction kernel
873 % Type 4: Diagonal X Junction kernel
874 % Type 5: Orthogonal + Junction kernel
876 % Find single pixel ridges or thin lines
877 % Type 1: Fine single pixel thick lines and ridges
878 % Type 2: Find two pixel thick lines and ridges
880 % Octagonal Thickening Kernel, to generate convex hulls of 45 degrees
882 % Traditional skeleton generating kernels.
883 % Type 1: Tradional Skeleton kernel (4 connected skeleton)
884 % Type 2: HIPR2 Skeleton kernel (8 connected skeleton)
885 % Type 3: Thinning skeleton based on a ressearch paper by
886 % Dan S. Bloomberg (Default Type)
888 % A huge variety of Thinning Kernels designed to preserve conectivity.
889 % many other kernel sets use these kernels as source definitions.
890 % Type numbers are 41-49, 81-89, 481, and 482 which are based on
891 % the super and sub notations used in the source research paper.
893 % Distance Measuring Kernels
895 % Different types of distance measuring methods, which are used with the
896 % a 'Distance' morphology method for generating a gradient based on
897 % distance from an edge of a binary shape, though there is a technique
898 % for handling a anti-aliased shape.
900 % See the 'Distance' Morphological Method, for information of how it is
903 % Chebyshev:[{radius}][x{scale}[%!]]
904 % Chebyshev Distance (also known as Tchebychev or Chessboard distance)
905 % is a value of one to any neighbour, orthogonal or diagonal. One why
906 % of thinking of it is the number of squares a 'King' or 'Queen' in
907 % chess needs to traverse reach any other position on a chess board.
908 % It results in a 'square' like distance function, but one where
909 % diagonals are given a value that is closer than expected.
911 % Manhattan:[{radius}][x{scale}[%!]]
912 % Manhattan Distance (also known as Rectilinear, City Block, or the Taxi
913 % Cab distance metric), it is the distance needed when you can only
914 % travel in horizontal or vertical directions only. It is the
915 % distance a 'Rook' in chess would have to travel, and results in a
916 % diamond like distances, where diagonals are further than expected.
918 % Octagonal:[{radius}][x{scale}[%!]]
919 % An interleving of Manhatten and Chebyshev metrics producing an
920 % increasing octagonally shaped distance. Distances matches those of
921 % the "Octagon" shaped kernel of the same radius. The minimum radius
922 % and default is 2, producing a 5x5 kernel.
924 % Euclidean:[{radius}][x{scale}[%!]]
925 % Euclidean distance is the 'direct' or 'as the crow flys' distance.
926 % However by default the kernel size only has a radius of 1, which
927 % limits the distance to 'Knight' like moves, with only orthogonal and
928 % diagonal measurements being correct. As such for the default kernel
929 % you will get octagonal like distance function.
931 % However using a larger radius such as "Euclidean:4" you will get a
932 % much smoother distance gradient from the edge of the shape. Especially
933 % if the image is pre-processed to include any anti-aliasing pixels.
934 % Of course a larger kernel is slower to use, and not always needed.
936 % The first three Distance Measuring Kernels will only generate distances
937 % of exact multiples of {scale} in binary images. As such you can use a
938 % scale of 1 without loosing any information. However you also need some
939 % scaling when handling non-binary anti-aliased shapes.
941 % The "Euclidean" Distance Kernel however does generate a non-integer
942 % fractional results, and as such scaling is vital even for binary shapes.
946 MagickExport KernelInfo *AcquireKernelBuiltIn(const KernelInfoType type,
947 const GeometryInfo *args)
960 nan = sqrt((double)-1.0); /* Special Value : Not A Number */
962 /* Generate a new empty kernel if needed */
963 kernel=(KernelInfo *) NULL;
965 case UndefinedKernel: /* These should not call this function */
966 case UserDefinedKernel:
967 assert("Should not call this function" != (char *)NULL);
969 case LaplacianKernel: /* Named Descrete Convolution Kernels */
970 case SobelKernel: /* these are defined using other kernels */
976 case EdgesKernel: /* Hit and Miss kernels */
978 case DiagonalsKernel:
980 case LineJunctionsKernel:
982 case ConvexHullKernel:
985 break; /* A pre-generated kernel is not needed */
987 /* set to 1 to do a compile-time check that we haven't missed anything */
996 case RectangleKernel:
1003 case ChebyshevKernel:
1004 case ManhattanKernel:
1005 case OctangonalKernel:
1006 case EuclideanKernel:
1010 /* Generate the base Kernel Structure */
1011 kernel=(KernelInfo *) AcquireMagickMemory(sizeof(*kernel));
1012 if (kernel == (KernelInfo *) NULL)
1014 (void) ResetMagickMemory(kernel,0,sizeof(*kernel));
1015 kernel->minimum = kernel->maximum = kernel->angle = 0.0;
1016 kernel->negative_range = kernel->positive_range = 0.0;
1017 kernel->type = type;
1018 kernel->next = (KernelInfo *) NULL;
1019 kernel->signature = MagickSignature;
1029 kernel->height = kernel->width = (size_t) 1;
1030 kernel->x = kernel->y = (ssize_t) 0;
1031 kernel->values=(double *) AcquireQuantumMemory(1,sizeof(double));
1032 if (kernel->values == (double *) NULL)
1033 return(DestroyKernelInfo(kernel));
1034 kernel->maximum = kernel->values[0] = args->rho;
1038 case GaussianKernel:
1042 sigma = fabs(args->sigma),
1043 sigma2 = fabs(args->xi),
1046 if ( args->rho >= 1.0 )
1047 kernel->width = (size_t)args->rho*2+1;
1048 else if ( (type != DoGKernel) || (sigma >= sigma2) )
1049 kernel->width = GetOptimalKernelWidth2D(args->rho,sigma);
1051 kernel->width = GetOptimalKernelWidth2D(args->rho,sigma2);
1052 kernel->height = kernel->width;
1053 kernel->x = kernel->y = (ssize_t) (kernel->width-1)/2;
1054 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
1055 kernel->height*sizeof(double));
1056 if (kernel->values == (double *) NULL)
1057 return(DestroyKernelInfo(kernel));
1059 /* WARNING: The following generates a 'sampled gaussian' kernel.
1060 * What we really want is a 'discrete gaussian' kernel.
1062 * How to do this is I don't know, but appears to be basied on the
1063 * Error Function 'erf()' (intergral of a gaussian)
1066 if ( type == GaussianKernel || type == DoGKernel )
1067 { /* Calculate a Gaussian, OR positive half of a DoG */
1068 if ( sigma > MagickEpsilon )
1069 { A = 1.0/(2.0*sigma*sigma); /* simplify loop expressions */
1070 B = (double) (1.0/(Magick2PI*sigma*sigma));
1071 for ( i=0, v=-kernel->y; v <= (ssize_t)kernel->y; v++)
1072 for ( u=-kernel->x; u <= (ssize_t)kernel->x; u++, i++)
1073 kernel->values[i] = exp(-((double)(u*u+v*v))*A)*B;
1075 else /* limiting case - a unity (normalized Dirac) kernel */
1076 { (void) ResetMagickMemory(kernel->values,0, (size_t)
1077 kernel->width*kernel->height*sizeof(double));
1078 kernel->values[kernel->x+kernel->y*kernel->width] = 1.0;
1082 if ( type == DoGKernel )
1083 { /* Subtract a Negative Gaussian for "Difference of Gaussian" */
1084 if ( sigma2 > MagickEpsilon )
1085 { sigma = sigma2; /* simplify loop expressions */
1086 A = 1.0/(2.0*sigma*sigma);
1087 B = (double) (1.0/(Magick2PI*sigma*sigma));
1088 for ( i=0, v=-kernel->y; v <= (ssize_t)kernel->y; v++)
1089 for ( u=-kernel->x; u <= (ssize_t)kernel->x; u++, i++)
1090 kernel->values[i] -= exp(-((double)(u*u+v*v))*A)*B;
1092 else /* limiting case - a unity (normalized Dirac) kernel */
1093 kernel->values[kernel->x+kernel->y*kernel->width] -= 1.0;
1096 if ( type == LoGKernel )
1097 { /* Calculate a Laplacian of a Gaussian - Or Mexician Hat */
1098 if ( sigma > MagickEpsilon )
1099 { A = 1.0/(2.0*sigma*sigma); /* simplify loop expressions */
1100 B = (double) (1.0/(MagickPI*sigma*sigma*sigma*sigma));
1101 for ( i=0, v=-kernel->y; v <= (ssize_t)kernel->y; v++)
1102 for ( u=-kernel->x; u <= (ssize_t)kernel->x; u++, i++)
1103 { R = ((double)(u*u+v*v))*A;
1104 kernel->values[i] = (1-R)*exp(-R)*B;
1107 else /* special case - generate a unity kernel */
1108 { (void) ResetMagickMemory(kernel->values,0, (size_t)
1109 kernel->width*kernel->height*sizeof(double));
1110 kernel->values[kernel->x+kernel->y*kernel->width] = 1.0;
1114 /* Note the above kernels may have been 'clipped' by a user defined
1115 ** radius, producing a smaller (darker) kernel. Also for very small
1116 ** sigma's (> 0.1) the central value becomes larger than one, and thus
1117 ** producing a very bright kernel.
1119 ** Normalization will still be needed.
1122 /* Normalize the 2D Gaussian Kernel
1124 ** NB: a CorrelateNormalize performs a normal Normalize if
1125 ** there are no negative values.
1127 CalcKernelMetaData(kernel); /* the other kernel meta-data */
1128 ScaleKernelInfo(kernel, 1.0, CorrelateNormalizeValue);
1134 sigma = fabs(args->sigma),
1137 if ( args->rho >= 1.0 )
1138 kernel->width = (size_t)args->rho*2+1;
1140 kernel->width = GetOptimalKernelWidth1D(args->rho,sigma);
1142 kernel->x = (ssize_t) (kernel->width-1)/2;
1144 kernel->negative_range = kernel->positive_range = 0.0;
1145 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
1146 kernel->height*sizeof(double));
1147 if (kernel->values == (double *) NULL)
1148 return(DestroyKernelInfo(kernel));
1151 #define KernelRank 3
1152 /* Formula derived from GetBlurKernel() in "effect.c" (plus bug fix).
1153 ** It generates a gaussian 3 times the width, and compresses it into
1154 ** the expected range. This produces a closer normalization of the
1155 ** resulting kernel, especially for very low sigma values.
1156 ** As such while wierd it is prefered.
1158 ** I am told this method originally came from Photoshop.
1160 ** A properly normalized curve is generated (apart from edge clipping)
1161 ** even though we later normalize the result (for edge clipping)
1162 ** to allow the correct generation of a "Difference of Blurs".
1166 v = (ssize_t) (kernel->width*KernelRank-1)/2; /* start/end points to fit range */
1167 (void) ResetMagickMemory(kernel->values,0, (size_t)
1168 kernel->width*kernel->height*sizeof(double));
1169 /* Calculate a Positive 1D Gaussian */
1170 if ( sigma > MagickEpsilon )
1171 { sigma *= KernelRank; /* simplify loop expressions */
1172 alpha = 1.0/(2.0*sigma*sigma);
1173 beta= (double) (1.0/(MagickSQ2PI*sigma ));
1174 for ( u=-v; u <= v; u++) {
1175 kernel->values[(u+v)/KernelRank] +=
1176 exp(-((double)(u*u))*alpha)*beta;
1179 else /* special case - generate a unity kernel */
1180 kernel->values[kernel->x+kernel->y*kernel->width] = 1.0;
1182 /* Direct calculation without curve averaging */
1184 /* Calculate a Positive Gaussian */
1185 if ( sigma > MagickEpsilon )
1186 { alpha = 1.0/(2.0*sigma*sigma); /* simplify loop expressions */
1187 beta = 1.0/(MagickSQ2PI*sigma);
1188 for ( i=0, u=-kernel->x; u <= (ssize_t)kernel->x; u++, i++)
1189 kernel->values[i] = exp(-((double)(u*u))*alpha)*beta;
1191 else /* special case - generate a unity kernel */
1192 { (void) ResetMagickMemory(kernel->values,0, (size_t)
1193 kernel->width*kernel->height*sizeof(double));
1194 kernel->values[kernel->x+kernel->y*kernel->width] = 1.0;
1197 /* Note the above kernel may have been 'clipped' by a user defined
1198 ** radius, producing a smaller (darker) kernel. Also for very small
1199 ** sigma's (> 0.1) the central value becomes larger than one, and thus
1200 ** producing a very bright kernel.
1202 ** Normalization will still be needed.
1205 /* Normalize the 1D Gaussian Kernel
1207 ** NB: a CorrelateNormalize performs a normal Normalize if
1208 ** there are no negative values.
1210 CalcKernelMetaData(kernel); /* the other kernel meta-data */
1211 ScaleKernelInfo(kernel, 1.0, CorrelateNormalizeValue);
1213 /* rotate the 1D kernel by given angle */
1214 RotateKernelInfo(kernel, args->xi );
1219 sigma = fabs(args->sigma),
1222 if ( args->rho < 1.0 )
1223 kernel->width = (GetOptimalKernelWidth1D(args->rho,sigma)-1)/2+1;
1225 kernel->width = (size_t)args->rho;
1226 kernel->x = kernel->y = 0;
1228 kernel->negative_range = kernel->positive_range = 0.0;
1229 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
1230 kernel->height*sizeof(double));
1231 if (kernel->values == (double *) NULL)
1232 return(DestroyKernelInfo(kernel));
1234 /* A comet blur is half a 1D gaussian curve, so that the object is
1235 ** blurred in one direction only. This may not be quite the right
1236 ** curve to use so may change in the future. The function must be
1237 ** normalised after generation, which also resolves any clipping.
1239 ** As we are normalizing and not subtracting gaussians,
1240 ** there is no need for a divisor in the gaussian formula
1242 ** It is less comples
1244 if ( sigma > MagickEpsilon )
1247 #define KernelRank 3
1248 v = (ssize_t) kernel->width*KernelRank; /* start/end points */
1249 (void) ResetMagickMemory(kernel->values,0, (size_t)
1250 kernel->width*sizeof(double));
1251 sigma *= KernelRank; /* simplify the loop expression */
1252 A = 1.0/(2.0*sigma*sigma);
1253 /* B = 1.0/(MagickSQ2PI*sigma); */
1254 for ( u=0; u < v; u++) {
1255 kernel->values[u/KernelRank] +=
1256 exp(-((double)(u*u))*A);
1257 /* exp(-((double)(i*i))/2.0*sigma*sigma)/(MagickSQ2PI*sigma); */
1259 for (i=0; i < (ssize_t) kernel->width; i++)
1260 kernel->positive_range += kernel->values[i];
1262 A = 1.0/(2.0*sigma*sigma); /* simplify the loop expression */
1263 /* B = 1.0/(MagickSQ2PI*sigma); */
1264 for ( i=0; i < (ssize_t) kernel->width; i++)
1265 kernel->positive_range +=
1267 exp(-((double)(i*i))*A);
1268 /* exp(-((double)(i*i))/2.0*sigma*sigma)/(MagickSQ2PI*sigma); */
1271 else /* special case - generate a unity kernel */
1272 { (void) ResetMagickMemory(kernel->values,0, (size_t)
1273 kernel->width*kernel->height*sizeof(double));
1274 kernel->values[kernel->x+kernel->y*kernel->width] = 1.0;
1275 kernel->positive_range = 1.0;
1278 kernel->minimum = 0.0;
1279 kernel->maximum = kernel->values[0];
1280 kernel->negative_range = 0.0;
1282 ScaleKernelInfo(kernel, 1.0, NormalizeValue); /* Normalize */
1283 RotateKernelInfo(kernel, args->xi); /* Rotate by angle */
1288 Convolution Kernels - Well Known Named Constant Kernels
1290 case LaplacianKernel:
1291 { switch ( (int) args->rho ) {
1293 default: /* laplacian square filter -- default */
1294 kernel=ParseKernelArray("3: -1,-1,-1 -1,8,-1 -1,-1,-1");
1296 case 1: /* laplacian diamond filter */
1297 kernel=ParseKernelArray("3: 0,-1,0 -1,4,-1 0,-1,0");
1300 kernel=ParseKernelArray("3: -2,1,-2 1,4,1 -2,1,-2");
1303 kernel=ParseKernelArray("3: 1,-2,1 -2,4,-2 1,-2,1");
1305 case 5: /* a 5x5 laplacian */
1306 kernel=ParseKernelArray(
1307 "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");
1309 case 7: /* a 7x7 laplacian */
1310 kernel=ParseKernelArray(
1311 "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" );
1313 case 15: /* a 5x5 LoG (sigma approx 1.4) */
1314 kernel=ParseKernelArray(
1315 "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");
1317 case 19: /* a 9x9 LoG (sigma approx 1.4) */
1318 /* http://www.cscjournals.org/csc/manuscript/Journals/IJIP/volume3/Issue1/IJIP-15.pdf */
1319 kernel=ParseKernelArray(
1320 "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");
1323 if (kernel == (KernelInfo *) NULL)
1325 kernel->type = type;
1329 { /* Simple Sobel Kernel */
1330 kernel=ParseKernelArray("3: 1,0,-1 2,0,-2 1,0,-1");
1331 if (kernel == (KernelInfo *) NULL)
1333 kernel->type = type;
1334 RotateKernelInfo(kernel, args->rho);
1339 kernel=ParseKernelArray("3: 0,0,0 1,-1,0 0,0,0");
1340 if (kernel == (KernelInfo *) NULL)
1342 kernel->type = type;
1343 RotateKernelInfo(kernel, args->rho);
1348 kernel=ParseKernelArray("3: 1,0,-1 1,0,-1 1,0,-1");
1349 if (kernel == (KernelInfo *) NULL)
1351 kernel->type = type;
1352 RotateKernelInfo(kernel, args->rho);
1357 kernel=ParseKernelArray("3: 1,1,-1 1,-2,-1 1,1,-1");
1358 if (kernel == (KernelInfo *) NULL)
1360 kernel->type = type;
1361 RotateKernelInfo(kernel, args->rho);
1366 kernel=ParseKernelArray("3: 5,-3,-3 5,0,-3 5,-3,-3");
1367 if (kernel == (KernelInfo *) NULL)
1369 kernel->type = type;
1370 RotateKernelInfo(kernel, args->rho);
1373 case FreiChenKernel:
1374 /* Direction is set to be left to right positive */
1375 /* http://www.math.tau.ac.il/~turkel/notes/edge_detectors.pdf -- RIGHT? */
1376 /* http://ltswww.epfl.ch/~courstiv/exos_labos/sol3.pdf -- WRONG? */
1377 { switch ( (int) args->rho ) {
1380 kernel=ParseKernelArray("3: 1,0,-1 2,0,-2 1,0,-1");
1381 if (kernel == (KernelInfo *) NULL)
1383 kernel->type = type;
1384 kernel->values[3] = +MagickSQ2;
1385 kernel->values[5] = -MagickSQ2;
1386 CalcKernelMetaData(kernel); /* recalculate meta-data */
1389 kernel=ParseKernelArray("3: 1,2,0 2,0,-2 0,-2,-1");
1390 if (kernel == (KernelInfo *) NULL)
1392 kernel->type = type;
1393 kernel->values[1] = kernel->values[3] = +MagickSQ2;
1394 kernel->values[5] = kernel->values[7] = -MagickSQ2;
1395 CalcKernelMetaData(kernel); /* recalculate meta-data */
1396 ScaleKernelInfo(kernel, (double) (1.0/2.0*MagickSQ2), NoValue);
1399 kernel=AcquireKernelInfo("FreiChen:11;FreiChen:12;FreiChen:13;FreiChen:14;FreiChen:15;FreiChen:16;FreiChen:17;FreiChen:18;FreiChen:19");
1400 if (kernel == (KernelInfo *) NULL)
1405 kernel=ParseKernelArray("3: 1,0,-1 2,0,-2 1,0,-1");
1406 if (kernel == (KernelInfo *) NULL)
1408 kernel->type = type;
1409 kernel->values[3] = +MagickSQ2;
1410 kernel->values[5] = -MagickSQ2;
1411 CalcKernelMetaData(kernel); /* recalculate meta-data */
1412 ScaleKernelInfo(kernel, (double) (1.0/2.0*MagickSQ2), NoValue);
1415 kernel=ParseKernelArray("3: 1,2,1 0,0,0 1,2,1");
1416 if (kernel == (KernelInfo *) NULL)
1418 kernel->type = type;
1419 kernel->values[1] = +MagickSQ2;
1420 kernel->values[7] = +MagickSQ2;
1421 CalcKernelMetaData(kernel);
1422 ScaleKernelInfo(kernel, (double) (1.0/2.0*MagickSQ2), NoValue);
1425 kernel=ParseKernelArray("3: 2,-1,0 -1,0,1 0,1,-2");
1426 if (kernel == (KernelInfo *) NULL)
1428 kernel->type = type;
1429 kernel->values[0] = +MagickSQ2;
1430 kernel->values[8] = -MagickSQ2;
1431 CalcKernelMetaData(kernel);
1432 ScaleKernelInfo(kernel, (double) (1.0/2.0*MagickSQ2), NoValue);
1435 kernel=ParseKernelArray("3: 0,1,-2 -1,0,1 2,-1,0");
1436 if (kernel == (KernelInfo *) NULL)
1438 kernel->type = type;
1439 kernel->values[2] = -MagickSQ2;
1440 kernel->values[6] = +MagickSQ2;
1441 CalcKernelMetaData(kernel);
1442 ScaleKernelInfo(kernel, (double) (1.0/2.0*MagickSQ2), NoValue);
1445 kernel=ParseKernelArray("3: 0,-1,0 1,0,1 0,-1,0");
1446 if (kernel == (KernelInfo *) NULL)
1448 kernel->type = type;
1449 ScaleKernelInfo(kernel, 1.0/2.0, NoValue);
1452 kernel=ParseKernelArray("3: 1,0,-1 0,0,0 -1,0,1");
1453 if (kernel == (KernelInfo *) NULL)
1455 kernel->type = type;
1456 ScaleKernelInfo(kernel, 1.0/2.0, NoValue);
1459 kernel=ParseKernelArray("3: 1,-2,1 -2,4,-2 -1,-2,1");
1460 if (kernel == (KernelInfo *) NULL)
1462 kernel->type = type;
1463 ScaleKernelInfo(kernel, 1.0/6.0, NoValue);
1466 kernel=ParseKernelArray("3: -2,1,-2 1,4,1 -2,1,-2");
1467 if (kernel == (KernelInfo *) NULL)
1469 kernel->type = type;
1470 ScaleKernelInfo(kernel, 1.0/6.0, NoValue);
1473 kernel=ParseKernelArray("3: 1,1,1 1,1,1 1,1,1");
1474 if (kernel == (KernelInfo *) NULL)
1476 kernel->type = type;
1477 ScaleKernelInfo(kernel, 1.0/3.0, NoValue);
1480 if ( fabs(args->sigma) > MagickEpsilon )
1481 /* Rotate by correctly supplied 'angle' */
1482 RotateKernelInfo(kernel, args->sigma);
1483 else if ( args->rho > 30.0 || args->rho < -30.0 )
1484 /* Rotate by out of bounds 'type' */
1485 RotateKernelInfo(kernel, args->rho);
1490 Boolean or Shaped Kernels
1494 if (args->rho < 1.0)
1495 kernel->width = kernel->height = 3; /* default radius = 1 */
1497 kernel->width = kernel->height = ((size_t)args->rho)*2+1;
1498 kernel->x = kernel->y = (ssize_t) (kernel->width-1)/2;
1500 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
1501 kernel->height*sizeof(double));
1502 if (kernel->values == (double *) NULL)
1503 return(DestroyKernelInfo(kernel));
1505 /* set all kernel values within diamond area to scale given */
1506 for ( i=0, v=-kernel->y; v <= (ssize_t)kernel->y; v++)
1507 for ( u=-kernel->x; u <= (ssize_t)kernel->x; u++, i++)
1508 if ( (labs((long) u)+labs((long) v)) <= (long) kernel->x)
1509 kernel->positive_range += kernel->values[i] = args->sigma;
1511 kernel->values[i] = nan;
1512 kernel->minimum = kernel->maximum = args->sigma; /* a flat shape */
1516 case RectangleKernel:
1519 if ( type == SquareKernel )
1521 if (args->rho < 1.0)
1522 kernel->width = kernel->height = 3; /* default radius = 1 */
1524 kernel->width = kernel->height = (size_t) (2*args->rho+1);
1525 kernel->x = kernel->y = (ssize_t) (kernel->width-1)/2;
1526 scale = args->sigma;
1529 /* NOTE: user defaults set in "AcquireKernelInfo()" */
1530 if ( args->rho < 1.0 || args->sigma < 1.0 )
1531 return(DestroyKernelInfo(kernel)); /* invalid args given */
1532 kernel->width = (size_t)args->rho;
1533 kernel->height = (size_t)args->sigma;
1534 if ( args->xi < 0.0 || args->xi > (double)kernel->width ||
1535 args->psi < 0.0 || args->psi > (double)kernel->height )
1536 return(DestroyKernelInfo(kernel)); /* invalid args given */
1537 kernel->x = (ssize_t) args->xi;
1538 kernel->y = (ssize_t) args->psi;
1541 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
1542 kernel->height*sizeof(double));
1543 if (kernel->values == (double *) NULL)
1544 return(DestroyKernelInfo(kernel));
1546 /* set all kernel values to scale given */
1547 u=(ssize_t) (kernel->width*kernel->height);
1548 for ( i=0; i < u; i++)
1549 kernel->values[i] = scale;
1550 kernel->minimum = kernel->maximum = scale; /* a flat shape */
1551 kernel->positive_range = scale*u;
1556 if (args->rho < 1.0)
1557 kernel->width = kernel->height = 5; /* default radius = 2 */
1559 kernel->width = kernel->height = ((size_t)args->rho)*2+1;
1560 kernel->x = kernel->y = (ssize_t) (kernel->width-1)/2;
1562 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
1563 kernel->height*sizeof(double));
1564 if (kernel->values == (double *) NULL)
1565 return(DestroyKernelInfo(kernel));
1567 for ( i=0, v=-kernel->y; v <= (ssize_t)kernel->y; v++)
1568 for ( u=-kernel->x; u <= (ssize_t)kernel->x; u++, i++)
1569 if ( (labs((long) u)+labs((long) v)) <=
1570 ((long)kernel->x + (long)(kernel->x/2)) )
1571 kernel->positive_range += kernel->values[i] = args->sigma;
1573 kernel->values[i] = nan;
1574 kernel->minimum = kernel->maximum = args->sigma; /* a flat shape */
1580 limit = (ssize_t)(args->rho*args->rho);
1582 if (args->rho < 0.4) /* default radius approx 4.3 */
1583 kernel->width = kernel->height = 9L, limit = 18L;
1585 kernel->width = kernel->height = (size_t)fabs(args->rho)*2+1;
1586 kernel->x = kernel->y = (ssize_t) (kernel->width-1)/2;
1588 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
1589 kernel->height*sizeof(double));
1590 if (kernel->values == (double *) NULL)
1591 return(DestroyKernelInfo(kernel));
1593 for ( i=0, v=-kernel->y; v <= (ssize_t)kernel->y; v++)
1594 for ( u=-kernel->x; u <= (ssize_t)kernel->x; u++, i++)
1595 if ((u*u+v*v) <= limit)
1596 kernel->positive_range += kernel->values[i] = args->sigma;
1598 kernel->values[i] = nan;
1599 kernel->minimum = kernel->maximum = args->sigma; /* a flat shape */
1604 if (args->rho < 1.0)
1605 kernel->width = kernel->height = 5; /* default radius 2 */
1607 kernel->width = kernel->height = ((size_t)args->rho)*2+1;
1608 kernel->x = kernel->y = (ssize_t) (kernel->width-1)/2;
1610 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
1611 kernel->height*sizeof(double));
1612 if (kernel->values == (double *) NULL)
1613 return(DestroyKernelInfo(kernel));
1615 /* set all kernel values along axises to given scale */
1616 for ( i=0, v=-kernel->y; v <= (ssize_t)kernel->y; v++)
1617 for ( u=-kernel->x; u <= (ssize_t)kernel->x; u++, i++)
1618 kernel->values[i] = (u == 0 || v == 0) ? args->sigma : nan;
1619 kernel->minimum = kernel->maximum = args->sigma; /* a flat shape */
1620 kernel->positive_range = args->sigma*(kernel->width*2.0 - 1.0);
1625 if (args->rho < 1.0)
1626 kernel->width = kernel->height = 5; /* default radius 2 */
1628 kernel->width = kernel->height = ((size_t)args->rho)*2+1;
1629 kernel->x = kernel->y = (ssize_t) (kernel->width-1)/2;
1631 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
1632 kernel->height*sizeof(double));
1633 if (kernel->values == (double *) NULL)
1634 return(DestroyKernelInfo(kernel));
1636 /* set all kernel values along axises to given scale */
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 kernel->values[i] = (u == v || u == -v) ? args->sigma : nan;
1640 kernel->minimum = kernel->maximum = args->sigma; /* a flat shape */
1641 kernel->positive_range = args->sigma*(kernel->width*2.0 - 1.0);
1655 if (args->rho < args->sigma)
1657 kernel->width = ((size_t)args->sigma)*2+1;
1658 limit1 = (ssize_t)(args->rho*args->rho);
1659 limit2 = (ssize_t)(args->sigma*args->sigma);
1663 kernel->width = ((size_t)args->rho)*2+1;
1664 limit1 = (ssize_t)(args->sigma*args->sigma);
1665 limit2 = (ssize_t)(args->rho*args->rho);
1668 kernel->width = 7L, limit1 = 7L, limit2 = 11L;
1670 kernel->height = kernel->width;
1671 kernel->x = kernel->y = (ssize_t) (kernel->width-1)/2;
1672 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
1673 kernel->height*sizeof(double));
1674 if (kernel->values == (double *) NULL)
1675 return(DestroyKernelInfo(kernel));
1677 /* set a ring of points of 'scale' ( 0.0 for PeaksKernel ) */
1678 scale = (ssize_t) (( type == PeaksKernel) ? 0.0 : args->xi);
1679 for ( i=0, v= -kernel->y; v <= (ssize_t)kernel->y; v++)
1680 for ( u=-kernel->x; u <= (ssize_t)kernel->x; u++, i++)
1681 { ssize_t radius=u*u+v*v;
1682 if (limit1 < radius && radius <= limit2)
1683 kernel->positive_range += kernel->values[i] = (double) scale;
1685 kernel->values[i] = nan;
1687 kernel->minimum = kernel->maximum = (double) scale;
1688 if ( type == PeaksKernel ) {
1689 /* set the central point in the middle */
1690 kernel->values[kernel->x+kernel->y*kernel->width] = 1.0;
1691 kernel->positive_range = 1.0;
1692 kernel->maximum = 1.0;
1698 kernel=AcquireKernelInfo("ThinSE:482");
1699 if (kernel == (KernelInfo *) NULL)
1701 kernel->type = type;
1702 ExpandMirrorKernelInfo(kernel); /* mirror expansion of kernels */
1707 kernel=AcquireKernelInfo("ThinSE:87");
1708 if (kernel == (KernelInfo *) NULL)
1710 kernel->type = type;
1711 ExpandRotateKernelInfo(kernel, 90.0); /* Expand 90 degree rotations */
1714 case DiagonalsKernel:
1716 switch ( (int) args->rho ) {
1721 kernel=ParseKernelArray("3: 0,0,0 0,-,1 1,1,-");
1722 if (kernel == (KernelInfo *) NULL)
1724 kernel->type = type;
1725 new_kernel=ParseKernelArray("3: 0,0,1 0,-,1 0,1,-");
1726 if (new_kernel == (KernelInfo *) NULL)
1727 return(DestroyKernelInfo(kernel));
1728 new_kernel->type = type;
1729 LastKernelInfo(kernel)->next = new_kernel;
1730 ExpandMirrorKernelInfo(kernel);
1734 kernel=ParseKernelArray("3: 0,0,0 0,-,1 1,1,-");
1737 kernel=ParseKernelArray("3: 0,0,1 0,-,1 0,1,-");
1740 if (kernel == (KernelInfo *) NULL)
1742 kernel->type = type;
1743 RotateKernelInfo(kernel, args->sigma);
1746 case LineEndsKernel:
1747 { /* Kernels for finding the end of thin lines */
1748 switch ( (int) args->rho ) {
1751 /* set of kernels to find all end of lines */
1752 return(AcquireKernelInfo("LineEnds:1>;LineEnds:2>"));
1754 /* kernel for 4-connected line ends - no rotation */
1755 kernel=ParseKernelArray("3: 0,0,- 0,1,1 0,0,-");
1758 /* kernel to add for 8-connected lines - no rotation */
1759 kernel=ParseKernelArray("3: 0,0,0 0,1,0 0,0,1");
1762 /* kernel to add for orthogonal line ends - does not find corners */
1763 kernel=ParseKernelArray("3: 0,0,0 0,1,1 0,0,0");
1766 /* traditional line end - fails on last T end */
1767 kernel=ParseKernelArray("3: 0,0,0 0,1,- 0,0,-");
1770 if (kernel == (KernelInfo *) NULL)
1772 kernel->type = type;
1773 RotateKernelInfo(kernel, args->sigma);
1776 case LineJunctionsKernel:
1777 { /* kernels for finding the junctions of multiple lines */
1778 switch ( (int) args->rho ) {
1781 /* set of kernels to find all line junctions */
1782 return(AcquireKernelInfo("LineJunctions:1@;LineJunctions:2>"));
1785 kernel=ParseKernelArray("3: 1,-,1 -,1,- -,1,-");
1788 /* Diagonal T Junctions */
1789 kernel=ParseKernelArray("3: 1,-,- -,1,- 1,-,1");
1792 /* Orthogonal T Junctions */
1793 kernel=ParseKernelArray("3: -,-,- 1,1,1 -,1,-");
1796 /* Diagonal X Junctions */
1797 kernel=ParseKernelArray("3: 1,-,1 -,1,- 1,-,1");
1800 /* Orthogonal X Junctions - minimal diamond kernel */
1801 kernel=ParseKernelArray("3: -,1,- 1,1,1 -,1,-");
1804 if (kernel == (KernelInfo *) NULL)
1806 kernel->type = type;
1807 RotateKernelInfo(kernel, args->sigma);
1811 { /* Ridges - Ridge finding kernels */
1814 switch ( (int) args->rho ) {
1817 kernel=ParseKernelArray("3x1:0,1,0");
1818 if (kernel == (KernelInfo *) NULL)
1820 kernel->type = type;
1821 ExpandRotateKernelInfo(kernel, 90.0); /* 2 rotated kernels (symmetrical) */
1824 kernel=ParseKernelArray("4x1:0,1,1,0");
1825 if (kernel == (KernelInfo *) NULL)
1827 kernel->type = type;
1828 ExpandRotateKernelInfo(kernel, 90.0); /* 4 rotated kernels */
1830 /* Kernels to find a stepped 'thick' line, 4 rotates + mirrors */
1831 /* Unfortunatally we can not yet rotate a non-square kernel */
1832 /* But then we can't flip a non-symetrical kernel either */
1833 new_kernel=ParseKernelArray("4x3+1+1:0,1,1,- -,1,1,- -,1,1,0");
1834 if (new_kernel == (KernelInfo *) NULL)
1835 return(DestroyKernelInfo(kernel));
1836 new_kernel->type = type;
1837 LastKernelInfo(kernel)->next = new_kernel;
1838 new_kernel=ParseKernelArray("4x3+2+1:0,1,1,- -,1,1,- -,1,1,0");
1839 if (new_kernel == (KernelInfo *) NULL)
1840 return(DestroyKernelInfo(kernel));
1841 new_kernel->type = type;
1842 LastKernelInfo(kernel)->next = new_kernel;
1843 new_kernel=ParseKernelArray("4x3+1+1:-,1,1,0 -,1,1,- 0,1,1,-");
1844 if (new_kernel == (KernelInfo *) NULL)
1845 return(DestroyKernelInfo(kernel));
1846 new_kernel->type = type;
1847 LastKernelInfo(kernel)->next = new_kernel;
1848 new_kernel=ParseKernelArray("4x3+2+1:-,1,1,0 -,1,1,- 0,1,1,-");
1849 if (new_kernel == (KernelInfo *) NULL)
1850 return(DestroyKernelInfo(kernel));
1851 new_kernel->type = type;
1852 LastKernelInfo(kernel)->next = new_kernel;
1853 new_kernel=ParseKernelArray("3x4+1+1:0,-,- 1,1,1 1,1,1 -,-,0");
1854 if (new_kernel == (KernelInfo *) NULL)
1855 return(DestroyKernelInfo(kernel));
1856 new_kernel->type = type;
1857 LastKernelInfo(kernel)->next = new_kernel;
1858 new_kernel=ParseKernelArray("3x4+1+2:0,-,- 1,1,1 1,1,1 -,-,0");
1859 if (new_kernel == (KernelInfo *) NULL)
1860 return(DestroyKernelInfo(kernel));
1861 new_kernel->type = type;
1862 LastKernelInfo(kernel)->next = new_kernel;
1863 new_kernel=ParseKernelArray("3x4+1+1:-,-,0 1,1,1 1,1,1 0,-,-");
1864 if (new_kernel == (KernelInfo *) NULL)
1865 return(DestroyKernelInfo(kernel));
1866 new_kernel->type = type;
1867 LastKernelInfo(kernel)->next = new_kernel;
1868 new_kernel=ParseKernelArray("3x4+1+2:-,-,0 1,1,1 1,1,1 0,-,-");
1869 if (new_kernel == (KernelInfo *) NULL)
1870 return(DestroyKernelInfo(kernel));
1871 new_kernel->type = type;
1872 LastKernelInfo(kernel)->next = new_kernel;
1877 case ConvexHullKernel:
1881 /* first set of 8 kernels */
1882 kernel=ParseKernelArray("3: 1,1,- 1,0,- 1,-,0");
1883 if (kernel == (KernelInfo *) NULL)
1885 kernel->type = type;
1886 ExpandRotateKernelInfo(kernel, 90.0);
1887 /* append the mirror versions too - no flip function yet */
1888 new_kernel=ParseKernelArray("3: 1,1,1 1,0,- -,-,0");
1889 if (new_kernel == (KernelInfo *) NULL)
1890 return(DestroyKernelInfo(kernel));
1891 new_kernel->type = type;
1892 ExpandRotateKernelInfo(new_kernel, 90.0);
1893 LastKernelInfo(kernel)->next = new_kernel;
1896 case SkeletonKernel:
1898 switch ( (int) args->rho ) {
1901 /* Traditional Skeleton...
1902 ** A cyclically rotated single kernel
1904 kernel=AcquireKernelInfo("ThinSE:482");
1905 if (kernel == (KernelInfo *) NULL)
1907 kernel->type = type;
1908 ExpandRotateKernelInfo(kernel, 45.0); /* 8 rotations */
1911 /* HIPR Variation of the cyclic skeleton
1912 ** Corners of the traditional method made more forgiving,
1913 ** but the retain the same cyclic order.
1915 kernel=AcquireKernelInfo("ThinSE:482; ThinSE:87x90;");
1916 if (kernel == (KernelInfo *) NULL)
1918 if (kernel->next == (KernelInfo *) NULL)
1919 return(DestroyKernelInfo(kernel));
1920 kernel->type = type;
1921 kernel->next->type = type;
1922 ExpandRotateKernelInfo(kernel, 90.0); /* 4 rotations of the 2 kernels */
1925 /* Dan Bloomberg Skeleton, from his paper on 3x3 thinning SE's
1926 ** "Connectivity-Preserving Morphological Image Thransformations"
1927 ** by Dan S. Bloomberg, available on Leptonica, Selected Papers,
1928 ** http://www.leptonica.com/papers/conn.pdf
1930 kernel=AcquireKernelInfo(
1931 "ThinSE:41; ThinSE:42; ThinSE:43");
1932 if (kernel == (KernelInfo *) NULL)
1934 kernel->type = type;
1935 kernel->next->type = type;
1936 kernel->next->next->type = type;
1937 ExpandMirrorKernelInfo(kernel); /* 12 kernels total */
1943 { /* Special kernels for general thinning, while preserving connections
1944 ** "Connectivity-Preserving Morphological Image Thransformations"
1945 ** by Dan S. Bloomberg, available on Leptonica, Selected Papers,
1946 ** http://www.leptonica.com/papers/conn.pdf
1948 ** http://tpgit.github.com/Leptonica/ccthin_8c_source.html
1950 ** Note kernels do not specify the origin pixel, allowing them
1951 ** to be used for both thickening and thinning operations.
1953 switch ( (int) args->rho ) {
1954 /* SE for 4-connected thinning */
1955 case 41: /* SE_4_1 */
1956 kernel=ParseKernelArray("3: -,-,1 0,-,1 -,-,1");
1958 case 42: /* SE_4_2 */
1959 kernel=ParseKernelArray("3: -,-,1 0,-,1 -,0,-");
1961 case 43: /* SE_4_3 */
1962 kernel=ParseKernelArray("3: -,0,- 0,-,1 -,-,1");
1964 case 44: /* SE_4_4 */
1965 kernel=ParseKernelArray("3: -,0,- 0,-,1 -,0,-");
1967 case 45: /* SE_4_5 */
1968 kernel=ParseKernelArray("3: -,0,1 0,-,1 -,0,-");
1970 case 46: /* SE_4_6 */
1971 kernel=ParseKernelArray("3: -,0,- 0,-,1 -,0,1");
1973 case 47: /* SE_4_7 */
1974 kernel=ParseKernelArray("3: -,1,1 0,-,1 -,0,-");
1976 case 48: /* SE_4_8 */
1977 kernel=ParseKernelArray("3: -,-,1 0,-,1 0,-,1");
1979 case 49: /* SE_4_9 */
1980 kernel=ParseKernelArray("3: 0,-,1 0,-,1 -,-,1");
1982 /* SE for 8-connected thinning - negatives of the above */
1983 case 81: /* SE_8_0 */
1984 kernel=ParseKernelArray("3: -,1,- 0,-,1 -,1,-");
1986 case 82: /* SE_8_2 */
1987 kernel=ParseKernelArray("3: -,1,- 0,-,1 0,-,-");
1989 case 83: /* SE_8_3 */
1990 kernel=ParseKernelArray("3: 0,-,- 0,-,1 -,1,-");
1992 case 84: /* SE_8_4 */
1993 kernel=ParseKernelArray("3: 0,-,- 0,-,1 0,-,-");
1995 case 85: /* SE_8_5 */
1996 kernel=ParseKernelArray("3: 0,-,1 0,-,1 0,-,-");
1998 case 86: /* SE_8_6 */
1999 kernel=ParseKernelArray("3: 0,-,- 0,-,1 0,-,1");
2001 case 87: /* SE_8_7 */
2002 kernel=ParseKernelArray("3: -,1,- 0,-,1 0,0,-");
2004 case 88: /* SE_8_8 */
2005 kernel=ParseKernelArray("3: -,1,- 0,-,1 0,1,-");
2007 case 89: /* SE_8_9 */
2008 kernel=ParseKernelArray("3: 0,1,- 0,-,1 -,1,-");
2010 /* Special combined SE kernels */
2011 case 423: /* SE_4_2 , SE_4_3 Combined Kernel */
2012 kernel=ParseKernelArray("3: -,-,1 0,-,- -,0,-");
2014 case 823: /* SE_8_2 , SE_8_3 Combined Kernel */
2015 kernel=ParseKernelArray("3: -,1,- -,-,1 0,-,-");
2017 case 481: /* SE_48_1 - General Connected Corner Kernel */
2018 kernel=ParseKernelArray("3: -,1,1 0,-,1 0,0,-");
2021 case 482: /* SE_48_2 - General Edge Kernel */
2022 kernel=ParseKernelArray("3: 0,-,1 0,-,1 0,-,1");
2025 if (kernel == (KernelInfo *) NULL)
2027 kernel->type = type;
2028 RotateKernelInfo(kernel, args->sigma);
2032 Distance Measuring Kernels
2034 case ChebyshevKernel:
2036 if (args->rho < 1.0)
2037 kernel->width = kernel->height = 3; /* default radius = 1 */
2039 kernel->width = kernel->height = ((size_t)args->rho)*2+1;
2040 kernel->x = kernel->y = (ssize_t) (kernel->width-1)/2;
2042 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
2043 kernel->height*sizeof(double));
2044 if (kernel->values == (double *) NULL)
2045 return(DestroyKernelInfo(kernel));
2047 for ( i=0, v=-kernel->y; v <= (ssize_t)kernel->y; v++)
2048 for ( u=-kernel->x; u <= (ssize_t)kernel->x; u++, i++)
2049 kernel->positive_range += ( kernel->values[i] =
2050 args->sigma*MagickMax(fabs((double)u),fabs((double)v)) );
2051 kernel->maximum = kernel->values[0];
2054 case ManhattanKernel:
2056 if (args->rho < 1.0)
2057 kernel->width = kernel->height = 3; /* default radius = 1 */
2059 kernel->width = kernel->height = ((size_t)args->rho)*2+1;
2060 kernel->x = kernel->y = (ssize_t) (kernel->width-1)/2;
2062 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
2063 kernel->height*sizeof(double));
2064 if (kernel->values == (double *) NULL)
2065 return(DestroyKernelInfo(kernel));
2067 for ( i=0, v=-kernel->y; v <= (ssize_t)kernel->y; v++)
2068 for ( u=-kernel->x; u <= (ssize_t)kernel->x; u++, i++)
2069 kernel->positive_range += ( kernel->values[i] =
2070 args->sigma*(labs((long) u)+labs((long) v)) );
2071 kernel->maximum = kernel->values[0];
2074 case OctagonalKernel:
2076 if (args->rho < 2.0)
2077 kernel->width = kernel->height = 5; /* default/minimum radius = 2 */
2079 kernel->width = kernel->height = ((size_t)args->rho)*2+1;
2080 kernel->x = kernel->y = (ssize_t) (kernel->width-1)/2;
2082 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
2083 kernel->height*sizeof(double));
2084 if (kernel->values == (double *) NULL)
2085 return(DestroyKernelInfo(kernel));
2087 for ( i=0, v=-kernel->y; v <= (ssize_t)kernel->y; v++)
2088 for ( u=-kernel->x; u <= (ssize_t)kernel->x; u++, i++)
2091 r1 = MagickMax(fabs((double)u),fabs((double)v)),
2092 r2 = floor((double)(labs((long)u)+labs((long)v)+1)/1.5);
2093 kernel->positive_range += kernel->values[i] =
2094 args->sigma*MagickMax(r1,r2);
2096 kernel->maximum = kernel->values[0];
2099 case EuclideanKernel:
2101 if (args->rho < 1.0)
2102 kernel->width = kernel->height = 3; /* default radius = 1 */
2104 kernel->width = kernel->height = ((size_t)args->rho)*2+1;
2105 kernel->x = kernel->y = (ssize_t) (kernel->width-1)/2;
2107 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
2108 kernel->height*sizeof(double));
2109 if (kernel->values == (double *) NULL)
2110 return(DestroyKernelInfo(kernel));
2112 for ( i=0, v=-kernel->y; v <= (ssize_t)kernel->y; v++)
2113 for ( u=-kernel->x; u <= (ssize_t)kernel->x; u++, i++)
2114 kernel->positive_range += ( kernel->values[i] =
2115 args->sigma*sqrt((double)(u*u+v*v)) );
2116 kernel->maximum = kernel->values[0];
2121 /* No-Op Kernel - Basically just a single pixel on its own */
2122 kernel=ParseKernelArray("1:1");
2123 if (kernel == (KernelInfo *) NULL)
2125 kernel->type = UndefinedKernel;
2134 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2138 % C l o n e K e r n e l I n f o %
2142 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2144 % CloneKernelInfo() creates a new clone of the given Kernel List so that its
2145 % can be modified without effecting the original. The cloned kernel should
2146 % be destroyed using DestoryKernelInfo() when no longer needed.
2148 % The format of the CloneKernelInfo method is:
2150 % KernelInfo *CloneKernelInfo(const KernelInfo *kernel)
2152 % A description of each parameter follows:
2154 % o kernel: the Morphology/Convolution kernel to be cloned
2157 MagickExport KernelInfo *CloneKernelInfo(const KernelInfo *kernel)
2165 assert(kernel != (KernelInfo *) NULL);
2166 new_kernel=(KernelInfo *) AcquireMagickMemory(sizeof(*kernel));
2167 if (new_kernel == (KernelInfo *) NULL)
2169 *new_kernel=(*kernel); /* copy values in structure */
2171 /* replace the values with a copy of the values */
2172 new_kernel->values=(double *) AcquireQuantumMemory(kernel->width,
2173 kernel->height*sizeof(double));
2174 if (new_kernel->values == (double *) NULL)
2175 return(DestroyKernelInfo(new_kernel));
2176 for (i=0; i < (ssize_t) (kernel->width*kernel->height); i++)
2177 new_kernel->values[i]=kernel->values[i];
2179 /* Also clone the next kernel in the kernel list */
2180 if ( kernel->next != (KernelInfo *) NULL ) {
2181 new_kernel->next = CloneKernelInfo(kernel->next);
2182 if ( new_kernel->next == (KernelInfo *) NULL )
2183 return(DestroyKernelInfo(new_kernel));
2190 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2194 % D e s t r o y K e r n e l I n f o %
2198 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2200 % DestroyKernelInfo() frees the memory used by a Convolution/Morphology
2203 % The format of the DestroyKernelInfo method is:
2205 % KernelInfo *DestroyKernelInfo(KernelInfo *kernel)
2207 % A description of each parameter follows:
2209 % o kernel: the Morphology/Convolution kernel to be destroyed
2212 MagickExport KernelInfo *DestroyKernelInfo(KernelInfo *kernel)
2214 assert(kernel != (KernelInfo *) NULL);
2216 if ( kernel->next != (KernelInfo *) NULL )
2217 kernel->next = DestroyKernelInfo(kernel->next);
2219 kernel->values = (double *)RelinquishMagickMemory(kernel->values);
2220 kernel = (KernelInfo *) RelinquishMagickMemory(kernel);
2225 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2229 + E x p a n d M i r r o r K e r n e l I n f o %
2233 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2235 % ExpandMirrorKernelInfo() takes a single kernel, and expands it into a
2236 % sequence of 90-degree rotated kernels but providing a reflected 180
2237 % rotatation, before the -/+ 90-degree rotations.
2239 % This special rotation order produces a better, more symetrical thinning of
2242 % The format of the ExpandMirrorKernelInfo method is:
2244 % void ExpandMirrorKernelInfo(KernelInfo *kernel)
2246 % A description of each parameter follows:
2248 % o kernel: the Morphology/Convolution kernel
2250 % This function is only internel to this module, as it is not finalized,
2251 % especially with regard to non-orthogonal angles, and rotation of larger
2256 static void FlopKernelInfo(KernelInfo *kernel)
2257 { /* Do a Flop by reversing each row. */
2265 for ( y=0, k=kernel->values; y < kernel->height; y++, k+=kernel->width)
2266 for ( x=0, r=kernel->width-1; x<kernel->width/2; x++, r--)
2267 t=k[x], k[x]=k[r], k[r]=t;
2269 kernel->x = kernel->width - kernel->x - 1;
2270 angle = fmod(angle+180.0, 360.0);
2274 static void ExpandMirrorKernelInfo(KernelInfo *kernel)
2282 clone = CloneKernelInfo(last);
2283 RotateKernelInfo(clone, 180); /* flip */
2284 LastKernelInfo(last)->next = clone;
2287 clone = CloneKernelInfo(last);
2288 RotateKernelInfo(clone, 90); /* transpose */
2289 LastKernelInfo(last)->next = clone;
2292 clone = CloneKernelInfo(last);
2293 RotateKernelInfo(clone, 180); /* flop */
2294 LastKernelInfo(last)->next = clone;
2300 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2304 + E x p a n d R o t a t e K e r n e l I n f o %
2308 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2310 % ExpandRotateKernelInfo() takes a kernel list, and expands it by rotating
2311 % incrementally by the angle given, until the kernel repeats.
2313 % WARNING: 45 degree rotations only works for 3x3 kernels.
2314 % While 90 degree roatations only works for linear and square kernels
2316 % The format of the ExpandRotateKernelInfo method is:
2318 % void ExpandRotateKernelInfo(KernelInfo *kernel, double angle)
2320 % A description of each parameter follows:
2322 % o kernel: the Morphology/Convolution kernel
2324 % o angle: angle to rotate in degrees
2326 % This function is only internel to this module, as it is not finalized,
2327 % especially with regard to non-orthogonal angles, and rotation of larger
2331 /* Internal Routine - Return true if two kernels are the same */
2332 static MagickBooleanType SameKernelInfo(const KernelInfo *kernel1,
2333 const KernelInfo *kernel2)
2338 /* check size and origin location */
2339 if ( kernel1->width != kernel2->width
2340 || kernel1->height != kernel2->height
2341 || kernel1->x != kernel2->x
2342 || kernel1->y != kernel2->y )
2345 /* check actual kernel values */
2346 for (i=0; i < (kernel1->width*kernel1->height); i++) {
2347 /* Test for Nan equivalence */
2348 if ( IsNan(kernel1->values[i]) && !IsNan(kernel2->values[i]) )
2350 if ( IsNan(kernel2->values[i]) && !IsNan(kernel1->values[i]) )
2352 /* Test actual values are equivalent */
2353 if ( fabs(kernel1->values[i] - kernel2->values[i]) > MagickEpsilon )
2360 static void ExpandRotateKernelInfo(KernelInfo *kernel, const double angle)
2368 clone = CloneKernelInfo(last);
2369 RotateKernelInfo(clone, angle);
2370 if ( SameKernelInfo(kernel, clone) == MagickTrue )
2372 LastKernelInfo(last)->next = clone;
2375 clone = DestroyKernelInfo(clone); /* kernel has repeated - junk the clone */
2380 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2384 + C a l c M e t a K e r n a l I n f o %
2388 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2390 % CalcKernelMetaData() recalculate the KernelInfo meta-data of this kernel only,
2391 % using the kernel values. This should only ne used if it is not possible to
2392 % calculate that meta-data in some easier way.
2394 % It is important that the meta-data is correct before ScaleKernelInfo() is
2395 % used to perform kernel normalization.
2397 % The format of the CalcKernelMetaData method is:
2399 % void CalcKernelMetaData(KernelInfo *kernel, const double scale )
2401 % A description of each parameter follows:
2403 % o kernel: the Morphology/Convolution kernel to modify
2405 % WARNING: Minimum and Maximum values are assumed to include zero, even if
2406 % zero is not part of the kernel (as in Gaussian Derived kernels). This
2407 % however is not true for flat-shaped morphological kernels.
2409 % WARNING: Only the specific kernel pointed to is modified, not a list of
2412 % This is an internal function and not expected to be useful outside this
2413 % module. This could change however.
2415 static void CalcKernelMetaData(KernelInfo *kernel)
2420 kernel->minimum = kernel->maximum = 0.0;
2421 kernel->negative_range = kernel->positive_range = 0.0;
2422 for (i=0; i < (kernel->width*kernel->height); i++)
2424 if ( fabs(kernel->values[i]) < MagickEpsilon )
2425 kernel->values[i] = 0.0;
2426 ( kernel->values[i] < 0)
2427 ? ( kernel->negative_range += kernel->values[i] )
2428 : ( kernel->positive_range += kernel->values[i] );
2429 Minimize(kernel->minimum, kernel->values[i]);
2430 Maximize(kernel->maximum, kernel->values[i]);
2437 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2441 % M o r p h o l o g y A p p l y %
2445 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2447 % MorphologyApply() applies a morphological method, multiple times using
2448 % a list of multiple kernels.
2450 % It is basically equivalent to as MorphologyImage() (see below) but
2451 % without any user controls. This allows internel programs to use this
2452 % function, to actually perform a specific task without possible interference
2453 % by any API user supplied settings.
2455 % It is MorphologyImage() task to extract any such user controls, and
2456 % pass them to this function for processing.
2458 % More specifically kernels are not normalized/scaled/blended by the
2459 % 'convolve:scale' Image Artifact (setting), nor is the convolve bias
2460 % (-bias setting or image->bias) loooked at, but must be supplied from the
2461 % function arguments.
2463 % The format of the MorphologyApply method is:
2465 % Image *MorphologyApply(const Image *image,MorphologyMethod method,
2466 % const ssize_t iterations,const KernelInfo *kernel,
2467 % const CompositeMethod compose,const double bias,
2468 % ExceptionInfo *exception)
2470 % A description of each parameter follows:
2472 % o image: the source image
2474 % o method: the morphology method to be applied.
2476 % o iterations: apply the operation this many times (or no change).
2477 % A value of -1 means loop until no change found.
2478 % How this is applied may depend on the morphology method.
2479 % Typically this is a value of 1.
2481 % o channel: the channel type.
2483 % o kernel: An array of double representing the morphology kernel.
2485 % o compose: How to handle or merge multi-kernel results.
2486 % If 'UndefinedCompositeOp' use default for the Morphology method.
2487 % If 'NoCompositeOp' force image to be re-iterated by each kernel.
2488 % Otherwise merge the results using the compose method given.
2490 % o bias: Convolution Output Bias.
2492 % o exception: return any errors or warnings in this structure.
2496 /* Apply a Morphology Primative to an image using the given kernel.
2497 ** Two pre-created images must be provided, and no image is created.
2498 ** It returns the number of pixels that changed between the images
2499 ** for result convergence determination.
2501 static ssize_t MorphologyPrimitive(const Image *image,Image *morphology_image,
2502 const MorphologyMethod method,const KernelInfo *kernel,const double bias,
2503 ExceptionInfo *exception)
2505 #define MorphologyTag "Morphology/Image"
2524 assert(image != (Image *) NULL);
2525 assert(image->signature == MagickSignature);
2526 assert(morphology_image != (Image *) NULL);
2527 assert(morphology_image->signature == MagickSignature);
2528 assert(kernel != (KernelInfo *) NULL);
2529 assert(kernel->signature == MagickSignature);
2530 assert(exception != (ExceptionInfo *) NULL);
2531 assert(exception->signature == MagickSignature);
2537 image_view=AcquireCacheView(image);
2538 morphology_view=AcquireCacheView(morphology_image);
2539 virt_width=image->columns+kernel->width-1;
2541 /* Some methods (including convolve) needs use a reflected kernel.
2542 * Adjust 'origin' offsets to loop though kernel as a reflection.
2547 case ConvolveMorphology:
2548 case DilateMorphology:
2549 case DilateIntensityMorphology:
2550 /*case DistanceMorphology:*/
2551 /* kernel needs to used with reflection about origin */
2552 offx = (ssize_t) kernel->width-offx-1;
2553 offy = (ssize_t) kernel->height-offy-1;
2555 case ErodeMorphology:
2556 case ErodeIntensityMorphology:
2557 case HitAndMissMorphology:
2558 case ThinningMorphology:
2559 case ThickenMorphology:
2560 /* kernel is used as is, without reflection */
2563 assert("Not a Primitive Morphology Method" != (char *) NULL);
2568 if ( method == ConvolveMorphology && kernel->width == 1 )
2569 { /* Special handling (for speed) of vertical (blur) kernels.
2570 ** This performs its handling in columns rather than in rows.
2571 ** This is only done for convolve as it is the only method that
2572 ** generates very large 1-D vertical kernels (such as a 'BlurKernel')
2574 ** Timing tests (on single CPU laptop)
2575 ** Using a vertical 1-d Blue with normal row-by-row (below)
2576 ** time convert logo: -morphology Convolve Blur:0x10+90 null:
2578 ** Using this column method
2579 ** time convert logo: -morphology Convolve Blur:0x10+90 null:
2582 ** Anthony Thyssen, 14 June 2010
2587 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2588 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
2590 for (x=0; x < (ssize_t) image->columns; x++)
2592 register const Quantum
2604 if (status == MagickFalse)
2606 p=GetCacheViewVirtualPixels(image_view,x,-offy,1,image->rows+
2607 kernel->height-1,exception);
2608 q=GetCacheViewAuthenticPixels(morphology_view,x,0,1,
2609 morphology_image->rows,exception);
2610 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
2615 /* offset to origin in 'p'. while 'q' points to it directly */
2618 for (y=0; y < (ssize_t) image->rows; y++)
2626 register const double
2629 register const Quantum
2632 /* Copy input image to the output image for unused channels
2633 * This removes need for 'cloning' a new image every iteration
2635 SetPixelRed(morphology_image,GetPixelRed(image,p+r*
2636 GetPixelChannels(image)),q);
2637 SetPixelGreen(morphology_image,GetPixelGreen(image,p+r*
2638 GetPixelChannels(image)),q);
2639 SetPixelBlue(morphology_image,GetPixelBlue(image,p+r*
2640 GetPixelChannels(image)),q);
2641 if (image->colorspace == CMYKColorspace)
2642 SetPixelBlack(morphology_image,GetPixelBlack(image,p+r*
2643 GetPixelChannels(image)),q);
2645 /* Set the bias of the weighted average output */
2650 result.black = bias;
2653 /* Weighted Average of pixels using reflected kernel
2655 ** NOTE for correct working of this operation for asymetrical
2656 ** kernels, the kernel needs to be applied in its reflected form.
2657 ** That is its values needs to be reversed.
2659 k = &kernel->values[ kernel->height-1 ];
2661 if ( (image->sync == MagickFalse) || (image->matte == MagickFalse) )
2662 { /* No 'Sync' involved.
2663 ** Convolution is simple greyscale channel operation
2665 for (v=0; v < (ssize_t) kernel->height; v++) {
2666 if ( IsNan(*k) ) continue;
2667 result.red += (*k)*GetPixelRed(image,k_pixels);
2668 result.green += (*k)*GetPixelGreen(image,k_pixels);
2669 result.blue += (*k)*GetPixelBlue(image,k_pixels);
2670 if (image->colorspace == CMYKColorspace)
2671 result.black+=(*k)*GetPixelBlack(image,k_pixels);
2672 result.alpha += (*k)*GetPixelAlpha(image,k_pixels);
2674 k_pixels+=GetPixelChannels(image);
2676 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
2677 SetPixelRed(morphology_image,ClampToQuantum(result.red),q);
2678 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
2679 SetPixelGreen(morphology_image,ClampToQuantum(result.green),q);
2680 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
2681 SetPixelBlue(morphology_image,ClampToQuantum(result.blue),q);
2682 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
2683 (image->colorspace == CMYKColorspace))
2684 SetPixelBlack(morphology_image,ClampToQuantum(result.black),q);
2685 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) &&
2686 (image->matte == MagickTrue))
2687 SetPixelAlpha(morphology_image,ClampToQuantum(result.alpha),q);
2690 { /* Channel 'Sync' Flag, and Alpha Channel enabled.
2691 ** Weight the color channels with Alpha Channel so that
2692 ** transparent pixels are not part of the results.
2695 alpha, /* alpha weighting of colors : kernel*alpha */
2696 gamma; /* divisor, sum of color weighting values */
2699 for (v=0; v < (ssize_t) kernel->height; v++) {
2700 if ( IsNan(*k) ) continue;
2701 alpha=(*k)*(QuantumScale*GetPixelAlpha(image,k_pixels));
2703 result.red += alpha*GetPixelRed(image,k_pixels);
2704 result.green += alpha*GetPixelGreen(image,k_pixels);
2705 result.blue += alpha*GetPixelBlue(image,k_pixels);
2706 if (image->colorspace == CMYKColorspace)
2707 result.black += alpha*GetPixelBlack(image,k_pixels);
2708 result.alpha += (*k)*GetPixelAlpha(image,k_pixels);
2710 k_pixels+=GetPixelChannels(image);
2712 /* Sync'ed channels, all channels are modified */
2713 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
2714 SetPixelRed(morphology_image,
2715 ClampToQuantum(gamma*result.red),q);
2716 SetPixelGreen(morphology_image,
2717 ClampToQuantum(gamma*result.green),q);
2718 SetPixelBlue(morphology_image,
2719 ClampToQuantum(gamma*result.blue),q);
2720 if (image->colorspace == CMYKColorspace)
2721 SetPixelBlack(morphology_image,
2722 ClampToQuantum(gamma*result.black),q);
2723 SetPixelAlpha(morphology_image,
2724 ClampToQuantum(result.alpha),q);
2727 /* Count up changed pixels */
2728 if ((GetPixelRed(image,p+r*GetPixelChannels(image)) != GetPixelRed(morphology_image,q))
2729 || (GetPixelGreen(image,p+r*GetPixelChannels(image)) != GetPixelGreen(morphology_image,q))
2730 || (GetPixelBlue(image,p+r*GetPixelChannels(image)) != GetPixelBlue(morphology_image,q))
2731 || (GetPixelAlpha(image,p+r*GetPixelChannels(image)) != GetPixelAlpha(morphology_image,q))
2732 || ((image->colorspace == CMYKColorspace) &&
2733 (GetPixelBlack(image,p+r*GetPixelChannels(image)) != GetPixelBlack(morphology_image,q))))
2734 changed++; /* The pixel was changed in some way! */
2735 p+=GetPixelChannels(image);
2736 q+=GetPixelChannels(morphology_image);
2738 if ( SyncCacheViewAuthenticPixels(morphology_view,exception) == MagickFalse)
2740 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2745 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2746 #pragma omp critical (MagickCore_MorphologyImage)
2748 proceed=SetImageProgress(image,MorphologyTag,progress++,image->rows);
2749 if (proceed == MagickFalse)
2753 morphology_image->type=image->type;
2754 morphology_view=DestroyCacheView(morphology_view);
2755 image_view=DestroyCacheView(image_view);
2756 return(status ? (ssize_t) changed : 0);
2760 ** Normal handling of horizontal or rectangular kernels (row by row)
2762 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2763 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
2765 for (y=0; y < (ssize_t) image->rows; y++)
2767 register const Quantum
2779 if (status == MagickFalse)
2781 p=GetCacheViewVirtualPixels(image_view, -offx, y-offy, virt_width,
2782 kernel->height, exception);
2783 q=GetCacheViewAuthenticPixels(morphology_view,0,y,
2784 morphology_image->columns,1,exception);
2785 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
2790 /* offset to origin in 'p'. while 'q' points to it directly */
2791 r = virt_width*offy + offx;
2793 for (x=0; x < (ssize_t) image->columns; x++)
2801 register const double
2804 register const Quantum
2812 /* Copy input image to the output image for unused channels
2813 * This removes need for 'cloning' a new image every iteration
2815 SetPixelRed(morphology_image,GetPixelRed(image,p+r*
2816 GetPixelChannels(image)),q);
2817 SetPixelGreen(morphology_image,GetPixelGreen(image,p+r*
2818 GetPixelChannels(image)),q);
2819 SetPixelBlue(morphology_image,GetPixelBlue(image,p+r*
2820 GetPixelChannels(image)),q);
2821 if (image->colorspace == CMYKColorspace)
2822 SetPixelBlack(morphology_image,GetPixelBlack(image,p+r*
2823 GetPixelChannels(image)),q);
2830 min.black = (MagickRealType) QuantumRange;
2835 max.black = (MagickRealType) 0;
2836 /* default result is the original pixel value */
2837 result.red = (MagickRealType) GetPixelRed(image,p+r*GetPixelChannels(image));
2838 result.green = (MagickRealType) GetPixelGreen(image,p+r*GetPixelChannels(image));
2839 result.blue = (MagickRealType) GetPixelBlue(image,p+r*GetPixelChannels(image));
2841 if (image->colorspace == CMYKColorspace)
2842 result.black = (MagickRealType) GetPixelBlack(image,p+r*GetPixelChannels(image));
2843 result.alpha=(MagickRealType) GetPixelAlpha(image,p+r*GetPixelChannels(image));
2846 case ConvolveMorphology:
2847 /* Set the bias of the weighted average output */
2852 result.black = bias;
2854 case DilateIntensityMorphology:
2855 case ErodeIntensityMorphology:
2856 /* use a boolean flag indicating when first match found */
2857 result.red = 0.0; /* result is not used otherwise */
2864 case ConvolveMorphology:
2865 /* Weighted Average of pixels using reflected kernel
2867 ** NOTE for correct working of this operation for asymetrical
2868 ** kernels, the kernel needs to be applied in its reflected form.
2869 ** That is its values needs to be reversed.
2871 ** Correlation is actually the same as this but without reflecting
2872 ** the kernel, and thus 'lower-level' that Convolution. However
2873 ** as Convolution is the more common method used, and it does not
2874 ** really cost us much in terms of processing to use a reflected
2875 ** kernel, so it is Convolution that is implemented.
2877 ** Correlation will have its kernel reflected before calling
2878 ** this function to do a Convolve.
2880 ** For more details of Correlation vs Convolution see
2881 ** http://www.cs.umd.edu/~djacobs/CMSC426/Convolution.pdf
2883 k = &kernel->values[ kernel->width*kernel->height-1 ];
2885 if ( (image->sync == MagickFalse) ||
2886 (image->matte == MagickFalse) )
2887 { /* No 'Sync' involved.
2888 ** Convolution is simple greyscale channel operation
2890 for (v=0; v < (ssize_t) kernel->height; v++) {
2891 for (u=0; u < (ssize_t) kernel->width; u++, k--) {
2892 if ( IsNan(*k) ) continue;
2894 GetPixelRed(image,k_pixels+u*GetPixelChannels(image));
2895 result.green += (*k)*
2896 GetPixelGreen(image,k_pixels+u*GetPixelChannels(image));
2897 result.blue += (*k)*
2898 GetPixelBlue(image,k_pixels+u*GetPixelChannels(image));
2899 if (image->colorspace == CMYKColorspace)
2900 result.black += (*k)*
2901 GetPixelBlack(image,k_pixels+u*GetPixelChannels(image));
2902 result.alpha += (*k)*
2903 GetPixelAlpha(image,k_pixels+u*GetPixelChannels(image));
2905 k_pixels += virt_width*GetPixelChannels(image);
2907 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
2908 SetPixelRed(morphology_image,ClampToQuantum(result.red),
2910 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
2911 SetPixelGreen(morphology_image,ClampToQuantum(result.green),
2913 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
2914 SetPixelBlue(morphology_image,ClampToQuantum(result.blue),
2916 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
2917 (image->colorspace == CMYKColorspace))
2918 SetPixelBlack(morphology_image,ClampToQuantum(result.black),
2920 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) &&
2921 (image->matte == MagickTrue))
2922 SetPixelAlpha(morphology_image,ClampToQuantum(result.alpha),
2926 { /* Channel 'Sync' Flag, and Alpha Channel enabled.
2927 ** Weight the color channels with Alpha Channel so that
2928 ** transparent pixels are not part of the results.
2931 alpha, /* alpha weighting of colors : kernel*alpha */
2932 gamma; /* divisor, sum of color weighting values */
2935 for (v=0; v < (ssize_t) kernel->height; v++) {
2936 for (u=0; u < (ssize_t) kernel->width; u++, k--) {
2937 if ( IsNan(*k) ) continue;
2938 alpha=(*k)*(QuantumScale*GetPixelAlpha(image,k_pixels+u*
2939 GetPixelChannels(image)));
2941 result.red += alpha*
2942 GetPixelRed(image,k_pixels+u*GetPixelChannels(image));
2943 result.green += alpha*
2944 GetPixelGreen(image,k_pixels+u*GetPixelChannels(image));
2945 result.blue += alpha*
2946 GetPixelBlue(image,k_pixels+u*GetPixelChannels(image));
2947 if (image->colorspace == CMYKColorspace)
2948 result.black+=alpha*
2949 GetPixelBlack(image,k_pixels+u*GetPixelChannels(image));
2950 result.alpha += (*k)*
2951 GetPixelAlpha(image,k_pixels+u*GetPixelChannels(image));
2953 k_pixels += virt_width*GetPixelChannels(image);
2955 /* Sync'ed channels, all channels are modified */
2956 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
2957 SetPixelRed(morphology_image,
2958 ClampToQuantum(gamma*result.red),q);
2959 SetPixelGreen(morphology_image,
2960 ClampToQuantum(gamma*result.green),q);
2961 SetPixelBlue(morphology_image,
2962 ClampToQuantum(gamma*result.blue),q);
2963 if (image->colorspace == CMYKColorspace)
2964 SetPixelBlack(morphology_image,
2965 ClampToQuantum(gamma*result.black),q);
2966 SetPixelAlpha(morphology_image,ClampToQuantum(result.alpha),q);
2970 case ErodeMorphology:
2971 /* Minimum Value within kernel neighbourhood
2973 ** NOTE that the kernel is not reflected for this operation!
2975 ** NOTE: in normal Greyscale Morphology, the kernel value should
2976 ** be added to the real value, this is currently not done, due to
2977 ** the nature of the boolean kernels being used.
2981 for (v=0; v < (ssize_t) kernel->height; v++) {
2982 for (u=0; u < (ssize_t) kernel->width; u++, k++) {
2983 if ( IsNan(*k) || (*k) < 0.5 ) continue;
2984 Minimize(min.red, (double)
2985 GetPixelRed(image,k_pixels+u*GetPixelChannels(image)));
2986 Minimize(min.green, (double)
2987 GetPixelGreen(image,k_pixels+u*GetPixelChannels(image)));
2988 Minimize(min.blue, (double)
2989 GetPixelBlue(image,k_pixels+u*GetPixelChannels(image)));
2990 if (image->colorspace == CMYKColorspace)
2991 Minimize(min.black,(double)
2992 GetPixelBlack(image,k_pixels+u*GetPixelChannels(image)));
2993 Minimize(min.alpha,(double)
2994 GetPixelAlpha(image,k_pixels+u*GetPixelChannels(image)));
2996 k_pixels += virt_width*GetPixelChannels(image);
3000 case DilateMorphology:
3001 /* Maximum Value within kernel neighbourhood
3003 ** NOTE for correct working of this operation for asymetrical
3004 ** kernels, the kernel needs to be applied in its reflected form.
3005 ** That is its values needs to be reversed.
3007 ** NOTE: in normal Greyscale Morphology, the kernel value should
3008 ** be added to the real value, this is currently not done, due to
3009 ** the nature of the boolean kernels being used.
3012 k = &kernel->values[ kernel->width*kernel->height-1 ];
3014 for (v=0; v < (ssize_t) kernel->height; v++) {
3015 for (u=0; u < (ssize_t) kernel->width; u++, k--) {
3016 if ( IsNan(*k) || (*k) < 0.5 ) continue;
3017 Maximize(max.red, (double)
3018 GetPixelRed(image,k_pixels+u*GetPixelChannels(image)));
3019 Maximize(max.green, (double)
3020 GetPixelGreen(image,k_pixels+u*GetPixelChannels(image)));
3021 Maximize(max.blue, (double)
3022 GetPixelBlue(image,k_pixels+u*GetPixelChannels(image)));
3023 if (image->colorspace == CMYKColorspace)
3024 Maximize(max.black, (double)
3025 GetPixelBlack(image,k_pixels+u*GetPixelChannels(image)));
3026 Maximize(max.alpha,(double)
3027 GetPixelAlpha(image,k_pixels+u*GetPixelChannels(image)));
3029 k_pixels += virt_width*GetPixelChannels(image);
3033 case HitAndMissMorphology:
3034 case ThinningMorphology:
3035 case ThickenMorphology:
3036 /* Minimum of Foreground Pixel minus Maxumum of Background Pixels
3038 ** NOTE that the kernel is not reflected for this operation,
3039 ** and consists of both foreground and background pixel
3040 ** neighbourhoods, 0.0 for background, and 1.0 for foreground
3041 ** with either Nan or 0.5 values for don't care.
3043 ** Note that this will never produce a meaningless negative
3044 ** result. Such results can cause Thinning/Thicken to not work
3045 ** correctly when used against a greyscale image.
3049 for (v=0; v < (ssize_t) kernel->height; v++) {
3050 for (u=0; u < (ssize_t) kernel->width; u++, k++) {
3051 if ( IsNan(*k) ) continue;
3053 { /* minimim of foreground pixels */
3054 Minimize(min.red, (double)
3055 GetPixelRed(image,k_pixels+u*GetPixelChannels(image)));
3056 Minimize(min.green, (double)
3057 GetPixelGreen(image,k_pixels+u*GetPixelChannels(image)));
3058 Minimize(min.blue, (double)
3059 GetPixelBlue(image,k_pixels+u*GetPixelChannels(image)));
3060 if ( image->colorspace == CMYKColorspace)
3061 Minimize(min.black,(double)
3062 GetPixelBlack(image,k_pixels+u*GetPixelChannels(image)));
3063 Minimize(min.alpha,(double)
3064 GetPixelAlpha(image,k_pixels+u*GetPixelChannels(image)));
3066 else if ( (*k) < 0.3 )
3067 { /* maximum of background pixels */
3068 Maximize(max.red, (double)
3069 GetPixelRed(image,k_pixels+u*GetPixelChannels(image)));
3070 Maximize(max.green, (double)
3071 GetPixelGreen(image,k_pixels+u*GetPixelChannels(image)));
3072 Maximize(max.blue, (double)
3073 GetPixelBlue(image,k_pixels+u*GetPixelChannels(image)));
3074 if (image->colorspace == CMYKColorspace)
3075 Maximize(max.black, (double)
3076 GetPixelBlack(image,k_pixels+u*GetPixelChannels(image)));
3077 Maximize(max.alpha,(double)
3078 GetPixelAlpha(image,k_pixels+u*GetPixelChannels(image)));
3081 k_pixels += virt_width*GetPixelChannels(image);
3083 /* Pattern Match if difference is positive */
3084 min.red -= max.red; Maximize( min.red, 0.0 );
3085 min.green -= max.green; Maximize( min.green, 0.0 );
3086 min.blue -= max.blue; Maximize( min.blue, 0.0 );
3087 min.black -= max.black; Maximize( min.black, 0.0 );
3088 min.alpha -= max.alpha; Maximize( min.alpha, 0.0 );
3091 case ErodeIntensityMorphology:
3092 /* Select Pixel with Minimum Intensity within kernel neighbourhood
3094 ** WARNING: the intensity test fails for CMYK and does not
3095 ** take into account the moderating effect of the alpha channel
3096 ** on the intensity.
3098 ** NOTE that the kernel is not reflected for this operation!
3102 for (v=0; v < (ssize_t) kernel->height; v++) {
3103 for (u=0; u < (ssize_t) kernel->width; u++, k++) {
3104 if ( IsNan(*k) || (*k) < 0.5 ) continue;
3105 if ( result.red == 0.0 ||
3106 GetPixelIntensity(image,k_pixels+u*GetPixelChannels(image)) < GetPixelIntensity(morphology_image,q) ) {
3107 /* copy the whole pixel - no channel selection */
3108 SetPixelRed(morphology_image,GetPixelRed(image,
3109 k_pixels+u*GetPixelChannels(image)),q);
3110 SetPixelGreen(morphology_image,GetPixelGreen(image,
3111 k_pixels+u*GetPixelChannels(image)),q);
3112 SetPixelBlue(morphology_image,GetPixelBlue(image,
3113 k_pixels+u*GetPixelChannels(image)),q);
3114 SetPixelAlpha(morphology_image,GetPixelAlpha(image,
3115 k_pixels+u*GetPixelChannels(image)),q);
3116 if ( result.red > 0.0 ) changed++;
3120 k_pixels += virt_width*GetPixelChannels(image);
3124 case DilateIntensityMorphology:
3125 /* Select Pixel with Maximum Intensity within kernel neighbourhood
3127 ** WARNING: the intensity test fails for CMYK and does not
3128 ** take into account the moderating effect of the alpha channel
3129 ** on the intensity (yet).
3131 ** NOTE for correct working of this operation for asymetrical
3132 ** kernels, the kernel needs to be applied in its reflected form.
3133 ** That is its values needs to be reversed.
3135 k = &kernel->values[ kernel->width*kernel->height-1 ];
3137 for (v=0; v < (ssize_t) kernel->height; v++) {
3138 for (u=0; u < (ssize_t) kernel->width; u++, k--) {
3139 if ( IsNan(*k) || (*k) < 0.5 ) continue; /* boolean kernel */
3140 if ( result.red == 0.0 ||
3141 GetPixelIntensity(image,k_pixels+u*GetPixelChannels(image)) > GetPixelIntensity(morphology_image,q) ) {
3142 /* copy the whole pixel - no channel selection */
3143 SetPixelRed(morphology_image,GetPixelRed(image,
3144 k_pixels+u*GetPixelChannels(image)),q);
3145 SetPixelGreen(morphology_image,GetPixelGreen(image,
3146 k_pixels+u*GetPixelChannels(image)),q);
3147 SetPixelBlue(morphology_image,GetPixelBlue(image,
3148 k_pixels+u*GetPixelChannels(image)),q);
3149 SetPixelAlpha(morphology_image,GetPixelAlpha(image,
3150 k_pixels+u*GetPixelChannels(image)),q);
3151 if ( result.red > 0.0 ) changed++;
3155 k_pixels += virt_width*GetPixelChannels(image);
3159 This code has been obsoleted by the MorphologyPrimitiveDirect() function.
3160 However it is still (almost) correct coding for Grayscale Morphology.
3163 GrayErode is equivalent but with kernel values subtracted from pixels
3164 without the kernel rotation
3165 GreyDilate is equivalent but using Maximum() instead of Minimum()
3166 useing kernel rotation
3168 case DistanceMorphology:
3169 /* Add kernel Value and select the minimum value found.
3170 ** The result is a iterative distance from edge of image shape.
3172 ** All Distance Kernels are symetrical, but that may not always
3173 ** be the case. For example how about a distance from left edges?
3174 ** To work correctly with asymetrical kernels the reflected kernel
3175 ** needs to be applied.
3177 k = &kernel->values[ kernel->width*kernel->height-1 ];
3179 for (v=0; v < (ssize_t) kernel->height; v++) {
3180 for (u=0; u < (ssize_t) kernel->width; u++, k--) {
3181 if ( IsNan(*k) ) continue;
3182 Minimize(result.red, (*k)+k_pixels[u].red);
3183 Minimize(result.green, (*k)+k_pixels[u].green);
3184 Minimize(result.blue, (*k)+k_pixels[u].blue);
3185 Minimize(result.alpha, (*k)+k_pixels[u].alpha);
3186 if ( image->colorspace == CMYKColorspace)
3187 Minimize(result.black,(*k)+GetPixelBlack(p_image,k_indexes+u));
3189 k_pixels += virt_width*GetPixelChannels(image);
3193 case UndefinedMorphology:
3195 break; /* Do nothing */
3197 /* Final mathematics of results (combine with original image?)
3199 ** NOTE: Difference Morphology operators Edge* and *Hat could also
3200 ** be done here but works better with iteration as a image difference
3201 ** in the controling function (below). Thicken and Thinning however
3202 ** should be done here so thay can be iterated correctly.
3205 case HitAndMissMorphology:
3206 case ErodeMorphology:
3207 result = min; /* minimum of neighbourhood */
3209 case DilateMorphology:
3210 result = max; /* maximum of neighbourhood */
3212 case ThinningMorphology:
3213 /* subtract pattern match from original */
3214 result.red -= min.red;
3215 result.green -= min.green;
3216 result.blue -= min.blue;
3217 result.black -= min.black;
3218 result.alpha -= min.alpha;
3220 case ThickenMorphology:
3221 /* Add the pattern matchs to the original */
3222 result.red += min.red;
3223 result.green += min.green;
3224 result.blue += min.blue;
3225 result.black += min.black;
3226 result.alpha += min.alpha;
3229 /* result directly calculated or assigned */
3232 /* Assign the resulting pixel values - Clamping Result */
3234 case UndefinedMorphology:
3235 case ConvolveMorphology:
3236 case DilateIntensityMorphology:
3237 case ErodeIntensityMorphology:
3238 break; /* full pixel was directly assigned - not a channel method */
3240 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
3241 SetPixelRed(morphology_image,ClampToQuantum(result.red),q);
3242 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
3243 SetPixelGreen(morphology_image,ClampToQuantum(result.green),q);
3244 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
3245 SetPixelBlue(morphology_image,ClampToQuantum(result.blue),q);
3246 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
3247 (image->colorspace == CMYKColorspace))
3248 SetPixelBlack(morphology_image,ClampToQuantum(result.black),q);
3249 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) &&
3250 (image->matte == MagickTrue))
3251 SetPixelAlpha(morphology_image,ClampToQuantum(result.alpha),q);
3254 /* Count up changed pixels */
3255 if ((GetPixelRed(image,p+r*GetPixelChannels(image)) != GetPixelRed(morphology_image,q)) ||
3256 (GetPixelGreen(image,p+r*GetPixelChannels(image)) != GetPixelGreen(morphology_image,q)) ||
3257 (GetPixelBlue(image,p+r*GetPixelChannels(image)) != GetPixelBlue(morphology_image,q)) ||
3258 (GetPixelAlpha(image,p+r*GetPixelChannels(image)) != GetPixelAlpha(morphology_image,q)) ||
3259 ((image->colorspace == CMYKColorspace) &&
3260 (GetPixelBlack(image,p+r*GetPixelChannels(image)) != GetPixelBlack(morphology_image,q))))
3261 changed++; /* The pixel was changed in some way! */
3262 p+=GetPixelChannels(image);
3263 q+=GetPixelChannels(morphology_image);
3265 if ( SyncCacheViewAuthenticPixels(morphology_view,exception) == MagickFalse)
3267 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3272 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3273 #pragma omp critical (MagickCore_MorphologyImage)
3275 proceed=SetImageProgress(image,MorphologyTag,progress++,image->rows);
3276 if (proceed == MagickFalse)
3280 morphology_view=DestroyCacheView(morphology_view);
3281 image_view=DestroyCacheView(image_view);
3282 return(status ? (ssize_t)changed : -1);
3285 /* This is almost identical to the MorphologyPrimative() function above,
3286 ** but will apply the primitive directly to the image in two passes.
3288 ** That is after each row is 'Sync'ed' into the image, the next row will
3289 ** make use of those values as part of the calculation of the next row.
3290 ** It then repeats, but going in the oppisite (bottom-up) direction.
3292 ** Because of this 'iterative' handling this function can not make use
3293 ** of multi-threaded, parellel processing.
3295 static ssize_t MorphologyPrimitiveDirect(Image *image,
3296 const MorphologyMethod method,const KernelInfo *kernel,
3297 ExceptionInfo *exception)
3320 assert(image != (Image *) NULL);
3321 assert(image->signature == MagickSignature);
3322 assert(kernel != (KernelInfo *) NULL);
3323 assert(kernel->signature == MagickSignature);
3324 assert(exception != (ExceptionInfo *) NULL);
3325 assert(exception->signature == MagickSignature);
3327 /* Some methods (including convolve) needs use a reflected kernel.
3328 * Adjust 'origin' offsets to loop though kernel as a reflection.
3333 case DistanceMorphology:
3334 case VoronoiMorphology:
3335 /* kernel needs to used with reflection about origin */
3336 offx = (ssize_t) kernel->width-offx-1;
3337 offy = (ssize_t) kernel->height-offy-1;
3340 case ?????Morphology:
3341 /* kernel is used as is, without reflection */
3345 assert("Not a PrimativeDirect Morphology Method" != (char *) NULL);
3349 /* DO NOT THREAD THIS CODE! */
3350 /* two views into same image (virtual, and actual) */
3351 virt_view=AcquireCacheView(image);
3352 auth_view=AcquireCacheView(image);
3353 virt_width=image->columns+kernel->width-1;
3355 for (y=0; y < (ssize_t) image->rows; y++)
3357 register const Quantum
3369 /* NOTE read virtual pixels, and authentic pixels, from the same image!
3370 ** we read using virtual to get virtual pixel handling, but write back
3371 ** into the same image.
3373 ** Only top half of kernel is processed as we do a single pass downward
3374 ** through the image iterating the distance function as we go.
3376 if (status == MagickFalse)
3378 p=GetCacheViewVirtualPixels(virt_view,-offx,y-offy,virt_width,(size_t)
3380 q=GetCacheViewAuthenticPixels(auth_view, 0, y, image->columns, 1,
3382 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
3384 if (status == MagickFalse)
3387 /* offset to origin in 'p'. while 'q' points to it directly */
3388 r = (ssize_t) virt_width*offy + offx;
3390 for (x=0; x < (ssize_t) image->columns; x++)
3398 register const double
3401 register const Quantum
3407 /* Starting Defaults */
3408 GetPixelInfo(image,&result);
3409 SetPixelInfo(image,q,&result);
3410 if ( method != VoronoiMorphology )
3411 result.alpha = QuantumRange - result.alpha;
3414 case DistanceMorphology:
3415 /* Add kernel Value and select the minimum value found. */
3416 k = &kernel->values[ kernel->width*kernel->height-1 ];
3418 for (v=0; v <= (ssize_t) offy; v++) {
3419 for (u=0; u < (ssize_t) kernel->width; u++, k--) {
3420 if ( IsNan(*k) ) continue;
3421 Minimize(result.red, (*k)+
3422 GetPixelRed(image,k_pixels+u*GetPixelChannels(image)));
3423 Minimize(result.green, (*k)+
3424 GetPixelGreen(image,k_pixels+u*GetPixelChannels(image)));
3425 Minimize(result.blue, (*k)+
3426 GetPixelBlue(image,k_pixels+u*GetPixelChannels(image)));
3427 if (image->colorspace == CMYKColorspace)
3428 Minimize(result.black,(*k)+
3429 GetPixelBlue(image,k_pixels+u*GetPixelChannels(image)));
3430 Minimize(result.alpha, (*k)+
3431 GetPixelAlpha(image,k_pixels+u*GetPixelChannels(image)));
3433 k_pixels += virt_width*GetPixelChannels(image);
3435 /* repeat with the just processed pixels of this row */
3436 k = &kernel->values[ kernel->width*(kernel->y+1)-1 ];
3437 k_pixels = q-offx*GetPixelChannels(image);
3438 for (u=0; u < (ssize_t) offx; u++, k--) {
3439 if ( x+u-offx < 0 ) continue; /* off the edge! */
3440 if ( IsNan(*k) ) continue;
3441 Minimize(result.red, (*k)+
3442 GetPixelRed(image,k_pixels+u*GetPixelChannels(image)));
3443 Minimize(result.green, (*k)+
3444 GetPixelGreen(image,k_pixels+u*GetPixelChannels(image)));
3445 Minimize(result.blue, (*k)+
3446 GetPixelBlue(image,k_pixels+u*GetPixelChannels(image)));
3447 if (image->colorspace == CMYKColorspace)
3448 Minimize(result.black,(*k)+
3449 GetPixelBlack(image,k_pixels+u*GetPixelChannels(image)));
3450 Minimize(result.alpha,(*k)+
3451 GetPixelAlpha(image,k_pixels+u*GetPixelChannels(image)));
3454 case VoronoiMorphology:
3455 /* Apply Distance to 'Matte' channel, coping the closest color.
3457 ** This is experimental, and realy the 'alpha' component should
3458 ** be completely separate 'masking' channel.
3460 k = &kernel->values[ kernel->width*kernel->height-1 ];
3462 for (v=0; v <= (ssize_t) offy; v++) {
3463 for (u=0; u < (ssize_t) kernel->width; u++, k--) {
3464 if ( IsNan(*k) ) continue;
3465 if( result.alpha > (*k)+GetPixelAlpha(image,k_pixels+u*GetPixelChannels(image)) )
3467 SetPixelInfo(image,k_pixels+u*GetPixelChannels(image),
3472 k_pixels += virt_width*GetPixelChannels(image);
3474 /* repeat with the just processed pixels of this row */
3475 k = &kernel->values[ kernel->width*(kernel->y+1)-1 ];
3476 k_pixels = q-offx*GetPixelChannels(image);
3477 for (u=0; u < (ssize_t) offx; u++, k--) {
3478 if ( x+u-offx < 0 ) continue; /* off the edge! */
3479 if ( IsNan(*k) ) continue;
3480 if( result.alpha > (*k)+GetPixelAlpha(image,k_pixels+u*GetPixelChannels(image)) )
3482 SetPixelInfo(image,k_pixels+u*GetPixelChannels(image),
3489 /* result directly calculated or assigned */
3492 /* Assign the resulting pixel values - Clamping Result */
3494 case VoronoiMorphology:
3495 SetPixelPixelInfo(image,&result,q);
3498 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
3499 SetPixelRed(image,ClampToQuantum(result.red),q);
3500 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
3501 SetPixelGreen(image,ClampToQuantum(result.green),q);
3502 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
3503 SetPixelBlue(image,ClampToQuantum(result.blue),q);
3504 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
3505 (image->colorspace == CMYKColorspace))
3506 SetPixelBlack(image,ClampToQuantum(result.black),q);
3507 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0 &&
3508 (image->matte == MagickTrue))
3509 SetPixelAlpha(image,ClampToQuantum(result.alpha),q);
3512 /* Count up changed pixels */
3513 if ((GetPixelRed(image,p+r*GetPixelChannels(image)) != GetPixelRed(image,q)) ||
3514 (GetPixelGreen(image,p+r*GetPixelChannels(image)) != GetPixelGreen(image,q)) ||
3515 (GetPixelBlue(image,p+r*GetPixelChannels(image)) != GetPixelBlue(image,q)) ||
3516 (GetPixelAlpha(image,p+r*GetPixelChannels(image)) != GetPixelAlpha(image,q)) ||
3517 ((image->colorspace == CMYKColorspace) &&
3518 (GetPixelBlack(image,p+r*GetPixelChannels(image)) != GetPixelBlack(image,q))))
3519 changed++; /* The pixel was changed in some way! */
3521 p+=GetPixelChannels(image); /* increment pixel buffers */
3522 q+=GetPixelChannels(image);
3525 if ( SyncCacheViewAuthenticPixels(auth_view,exception) == MagickFalse)
3527 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3528 if ( SetImageProgress(image,MorphologyTag,progress++,image->rows)
3534 /* Do the reversed pass through the image */
3535 for (y=(ssize_t)image->rows-1; y >= 0; y--)
3537 register const Quantum
3549 if (status == MagickFalse)
3551 /* NOTE read virtual pixels, and authentic pixels, from the same image!
3552 ** we read using virtual to get virtual pixel handling, but write back
3553 ** into the same image.
3555 ** Only the bottom half of the kernel will be processes as we
3558 p=GetCacheViewVirtualPixels(virt_view,-offx,y,virt_width,(size_t)
3559 kernel->y+1,exception);
3560 q=GetCacheViewAuthenticPixels(auth_view, 0, y, image->columns, 1,
3562 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
3564 if (status == MagickFalse)
3567 /* adjust positions to end of row */
3568 p += (image->columns-1)*GetPixelChannels(image);
3569 q += (image->columns-1)*GetPixelChannels(image);
3571 /* offset to origin in 'p'. while 'q' points to it directly */
3574 for (x=(ssize_t)image->columns-1; x >= 0; x--)
3582 register const double
3585 register const Quantum
3591 /* Default - previously modified pixel */
3592 GetPixelInfo(image,&result);
3593 SetPixelInfo(image,q,&result);
3594 if ( method != VoronoiMorphology )
3595 result.alpha = QuantumRange - result.alpha;
3598 case DistanceMorphology:
3599 /* Add kernel Value and select the minimum value found. */
3600 k = &kernel->values[ kernel->width*(kernel->y+1)-1 ];
3602 for (v=offy; v < (ssize_t) kernel->height; v++) {
3603 for (u=0; u < (ssize_t) kernel->width; u++, k--) {
3604 if ( IsNan(*k) ) continue;
3605 Minimize(result.red, (*k)+
3606 GetPixelRed(image,k_pixels+u*GetPixelChannels(image)));
3607 Minimize(result.green, (*k)+
3608 GetPixelGreen(image,k_pixels+u*GetPixelChannels(image)));
3609 Minimize(result.blue, (*k)+
3610 GetPixelBlue(image,k_pixels+u*GetPixelChannels(image)));
3611 if ( image->colorspace == CMYKColorspace)
3612 Minimize(result.black,(*k)+
3613 GetPixelBlack(image,k_pixels+u*GetPixelChannels(image)));
3614 Minimize(result.alpha, (*k)+
3615 GetPixelAlpha(image,k_pixels+u*GetPixelChannels(image)));
3617 k_pixels += virt_width*GetPixelChannels(image);
3619 /* repeat with the just processed pixels of this row */
3620 k = &kernel->values[ kernel->width*(kernel->y)+kernel->x-1 ];
3621 k_pixels = q-offx*GetPixelChannels(image);
3622 for (u=offx+1; u < (ssize_t) kernel->width; u++, k--) {
3623 if ( (x+u-offx) >= (ssize_t)image->columns ) continue;
3624 if ( IsNan(*k) ) continue;
3625 Minimize(result.red, (*k)+
3626 GetPixelRed(image,k_pixels+u*GetPixelChannels(image)));
3627 Minimize(result.green, (*k)+
3628 GetPixelGreen(image,k_pixels+u*GetPixelChannels(image)));
3629 Minimize(result.blue, (*k)+
3630 GetPixelBlue(image,k_pixels+u*GetPixelChannels(image)));
3631 if ( image->colorspace == CMYKColorspace)
3632 Minimize(result.black, (*k)+
3633 GetPixelBlack(image,k_pixels+u*GetPixelChannels(image)));
3634 Minimize(result.alpha, (*k)+
3635 GetPixelAlpha(image,k_pixels+u*GetPixelChannels(image)));
3638 case VoronoiMorphology:
3639 /* Apply Distance to 'Matte' channel, coping the closest color.
3641 ** This is experimental, and realy the 'alpha' component should
3642 ** be completely separate 'masking' channel.
3644 k = &kernel->values[ kernel->width*(kernel->y+1)-1 ];
3646 for (v=offy; v < (ssize_t) kernel->height; v++) {
3647 for (u=0; u < (ssize_t) kernel->width; u++, k--) {
3648 if ( IsNan(*k) ) continue;
3649 if( result.alpha > (*k)+GetPixelAlpha(image,k_pixels+u*GetPixelChannels(image)) )
3651 SetPixelInfo(image,k_pixels+u*GetPixelChannels(image),
3656 k_pixels += virt_width*GetPixelChannels(image);
3658 /* repeat with the just processed pixels of this row */
3659 k = &kernel->values[ kernel->width*(kernel->y)+kernel->x-1 ];
3660 k_pixels = q-offx*GetPixelChannels(image);
3661 for (u=offx+1; u < (ssize_t) kernel->width; u++, k--) {
3662 if ( (x+u-offx) >= (ssize_t)image->columns ) continue;
3663 if ( IsNan(*k) ) continue;
3664 if( result.alpha > (*k)+GetPixelAlpha(image,k_pixels+u*GetPixelChannels(image)) )
3666 SetPixelInfo(image,k_pixels+u*GetPixelChannels(image),
3673 /* result directly calculated or assigned */
3676 /* Assign the resulting pixel values - Clamping Result */
3678 case VoronoiMorphology:
3679 SetPixelPixelInfo(image,&result,q);
3682 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
3683 SetPixelRed(image,ClampToQuantum(result.red),q);
3684 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
3685 SetPixelGreen(image,ClampToQuantum(result.green),q);
3686 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
3687 SetPixelBlue(image,ClampToQuantum(result.blue),q);
3688 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
3689 (image->colorspace == CMYKColorspace))
3690 SetPixelBlack(image,ClampToQuantum(result.black),q);
3691 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0 &&
3692 (image->matte == MagickTrue))
3693 SetPixelAlpha(image,ClampToQuantum(result.alpha),q);
3696 /* Count up changed pixels */
3697 if ( (GetPixelRed(image,p+r*GetPixelChannels(image)) != GetPixelRed(image,q))
3698 || (GetPixelGreen(image,p+r*GetPixelChannels(image)) != GetPixelGreen(image,q))
3699 || (GetPixelBlue(image,p+r*GetPixelChannels(image)) != GetPixelBlue(image,q))
3700 || (GetPixelAlpha(image,p+r*GetPixelChannels(image)) != GetPixelAlpha(image,q))
3701 || ((image->colorspace == CMYKColorspace) &&
3702 (GetPixelBlack(image,p+r*GetPixelChannels(image)) != GetPixelBlack(image,q))))
3703 changed++; /* The pixel was changed in some way! */
3705 p-=GetPixelChannels(image); /* go backward through pixel buffers */
3706 q-=GetPixelChannels(image);
3708 if ( SyncCacheViewAuthenticPixels(auth_view,exception) == MagickFalse)
3710 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3711 if ( SetImageProgress(image,MorphologyTag,progress++,image->rows)
3717 auth_view=DestroyCacheView(auth_view);
3718 virt_view=DestroyCacheView(virt_view);
3719 return(status ? (ssize_t) changed : -1);
3722 /* Apply a Morphology by calling theabove low level primitive application
3723 ** functions. This function handles any iteration loops, composition or
3724 ** re-iteration of results, and compound morphology methods that is based
3725 ** on multiple low-level (staged) morphology methods.
3727 ** Basically this provides the complex grue between the requested morphology
3728 ** method and raw low-level implementation (above).
3730 MagickExport Image *MorphologyApply(const Image *image,
3731 const MorphologyMethod method, const ssize_t iterations,
3732 const KernelInfo *kernel, const CompositeOperator compose,const double bias,
3733 ExceptionInfo *exception)
3739 *curr_image, /* Image we are working with or iterating */
3740 *work_image, /* secondary image for primitive iteration */
3741 *save_image, /* saved image - for 'edge' method only */
3742 *rslt_image; /* resultant image - after multi-kernel handling */
3745 *reflected_kernel, /* A reflected copy of the kernel (if needed) */
3746 *norm_kernel, /* the current normal un-reflected kernel */
3747 *rflt_kernel, /* the current reflected kernel (if needed) */
3748 *this_kernel; /* the kernel being applied */
3751 primitive; /* the current morphology primitive being applied */
3754 rslt_compose; /* multi-kernel compose method for results to use */
3757 special, /* do we use a direct modify function? */
3758 verbose; /* verbose output of results */
3761 method_loop, /* Loop 1: number of compound method iterations (norm 1) */
3762 method_limit, /* maximum number of compound method iterations */
3763 kernel_number, /* Loop 2: the kernel number being applied */
3764 stage_loop, /* Loop 3: primitive loop for compound morphology */
3765 stage_limit, /* how many primitives are in this compound */
3766 kernel_loop, /* Loop 4: iterate the kernel over image */
3767 kernel_limit, /* number of times to iterate kernel */
3768 count, /* total count of primitive steps applied */
3769 kernel_changed, /* total count of changed using iterated kernel */
3770 method_changed; /* total count of changed over method iteration */
3773 changed; /* number pixels changed by last primitive operation */
3778 assert(image != (Image *) NULL);
3779 assert(image->signature == MagickSignature);
3780 assert(kernel != (KernelInfo *) NULL);
3781 assert(kernel->signature == MagickSignature);
3782 assert(exception != (ExceptionInfo *) NULL);
3783 assert(exception->signature == MagickSignature);
3785 count = 0; /* number of low-level morphology primitives performed */
3786 if ( iterations == 0 )
3787 return((Image *)NULL); /* null operation - nothing to do! */
3789 kernel_limit = (size_t) iterations;
3790 if ( iterations < 0 ) /* negative interations = infinite (well alomst) */
3791 kernel_limit = image->columns>image->rows ? image->columns : image->rows;
3793 verbose = IsMagickTrue(GetImageArtifact(image,"verbose"));
3795 /* initialise for cleanup */
3796 curr_image = (Image *) image;
3797 curr_compose = image->compose;
3798 (void) curr_compose;
3799 work_image = save_image = rslt_image = (Image *) NULL;
3800 reflected_kernel = (KernelInfo *) NULL;
3802 /* Initialize specific methods
3803 * + which loop should use the given iteratations
3804 * + how many primitives make up the compound morphology
3805 * + multi-kernel compose method to use (by default)
3807 method_limit = 1; /* just do method once, unless otherwise set */
3808 stage_limit = 1; /* assume method is not a compound */
3809 special = MagickFalse; /* assume it is NOT a direct modify primitive */
3810 rslt_compose = compose; /* and we are composing multi-kernels as given */
3812 case SmoothMorphology: /* 4 primitive compound morphology */
3815 case OpenMorphology: /* 2 primitive compound morphology */
3816 case OpenIntensityMorphology:
3817 case TopHatMorphology:
3818 case CloseMorphology:
3819 case CloseIntensityMorphology:
3820 case BottomHatMorphology:
3821 case EdgeMorphology:
3824 case HitAndMissMorphology:
3825 rslt_compose = LightenCompositeOp; /* Union of multi-kernel results */
3827 case ThinningMorphology:
3828 case ThickenMorphology:
3829 method_limit = kernel_limit; /* iterate the whole method */
3830 kernel_limit = 1; /* do not do kernel iteration */
3832 case DistanceMorphology:
3833 case VoronoiMorphology:
3834 special = MagickTrue;
3840 /* Apply special methods with special requirments
3841 ** For example, single run only, or post-processing requirements
3843 if ( special == MagickTrue )
3845 rslt_image=CloneImage(image,0,0,MagickTrue,exception);
3846 if (rslt_image == (Image *) NULL)
3848 if (SetImageStorageClass(rslt_image,DirectClass) == MagickFalse)
3850 InheritException(exception,&rslt_image->exception);
3854 changed = MorphologyPrimitiveDirect(rslt_image, method,
3857 if ( verbose == MagickTrue )
3858 (void) (void) FormatLocaleFile(stderr,
3859 "%s:%.20g.%.20g #%.20g => Changed %.20g\n",
3860 CommandOptionToMnemonic(MagickMorphologyOptions, method),
3861 1.0,0.0,1.0, (double) changed);
3866 if ( method == VoronoiMorphology ) {
3867 /* Preserve the alpha channel of input image - but turned off */
3868 (void) SetImageAlphaChannel(rslt_image, DeactivateAlphaChannel);
3869 (void) CompositeImage(rslt_image, CopyOpacityCompositeOp, image, 0, 0);
3870 (void) SetImageAlphaChannel(rslt_image, DeactivateAlphaChannel);
3875 /* Handle user (caller) specified multi-kernel composition method */
3876 if ( compose != UndefinedCompositeOp )
3877 rslt_compose = compose; /* override default composition for method */
3878 if ( rslt_compose == UndefinedCompositeOp )
3879 rslt_compose = NoCompositeOp; /* still not defined! Then re-iterate */
3881 /* Some methods require a reflected kernel to use with primitives.
3882 * Create the reflected kernel for those methods. */
3884 case CorrelateMorphology:
3885 case CloseMorphology:
3886 case CloseIntensityMorphology:
3887 case BottomHatMorphology:
3888 case SmoothMorphology:
3889 reflected_kernel = CloneKernelInfo(kernel);
3890 if (reflected_kernel == (KernelInfo *) NULL)
3892 RotateKernelInfo(reflected_kernel,180);
3898 /* Loops around more primitive morpholgy methods
3899 ** erose, dilate, open, close, smooth, edge, etc...
3901 /* Loop 1: iterate the compound method */
3904 while ( method_loop < method_limit && method_changed > 0 ) {
3908 /* Loop 2: iterate over each kernel in a multi-kernel list */
3909 norm_kernel = (KernelInfo *) kernel;
3910 this_kernel = (KernelInfo *) kernel;
3911 rflt_kernel = reflected_kernel;
3914 while ( norm_kernel != NULL ) {
3916 /* Loop 3: Compound Morphology Staging - Select Primative to apply */
3917 stage_loop = 0; /* the compound morphology stage number */
3918 while ( stage_loop < stage_limit ) {
3919 stage_loop++; /* The stage of the compound morphology */
3921 /* Select primitive morphology for this stage of compound method */
3922 this_kernel = norm_kernel; /* default use unreflected kernel */
3923 primitive = method; /* Assume method is a primitive */
3925 case ErodeMorphology: /* just erode */
3926 case EdgeInMorphology: /* erode and image difference */
3927 primitive = ErodeMorphology;
3929 case DilateMorphology: /* just dilate */
3930 case EdgeOutMorphology: /* dilate and image difference */
3931 primitive = DilateMorphology;
3933 case OpenMorphology: /* erode then dialate */
3934 case TopHatMorphology: /* open and image difference */
3935 primitive = ErodeMorphology;
3936 if ( stage_loop == 2 )
3937 primitive = DilateMorphology;
3939 case OpenIntensityMorphology:
3940 primitive = ErodeIntensityMorphology;
3941 if ( stage_loop == 2 )
3942 primitive = DilateIntensityMorphology;
3944 case CloseMorphology: /* dilate, then erode */
3945 case BottomHatMorphology: /* close and image difference */
3946 this_kernel = rflt_kernel; /* use the reflected kernel */
3947 primitive = DilateMorphology;
3948 if ( stage_loop == 2 )
3949 primitive = ErodeMorphology;
3951 case CloseIntensityMorphology:
3952 this_kernel = rflt_kernel; /* use the reflected kernel */
3953 primitive = DilateIntensityMorphology;
3954 if ( stage_loop == 2 )
3955 primitive = ErodeIntensityMorphology;
3957 case SmoothMorphology: /* open, close */
3958 switch ( stage_loop ) {
3959 case 1: /* start an open method, which starts with Erode */
3960 primitive = ErodeMorphology;
3962 case 2: /* now Dilate the Erode */
3963 primitive = DilateMorphology;
3965 case 3: /* Reflect kernel a close */
3966 this_kernel = rflt_kernel; /* use the reflected kernel */
3967 primitive = DilateMorphology;
3969 case 4: /* Finish the Close */
3970 this_kernel = rflt_kernel; /* use the reflected kernel */
3971 primitive = ErodeMorphology;
3975 case EdgeMorphology: /* dilate and erode difference */
3976 primitive = DilateMorphology;
3977 if ( stage_loop == 2 ) {
3978 save_image = curr_image; /* save the image difference */
3979 curr_image = (Image *) image;
3980 primitive = ErodeMorphology;
3983 case CorrelateMorphology:
3984 /* A Correlation is a Convolution with a reflected kernel.
3985 ** However a Convolution is a weighted sum using a reflected
3986 ** kernel. It may seem stange to convert a Correlation into a
3987 ** Convolution as the Correlation is the simplier method, but
3988 ** Convolution is much more commonly used, and it makes sense to
3989 ** implement it directly so as to avoid the need to duplicate the
3990 ** kernel when it is not required (which is typically the
3993 this_kernel = rflt_kernel; /* use the reflected kernel */
3994 primitive = ConvolveMorphology;
3999 assert( this_kernel != (KernelInfo *) NULL );
4001 /* Extra information for debugging compound operations */
4002 if ( verbose == MagickTrue ) {
4003 if ( stage_limit > 1 )
4004 (void) FormatLocaleString(v_info,MaxTextExtent,"%s:%.20g.%.20g -> ",
4005 CommandOptionToMnemonic(MagickMorphologyOptions,method),(double)
4006 method_loop,(double) stage_loop);
4007 else if ( primitive != method )
4008 (void) FormatLocaleString(v_info, MaxTextExtent, "%s:%.20g -> ",
4009 CommandOptionToMnemonic(MagickMorphologyOptions, method),(double)
4015 /* Loop 4: Iterate the kernel with primitive */
4019 while ( kernel_loop < kernel_limit && changed > 0 ) {
4020 kernel_loop++; /* the iteration of this kernel */
4022 /* Create a clone as the destination image, if not yet defined */
4023 if ( work_image == (Image *) NULL )
4025 work_image=CloneImage(image,0,0,MagickTrue,exception);
4026 if (work_image == (Image *) NULL)
4028 if (SetImageStorageClass(work_image,DirectClass) == MagickFalse)
4030 InheritException(exception,&work_image->exception);
4033 /* work_image->type=image->type; ??? */
4036 /* APPLY THE MORPHOLOGICAL PRIMITIVE (curr -> work) */
4038 changed = MorphologyPrimitive(curr_image, work_image, primitive,
4039 this_kernel, bias, exception);
4041 if ( verbose == MagickTrue ) {
4042 if ( kernel_loop > 1 )
4043 (void) FormatLocaleFile(stderr, "\n"); /* add end-of-line from previous */
4044 (void) (void) FormatLocaleFile(stderr,
4045 "%s%s%s:%.20g.%.20g #%.20g => Changed %.20g",
4046 v_info,CommandOptionToMnemonic(MagickMorphologyOptions,
4047 primitive),(this_kernel == rflt_kernel ) ? "*" : "",
4048 (double) (method_loop+kernel_loop-1),(double) kernel_number,
4049 (double) count,(double) changed);
4053 kernel_changed += changed;
4054 method_changed += changed;
4056 /* prepare next loop */
4057 { Image *tmp = work_image; /* swap images for iteration */
4058 work_image = curr_image;
4061 if ( work_image == image )
4062 work_image = (Image *) NULL; /* replace input 'image' */
4064 } /* End Loop 4: Iterate the kernel with primitive */
4066 if ( verbose == MagickTrue && kernel_changed != (size_t)changed )
4067 (void) FormatLocaleFile(stderr, " Total %.20g",(double) kernel_changed);
4068 if ( verbose == MagickTrue && stage_loop < stage_limit )
4069 (void) FormatLocaleFile(stderr, "\n"); /* add end-of-line before looping */
4072 (void) FormatLocaleFile(stderr, "--E-- image=0x%lx\n", (unsigned long)image);
4073 (void) FormatLocaleFile(stderr, " curr =0x%lx\n", (unsigned long)curr_image);
4074 (void) FormatLocaleFile(stderr, " work =0x%lx\n", (unsigned long)work_image);
4075 (void) FormatLocaleFile(stderr, " save =0x%lx\n", (unsigned long)save_image);
4076 (void) FormatLocaleFile(stderr, " union=0x%lx\n", (unsigned long)rslt_image);
4079 } /* End Loop 3: Primative (staging) Loop for Coumpound Methods */
4081 /* Final Post-processing for some Compound Methods
4083 ** The removal of any 'Sync' channel flag in the Image Compositon
4084 ** below ensures the methematical compose method is applied in a
4085 ** purely mathematical way, and only to the selected channels.
4086 ** Turn off SVG composition 'alpha blending'.
4089 case EdgeOutMorphology:
4090 case EdgeInMorphology:
4091 case TopHatMorphology:
4092 case BottomHatMorphology:
4093 if ( verbose == MagickTrue )
4094 (void) FormatLocaleFile(stderr, "\n%s: Difference with original image",
4095 CommandOptionToMnemonic(MagickMorphologyOptions, method) );
4096 curr_image->sync=MagickFalse;
4097 (void) CompositeImage(curr_image,DifferenceCompositeOp,image,0,0);
4099 case EdgeMorphology:
4100 if ( verbose == MagickTrue )
4101 (void) FormatLocaleFile(stderr, "\n%s: Difference of Dilate and Erode",
4102 CommandOptionToMnemonic(MagickMorphologyOptions, method) );
4103 curr_image->sync=MagickFalse;
4104 (void) CompositeImage(curr_image,DifferenceCompositeOp,save_image,0,
4106 save_image = DestroyImage(save_image); /* finished with save image */
4112 /* multi-kernel handling: re-iterate, or compose results */
4113 if ( kernel->next == (KernelInfo *) NULL )
4114 rslt_image = curr_image; /* just return the resulting image */
4115 else if ( rslt_compose == NoCompositeOp )
4116 { if ( verbose == MagickTrue ) {
4117 if ( this_kernel->next != (KernelInfo *) NULL )
4118 (void) FormatLocaleFile(stderr, " (re-iterate)");
4120 (void) FormatLocaleFile(stderr, " (done)");
4122 rslt_image = curr_image; /* return result, and re-iterate */
4124 else if ( rslt_image == (Image *) NULL)
4125 { if ( verbose == MagickTrue )
4126 (void) FormatLocaleFile(stderr, " (save for compose)");
4127 rslt_image = curr_image;
4128 curr_image = (Image *) image; /* continue with original image */
4131 { /* Add the new 'current' result to the composition
4133 ** The removal of any 'Sync' channel flag in the Image Compositon
4134 ** below ensures the methematical compose method is applied in a
4135 ** purely mathematical way, and only to the selected channels.
4136 ** IE: Turn off SVG composition 'alpha blending'.
4138 if ( verbose == MagickTrue )
4139 (void) FormatLocaleFile(stderr, " (compose \"%s\")",
4140 CommandOptionToMnemonic(MagickComposeOptions, rslt_compose) );
4141 rslt_image->sync=MagickFalse;
4142 (void) CompositeImage(rslt_image, rslt_compose, curr_image, 0, 0);
4143 curr_image = DestroyImage(curr_image);
4144 curr_image = (Image *) image; /* continue with original image */
4146 if ( verbose == MagickTrue )
4147 (void) FormatLocaleFile(stderr, "\n");
4149 /* loop to the next kernel in a multi-kernel list */
4150 norm_kernel = norm_kernel->next;
4151 if ( rflt_kernel != (KernelInfo *) NULL )
4152 rflt_kernel = rflt_kernel->next;
4154 } /* End Loop 2: Loop over each kernel */
4156 } /* End Loop 1: compound method interation */
4160 /* Yes goto's are bad, but it makes cleanup lot more efficient */
4162 if ( curr_image == rslt_image )
4163 curr_image = (Image *) NULL;
4164 if ( rslt_image != (Image *) NULL )
4165 rslt_image = DestroyImage(rslt_image);
4167 if ( curr_image == rslt_image || curr_image == image )
4168 curr_image = (Image *) NULL;
4169 if ( curr_image != (Image *) NULL )
4170 curr_image = DestroyImage(curr_image);
4171 if ( work_image != (Image *) NULL )
4172 work_image = DestroyImage(work_image);
4173 if ( save_image != (Image *) NULL )
4174 save_image = DestroyImage(save_image);
4175 if ( reflected_kernel != (KernelInfo *) NULL )
4176 reflected_kernel = DestroyKernelInfo(reflected_kernel);
4182 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4186 % M o r p h o l o g y I m a g e %
4190 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4192 % MorphologyImage() applies a user supplied kernel to the image
4193 % according to the given mophology method.
4195 % This function applies any and all user defined settings before calling
4196 % the above internal function MorphologyApply().
4198 % User defined settings include...
4199 % * Output Bias for Convolution and correlation ("-bias")
4200 % * Kernel Scale/normalize settings ("-set 'option:convolve:scale'")
4201 % This can also includes the addition of a scaled unity kernel.
4202 % * Show Kernel being applied ("-set option:showkernel 1")
4204 % The format of the MorphologyImage method is:
4206 % Image *MorphologyImage(const Image *image,MorphologyMethod method,
4207 % const ssize_t iterations,KernelInfo *kernel,ExceptionInfo *exception)
4209 % Image *MorphologyImage(const Image *image, const ChannelType
4210 % channel,MorphologyMethod method,const ssize_t iterations,
4211 % KernelInfo *kernel,ExceptionInfo *exception)
4213 % A description of each parameter follows:
4215 % o image: the image.
4217 % o method: the morphology method to be applied.
4219 % o iterations: apply the operation this many times (or no change).
4220 % A value of -1 means loop until no change found.
4221 % How this is applied may depend on the morphology method.
4222 % Typically this is a value of 1.
4224 % o kernel: An array of double representing the morphology kernel.
4225 % Warning: kernel may be normalized for the Convolve method.
4227 % o exception: return any errors or warnings in this structure.
4230 MagickExport Image *MorphologyImage(const Image *image,
4231 const MorphologyMethod method,const ssize_t iterations,
4232 const KernelInfo *kernel,ExceptionInfo *exception)
4244 /* Apply Convolve/Correlate Normalization and Scaling Factors.
4245 * This is done BEFORE the ShowKernelInfo() function is called so that
4246 * users can see the results of the 'option:convolve:scale' option.
4248 curr_kernel = (KernelInfo *) kernel;
4249 if ( method == ConvolveMorphology || method == CorrelateMorphology )
4253 artifact = GetImageArtifact(image,"convolve:scale");
4254 if ( artifact != (const char *)NULL ) {
4255 if ( curr_kernel == kernel )
4256 curr_kernel = CloneKernelInfo(kernel);
4257 if (curr_kernel == (KernelInfo *) NULL) {
4258 curr_kernel=DestroyKernelInfo(curr_kernel);
4259 return((Image *) NULL);
4261 ScaleGeometryKernelInfo(curr_kernel, artifact);
4265 /* display the (normalized) kernel via stderr */
4266 if ( IsMagickTrue(GetImageArtifact(image,"showkernel"))
4267 || IsMagickTrue(GetImageArtifact(image,"convolve:showkernel"))
4268 || IsMagickTrue(GetImageArtifact(image,"morphology:showkernel")) )
4269 ShowKernelInfo(curr_kernel);
4271 /* Override the default handling of multi-kernel morphology results
4272 * If 'Undefined' use the default method
4273 * If 'None' (default for 'Convolve') re-iterate previous result
4274 * Otherwise merge resulting images using compose method given.
4275 * Default for 'HitAndMiss' is 'Lighten'.
4279 artifact = GetImageArtifact(image,"morphology:compose");
4280 compose = UndefinedCompositeOp; /* use default for method */
4281 if ( artifact != (const char *) NULL)
4282 compose=(CompositeOperator) ParseCommandOption(MagickComposeOptions,
4283 MagickFalse,artifact);
4285 /* Apply the Morphology */
4286 morphology_image = MorphologyApply(image, method, iterations,
4287 curr_kernel, compose, image->bias, exception);
4289 /* Cleanup and Exit */
4290 if ( curr_kernel != kernel )
4291 curr_kernel=DestroyKernelInfo(curr_kernel);
4292 return(morphology_image);
4296 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4300 + R o t a t e K e r n e l I n f o %
4304 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4306 % RotateKernelInfo() rotates the kernel by the angle given.
4308 % Currently it is restricted to 90 degree angles, of either 1D kernels
4309 % or square kernels. And 'circular' rotations of 45 degrees for 3x3 kernels.
4310 % It will ignore usless rotations for specific 'named' built-in kernels.
4312 % The format of the RotateKernelInfo method is:
4314 % void RotateKernelInfo(KernelInfo *kernel, double angle)
4316 % A description of each parameter follows:
4318 % o kernel: the Morphology/Convolution kernel
4320 % o angle: angle to rotate in degrees
4322 % This function is currently internal to this module only, but can be exported
4323 % to other modules if needed.
4325 static void RotateKernelInfo(KernelInfo *kernel, double angle)
4327 /* angle the lower kernels first */
4328 if ( kernel->next != (KernelInfo *) NULL)
4329 RotateKernelInfo(kernel->next, angle);
4331 /* WARNING: Currently assumes the kernel (rightly) is horizontally symetrical
4333 ** TODO: expand beyond simple 90 degree rotates, flips and flops
4336 /* Modulus the angle */
4337 angle = fmod(angle, 360.0);
4341 if ( 337.5 < angle || angle <= 22.5 )
4342 return; /* Near zero angle - no change! - At least not at this time */
4344 /* Handle special cases */
4345 switch (kernel->type) {
4346 /* These built-in kernels are cylindrical kernels, rotating is useless */
4347 case GaussianKernel:
4352 case LaplacianKernel:
4353 case ChebyshevKernel:
4354 case ManhattanKernel:
4355 case EuclideanKernel:
4358 /* These may be rotatable at non-90 angles in the future */
4359 /* but simply rotating them in multiples of 90 degrees is useless */
4366 /* These only allows a +/-90 degree rotation (by transpose) */
4367 /* A 180 degree rotation is useless */
4369 case RectangleKernel:
4370 if ( 135.0 < angle && angle <= 225.0 )
4372 if ( 225.0 < angle && angle <= 315.0 )
4379 /* Attempt rotations by 45 degrees */
4380 if ( 22.5 < fmod(angle,90.0) && fmod(angle,90.0) <= 67.5 )
4382 if ( kernel->width == 3 && kernel->height == 3 )
4383 { /* Rotate a 3x3 square by 45 degree angle */
4384 MagickRealType t = kernel->values[0];
4385 kernel->values[0] = kernel->values[3];
4386 kernel->values[3] = kernel->values[6];
4387 kernel->values[6] = kernel->values[7];
4388 kernel->values[7] = kernel->values[8];
4389 kernel->values[8] = kernel->values[5];
4390 kernel->values[5] = kernel->values[2];
4391 kernel->values[2] = kernel->values[1];
4392 kernel->values[1] = t;
4393 /* rotate non-centered origin */
4394 if ( kernel->x != 1 || kernel->y != 1 ) {
4396 x = (ssize_t) kernel->x-1;
4397 y = (ssize_t) kernel->y-1;
4398 if ( x == y ) x = 0;
4399 else if ( x == 0 ) x = -y;
4400 else if ( x == -y ) y = 0;
4401 else if ( y == 0 ) y = x;
4402 kernel->x = (ssize_t) x+1;
4403 kernel->y = (ssize_t) y+1;
4405 angle = fmod(angle+315.0, 360.0); /* angle reduced 45 degrees */
4406 kernel->angle = fmod(kernel->angle+45.0, 360.0);
4409 perror("Unable to rotate non-3x3 kernel by 45 degrees");
4411 if ( 45.0 < fmod(angle, 180.0) && fmod(angle,180.0) <= 135.0 )
4413 if ( kernel->width == 1 || kernel->height == 1 )
4414 { /* Do a transpose of a 1 dimensional kernel,
4415 ** which results in a fast 90 degree rotation of some type.
4419 t = (ssize_t) kernel->width;
4420 kernel->width = kernel->height;
4421 kernel->height = (size_t) t;
4423 kernel->x = kernel->y;
4425 if ( kernel->width == 1 ) {
4426 angle = fmod(angle+270.0, 360.0); /* angle reduced 90 degrees */
4427 kernel->angle = fmod(kernel->angle+90.0, 360.0);
4429 angle = fmod(angle+90.0, 360.0); /* angle increased 90 degrees */
4430 kernel->angle = fmod(kernel->angle+270.0, 360.0);
4433 else if ( kernel->width == kernel->height )
4434 { /* Rotate a square array of values by 90 degrees */
4437 register MagickRealType
4440 for( i=0, x=kernel->width-1; i<=x; i++, x--)
4441 for( j=0, y=kernel->height-1; j<y; j++, y--)
4442 { t = k[i+j*kernel->width];
4443 k[i+j*kernel->width] = k[j+x*kernel->width];
4444 k[j+x*kernel->width] = k[x+y*kernel->width];
4445 k[x+y*kernel->width] = k[y+i*kernel->width];
4446 k[y+i*kernel->width] = t;
4449 /* rotate the origin - relative to center of array */
4450 { register ssize_t x,y;
4451 x = (ssize_t) (kernel->x*2-kernel->width+1);
4452 y = (ssize_t) (kernel->y*2-kernel->height+1);
4453 kernel->x = (ssize_t) ( -y +(ssize_t) kernel->width-1)/2;
4454 kernel->y = (ssize_t) ( +x +(ssize_t) kernel->height-1)/2;
4456 angle = fmod(angle+270.0, 360.0); /* angle reduced 90 degrees */
4457 kernel->angle = fmod(kernel->angle+90.0, 360.0);
4460 perror("Unable to rotate a non-square, non-linear kernel 90 degrees");
4462 if ( 135.0 < angle && angle <= 225.0 )
4464 /* For a 180 degree rotation - also know as a reflection
4465 * This is actually a very very common operation!
4466 * Basically all that is needed is a reversal of the kernel data!
4467 * And a reflection of the origon
4475 for ( i=0, j=kernel->width*kernel->height-1; i<j; i++, j--)
4476 t=k[i], k[i]=k[j], k[j]=t;
4478 kernel->x = (ssize_t) kernel->width - kernel->x - 1;
4479 kernel->y = (ssize_t) kernel->height - kernel->y - 1;
4480 angle = fmod(angle-180.0, 360.0); /* angle+180 degrees */
4481 kernel->angle = fmod(kernel->angle+180.0, 360.0);
4483 /* At this point angle should at least between -45 (315) and +45 degrees
4484 * In the future some form of non-orthogonal angled rotates could be
4485 * performed here, posibily with a linear kernel restriction.
4492 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4496 % S c a l e G e o m e t r y K e r n e l I n f o %
4500 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4502 % ScaleGeometryKernelInfo() takes a geometry argument string, typically
4503 % provided as a "-set option:convolve:scale {geometry}" user setting,
4504 % and modifies the kernel according to the parsed arguments of that setting.
4506 % The first argument (and any normalization flags) are passed to
4507 % ScaleKernelInfo() to scale/normalize the kernel. The second argument
4508 % is then passed to UnityAddKernelInfo() to add a scled unity kernel
4509 % into the scaled/normalized kernel.
4511 % The format of the ScaleGeometryKernelInfo method is:
4513 % void ScaleGeometryKernelInfo(KernelInfo *kernel,
4514 % const double scaling_factor,const MagickStatusType normalize_flags)
4516 % A description of each parameter follows:
4518 % o kernel: the Morphology/Convolution kernel to modify
4521 % The geometry string to parse, typically from the user provided
4522 % "-set option:convolve:scale {geometry}" setting.
4525 MagickExport void ScaleGeometryKernelInfo (KernelInfo *kernel,
4526 const char *geometry)
4533 SetGeometryInfo(&args);
4534 flags = (GeometryFlags) ParseGeometry(geometry, &args);
4537 /* For Debugging Geometry Input */
4538 (void) FormatLocaleFile(stderr, "Geometry = 0x%04X : %lg x %lg %+lg %+lg\n",
4539 flags, args.rho, args.sigma, args.xi, args.psi );
4542 if ( (flags & PercentValue) != 0 ) /* Handle Percentage flag*/
4543 args.rho *= 0.01, args.sigma *= 0.01;
4545 if ( (flags & RhoValue) == 0 ) /* Set Defaults for missing args */
4547 if ( (flags & SigmaValue) == 0 )
4550 /* Scale/Normalize the input kernel */
4551 ScaleKernelInfo(kernel, args.rho, flags);
4553 /* Add Unity Kernel, for blending with original */
4554 if ( (flags & SigmaValue) != 0 )
4555 UnityAddKernelInfo(kernel, args.sigma);
4560 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4564 % S c a l e K e r n e l I n f o %
4568 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4570 % ScaleKernelInfo() scales the given kernel list by the given amount, with or
4571 % without normalization of the sum of the kernel values (as per given flags).
4573 % By default (no flags given) the values within the kernel is scaled
4574 % directly using given scaling factor without change.
4576 % If either of the two 'normalize_flags' are given the kernel will first be
4577 % normalized and then further scaled by the scaling factor value given.
4579 % Kernel normalization ('normalize_flags' given) is designed to ensure that
4580 % any use of the kernel scaling factor with 'Convolve' or 'Correlate'
4581 % morphology methods will fall into -1.0 to +1.0 range. Note that for
4582 % non-HDRI versions of IM this may cause images to have any negative results
4583 % clipped, unless some 'bias' is used.
4585 % More specifically. Kernels which only contain positive values (such as a
4586 % 'Gaussian' kernel) will be scaled so that those values sum to +1.0,
4587 % ensuring a 0.0 to +1.0 output range for non-HDRI images.
4589 % For Kernels that contain some negative values, (such as 'Sharpen' kernels)
4590 % the kernel will be scaled by the absolute of the sum of kernel values, so
4591 % that it will generally fall within the +/- 1.0 range.
4593 % For kernels whose values sum to zero, (such as 'Laplician' kernels) kernel
4594 % will be scaled by just the sum of the postive values, so that its output
4595 % range will again fall into the +/- 1.0 range.
4597 % For special kernels designed for locating shapes using 'Correlate', (often
4598 % only containing +1 and -1 values, representing foreground/brackground
4599 % matching) a special normalization method is provided to scale the positive
4600 % values separately to those of the negative values, so the kernel will be
4601 % forced to become a zero-sum kernel better suited to such searches.
4603 % WARNING: Correct normalization of the kernel assumes that the '*_range'
4604 % attributes within the kernel structure have been correctly set during the
4607 % NOTE: The values used for 'normalize_flags' have been selected specifically
4608 % to match the use of geometry options, so that '!' means NormalizeValue, '^'
4609 % means CorrelateNormalizeValue. All other GeometryFlags values are ignored.
4611 % The format of the ScaleKernelInfo method is:
4613 % void ScaleKernelInfo(KernelInfo *kernel, const double scaling_factor,
4614 % const MagickStatusType normalize_flags )
4616 % A description of each parameter follows:
4618 % o kernel: the Morphology/Convolution kernel
4621 % multiply all values (after normalization) by this factor if not
4622 % zero. If the kernel is normalized regardless of any flags.
4624 % o normalize_flags:
4625 % GeometryFlags defining normalization method to use.
4626 % specifically: NormalizeValue, CorrelateNormalizeValue,
4627 % and/or PercentValue
4630 MagickExport void ScaleKernelInfo(KernelInfo *kernel,
4631 const double scaling_factor,const GeometryFlags normalize_flags)
4640 /* do the other kernels in a multi-kernel list first */
4641 if ( kernel->next != (KernelInfo *) NULL)
4642 ScaleKernelInfo(kernel->next, scaling_factor, normalize_flags);
4644 /* Normalization of Kernel */
4646 if ( (normalize_flags&NormalizeValue) != 0 ) {
4647 if ( fabs(kernel->positive_range + kernel->negative_range) > MagickEpsilon )
4648 /* non-zero-summing kernel (generally positive) */
4649 pos_scale = fabs(kernel->positive_range + kernel->negative_range);
4651 /* zero-summing kernel */
4652 pos_scale = kernel->positive_range;
4654 /* Force kernel into a normalized zero-summing kernel */
4655 if ( (normalize_flags&CorrelateNormalizeValue) != 0 ) {
4656 pos_scale = ( fabs(kernel->positive_range) > MagickEpsilon )
4657 ? kernel->positive_range : 1.0;
4658 neg_scale = ( fabs(kernel->negative_range) > MagickEpsilon )
4659 ? -kernel->negative_range : 1.0;
4662 neg_scale = pos_scale;
4664 /* finialize scaling_factor for positive and negative components */
4665 pos_scale = scaling_factor/pos_scale;
4666 neg_scale = scaling_factor/neg_scale;
4668 for (i=0; i < (ssize_t) (kernel->width*kernel->height); i++)
4669 if ( ! IsNan(kernel->values[i]) )
4670 kernel->values[i] *= (kernel->values[i] >= 0) ? pos_scale : neg_scale;
4672 /* convolution output range */
4673 kernel->positive_range *= pos_scale;
4674 kernel->negative_range *= neg_scale;
4675 /* maximum and minimum values in kernel */
4676 kernel->maximum *= (kernel->maximum >= 0.0) ? pos_scale : neg_scale;
4677 kernel->minimum *= (kernel->minimum >= 0.0) ? pos_scale : neg_scale;
4679 /* swap kernel settings if user's scaling factor is negative */
4680 if ( scaling_factor < MagickEpsilon ) {
4682 t = kernel->positive_range;
4683 kernel->positive_range = kernel->negative_range;
4684 kernel->negative_range = t;
4685 t = kernel->maximum;
4686 kernel->maximum = kernel->minimum;
4687 kernel->minimum = 1;
4694 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4698 % S h o w K e r n e l I n f o %
4702 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4704 % ShowKernelInfo() outputs the details of the given kernel defination to
4705 % standard error, generally due to a users 'showkernel' option request.
4707 % The format of the ShowKernel method is:
4709 % void ShowKernelInfo(KernelInfo *kernel)
4711 % A description of each parameter follows:
4713 % o kernel: the Morphology/Convolution kernel
4716 MagickExport void ShowKernelInfo(KernelInfo *kernel)
4724 for (c=0, k=kernel; k != (KernelInfo *) NULL; c++, k=k->next ) {
4726 (void) FormatLocaleFile(stderr, "Kernel");
4727 if ( kernel->next != (KernelInfo *) NULL )
4728 (void) FormatLocaleFile(stderr, " #%lu", (unsigned long) c );
4729 (void) FormatLocaleFile(stderr, " \"%s",
4730 CommandOptionToMnemonic(MagickKernelOptions, k->type) );
4731 if ( fabs(k->angle) > MagickEpsilon )
4732 (void) FormatLocaleFile(stderr, "@%lg", k->angle);
4733 (void) FormatLocaleFile(stderr, "\" of size %lux%lu%+ld%+ld",(unsigned long)
4734 k->width,(unsigned long) k->height,(long) k->x,(long) k->y);
4735 (void) FormatLocaleFile(stderr,
4736 " with values from %.*lg to %.*lg\n",
4737 GetMagickPrecision(), k->minimum,
4738 GetMagickPrecision(), k->maximum);
4739 (void) FormatLocaleFile(stderr, "Forming a output range from %.*lg to %.*lg",
4740 GetMagickPrecision(), k->negative_range,
4741 GetMagickPrecision(), k->positive_range);
4742 if ( fabs(k->positive_range+k->negative_range) < MagickEpsilon )
4743 (void) FormatLocaleFile(stderr, " (Zero-Summing)\n");
4744 else if ( fabs(k->positive_range+k->negative_range-1.0) < MagickEpsilon )
4745 (void) FormatLocaleFile(stderr, " (Normalized)\n");
4747 (void) FormatLocaleFile(stderr, " (Sum %.*lg)\n",
4748 GetMagickPrecision(), k->positive_range+k->negative_range);
4749 for (i=v=0; v < k->height; v++) {
4750 (void) FormatLocaleFile(stderr, "%2lu:", (unsigned long) v );
4751 for (u=0; u < k->width; u++, i++)
4752 if ( IsNan(k->values[i]) )
4753 (void) FormatLocaleFile(stderr," %*s", GetMagickPrecision()+3, "nan");
4755 (void) FormatLocaleFile(stderr," %*.*lg", GetMagickPrecision()+3,
4756 GetMagickPrecision(), k->values[i]);
4757 (void) FormatLocaleFile(stderr,"\n");
4763 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4767 % U n i t y A d d K e r n a l I n f o %
4771 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4773 % UnityAddKernelInfo() Adds a given amount of the 'Unity' Convolution Kernel
4774 % to the given pre-scaled and normalized Kernel. This in effect adds that
4775 % amount of the original image into the resulting convolution kernel. This
4776 % value is usually provided by the user as a percentage value in the
4777 % 'convolve:scale' setting.
4779 % The resulting effect is to convert the defined kernels into blended
4780 % soft-blurs, unsharp kernels or into sharpening kernels.
4782 % The format of the UnityAdditionKernelInfo method is:
4784 % void UnityAdditionKernelInfo(KernelInfo *kernel, const double scale )
4786 % A description of each parameter follows:
4788 % o kernel: the Morphology/Convolution kernel
4791 % scaling factor for the unity kernel to be added to
4795 MagickExport void UnityAddKernelInfo(KernelInfo *kernel,
4798 /* do the other kernels in a multi-kernel list first */
4799 if ( kernel->next != (KernelInfo *) NULL)
4800 UnityAddKernelInfo(kernel->next, scale);
4802 /* Add the scaled unity kernel to the existing kernel */
4803 kernel->values[kernel->x+kernel->y*kernel->width] += scale;
4804 CalcKernelMetaData(kernel); /* recalculate the meta-data */
4810 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4814 % Z e r o K e r n e l N a n s %
4818 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4820 % ZeroKernelNans() replaces any special 'nan' value that may be present in
4821 % the kernel with a zero value. This is typically done when the kernel will
4822 % be used in special hardware (GPU) convolution processors, to simply
4825 % The format of the ZeroKernelNans method is:
4827 % void ZeroKernelNans (KernelInfo *kernel)
4829 % A description of each parameter follows:
4831 % o kernel: the Morphology/Convolution kernel
4834 MagickExport void ZeroKernelNans(KernelInfo *kernel)
4839 /* do the other kernels in a multi-kernel list first */
4840 if ( kernel->next != (KernelInfo *) NULL)
4841 ZeroKernelNans(kernel->next);
4843 for (i=0; i < (kernel->width*kernel->height); i++)
4844 if ( IsNan(kernel->values[i]) )
4845 kernel->values[i] = 0.0;