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 "magick/studio.h"
53 #include "magick/artifact.h"
54 #include "magick/cache-view.h"
55 #include "magick/color-private.h"
56 #include "magick/enhance.h"
57 #include "magick/exception.h"
58 #include "magick/exception-private.h"
59 #include "magick/gem.h"
60 #include "magick/hashmap.h"
61 #include "magick/image.h"
62 #include "magick/image-private.h"
63 #include "magick/list.h"
64 #include "magick/magick.h"
65 #include "magick/memory_.h"
66 #include "magick/monitor-private.h"
67 #include "magick/morphology.h"
68 #include "magick/morphology-private.h"
69 #include "magick/option.h"
70 #include "magick/pixel-private.h"
71 #include "magick/prepress.h"
72 #include "magick/quantize.h"
73 #include "magick/registry.h"
74 #include "magick/semaphore.h"
75 #include "magick/splay-tree.h"
76 #include "magick/statistic.h"
77 #include "magick/string_.h"
78 #include "magick/string-private.h"
79 #include "magick/token.h"
80 #include "magick/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;
251 /* find end of this specific kernel definition string */
252 end = strchr(kernel_string, ';');
253 if ( end == (char *) NULL )
254 end = strchr(kernel_string, '\0');
256 /* clear flags - for Expanding kernel lists thorugh rotations */
259 /* Has a ':' in argument - New user kernel specification */
260 p = strchr(kernel_string, ':');
261 if ( p != (char *) NULL && p < end)
263 /* ParseGeometry() needs the geometry separated! -- Arrgghh */
264 memcpy(token, kernel_string, (size_t) (p-kernel_string));
265 token[p-kernel_string] = '\0';
266 SetGeometryInfo(&args);
267 flags = ParseGeometry(token, &args);
269 /* Size handling and checks of geometry settings */
270 if ( (flags & WidthValue) == 0 ) /* if no width then */
271 args.rho = args.sigma; /* then width = height */
272 if ( args.rho < 1.0 ) /* if width too small */
273 args.rho = 1.0; /* then width = 1 */
274 if ( args.sigma < 1.0 ) /* if height too small */
275 args.sigma = args.rho; /* then height = width */
276 kernel->width = (size_t)args.rho;
277 kernel->height = (size_t)args.sigma;
279 /* Offset Handling and Checks */
280 if ( args.xi < 0.0 || args.psi < 0.0 )
281 return(DestroyKernelInfo(kernel));
282 kernel->x = ((flags & XValue)!=0) ? (ssize_t)args.xi
283 : (ssize_t) (kernel->width-1)/2;
284 kernel->y = ((flags & YValue)!=0) ? (ssize_t)args.psi
285 : (ssize_t) (kernel->height-1)/2;
286 if ( kernel->x >= (ssize_t) kernel->width ||
287 kernel->y >= (ssize_t) kernel->height )
288 return(DestroyKernelInfo(kernel));
290 p++; /* advance beyond the ':' */
293 { /* ELSE - Old old specification, forming odd-square kernel */
294 /* count up number of values given */
295 p=(const char *) kernel_string;
296 while ((isspace((int) ((unsigned char) *p)) != 0) || (*p == '\''))
297 p++; /* ignore "'" chars for convolve filter usage - Cristy */
298 for (i=0; p < end; i++)
300 GetMagickToken(p,&p,token);
302 GetMagickToken(p,&p,token);
304 /* set the size of the kernel - old sized square */
305 kernel->width = kernel->height= (size_t) sqrt((double) i+1.0);
306 kernel->x = kernel->y = (ssize_t) (kernel->width-1)/2;
307 p=(const char *) kernel_string;
308 while ((isspace((int) ((unsigned char) *p)) != 0) || (*p == '\''))
309 p++; /* ignore "'" chars for convolve filter usage - Cristy */
312 /* Read in the kernel values from rest of input string argument */
313 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
314 kernel->height*sizeof(double));
315 if (kernel->values == (double *) NULL)
316 return(DestroyKernelInfo(kernel));
318 kernel->minimum = +MagickHuge;
319 kernel->maximum = -MagickHuge;
320 kernel->negative_range = kernel->positive_range = 0.0;
322 for (i=0; (i < (ssize_t) (kernel->width*kernel->height)) && (p < end); i++)
324 GetMagickToken(p,&p,token);
326 GetMagickToken(p,&p,token);
327 if ( LocaleCompare("nan",token) == 0
328 || LocaleCompare("-",token) == 0 ) {
329 kernel->values[i] = nan; /* do not include this value in kernel */
332 kernel->values[i] = StringToDouble(token);
333 ( kernel->values[i] < 0)
334 ? ( kernel->negative_range += kernel->values[i] )
335 : ( kernel->positive_range += kernel->values[i] );
336 Minimize(kernel->minimum, kernel->values[i]);
337 Maximize(kernel->maximum, kernel->values[i]);
341 /* sanity check -- no more values in kernel definition */
342 GetMagickToken(p,&p,token);
343 if ( *token != '\0' && *token != ';' && *token != '\'' )
344 return(DestroyKernelInfo(kernel));
347 /* this was the old method of handling a incomplete kernel */
348 if ( i < (ssize_t) (kernel->width*kernel->height) ) {
349 Minimize(kernel->minimum, kernel->values[i]);
350 Maximize(kernel->maximum, kernel->values[i]);
351 for ( ; i < (ssize_t) (kernel->width*kernel->height); i++)
352 kernel->values[i]=0.0;
355 /* Number of values for kernel was not enough - Report Error */
356 if ( i < (ssize_t) (kernel->width*kernel->height) )
357 return(DestroyKernelInfo(kernel));
360 /* check that we recieved at least one real (non-nan) value! */
361 if ( kernel->minimum == MagickHuge )
362 return(DestroyKernelInfo(kernel));
364 if ( (flags & AreaValue) != 0 ) /* '@' symbol in kernel size */
365 ExpandRotateKernelInfo(kernel, 45.0); /* cyclic rotate 3x3 kernels */
366 else if ( (flags & GreaterValue) != 0 ) /* '>' symbol in kernel args */
367 ExpandRotateKernelInfo(kernel, 90.0); /* 90 degree rotate of kernel */
368 else if ( (flags & LessValue) != 0 ) /* '<' symbol in kernel args */
369 ExpandMirrorKernelInfo(kernel); /* 90 degree mirror rotate */
374 static KernelInfo *ParseKernelName(const char *kernel_string)
377 token[MaxTextExtent];
395 /* Parse special 'named' kernel */
396 GetMagickToken(kernel_string,&p,token);
397 type=ParseMagickOption(MagickKernelOptions,MagickFalse,token);
398 if ( type < 0 || type == UserDefinedKernel )
399 return((KernelInfo *)NULL); /* not a valid named kernel */
401 while (((isspace((int) ((unsigned char) *p)) != 0) ||
402 (*p == ',') || (*p == ':' )) && (*p != '\0') && (*p != ';'))
405 end = strchr(p, ';'); /* end of this kernel defintion */
406 if ( end == (char *) NULL )
407 end = strchr(p, '\0');
409 /* ParseGeometry() needs the geometry separated! -- Arrgghh */
410 memcpy(token, p, (size_t) (end-p));
412 SetGeometryInfo(&args);
413 flags = ParseGeometry(token, &args);
416 /* For Debugging Geometry Input */
417 fprintf(stderr, "Geometry = 0x%04X : %lg x %lg %+lg %+lg\n",
418 flags, args.rho, args.sigma, args.xi, args.psi );
421 /* special handling of missing values in input string */
423 /* Shape Kernel Defaults */
425 if ( (flags & WidthValue) == 0 )
426 args.rho = 1.0; /* Default scale = 1.0, zero is valid */
434 if ( (flags & HeightValue) == 0 )
435 args.sigma = 1.0; /* Default scale = 1.0, zero is valid */
438 if ( (flags & XValue) == 0 )
439 args.xi = 1.0; /* Default scale = 1.0, zero is valid */
441 case RectangleKernel: /* Rectangle - set size defaults */
442 if ( (flags & WidthValue) == 0 ) /* if no width then */
443 args.rho = args.sigma; /* then width = height */
444 if ( args.rho < 1.0 ) /* if width too small */
445 args.rho = 3; /* then width = 3 */
446 if ( args.sigma < 1.0 ) /* if height too small */
447 args.sigma = args.rho; /* then height = width */
448 if ( (flags & XValue) == 0 ) /* center offset if not defined */
449 args.xi = (double)(((ssize_t)args.rho-1)/2);
450 if ( (flags & YValue) == 0 )
451 args.psi = (double)(((ssize_t)args.sigma-1)/2);
453 /* Distance Kernel Defaults */
454 case ChebyshevKernel:
455 case ManhattanKernel:
456 case OctagonalKernel:
457 case EuclideanKernel:
458 if ( (flags & HeightValue) == 0 ) /* no distance scale */
459 args.sigma = 100.0; /* default distance scaling */
460 else if ( (flags & AspectValue ) != 0 ) /* '!' flag */
461 args.sigma = QuantumRange/(args.sigma+1); /* maximum pixel distance */
462 else if ( (flags & PercentValue ) != 0 ) /* '%' flag */
463 args.sigma *= QuantumRange/100.0; /* percentage of color range */
469 kernel = AcquireKernelBuiltIn((KernelInfoType)type, &args);
470 if ( kernel == (KernelInfo *) NULL )
473 /* global expand to rotated kernel list - only for single kernels */
474 if ( kernel->next == (KernelInfo *) NULL ) {
475 if ( (flags & AreaValue) != 0 ) /* '@' symbol in kernel args */
476 ExpandRotateKernelInfo(kernel, 45.0);
477 else if ( (flags & GreaterValue) != 0 ) /* '>' symbol in kernel args */
478 ExpandRotateKernelInfo(kernel, 90.0);
479 else if ( (flags & LessValue) != 0 ) /* '<' symbol in kernel args */
480 ExpandMirrorKernelInfo(kernel);
486 MagickExport KernelInfo *AcquireKernelInfo(const char *kernel_string)
494 token[MaxTextExtent];
506 while ( GetMagickToken(p,NULL,token), *token != '\0' ) {
508 /* ignore extra or multiple ';' kernel separators */
509 if ( *token != ';' ) {
511 /* tokens starting with alpha is a Named kernel */
512 if (isalpha((int) *token) != 0)
513 new_kernel = ParseKernelName(p);
514 else /* otherwise a user defined kernel array */
515 new_kernel = ParseKernelArray(p);
517 /* Error handling -- this is not proper error handling! */
518 if ( new_kernel == (KernelInfo *) NULL ) {
519 fprintf(stderr, "Failed to parse kernel number #%.20g\n",(double)
521 if ( kernel != (KernelInfo *) NULL )
522 kernel=DestroyKernelInfo(kernel);
523 return((KernelInfo *) NULL);
526 /* initialise or append the kernel list */
527 if ( kernel == (KernelInfo *) NULL )
530 LastKernelInfo(kernel)->next = new_kernel;
533 /* look for the next kernel in list */
535 if ( p == (char *) NULL )
545 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
549 % A c q u i r e K e r n e l B u i l t I n %
553 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
555 % AcquireKernelBuiltIn() returned one of the 'named' built-in types of
556 % kernels used for special purposes such as gaussian blurring, skeleton
557 % pruning, and edge distance determination.
559 % They take a KernelType, and a set of geometry style arguments, which were
560 % typically decoded from a user supplied string, or from a more complex
561 % Morphology Method that was requested.
563 % The format of the AcquireKernalBuiltIn method is:
565 % KernelInfo *AcquireKernelBuiltIn(const KernelInfoType type,
566 % const GeometryInfo args)
568 % A description of each parameter follows:
570 % o type: the pre-defined type of kernel wanted
572 % o args: arguments defining or modifying the kernel
574 % Convolution Kernels
577 % The a No-Op or Scaling single element kernel.
579 % Gaussian:{radius},{sigma}
580 % Generate a two-dimentional gaussian kernel, as used by -gaussian.
581 % The sigma for the curve is required. The resulting kernel is
584 % If 'sigma' is zero, you get a single pixel on a field of zeros.
586 % NOTE: that the 'radius' is optional, but if provided can limit (clip)
587 % the final size of the resulting kernel to a square 2*radius+1 in size.
588 % The radius should be at least 2 times that of the sigma value, or
589 % sever clipping and aliasing may result. If not given or set to 0 the
590 % radius will be determined so as to produce the best minimal error
591 % result, which is usally much larger than is normally needed.
593 % LoG:{radius},{sigma}
594 % "Laplacian of a Gaussian" or "Mexician Hat" Kernel.
595 % The supposed ideal edge detection, zero-summing kernel.
597 % An alturnative to this kernel is to use a "DoG" with a sigma ratio of
598 % approx 1.6 (according to wikipedia).
600 % DoG:{radius},{sigma1},{sigma2}
601 % "Difference of Gaussians" Kernel.
602 % As "Gaussian" but with a gaussian produced by 'sigma2' subtracted
603 % from the gaussian produced by 'sigma1'. Typically sigma2 > sigma1.
604 % The result is a zero-summing kernel.
606 % Blur:{radius},{sigma}[,{angle}]
607 % Generates a 1 dimensional or linear gaussian blur, at the angle given
608 % (current restricted to orthogonal angles). If a 'radius' is given the
609 % kernel is clipped to a width of 2*radius+1. Kernel can be rotated
610 % by a 90 degree angle.
612 % If 'sigma' is zero, you get a single pixel on a field of zeros.
614 % Note that two convolutions with two "Blur" kernels perpendicular to
615 % each other, is equivelent to a far larger "Gaussian" kernel with the
616 % same sigma value, However it is much faster to apply. This is how the
617 % "-blur" operator actually works.
619 % Comet:{width},{sigma},{angle}
620 % Blur in one direction only, much like how a bright object leaves
621 % a comet like trail. The Kernel is actually half a gaussian curve,
622 % Adding two such blurs in opposite directions produces a Blur Kernel.
623 % Angle can be rotated in multiples of 90 degrees.
625 % Note that the first argument is the width of the kernel and not the
626 % radius of the kernel.
628 % # Still to be implemented...
632 % # Set kernel values using a resize filter, and given scale (sigma)
633 % # Cylindrical or Linear. Is this posible with an image?
636 % Named Constant Convolution Kernels
638 % All these are unscaled, zero-summing kernels by default. As such for
639 % non-HDRI version of ImageMagick some form of normalization, user scaling,
640 % and biasing the results is recommended, to prevent the resulting image
643 % The 3x3 kernels (most of these) can be circularly rotated in multiples of
644 % 45 degrees to generate the 8 angled varients of each of the kernels.
647 % Discrete Lapacian Kernels, (without normalization)
648 % Type 0 : 3x3 with center:8 surounded by -1 (8 neighbourhood)
649 % Type 1 : 3x3 with center:4 edge:-1 corner:0 (4 neighbourhood)
650 % Type 2 : 3x3 with center:4 edge:1 corner:-2
651 % Type 3 : 3x3 with center:4 edge:-2 corner:1
652 % Type 5 : 5x5 laplacian
653 % Type 7 : 7x7 laplacian
654 % Type 15 : 5x5 LoG (sigma approx 1.4)
655 % Type 19 : 9x9 LoG (sigma approx 1.4)
658 % Sobel 'Edge' convolution kernel (3x3)
664 % Roberts convolution kernel (3x3)
670 % Prewitt Edge convolution kernel (3x3)
676 % Prewitt's "Compass" convolution kernel (3x3)
682 % Kirsch's "Compass" convolution kernel (3x3)
688 % Frei-Chen Edge Detector is based on a kernel that is similar to
689 % the Sobel Kernel, but is designed to be isotropic. That is it takes
690 % into account the distance of the diagonal in the kernel.
693 % | sqrt(2), 0, -sqrt(2) |
696 % FreiChen:{type},{angle}
698 % Frei-Chen Pre-weighted kernels...
700 % Type 0: default un-nomalized version shown above.
702 % Type 1: Orthogonal Kernel (same as type 11 below)
704 % | sqrt(2), 0, -sqrt(2) | / 2*sqrt(2)
707 % Type 2: Diagonal form of Kernel...
709 % | sqrt(2), 0, -sqrt(2) | / 2*sqrt(2)
712 % However this kernel is als at the heart of the FreiChen Edge Detection
713 % Process which uses a set of 9 specially weighted kernel. These 9
714 % kernels not be normalized, but directly applied to the image. The
715 % results is then added together, to produce the intensity of an edge in
716 % a specific direction. The square root of the pixel value can then be
717 % taken as the cosine of the edge, and at least 2 such runs at 90 degrees
718 % from each other, both the direction and the strength of the edge can be
721 % Type 10: All 9 of the following pre-weighted kernels...
723 % Type 11: | 1, 0, -1 |
724 % | sqrt(2), 0, -sqrt(2) | / 2*sqrt(2)
727 % Type 12: | 1, sqrt(2), 1 |
728 % | 0, 0, 0 | / 2*sqrt(2)
731 % Type 13: | sqrt(2), -1, 0 |
732 % | -1, 0, 1 | / 2*sqrt(2)
735 % Type 14: | 0, 1, -sqrt(2) |
736 % | -1, 0, 1 | / 2*sqrt(2)
739 % Type 15: | 0, -1, 0 |
743 % Type 16: | 1, 0, -1 |
747 % Type 17: | 1, -2, 1 |
751 % Type 18: | -2, 1, -2 |
755 % Type 19: | 1, 1, 1 |
759 % The first 4 are for edge detection, the next 4 are for line detection
760 % and the last is to add a average component to the results.
762 % Using a special type of '-1' will return all 9 pre-weighted kernels
763 % as a multi-kernel list, so that you can use them directly (without
764 % normalization) with the special "-set option:morphology:compose Plus"
765 % setting to apply the full FreiChen Edge Detection Technique.
767 % If 'type' is large it will be taken to be an actual rotation angle for
768 % the default FreiChen (type 0) kernel. As such FreiChen:45 will look
769 % like a Sobel:45 but with 'sqrt(2)' instead of '2' values.
771 % WARNING: The above was layed out as per
772 % http://www.math.tau.ac.il/~turkel/notes/edge_detectors.pdf
773 % But rotated 90 degrees so direction is from left rather than the top.
774 % I have yet to find any secondary confirmation of the above. The only
775 % other source found was actual source code at
776 % http://ltswww.epfl.ch/~courstiv/exos_labos/sol3.pdf
777 % Neigher paper defineds the kernels in a way that looks locical or
778 % correct when taken as a whole.
782 % Diamond:[{radius}[,{scale}]]
783 % Generate a diamond shaped kernel with given radius to the points.
784 % Kernel size will again be radius*2+1 square and defaults to radius 1,
785 % generating a 3x3 kernel that is slightly larger than a square.
787 % Square:[{radius}[,{scale}]]
788 % Generate a square shaped kernel of size radius*2+1, and defaulting
789 % to a 3x3 (radius 1).
791 % Octagon:[{radius}[,{scale}]]
792 % Generate octagonal shaped kernel of given radius and constant scale.
793 % Default radius is 3 producing a 7x7 kernel. A radius of 1 will result
794 % in "Diamond" kernel.
796 % Disk:[{radius}[,{scale}]]
797 % Generate a binary disk, thresholded at the radius given, the radius
798 % may be a float-point value. Final Kernel size is floor(radius)*2+1
799 % square. A radius of 5.3 is the default.
801 % NOTE: That a low radii Disk kernels produce the same results as
802 % many of the previously defined kernels, but differ greatly at larger
803 % radii. Here is a table of equivalences...
804 % "Disk:1" => "Diamond", "Octagon:1", or "Cross:1"
805 % "Disk:1.5" => "Square"
806 % "Disk:2" => "Diamond:2"
807 % "Disk:2.5" => "Octagon"
808 % "Disk:2.9" => "Square:2"
809 % "Disk:3.5" => "Octagon:3"
810 % "Disk:4.5" => "Octagon:4"
811 % "Disk:5.4" => "Octagon:5"
812 % "Disk:6.4" => "Octagon:6"
813 % All other Disk shapes are unique to this kernel, but because a "Disk"
814 % is more circular when using a larger radius, using a larger radius is
815 % preferred over iterating the morphological operation.
817 % Rectangle:{geometry}
818 % Simply generate a rectangle of 1's with the size given. You can also
819 % specify the location of the 'control point', otherwise the closest
820 % pixel to the center of the rectangle is selected.
822 % Properly centered and odd sized rectangles work the best.
824 % Symbol Dilation Kernels
826 % These kernel is not a good general morphological kernel, but is used
827 % more for highlighting and marking any single pixels in an image using,
828 % a "Dilate" method as appropriate.
830 % For the same reasons iterating these kernels does not produce the
831 % same result as using a larger radius for the symbol.
833 % Plus:[{radius}[,{scale}]]
834 % Cross:[{radius}[,{scale}]]
835 % Generate a kernel in the shape of a 'plus' or a 'cross' with
836 % a each arm the length of the given radius (default 2).
838 % NOTE: "plus:1" is equivelent to a "Diamond" kernel.
840 % Ring:{radius1},{radius2}[,{scale}]
841 % A ring of the values given that falls between the two radii.
842 % Defaults to a ring of approximataly 3 radius in a 7x7 kernel.
843 % This is the 'edge' pixels of the default "Disk" kernel,
844 % More specifically, "Ring" -> "Ring:2.5,3.5,1.0"
846 % Hit and Miss Kernels
848 % Peak:radius1,radius2
849 % Find any peak larger than the pixels the fall between the two radii.
850 % The default ring of pixels is as per "Ring".
852 % Find flat orthogonal edges of a binary shape
854 % Find 90 degree corners of a binary shape
856 % A special kernel to thin the 'outside' of diagonals
858 % Find end points of lines (for pruning a skeletion)
859 % Two types of lines ends (default to both) can be searched for
860 % Type 0: All line ends
861 % Type 1: single kernel for 4-conneected line ends
862 % Type 2: single kernel for simple line ends
864 % Find three line junctions (within a skeletion)
865 % Type 0: all line junctions
866 % Type 1: Y Junction kernel
867 % Type 2: Diagonal T Junction kernel
868 % Type 3: Orthogonal T Junction kernel
869 % Type 4: Diagonal X Junction kernel
870 % Type 5: Orthogonal + Junction kernel
872 % Find single pixel ridges or thin lines
873 % Type 1: Fine single pixel thick lines and ridges
874 % Type 2: Find two pixel thick lines and ridges
876 % Octagonal Thickening Kernel, to generate convex hulls of 45 degrees
878 % Traditional skeleton generating kernels.
879 % Type 1: Tradional Skeleton kernel (4 connected skeleton)
880 % Type 2: HIPR2 Skeleton kernel (8 connected skeleton)
881 % Type 3: Thinning skeleton based on a ressearch paper by
882 % Dan S. Bloomberg (Default Type)
884 % A huge variety of Thinning Kernels designed to preserve conectivity.
885 % many other kernel sets use these kernels as source definitions.
886 % Type numbers are 41-49, 81-89, 481, and 482 which are based on
887 % the super and sub notations used in the source research paper.
889 % Distance Measuring Kernels
891 % Different types of distance measuring methods, which are used with the
892 % a 'Distance' morphology method for generating a gradient based on
893 % distance from an edge of a binary shape, though there is a technique
894 % for handling a anti-aliased shape.
896 % See the 'Distance' Morphological Method, for information of how it is
899 % Chebyshev:[{radius}][x{scale}[%!]]
900 % Chebyshev Distance (also known as Tchebychev or Chessboard distance)
901 % is a value of one to any neighbour, orthogonal or diagonal. One why
902 % of thinking of it is the number of squares a 'King' or 'Queen' in
903 % chess needs to traverse reach any other position on a chess board.
904 % It results in a 'square' like distance function, but one where
905 % diagonals are given a value that is closer than expected.
907 % Manhattan:[{radius}][x{scale}[%!]]
908 % Manhattan Distance (also known as Rectilinear, City Block, or the Taxi
909 % Cab distance metric), it is the distance needed when you can only
910 % travel in horizontal or vertical directions only. It is the
911 % distance a 'Rook' in chess would have to travel, and results in a
912 % diamond like distances, where diagonals are further than expected.
914 % Octagonal:[{radius}][x{scale}[%!]]
915 % An interleving of Manhatten and Chebyshev metrics producing an
916 % increasing octagonally shaped distance. Distances matches those of
917 % the "Octagon" shaped kernel of the same radius. The minimum radius
918 % and default is 2, producing a 5x5 kernel.
920 % Euclidean:[{radius}][x{scale}[%!]]
921 % Euclidean distance is the 'direct' or 'as the crow flys' distance.
922 % However by default the kernel size only has a radius of 1, which
923 % limits the distance to 'Knight' like moves, with only orthogonal and
924 % diagonal measurements being correct. As such for the default kernel
925 % you will get octagonal like distance function.
927 % However using a larger radius such as "Euclidean:4" you will get a
928 % much smoother distance gradient from the edge of the shape. Especially
929 % if the image is pre-processed to include any anti-aliasing pixels.
930 % Of course a larger kernel is slower to use, and not always needed.
932 % The first three Distance Measuring Kernels will only generate distances
933 % of exact multiples of {scale} in binary images. As such you can use a
934 % scale of 1 without loosing any information. However you also need some
935 % scaling when handling non-binary anti-aliased shapes.
937 % The "Euclidean" Distance Kernel however does generate a non-integer
938 % fractional results, and as such scaling is vital even for binary shapes.
942 MagickExport KernelInfo *AcquireKernelBuiltIn(const KernelInfoType type,
943 const GeometryInfo *args)
956 nan = sqrt((double)-1.0); /* Special Value : Not A Number */
958 /* Generate a new empty kernel if needed */
959 kernel=(KernelInfo *) NULL;
961 case UndefinedKernel: /* These should not call this function */
962 case UserDefinedKernel:
963 assert("Should not call this function" != (char *)NULL);
965 case LaplacianKernel: /* Named Descrete Convolution Kernels */
966 case SobelKernel: /* these are defined using other kernels */
972 case EdgesKernel: /* Hit and Miss kernels */
974 case DiagonalsKernel:
976 case LineJunctionsKernel:
978 case ConvexHullKernel:
981 break; /* A pre-generated kernel is not needed */
983 /* set to 1 to do a compile-time check that we haven't missed anything */
992 case RectangleKernel:
999 case ChebyshevKernel:
1000 case ManhattanKernel:
1001 case OctangonalKernel:
1002 case EuclideanKernel:
1006 /* Generate the base Kernel Structure */
1007 kernel=(KernelInfo *) AcquireMagickMemory(sizeof(*kernel));
1008 if (kernel == (KernelInfo *) NULL)
1010 (void) ResetMagickMemory(kernel,0,sizeof(*kernel));
1011 kernel->minimum = kernel->maximum = kernel->angle = 0.0;
1012 kernel->negative_range = kernel->positive_range = 0.0;
1013 kernel->type = type;
1014 kernel->next = (KernelInfo *) NULL;
1015 kernel->signature = MagickSignature;
1025 kernel->height = kernel->width = (size_t) 1;
1026 kernel->x = kernel->y = (ssize_t) 0;
1027 kernel->values=(double *) AcquireQuantumMemory(1,sizeof(double));
1028 if (kernel->values == (double *) NULL)
1029 return(DestroyKernelInfo(kernel));
1030 kernel->maximum = kernel->values[0] = args->rho;
1034 case GaussianKernel:
1038 sigma = fabs(args->sigma),
1039 sigma2 = fabs(args->xi),
1042 if ( args->rho >= 1.0 )
1043 kernel->width = (size_t)args->rho*2+1;
1044 else if ( (type != DoGKernel) || (sigma >= sigma2) )
1045 kernel->width = GetOptimalKernelWidth2D(args->rho,sigma);
1047 kernel->width = GetOptimalKernelWidth2D(args->rho,sigma2);
1048 kernel->height = kernel->width;
1049 kernel->x = kernel->y = (ssize_t) (kernel->width-1)/2;
1050 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
1051 kernel->height*sizeof(double));
1052 if (kernel->values == (double *) NULL)
1053 return(DestroyKernelInfo(kernel));
1055 /* WARNING: The following generates a 'sampled gaussian' kernel.
1056 * What we really want is a 'discrete gaussian' kernel.
1058 * How to do this is I don't know, but appears to be basied on the
1059 * Error Function 'erf()' (intergral of a gaussian)
1062 if ( type == GaussianKernel || type == DoGKernel )
1063 { /* Calculate a Gaussian, OR positive half of a DoG */
1064 if ( sigma > MagickEpsilon )
1065 { A = 1.0/(2.0*sigma*sigma); /* simplify loop expressions */
1066 B = (double) (1.0/(Magick2PI*sigma*sigma));
1067 for ( i=0, v=-kernel->y; v <= (ssize_t)kernel->y; v++)
1068 for ( u=-kernel->x; u <= (ssize_t)kernel->x; u++, i++)
1069 kernel->values[i] = exp(-((double)(u*u+v*v))*A)*B;
1071 else /* limiting case - a unity (normalized Dirac) kernel */
1072 { (void) ResetMagickMemory(kernel->values,0, (size_t)
1073 kernel->width*kernel->height*sizeof(double));
1074 kernel->values[kernel->x+kernel->y*kernel->width] = 1.0;
1078 if ( type == DoGKernel )
1079 { /* Subtract a Negative Gaussian for "Difference of Gaussian" */
1080 if ( sigma2 > MagickEpsilon )
1081 { sigma = sigma2; /* simplify loop expressions */
1082 A = 1.0/(2.0*sigma*sigma);
1083 B = (double) (1.0/(Magick2PI*sigma*sigma));
1084 for ( i=0, v=-kernel->y; v <= (ssize_t)kernel->y; v++)
1085 for ( u=-kernel->x; u <= (ssize_t)kernel->x; u++, i++)
1086 kernel->values[i] -= exp(-((double)(u*u+v*v))*A)*B;
1088 else /* limiting case - a unity (normalized Dirac) kernel */
1089 kernel->values[kernel->x+kernel->y*kernel->width] -= 1.0;
1092 if ( type == LoGKernel )
1093 { /* Calculate a Laplacian of a Gaussian - Or Mexician Hat */
1094 if ( sigma > MagickEpsilon )
1095 { A = 1.0/(2.0*sigma*sigma); /* simplify loop expressions */
1096 B = (double) (1.0/(MagickPI*sigma*sigma*sigma*sigma));
1097 for ( i=0, v=-kernel->y; v <= (ssize_t)kernel->y; v++)
1098 for ( u=-kernel->x; u <= (ssize_t)kernel->x; u++, i++)
1099 { R = ((double)(u*u+v*v))*A;
1100 kernel->values[i] = (1-R)*exp(-R)*B;
1103 else /* special case - generate a unity kernel */
1104 { (void) ResetMagickMemory(kernel->values,0, (size_t)
1105 kernel->width*kernel->height*sizeof(double));
1106 kernel->values[kernel->x+kernel->y*kernel->width] = 1.0;
1110 /* Note the above kernels may have been 'clipped' by a user defined
1111 ** radius, producing a smaller (darker) kernel. Also for very small
1112 ** sigma's (> 0.1) the central value becomes larger than one, and thus
1113 ** producing a very bright kernel.
1115 ** Normalization will still be needed.
1118 /* Normalize the 2D Gaussian Kernel
1120 ** NB: a CorrelateNormalize performs a normal Normalize if
1121 ** there are no negative values.
1123 CalcKernelMetaData(kernel); /* the other kernel meta-data */
1124 ScaleKernelInfo(kernel, 1.0, CorrelateNormalizeValue);
1130 sigma = fabs(args->sigma),
1133 if ( args->rho >= 1.0 )
1134 kernel->width = (size_t)args->rho*2+1;
1136 kernel->width = GetOptimalKernelWidth1D(args->rho,sigma);
1138 kernel->x = (ssize_t) (kernel->width-1)/2;
1140 kernel->negative_range = kernel->positive_range = 0.0;
1141 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
1142 kernel->height*sizeof(double));
1143 if (kernel->values == (double *) NULL)
1144 return(DestroyKernelInfo(kernel));
1147 #define KernelRank 3
1148 /* Formula derived from GetBlurKernel() in "effect.c" (plus bug fix).
1149 ** It generates a gaussian 3 times the width, and compresses it into
1150 ** the expected range. This produces a closer normalization of the
1151 ** resulting kernel, especially for very low sigma values.
1152 ** As such while wierd it is prefered.
1154 ** I am told this method originally came from Photoshop.
1156 ** A properly normalized curve is generated (apart from edge clipping)
1157 ** even though we later normalize the result (for edge clipping)
1158 ** to allow the correct generation of a "Difference of Blurs".
1162 v = (ssize_t) (kernel->width*KernelRank-1)/2; /* start/end points to fit range */
1163 (void) ResetMagickMemory(kernel->values,0, (size_t)
1164 kernel->width*kernel->height*sizeof(double));
1165 /* Calculate a Positive 1D Gaussian */
1166 if ( sigma > MagickEpsilon )
1167 { sigma *= KernelRank; /* simplify loop expressions */
1168 alpha = 1.0/(2.0*sigma*sigma);
1169 beta= (double) (1.0/(MagickSQ2PI*sigma ));
1170 for ( u=-v; u <= v; u++) {
1171 kernel->values[(u+v)/KernelRank] +=
1172 exp(-((double)(u*u))*alpha)*beta;
1175 else /* special case - generate a unity kernel */
1176 kernel->values[kernel->x+kernel->y*kernel->width] = 1.0;
1178 /* Direct calculation without curve averaging */
1180 /* Calculate a Positive Gaussian */
1181 if ( sigma > MagickEpsilon )
1182 { alpha = 1.0/(2.0*sigma*sigma); /* simplify loop expressions */
1183 beta = 1.0/(MagickSQ2PI*sigma);
1184 for ( i=0, u=-kernel->x; u <= (ssize_t)kernel->x; u++, i++)
1185 kernel->values[i] = exp(-((double)(u*u))*alpha)*beta;
1187 else /* special case - generate a unity kernel */
1188 { (void) ResetMagickMemory(kernel->values,0, (size_t)
1189 kernel->width*kernel->height*sizeof(double));
1190 kernel->values[kernel->x+kernel->y*kernel->width] = 1.0;
1193 /* Note the above kernel may have been 'clipped' by a user defined
1194 ** radius, producing a smaller (darker) kernel. Also for very small
1195 ** sigma's (> 0.1) the central value becomes larger than one, and thus
1196 ** producing a very bright kernel.
1198 ** Normalization will still be needed.
1201 /* Normalize the 1D Gaussian Kernel
1203 ** NB: a CorrelateNormalize performs a normal Normalize if
1204 ** there are no negative values.
1206 CalcKernelMetaData(kernel); /* the other kernel meta-data */
1207 ScaleKernelInfo(kernel, 1.0, CorrelateNormalizeValue);
1209 /* rotate the 1D kernel by given angle */
1210 RotateKernelInfo(kernel, args->xi );
1215 sigma = fabs(args->sigma),
1218 if ( args->rho < 1.0 )
1219 kernel->width = (GetOptimalKernelWidth1D(args->rho,sigma)-1)/2+1;
1221 kernel->width = (size_t)args->rho;
1222 kernel->x = kernel->y = 0;
1224 kernel->negative_range = kernel->positive_range = 0.0;
1225 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
1226 kernel->height*sizeof(double));
1227 if (kernel->values == (double *) NULL)
1228 return(DestroyKernelInfo(kernel));
1230 /* A comet blur is half a 1D gaussian curve, so that the object is
1231 ** blurred in one direction only. This may not be quite the right
1232 ** curve to use so may change in the future. The function must be
1233 ** normalised after generation, which also resolves any clipping.
1235 ** As we are normalizing and not subtracting gaussians,
1236 ** there is no need for a divisor in the gaussian formula
1238 ** It is less comples
1240 if ( sigma > MagickEpsilon )
1243 #define KernelRank 3
1244 v = (ssize_t) kernel->width*KernelRank; /* start/end points */
1245 (void) ResetMagickMemory(kernel->values,0, (size_t)
1246 kernel->width*sizeof(double));
1247 sigma *= KernelRank; /* simplify the loop expression */
1248 A = 1.0/(2.0*sigma*sigma);
1249 /* B = 1.0/(MagickSQ2PI*sigma); */
1250 for ( u=0; u < v; u++) {
1251 kernel->values[u/KernelRank] +=
1252 exp(-((double)(u*u))*A);
1253 /* exp(-((double)(i*i))/2.0*sigma*sigma)/(MagickSQ2PI*sigma); */
1255 for (i=0; i < (ssize_t) kernel->width; i++)
1256 kernel->positive_range += kernel->values[i];
1258 A = 1.0/(2.0*sigma*sigma); /* simplify the loop expression */
1259 /* B = 1.0/(MagickSQ2PI*sigma); */
1260 for ( i=0; i < (ssize_t) kernel->width; i++)
1261 kernel->positive_range +=
1263 exp(-((double)(i*i))*A);
1264 /* exp(-((double)(i*i))/2.0*sigma*sigma)/(MagickSQ2PI*sigma); */
1267 else /* special case - generate a unity kernel */
1268 { (void) ResetMagickMemory(kernel->values,0, (size_t)
1269 kernel->width*kernel->height*sizeof(double));
1270 kernel->values[kernel->x+kernel->y*kernel->width] = 1.0;
1271 kernel->positive_range = 1.0;
1274 kernel->minimum = 0.0;
1275 kernel->maximum = kernel->values[0];
1276 kernel->negative_range = 0.0;
1278 ScaleKernelInfo(kernel, 1.0, NormalizeValue); /* Normalize */
1279 RotateKernelInfo(kernel, args->xi); /* Rotate by angle */
1284 Convolution Kernels - Well Known Named Constant Kernels
1286 case LaplacianKernel:
1287 { switch ( (int) args->rho ) {
1289 default: /* laplacian square filter -- default */
1290 kernel=ParseKernelArray("3: -1,-1,-1 -1,8,-1 -1,-1,-1");
1292 case 1: /* laplacian diamond filter */
1293 kernel=ParseKernelArray("3: 0,-1,0 -1,4,-1 0,-1,0");
1296 kernel=ParseKernelArray("3: -2,1,-2 1,4,1 -2,1,-2");
1299 kernel=ParseKernelArray("3: 1,-2,1 -2,4,-2 1,-2,1");
1301 case 5: /* a 5x5 laplacian */
1302 kernel=ParseKernelArray(
1303 "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");
1305 case 7: /* a 7x7 laplacian */
1306 kernel=ParseKernelArray(
1307 "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" );
1309 case 15: /* a 5x5 LoG (sigma approx 1.4) */
1310 kernel=ParseKernelArray(
1311 "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");
1313 case 19: /* a 9x9 LoG (sigma approx 1.4) */
1314 /* http://www.cscjournals.org/csc/manuscript/Journals/IJIP/volume3/Issue1/IJIP-15.pdf */
1315 kernel=ParseKernelArray(
1316 "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");
1319 if (kernel == (KernelInfo *) NULL)
1321 kernel->type = type;
1325 { /* Simple Sobel Kernel */
1326 kernel=ParseKernelArray("3: 1,0,-1 2,0,-2 1,0,-1");
1327 if (kernel == (KernelInfo *) NULL)
1329 kernel->type = type;
1330 RotateKernelInfo(kernel, args->rho);
1335 kernel=ParseKernelArray("3: 0,0,0 1,-1,0 0,0,0");
1336 if (kernel == (KernelInfo *) NULL)
1338 kernel->type = type;
1339 RotateKernelInfo(kernel, args->rho);
1344 kernel=ParseKernelArray("3: 1,0,-1 1,0,-1 1,0,-1");
1345 if (kernel == (KernelInfo *) NULL)
1347 kernel->type = type;
1348 RotateKernelInfo(kernel, args->rho);
1353 kernel=ParseKernelArray("3: 1,1,-1 1,-2,-1 1,1,-1");
1354 if (kernel == (KernelInfo *) NULL)
1356 kernel->type = type;
1357 RotateKernelInfo(kernel, args->rho);
1362 kernel=ParseKernelArray("3: 5,-3,-3 5,0,-3 5,-3,-3");
1363 if (kernel == (KernelInfo *) NULL)
1365 kernel->type = type;
1366 RotateKernelInfo(kernel, args->rho);
1369 case FreiChenKernel:
1370 /* Direction is set to be left to right positive */
1371 /* http://www.math.tau.ac.il/~turkel/notes/edge_detectors.pdf -- RIGHT? */
1372 /* http://ltswww.epfl.ch/~courstiv/exos_labos/sol3.pdf -- WRONG? */
1373 { switch ( (int) args->rho ) {
1376 kernel=ParseKernelArray("3: 1,0,-1 2,0,-2 1,0,-1");
1377 if (kernel == (KernelInfo *) NULL)
1379 kernel->type = type;
1380 kernel->values[3] = +MagickSQ2;
1381 kernel->values[5] = -MagickSQ2;
1382 CalcKernelMetaData(kernel); /* recalculate meta-data */
1385 kernel=ParseKernelArray("3: 1,2,0 2,0,-2 0,-2,-1");
1386 if (kernel == (KernelInfo *) NULL)
1388 kernel->type = type;
1389 kernel->values[1] = kernel->values[3] = +MagickSQ2;
1390 kernel->values[5] = kernel->values[7] = -MagickSQ2;
1391 CalcKernelMetaData(kernel); /* recalculate meta-data */
1392 ScaleKernelInfo(kernel, (double) (1.0/2.0*MagickSQ2), NoValue);
1395 kernel=AcquireKernelInfo("FreiChen:11;FreiChen:12;FreiChen:13;FreiChen:14;FreiChen:15;FreiChen:16;FreiChen:17;FreiChen:18;FreiChen:19");
1396 if (kernel == (KernelInfo *) NULL)
1401 kernel=ParseKernelArray("3: 1,0,-1 2,0,-2 1,0,-1");
1402 if (kernel == (KernelInfo *) NULL)
1404 kernel->type = type;
1405 kernel->values[3] = +MagickSQ2;
1406 kernel->values[5] = -MagickSQ2;
1407 CalcKernelMetaData(kernel); /* recalculate meta-data */
1408 ScaleKernelInfo(kernel, (double) (1.0/2.0*MagickSQ2), NoValue);
1411 kernel=ParseKernelArray("3: 1,2,1 0,0,0 1,2,1");
1412 if (kernel == (KernelInfo *) NULL)
1414 kernel->type = type;
1415 kernel->values[1] = +MagickSQ2;
1416 kernel->values[7] = +MagickSQ2;
1417 CalcKernelMetaData(kernel);
1418 ScaleKernelInfo(kernel, (double) (1.0/2.0*MagickSQ2), NoValue);
1421 kernel=ParseKernelArray("3: 2,-1,0 -1,0,1 0,1,-2");
1422 if (kernel == (KernelInfo *) NULL)
1424 kernel->type = type;
1425 kernel->values[0] = +MagickSQ2;
1426 kernel->values[8] = -MagickSQ2;
1427 CalcKernelMetaData(kernel);
1428 ScaleKernelInfo(kernel, (double) (1.0/2.0*MagickSQ2), NoValue);
1431 kernel=ParseKernelArray("3: 0,1,-2 -1,0,1 2,-1,0");
1432 if (kernel == (KernelInfo *) NULL)
1434 kernel->type = type;
1435 kernel->values[2] = -MagickSQ2;
1436 kernel->values[6] = +MagickSQ2;
1437 CalcKernelMetaData(kernel);
1438 ScaleKernelInfo(kernel, (double) (1.0/2.0*MagickSQ2), NoValue);
1441 kernel=ParseKernelArray("3: 0,-1,0 1,0,1 0,-1,0");
1442 if (kernel == (KernelInfo *) NULL)
1444 kernel->type = type;
1445 ScaleKernelInfo(kernel, 1.0/2.0, NoValue);
1448 kernel=ParseKernelArray("3: 1,0,-1 0,0,0 -1,0,1");
1449 if (kernel == (KernelInfo *) NULL)
1451 kernel->type = type;
1452 ScaleKernelInfo(kernel, 1.0/2.0, NoValue);
1455 kernel=ParseKernelArray("3: 1,-2,1 -2,4,-2 -1,-2,1");
1456 if (kernel == (KernelInfo *) NULL)
1458 kernel->type = type;
1459 ScaleKernelInfo(kernel, 1.0/6.0, NoValue);
1462 kernel=ParseKernelArray("3: -2,1,-2 1,4,1 -2,1,-2");
1463 if (kernel == (KernelInfo *) NULL)
1465 kernel->type = type;
1466 ScaleKernelInfo(kernel, 1.0/6.0, NoValue);
1469 kernel=ParseKernelArray("3: 1,1,1 1,1,1 1,1,1");
1470 if (kernel == (KernelInfo *) NULL)
1472 kernel->type = type;
1473 ScaleKernelInfo(kernel, 1.0/3.0, NoValue);
1476 if ( fabs(args->sigma) > MagickEpsilon )
1477 /* Rotate by correctly supplied 'angle' */
1478 RotateKernelInfo(kernel, args->sigma);
1479 else if ( args->rho > 30.0 || args->rho < -30.0 )
1480 /* Rotate by out of bounds 'type' */
1481 RotateKernelInfo(kernel, args->rho);
1486 Boolean or Shaped Kernels
1490 if (args->rho < 1.0)
1491 kernel->width = kernel->height = 3; /* default radius = 1 */
1493 kernel->width = kernel->height = ((size_t)args->rho)*2+1;
1494 kernel->x = kernel->y = (ssize_t) (kernel->width-1)/2;
1496 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
1497 kernel->height*sizeof(double));
1498 if (kernel->values == (double *) NULL)
1499 return(DestroyKernelInfo(kernel));
1501 /* set all kernel values within diamond area to scale given */
1502 for ( i=0, v=-kernel->y; v <= (ssize_t)kernel->y; v++)
1503 for ( u=-kernel->x; u <= (ssize_t)kernel->x; u++, i++)
1504 if ( (labs((long) u)+labs((long) v)) <= (long) kernel->x)
1505 kernel->positive_range += kernel->values[i] = args->sigma;
1507 kernel->values[i] = nan;
1508 kernel->minimum = kernel->maximum = args->sigma; /* a flat shape */
1512 case RectangleKernel:
1515 if ( type == SquareKernel )
1517 if (args->rho < 1.0)
1518 kernel->width = kernel->height = 3; /* default radius = 1 */
1520 kernel->width = kernel->height = (size_t) (2*args->rho+1);
1521 kernel->x = kernel->y = (ssize_t) (kernel->width-1)/2;
1522 scale = args->sigma;
1525 /* NOTE: user defaults set in "AcquireKernelInfo()" */
1526 if ( args->rho < 1.0 || args->sigma < 1.0 )
1527 return(DestroyKernelInfo(kernel)); /* invalid args given */
1528 kernel->width = (size_t)args->rho;
1529 kernel->height = (size_t)args->sigma;
1530 if ( args->xi < 0.0 || args->xi > (double)kernel->width ||
1531 args->psi < 0.0 || args->psi > (double)kernel->height )
1532 return(DestroyKernelInfo(kernel)); /* invalid args given */
1533 kernel->x = (ssize_t) args->xi;
1534 kernel->y = (ssize_t) args->psi;
1537 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
1538 kernel->height*sizeof(double));
1539 if (kernel->values == (double *) NULL)
1540 return(DestroyKernelInfo(kernel));
1542 /* set all kernel values to scale given */
1543 u=(ssize_t) (kernel->width*kernel->height);
1544 for ( i=0; i < u; i++)
1545 kernel->values[i] = scale;
1546 kernel->minimum = kernel->maximum = scale; /* a flat shape */
1547 kernel->positive_range = scale*u;
1552 if (args->rho < 1.0)
1553 kernel->width = kernel->height = 5; /* default radius = 2 */
1555 kernel->width = kernel->height = ((size_t)args->rho)*2+1;
1556 kernel->x = kernel->y = (ssize_t) (kernel->width-1)/2;
1558 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
1559 kernel->height*sizeof(double));
1560 if (kernel->values == (double *) NULL)
1561 return(DestroyKernelInfo(kernel));
1563 for ( i=0, v=-kernel->y; v <= (ssize_t)kernel->y; v++)
1564 for ( u=-kernel->x; u <= (ssize_t)kernel->x; u++, i++)
1565 if ( (labs((long) u)+labs((long) v)) <=
1566 ((long)kernel->x + (long)(kernel->x/2)) )
1567 kernel->positive_range += kernel->values[i] = args->sigma;
1569 kernel->values[i] = nan;
1570 kernel->minimum = kernel->maximum = args->sigma; /* a flat shape */
1576 limit = (ssize_t)(args->rho*args->rho);
1578 if (args->rho < 0.4) /* default radius approx 4.3 */
1579 kernel->width = kernel->height = 9L, limit = 18L;
1581 kernel->width = kernel->height = (size_t)fabs(args->rho)*2+1;
1582 kernel->x = kernel->y = (ssize_t) (kernel->width-1)/2;
1584 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
1585 kernel->height*sizeof(double));
1586 if (kernel->values == (double *) NULL)
1587 return(DestroyKernelInfo(kernel));
1589 for ( i=0, v=-kernel->y; v <= (ssize_t)kernel->y; v++)
1590 for ( u=-kernel->x; u <= (ssize_t)kernel->x; u++, i++)
1591 if ((u*u+v*v) <= limit)
1592 kernel->positive_range += kernel->values[i] = args->sigma;
1594 kernel->values[i] = nan;
1595 kernel->minimum = kernel->maximum = args->sigma; /* a flat shape */
1600 if (args->rho < 1.0)
1601 kernel->width = kernel->height = 5; /* default radius 2 */
1603 kernel->width = kernel->height = ((size_t)args->rho)*2+1;
1604 kernel->x = kernel->y = (ssize_t) (kernel->width-1)/2;
1606 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
1607 kernel->height*sizeof(double));
1608 if (kernel->values == (double *) NULL)
1609 return(DestroyKernelInfo(kernel));
1611 /* set all kernel values along axises to given scale */
1612 for ( i=0, v=-kernel->y; v <= (ssize_t)kernel->y; v++)
1613 for ( u=-kernel->x; u <= (ssize_t)kernel->x; u++, i++)
1614 kernel->values[i] = (u == 0 || v == 0) ? args->sigma : nan;
1615 kernel->minimum = kernel->maximum = args->sigma; /* a flat shape */
1616 kernel->positive_range = args->sigma*(kernel->width*2.0 - 1.0);
1621 if (args->rho < 1.0)
1622 kernel->width = kernel->height = 5; /* default radius 2 */
1624 kernel->width = kernel->height = ((size_t)args->rho)*2+1;
1625 kernel->x = kernel->y = (ssize_t) (kernel->width-1)/2;
1627 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
1628 kernel->height*sizeof(double));
1629 if (kernel->values == (double *) NULL)
1630 return(DestroyKernelInfo(kernel));
1632 /* set all kernel values along axises to given scale */
1633 for ( i=0, v=-kernel->y; v <= (ssize_t)kernel->y; v++)
1634 for ( u=-kernel->x; u <= (ssize_t)kernel->x; u++, i++)
1635 kernel->values[i] = (u == v || u == -v) ? args->sigma : nan;
1636 kernel->minimum = kernel->maximum = args->sigma; /* a flat shape */
1637 kernel->positive_range = args->sigma*(kernel->width*2.0 - 1.0);
1651 if (args->rho < args->sigma)
1653 kernel->width = ((size_t)args->sigma)*2+1;
1654 limit1 = (ssize_t)(args->rho*args->rho);
1655 limit2 = (ssize_t)(args->sigma*args->sigma);
1659 kernel->width = ((size_t)args->rho)*2+1;
1660 limit1 = (ssize_t)(args->sigma*args->sigma);
1661 limit2 = (ssize_t)(args->rho*args->rho);
1664 kernel->width = 7L, limit1 = 7L, limit2 = 11L;
1666 kernel->height = kernel->width;
1667 kernel->x = kernel->y = (ssize_t) (kernel->width-1)/2;
1668 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
1669 kernel->height*sizeof(double));
1670 if (kernel->values == (double *) NULL)
1671 return(DestroyKernelInfo(kernel));
1673 /* set a ring of points of 'scale' ( 0.0 for PeaksKernel ) */
1674 scale = (ssize_t) (( type == PeaksKernel) ? 0.0 : args->xi);
1675 for ( i=0, v= -kernel->y; v <= (ssize_t)kernel->y; v++)
1676 for ( u=-kernel->x; u <= (ssize_t)kernel->x; u++, i++)
1677 { ssize_t radius=u*u+v*v;
1678 if (limit1 < radius && radius <= limit2)
1679 kernel->positive_range += kernel->values[i] = (double) scale;
1681 kernel->values[i] = nan;
1683 kernel->minimum = kernel->maximum = (double) scale;
1684 if ( type == PeaksKernel ) {
1685 /* set the central point in the middle */
1686 kernel->values[kernel->x+kernel->y*kernel->width] = 1.0;
1687 kernel->positive_range = 1.0;
1688 kernel->maximum = 1.0;
1694 kernel=AcquireKernelInfo("ThinSE:482");
1695 if (kernel == (KernelInfo *) NULL)
1697 kernel->type = type;
1698 ExpandMirrorKernelInfo(kernel); /* mirror expansion of kernels */
1703 kernel=AcquireKernelInfo("ThinSE:87");
1704 if (kernel == (KernelInfo *) NULL)
1706 kernel->type = type;
1707 ExpandRotateKernelInfo(kernel, 90.0); /* Expand 90 degree rotations */
1710 case DiagonalsKernel:
1712 switch ( (int) args->rho ) {
1717 kernel=ParseKernelArray("3: 0,0,0 0,-,1 1,1,-");
1718 if (kernel == (KernelInfo *) NULL)
1720 kernel->type = type;
1721 new_kernel=ParseKernelArray("3: 0,0,1 0,-,1 0,1,-");
1722 if (new_kernel == (KernelInfo *) NULL)
1723 return(DestroyKernelInfo(kernel));
1724 new_kernel->type = type;
1725 LastKernelInfo(kernel)->next = new_kernel;
1726 ExpandMirrorKernelInfo(kernel);
1730 kernel=ParseKernelArray("3: 0,0,0 0,-,1 1,1,-");
1733 kernel=ParseKernelArray("3: 0,0,1 0,-,1 0,1,-");
1736 if (kernel == (KernelInfo *) NULL)
1738 kernel->type = type;
1739 RotateKernelInfo(kernel, args->sigma);
1742 case LineEndsKernel:
1743 { /* Kernels for finding the end of thin lines */
1744 switch ( (int) args->rho ) {
1747 /* set of kernels to find all end of lines */
1748 return(AcquireKernelInfo("LineEnds:1>;LineEnds:2>"));
1750 /* kernel for 4-connected line ends - no rotation */
1751 kernel=ParseKernelArray("3: 0,0,- 0,1,1 0,0,-");
1754 /* kernel to add for 8-connected lines - no rotation */
1755 kernel=ParseKernelArray("3: 0,0,0 0,1,0 0,0,1");
1758 /* kernel to add for orthogonal line ends - does not find corners */
1759 kernel=ParseKernelArray("3: 0,0,0 0,1,1 0,0,0");
1762 /* traditional line end - fails on last T end */
1763 kernel=ParseKernelArray("3: 0,0,0 0,1,- 0,0,-");
1766 if (kernel == (KernelInfo *) NULL)
1768 kernel->type = type;
1769 RotateKernelInfo(kernel, args->sigma);
1772 case LineJunctionsKernel:
1773 { /* kernels for finding the junctions of multiple lines */
1774 switch ( (int) args->rho ) {
1777 /* set of kernels to find all line junctions */
1778 return(AcquireKernelInfo("LineJunctions:1@;LineJunctions:2>"));
1781 kernel=ParseKernelArray("3: 1,-,1 -,1,- -,1,-");
1784 /* Diagonal T Junctions */
1785 kernel=ParseKernelArray("3: 1,-,- -,1,- 1,-,1");
1788 /* Orthogonal T Junctions */
1789 kernel=ParseKernelArray("3: -,-,- 1,1,1 -,1,-");
1792 /* Diagonal X Junctions */
1793 kernel=ParseKernelArray("3: 1,-,1 -,1,- 1,-,1");
1796 /* Orthogonal X Junctions - minimal diamond kernel */
1797 kernel=ParseKernelArray("3: -,1,- 1,1,1 -,1,-");
1800 if (kernel == (KernelInfo *) NULL)
1802 kernel->type = type;
1803 RotateKernelInfo(kernel, args->sigma);
1807 { /* Ridges - Ridge finding kernels */
1810 switch ( (int) args->rho ) {
1813 kernel=ParseKernelArray("3x1:0,1,0");
1814 if (kernel == (KernelInfo *) NULL)
1816 kernel->type = type;
1817 ExpandRotateKernelInfo(kernel, 90.0); /* 2 rotated kernels (symmetrical) */
1820 kernel=ParseKernelArray("4x1:0,1,1,0");
1821 if (kernel == (KernelInfo *) NULL)
1823 kernel->type = type;
1824 ExpandRotateKernelInfo(kernel, 90.0); /* 4 rotated kernels */
1826 /* Kernels to find a stepped 'thick' line, 4 rotates + mirrors */
1827 /* Unfortunatally we can not yet rotate a non-square kernel */
1828 /* But then we can't flip a non-symetrical kernel either */
1829 new_kernel=ParseKernelArray("4x3+1+1:0,1,1,- -,1,1,- -,1,1,0");
1830 if (new_kernel == (KernelInfo *) NULL)
1831 return(DestroyKernelInfo(kernel));
1832 new_kernel->type = type;
1833 LastKernelInfo(kernel)->next = new_kernel;
1834 new_kernel=ParseKernelArray("4x3+2+1:0,1,1,- -,1,1,- -,1,1,0");
1835 if (new_kernel == (KernelInfo *) NULL)
1836 return(DestroyKernelInfo(kernel));
1837 new_kernel->type = type;
1838 LastKernelInfo(kernel)->next = new_kernel;
1839 new_kernel=ParseKernelArray("4x3+1+1:-,1,1,0 -,1,1,- 0,1,1,-");
1840 if (new_kernel == (KernelInfo *) NULL)
1841 return(DestroyKernelInfo(kernel));
1842 new_kernel->type = type;
1843 LastKernelInfo(kernel)->next = new_kernel;
1844 new_kernel=ParseKernelArray("4x3+2+1:-,1,1,0 -,1,1,- 0,1,1,-");
1845 if (new_kernel == (KernelInfo *) NULL)
1846 return(DestroyKernelInfo(kernel));
1847 new_kernel->type = type;
1848 LastKernelInfo(kernel)->next = new_kernel;
1849 new_kernel=ParseKernelArray("3x4+1+1:0,-,- 1,1,1 1,1,1 -,-,0");
1850 if (new_kernel == (KernelInfo *) NULL)
1851 return(DestroyKernelInfo(kernel));
1852 new_kernel->type = type;
1853 LastKernelInfo(kernel)->next = new_kernel;
1854 new_kernel=ParseKernelArray("3x4+1+2:0,-,- 1,1,1 1,1,1 -,-,0");
1855 if (new_kernel == (KernelInfo *) NULL)
1856 return(DestroyKernelInfo(kernel));
1857 new_kernel->type = type;
1858 LastKernelInfo(kernel)->next = new_kernel;
1859 new_kernel=ParseKernelArray("3x4+1+1:-,-,0 1,1,1 1,1,1 0,-,-");
1860 if (new_kernel == (KernelInfo *) NULL)
1861 return(DestroyKernelInfo(kernel));
1862 new_kernel->type = type;
1863 LastKernelInfo(kernel)->next = new_kernel;
1864 new_kernel=ParseKernelArray("3x4+1+2:-,-,0 1,1,1 1,1,1 0,-,-");
1865 if (new_kernel == (KernelInfo *) NULL)
1866 return(DestroyKernelInfo(kernel));
1867 new_kernel->type = type;
1868 LastKernelInfo(kernel)->next = new_kernel;
1873 case ConvexHullKernel:
1877 /* first set of 8 kernels */
1878 kernel=ParseKernelArray("3: 1,1,- 1,0,- 1,-,0");
1879 if (kernel == (KernelInfo *) NULL)
1881 kernel->type = type;
1882 ExpandRotateKernelInfo(kernel, 90.0);
1883 /* append the mirror versions too - no flip function yet */
1884 new_kernel=ParseKernelArray("3: 1,1,1 1,0,- -,-,0");
1885 if (new_kernel == (KernelInfo *) NULL)
1886 return(DestroyKernelInfo(kernel));
1887 new_kernel->type = type;
1888 ExpandRotateKernelInfo(new_kernel, 90.0);
1889 LastKernelInfo(kernel)->next = new_kernel;
1892 case SkeletonKernel:
1894 switch ( (int) args->rho ) {
1897 /* Traditional Skeleton...
1898 ** A cyclically rotated single kernel
1900 kernel=AcquireKernelInfo("ThinSE:482");
1901 if (kernel == (KernelInfo *) NULL)
1903 kernel->type = type;
1904 ExpandRotateKernelInfo(kernel, 45.0); /* 8 rotations */
1907 /* HIPR Variation of the cyclic skeleton
1908 ** Corners of the traditional method made more forgiving,
1909 ** but the retain the same cyclic order.
1911 kernel=AcquireKernelInfo("ThinSE:482; ThinSE:87x90;");
1912 if (kernel == (KernelInfo *) NULL)
1914 if (kernel->next == (KernelInfo *) NULL)
1915 return(DestroyKernelInfo(kernel));
1916 kernel->type = type;
1917 kernel->next->type = type;
1918 ExpandRotateKernelInfo(kernel, 90.0); /* 4 rotations of the 2 kernels */
1921 /* Dan Bloomberg Skeleton, from his paper on 3x3 thinning SE's
1922 ** "Connectivity-Preserving Morphological Image Thransformations"
1923 ** by Dan S. Bloomberg, available on Leptonica, Selected Papers,
1924 ** http://www.leptonica.com/papers/conn.pdf
1926 kernel=AcquireKernelInfo(
1927 "ThinSE:41; ThinSE:42; ThinSE:43");
1928 if (kernel == (KernelInfo *) NULL)
1930 kernel->type = type;
1931 kernel->next->type = type;
1932 kernel->next->next->type = type;
1933 ExpandMirrorKernelInfo(kernel); /* 12 kernels total */
1939 { /* Special kernels for general thinning, while preserving connections
1940 ** "Connectivity-Preserving Morphological Image Thransformations"
1941 ** by Dan S. Bloomberg, available on Leptonica, Selected Papers,
1942 ** http://www.leptonica.com/papers/conn.pdf
1944 ** http://tpgit.github.com/Leptonica/ccthin_8c_source.html
1946 ** Note kernels do not specify the origin pixel, allowing them
1947 ** to be used for both thickening and thinning operations.
1949 switch ( (int) args->rho ) {
1950 /* SE for 4-connected thinning */
1951 case 41: /* SE_4_1 */
1952 kernel=ParseKernelArray("3: -,-,1 0,-,1 -,-,1");
1954 case 42: /* SE_4_2 */
1955 kernel=ParseKernelArray("3: -,-,1 0,-,1 -,0,-");
1957 case 43: /* SE_4_3 */
1958 kernel=ParseKernelArray("3: -,0,- 0,-,1 -,-,1");
1960 case 44: /* SE_4_4 */
1961 kernel=ParseKernelArray("3: -,0,- 0,-,1 -,0,-");
1963 case 45: /* SE_4_5 */
1964 kernel=ParseKernelArray("3: -,0,1 0,-,1 -,0,-");
1966 case 46: /* SE_4_6 */
1967 kernel=ParseKernelArray("3: -,0,- 0,-,1 -,0,1");
1969 case 47: /* SE_4_7 */
1970 kernel=ParseKernelArray("3: -,1,1 0,-,1 -,0,-");
1972 case 48: /* SE_4_8 */
1973 kernel=ParseKernelArray("3: -,-,1 0,-,1 0,-,1");
1975 case 49: /* SE_4_9 */
1976 kernel=ParseKernelArray("3: 0,-,1 0,-,1 -,-,1");
1978 /* SE for 8-connected thinning - negatives of the above */
1979 case 81: /* SE_8_0 */
1980 kernel=ParseKernelArray("3: -,1,- 0,-,1 -,1,-");
1982 case 82: /* SE_8_2 */
1983 kernel=ParseKernelArray("3: -,1,- 0,-,1 0,-,-");
1985 case 83: /* SE_8_3 */
1986 kernel=ParseKernelArray("3: 0,-,- 0,-,1 -,1,-");
1988 case 84: /* SE_8_4 */
1989 kernel=ParseKernelArray("3: 0,-,- 0,-,1 0,-,-");
1991 case 85: /* SE_8_5 */
1992 kernel=ParseKernelArray("3: 0,-,1 0,-,1 0,-,-");
1994 case 86: /* SE_8_6 */
1995 kernel=ParseKernelArray("3: 0,-,- 0,-,1 0,-,1");
1997 case 87: /* SE_8_7 */
1998 kernel=ParseKernelArray("3: -,1,- 0,-,1 0,0,-");
2000 case 88: /* SE_8_8 */
2001 kernel=ParseKernelArray("3: -,1,- 0,-,1 0,1,-");
2003 case 89: /* SE_8_9 */
2004 kernel=ParseKernelArray("3: 0,1,- 0,-,1 -,1,-");
2006 /* Special combined SE kernels */
2007 case 423: /* SE_4_2 , SE_4_3 Combined Kernel */
2008 kernel=ParseKernelArray("3: -,-,1 0,-,- -,0,-");
2010 case 823: /* SE_8_2 , SE_8_3 Combined Kernel */
2011 kernel=ParseKernelArray("3: -,1,- -,-,1 0,-,-");
2013 case 481: /* SE_48_1 - General Connected Corner Kernel */
2014 kernel=ParseKernelArray("3: -,1,1 0,-,1 0,0,-");
2017 case 482: /* SE_48_2 - General Edge Kernel */
2018 kernel=ParseKernelArray("3: 0,-,1 0,-,1 0,-,1");
2021 if (kernel == (KernelInfo *) NULL)
2023 kernel->type = type;
2024 RotateKernelInfo(kernel, args->sigma);
2028 Distance Measuring Kernels
2030 case ChebyshevKernel:
2032 if (args->rho < 1.0)
2033 kernel->width = kernel->height = 3; /* default radius = 1 */
2035 kernel->width = kernel->height = ((size_t)args->rho)*2+1;
2036 kernel->x = kernel->y = (ssize_t) (kernel->width-1)/2;
2038 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
2039 kernel->height*sizeof(double));
2040 if (kernel->values == (double *) NULL)
2041 return(DestroyKernelInfo(kernel));
2043 for ( i=0, v=-kernel->y; v <= (ssize_t)kernel->y; v++)
2044 for ( u=-kernel->x; u <= (ssize_t)kernel->x; u++, i++)
2045 kernel->positive_range += ( kernel->values[i] =
2046 args->sigma*MagickMax(fabs((double)u),fabs((double)v)) );
2047 kernel->maximum = kernel->values[0];
2050 case ManhattanKernel:
2052 if (args->rho < 1.0)
2053 kernel->width = kernel->height = 3; /* default radius = 1 */
2055 kernel->width = kernel->height = ((size_t)args->rho)*2+1;
2056 kernel->x = kernel->y = (ssize_t) (kernel->width-1)/2;
2058 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
2059 kernel->height*sizeof(double));
2060 if (kernel->values == (double *) NULL)
2061 return(DestroyKernelInfo(kernel));
2063 for ( i=0, v=-kernel->y; v <= (ssize_t)kernel->y; v++)
2064 for ( u=-kernel->x; u <= (ssize_t)kernel->x; u++, i++)
2065 kernel->positive_range += ( kernel->values[i] =
2066 args->sigma*(labs((long) u)+labs((long) v)) );
2067 kernel->maximum = kernel->values[0];
2070 case OctagonalKernel:
2072 if (args->rho < 2.0)
2073 kernel->width = kernel->height = 5; /* default/minimum radius = 2 */
2075 kernel->width = kernel->height = ((size_t)args->rho)*2+1;
2076 kernel->x = kernel->y = (ssize_t) (kernel->width-1)/2;
2078 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
2079 kernel->height*sizeof(double));
2080 if (kernel->values == (double *) NULL)
2081 return(DestroyKernelInfo(kernel));
2083 for ( i=0, v=-kernel->y; v <= (ssize_t)kernel->y; v++)
2084 for ( u=-kernel->x; u <= (ssize_t)kernel->x; u++, i++)
2087 r1 = MagickMax(fabs((double)u),fabs((double)v)),
2088 r2 = floor((double)(labs((long)u)+labs((long)v)+1)/1.5);
2089 kernel->positive_range += kernel->values[i] =
2090 args->sigma*MagickMax(r1,r2);
2092 kernel->maximum = kernel->values[0];
2095 case EuclideanKernel:
2097 if (args->rho < 1.0)
2098 kernel->width = kernel->height = 3; /* default radius = 1 */
2100 kernel->width = kernel->height = ((size_t)args->rho)*2+1;
2101 kernel->x = kernel->y = (ssize_t) (kernel->width-1)/2;
2103 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
2104 kernel->height*sizeof(double));
2105 if (kernel->values == (double *) NULL)
2106 return(DestroyKernelInfo(kernel));
2108 for ( i=0, v=-kernel->y; v <= (ssize_t)kernel->y; v++)
2109 for ( u=-kernel->x; u <= (ssize_t)kernel->x; u++, i++)
2110 kernel->positive_range += ( kernel->values[i] =
2111 args->sigma*sqrt((double)(u*u+v*v)) );
2112 kernel->maximum = kernel->values[0];
2117 /* No-Op Kernel - Basically just a single pixel on its own */
2118 kernel=ParseKernelArray("1:1");
2119 if (kernel == (KernelInfo *) NULL)
2121 kernel->type = UndefinedKernel;
2130 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2134 % C l o n e K e r n e l I n f o %
2138 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2140 % CloneKernelInfo() creates a new clone of the given Kernel List so that its
2141 % can be modified without effecting the original. The cloned kernel should
2142 % be destroyed using DestoryKernelInfo() when no longer needed.
2144 % The format of the CloneKernelInfo method is:
2146 % KernelInfo *CloneKernelInfo(const KernelInfo *kernel)
2148 % A description of each parameter follows:
2150 % o kernel: the Morphology/Convolution kernel to be cloned
2153 MagickExport KernelInfo *CloneKernelInfo(const KernelInfo *kernel)
2161 assert(kernel != (KernelInfo *) NULL);
2162 new_kernel=(KernelInfo *) AcquireMagickMemory(sizeof(*kernel));
2163 if (new_kernel == (KernelInfo *) NULL)
2165 *new_kernel=(*kernel); /* copy values in structure */
2167 /* replace the values with a copy of the values */
2168 new_kernel->values=(double *) AcquireQuantumMemory(kernel->width,
2169 kernel->height*sizeof(double));
2170 if (new_kernel->values == (double *) NULL)
2171 return(DestroyKernelInfo(new_kernel));
2172 for (i=0; i < (ssize_t) (kernel->width*kernel->height); i++)
2173 new_kernel->values[i]=kernel->values[i];
2175 /* Also clone the next kernel in the kernel list */
2176 if ( kernel->next != (KernelInfo *) NULL ) {
2177 new_kernel->next = CloneKernelInfo(kernel->next);
2178 if ( new_kernel->next == (KernelInfo *) NULL )
2179 return(DestroyKernelInfo(new_kernel));
2186 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2190 % D e s t r o y K e r n e l I n f o %
2194 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2196 % DestroyKernelInfo() frees the memory used by a Convolution/Morphology
2199 % The format of the DestroyKernelInfo method is:
2201 % KernelInfo *DestroyKernelInfo(KernelInfo *kernel)
2203 % A description of each parameter follows:
2205 % o kernel: the Morphology/Convolution kernel to be destroyed
2208 MagickExport KernelInfo *DestroyKernelInfo(KernelInfo *kernel)
2210 assert(kernel != (KernelInfo *) NULL);
2212 if ( kernel->next != (KernelInfo *) NULL )
2213 kernel->next = DestroyKernelInfo(kernel->next);
2215 kernel->values = (double *)RelinquishMagickMemory(kernel->values);
2216 kernel = (KernelInfo *) RelinquishMagickMemory(kernel);
2221 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2225 + E x p a n d M i r r o r K e r n e l I n f o %
2229 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2231 % ExpandMirrorKernelInfo() takes a single kernel, and expands it into a
2232 % sequence of 90-degree rotated kernels but providing a reflected 180
2233 % rotatation, before the -/+ 90-degree rotations.
2235 % This special rotation order produces a better, more symetrical thinning of
2238 % The format of the ExpandMirrorKernelInfo method is:
2240 % void ExpandMirrorKernelInfo(KernelInfo *kernel)
2242 % A description of each parameter follows:
2244 % o kernel: the Morphology/Convolution kernel
2246 % This function is only internel to this module, as it is not finalized,
2247 % especially with regard to non-orthogonal angles, and rotation of larger
2252 static void FlopKernelInfo(KernelInfo *kernel)
2253 { /* Do a Flop by reversing each row. */
2261 for ( y=0, k=kernel->values; y < kernel->height; y++, k+=kernel->width)
2262 for ( x=0, r=kernel->width-1; x<kernel->width/2; x++, r--)
2263 t=k[x], k[x]=k[r], k[r]=t;
2265 kernel->x = kernel->width - kernel->x - 1;
2266 angle = fmod(angle+180.0, 360.0);
2270 static void ExpandMirrorKernelInfo(KernelInfo *kernel)
2278 clone = CloneKernelInfo(last);
2279 RotateKernelInfo(clone, 180); /* flip */
2280 LastKernelInfo(last)->next = clone;
2283 clone = CloneKernelInfo(last);
2284 RotateKernelInfo(clone, 90); /* transpose */
2285 LastKernelInfo(last)->next = clone;
2288 clone = CloneKernelInfo(last);
2289 RotateKernelInfo(clone, 180); /* flop */
2290 LastKernelInfo(last)->next = clone;
2296 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2300 + E x p a n d R o t a t e K e r n e l I n f o %
2304 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2306 % ExpandRotateKernelInfo() takes a kernel list, and expands it by rotating
2307 % incrementally by the angle given, until the kernel repeats.
2309 % WARNING: 45 degree rotations only works for 3x3 kernels.
2310 % While 90 degree roatations only works for linear and square kernels
2312 % The format of the ExpandRotateKernelInfo method is:
2314 % void ExpandRotateKernelInfo(KernelInfo *kernel, double angle)
2316 % A description of each parameter follows:
2318 % o kernel: the Morphology/Convolution kernel
2320 % o angle: angle to rotate in degrees
2322 % This function is only internel to this module, as it is not finalized,
2323 % especially with regard to non-orthogonal angles, and rotation of larger
2327 /* Internal Routine - Return true if two kernels are the same */
2328 static MagickBooleanType SameKernelInfo(const KernelInfo *kernel1,
2329 const KernelInfo *kernel2)
2334 /* check size and origin location */
2335 if ( kernel1->width != kernel2->width
2336 || kernel1->height != kernel2->height
2337 || kernel1->x != kernel2->x
2338 || kernel1->y != kernel2->y )
2341 /* check actual kernel values */
2342 for (i=0; i < (kernel1->width*kernel1->height); i++) {
2343 /* Test for Nan equivelence */
2344 if ( IsNan(kernel1->values[i]) && !IsNan(kernel2->values[i]) )
2346 if ( IsNan(kernel2->values[i]) && !IsNan(kernel1->values[i]) )
2348 /* Test actual values are equivelent */
2349 if ( fabs(kernel1->values[i] - kernel2->values[i]) > MagickEpsilon )
2356 static void ExpandRotateKernelInfo(KernelInfo *kernel, const double angle)
2364 clone = CloneKernelInfo(last);
2365 RotateKernelInfo(clone, angle);
2366 if ( SameKernelInfo(kernel, clone) == MagickTrue )
2368 LastKernelInfo(last)->next = clone;
2371 clone = DestroyKernelInfo(clone); /* kernel has repeated - junk the clone */
2376 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2380 + C a l c M e t a K e r n a l I n f o %
2384 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2386 % CalcKernelMetaData() recalculate the KernelInfo meta-data of this kernel only,
2387 % using the kernel values. This should only ne used if it is not posible to
2388 % calculate that meta-data in some easier way.
2390 % It is important that the meta-data is correct before ScaleKernelInfo() is
2391 % used to perform kernel normalization.
2393 % The format of the CalcKernelMetaData method is:
2395 % void CalcKernelMetaData(KernelInfo *kernel, const double scale )
2397 % A description of each parameter follows:
2399 % o kernel: the Morphology/Convolution kernel to modify
2401 % WARNING: Minimum and Maximum values are assumed to include zero, even if
2402 % zero is not part of the kernel (as in Gaussian Derived kernels). This
2403 % however is not true for flat-shaped morphological kernels.
2405 % WARNING: Only the specific kernel pointed to is modified, not a list of
2408 % This is an internal function and not expected to be useful outside this
2409 % module. This could change however.
2411 static void CalcKernelMetaData(KernelInfo *kernel)
2416 kernel->minimum = kernel->maximum = 0.0;
2417 kernel->negative_range = kernel->positive_range = 0.0;
2418 for (i=0; i < (kernel->width*kernel->height); i++)
2420 if ( fabs(kernel->values[i]) < MagickEpsilon )
2421 kernel->values[i] = 0.0;
2422 ( kernel->values[i] < 0)
2423 ? ( kernel->negative_range += kernel->values[i] )
2424 : ( kernel->positive_range += kernel->values[i] );
2425 Minimize(kernel->minimum, kernel->values[i]);
2426 Maximize(kernel->maximum, kernel->values[i]);
2433 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2437 % M o r p h o l o g y A p p l y %
2441 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2443 % MorphologyApply() applies a morphological method, multiple times using
2444 % a list of multiple kernels.
2446 % It is basically equivelent to as MorphologyImageChannel() (see below) but
2447 % without any user controls. This allows internel programs to use this
2448 % function, to actually perform a specific task without posible interference
2449 % by any API user supplied settings.
2451 % It is MorphologyImageChannel() task to extract any such user controls, and
2452 % pass them to this function for processing.
2454 % More specifically kernels are not normalized/scaled/blended by the
2455 % 'convolve:scale' Image Artifact (setting), nor is the convolve bias
2456 % (-bias setting or image->bias) loooked at, but must be supplied from the
2457 % function arguments.
2459 % The format of the MorphologyApply method is:
2461 % Image *MorphologyApply(const Image *image,MorphologyMethod method,
2462 % const ChannelType channel, const ssize_t iterations,
2463 % const KernelInfo *kernel, const CompositeMethod compose,
2464 % const double bias, ExceptionInfo *exception)
2466 % A description of each parameter follows:
2468 % o image: the source image
2470 % o method: the morphology method to be applied.
2472 % o channel: the channels to which the operations are applied
2473 % The channel 'sync' flag determines if 'alpha weighting' is
2474 % applied for convolution style operations.
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 *result_image,
2502 const MorphologyMethod method, const ChannelType channel,
2503 const KernelInfo *kernel,const double bias,ExceptionInfo *exception)
2505 #define MorphologyTag "Morphology/Image"
2524 assert(image != (Image *) NULL);
2525 assert(image->signature == MagickSignature);
2526 assert(result_image != (Image *) NULL);
2527 assert(result_image->signature == MagickSignature);
2528 assert(kernel != (KernelInfo *) NULL);
2529 assert(kernel->signature == MagickSignature);
2530 assert(exception != (ExceptionInfo *) NULL);
2531 assert(exception->signature == MagickSignature);
2537 p_view=AcquireCacheView(image);
2538 q_view=AcquireCacheView(result_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 PixelPacket
2595 register const IndexPacket
2596 *restrict p_indexes;
2598 register PixelPacket
2601 register IndexPacket
2602 *restrict q_indexes;
2610 if (status == MagickFalse)
2612 p=GetCacheViewVirtualPixels(p_view, x, -offy,1,
2613 image->rows+kernel->height-1, exception);
2614 q=GetCacheViewAuthenticPixels(q_view,x,0,1,result_image->rows,exception);
2615 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
2620 p_indexes=GetCacheViewVirtualIndexQueue(p_view);
2621 q_indexes=GetCacheViewAuthenticIndexQueue(q_view);
2623 /* offset to origin in 'p'. while 'q' points to it directly */
2626 for (y=0; y < (ssize_t) image->rows; y++)
2631 register const double
2634 register const PixelPacket
2637 register const IndexPacket
2638 *restrict k_indexes;
2643 /* Copy input image to the output image for unused channels
2644 * This removes need for 'cloning' a new image every iteration
2647 if (image->colorspace == CMYKColorspace)
2648 q_indexes[y] = p_indexes[r];
2650 /* Set the bias of the weighted average output */
2655 result.index = bias;
2658 /* Weighted Average of pixels using reflected kernel
2660 ** NOTE for correct working of this operation for asymetrical
2661 ** kernels, the kernel needs to be applied in its reflected form.
2662 ** That is its values needs to be reversed.
2664 k = &kernel->values[ kernel->height-1 ];
2666 k_indexes = p_indexes;
2667 if ( ((channel & SyncChannels) == 0 ) ||
2668 (image->matte == MagickFalse) )
2669 { /* No 'Sync' involved.
2670 ** Convolution is simple greyscale channel operation
2672 for (v=0; v < (ssize_t) kernel->height; v++) {
2673 if ( IsNan(*k) ) continue;
2674 result.red += (*k)*k_pixels->red;
2675 result.green += (*k)*k_pixels->green;
2676 result.blue += (*k)*k_pixels->blue;
2677 result.opacity += (*k)*k_pixels->opacity;
2678 if ( image->colorspace == CMYKColorspace)
2679 result.index += (*k)*(*k_indexes);
2684 if ((channel & RedChannel) != 0)
2685 q->red = ClampToQuantum(result.red);
2686 if ((channel & GreenChannel) != 0)
2687 q->green = ClampToQuantum(result.green);
2688 if ((channel & BlueChannel) != 0)
2689 q->blue = ClampToQuantum(result.blue);
2690 if ((channel & OpacityChannel) != 0
2691 && image->matte == MagickTrue )
2692 q->opacity = ClampToQuantum(result.opacity);
2693 if ((channel & IndexChannel) != 0
2694 && image->colorspace == CMYKColorspace)
2695 q_indexes[x] = ClampToQuantum(result.index);
2698 { /* Channel 'Sync' Flag, and Alpha Channel enabled.
2699 ** Weight the color channels with Alpha Channel so that
2700 ** transparent pixels are not part of the results.
2703 alpha, /* alpha weighting of colors : kernel*alpha */
2704 gamma; /* divisor, sum of color weighting values */
2707 for (v=0; v < (ssize_t) kernel->height; v++) {
2708 if ( IsNan(*k) ) continue;
2709 alpha=(*k)*(QuantumScale*(QuantumRange-k_pixels->opacity));
2711 result.red += alpha*k_pixels->red;
2712 result.green += alpha*k_pixels->green;
2713 result.blue += alpha*k_pixels->blue;
2714 result.opacity += (*k)*k_pixels->opacity;
2715 if ( image->colorspace == CMYKColorspace)
2716 result.index += alpha*(*k_indexes);
2721 /* Sync'ed channels, all channels are modified */
2722 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
2723 q->red = ClampToQuantum(gamma*result.red);
2724 q->green = ClampToQuantum(gamma*result.green);
2725 q->blue = ClampToQuantum(gamma*result.blue);
2726 q->opacity = ClampToQuantum(result.opacity);
2727 if (image->colorspace == CMYKColorspace)
2728 q_indexes[x] = ClampToQuantum(gamma*result.index);
2731 /* Count up changed pixels */
2732 if ( ( p[r].red != q->red )
2733 || ( p[r].green != q->green )
2734 || ( p[r].blue != q->blue )
2735 || ( p[r].opacity != q->opacity )
2736 || ( image->colorspace == CMYKColorspace &&
2737 p_indexes[r] != q_indexes[x] ) )
2738 changed++; /* The pixel was changed in some way! */
2742 if ( SyncCacheViewAuthenticPixels(q_view,exception) == MagickFalse)
2744 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2749 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2750 #pragma omp critical (MagickCore_MorphologyImage)
2752 proceed=SetImageProgress(image,MorphologyTag,progress++,image->rows);
2753 if (proceed == MagickFalse)
2757 result_image->type=image->type;
2758 q_view=DestroyCacheView(q_view);
2759 p_view=DestroyCacheView(p_view);
2760 return(status ? (ssize_t) changed : 0);
2764 ** Normal handling of horizontal or rectangular kernels (row by row)
2766 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2767 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
2769 for (y=0; y < (ssize_t) image->rows; y++)
2771 register const PixelPacket
2774 register const IndexPacket
2775 *restrict p_indexes;
2777 register PixelPacket
2780 register IndexPacket
2781 *restrict q_indexes;
2789 if (status == MagickFalse)
2791 p=GetCacheViewVirtualPixels(p_view, -offx, y-offy, virt_width,
2792 kernel->height, exception);
2793 q=GetCacheViewAuthenticPixels(q_view,0,y,result_image->columns,1,
2795 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
2800 p_indexes=GetCacheViewVirtualIndexQueue(p_view);
2801 q_indexes=GetCacheViewAuthenticIndexQueue(q_view);
2803 /* offset to origin in 'p'. while 'q' points to it directly */
2804 r = virt_width*offy + offx;
2806 for (x=0; x < (ssize_t) image->columns; x++)
2814 register const double
2817 register const PixelPacket
2820 register const IndexPacket
2821 *restrict k_indexes;
2828 /* Copy input image to the output image for unused channels
2829 * This removes need for 'cloning' a new image every iteration
2832 if (image->colorspace == CMYKColorspace)
2833 q_indexes[x] = p_indexes[r];
2840 min.index = (MagickRealType) QuantumRange;
2845 max.index = (MagickRealType) 0;
2846 /* default result is the original pixel value */
2847 result.red = (MagickRealType) p[r].red;
2848 result.green = (MagickRealType) p[r].green;
2849 result.blue = (MagickRealType) p[r].blue;
2850 result.opacity = QuantumRange - (MagickRealType) p[r].opacity;
2852 if ( image->colorspace == CMYKColorspace)
2853 result.index = (MagickRealType) p_indexes[r];
2856 case ConvolveMorphology:
2857 /* Set the bias of the weighted average output */
2862 result.index = bias;
2864 case DilateIntensityMorphology:
2865 case ErodeIntensityMorphology:
2866 /* use a boolean flag indicating when first match found */
2867 result.red = 0.0; /* result is not used otherwise */
2874 case ConvolveMorphology:
2875 /* Weighted Average of pixels using reflected kernel
2877 ** NOTE for correct working of this operation for asymetrical
2878 ** kernels, the kernel needs to be applied in its reflected form.
2879 ** That is its values needs to be reversed.
2881 ** Correlation is actually the same as this but without reflecting
2882 ** the kernel, and thus 'lower-level' that Convolution. However
2883 ** as Convolution is the more common method used, and it does not
2884 ** really cost us much in terms of processing to use a reflected
2885 ** kernel, so it is Convolution that is implemented.
2887 ** Correlation will have its kernel reflected before calling
2888 ** this function to do a Convolve.
2890 ** For more details of Correlation vs Convolution see
2891 ** http://www.cs.umd.edu/~djacobs/CMSC426/Convolution.pdf
2893 k = &kernel->values[ kernel->width*kernel->height-1 ];
2895 k_indexes = p_indexes;
2896 if ( ((channel & SyncChannels) == 0 ) ||
2897 (image->matte == MagickFalse) )
2898 { /* No 'Sync' involved.
2899 ** Convolution is simple greyscale channel operation
2901 for (v=0; v < (ssize_t) kernel->height; v++) {
2902 for (u=0; u < (ssize_t) kernel->width; u++, k--) {
2903 if ( IsNan(*k) ) continue;
2904 result.red += (*k)*k_pixels[u].red;
2905 result.green += (*k)*k_pixels[u].green;
2906 result.blue += (*k)*k_pixels[u].blue;
2907 result.opacity += (*k)*k_pixels[u].opacity;
2908 if ( image->colorspace == CMYKColorspace)
2909 result.index += (*k)*k_indexes[u];
2911 k_pixels += virt_width;
2912 k_indexes += virt_width;
2914 if ((channel & RedChannel) != 0)
2915 q->red = ClampToQuantum(result.red);
2916 if ((channel & GreenChannel) != 0)
2917 q->green = ClampToQuantum(result.green);
2918 if ((channel & BlueChannel) != 0)
2919 q->blue = ClampToQuantum(result.blue);
2920 if ((channel & OpacityChannel) != 0
2921 && image->matte == MagickTrue )
2922 q->opacity = ClampToQuantum(result.opacity);
2923 if ((channel & IndexChannel) != 0
2924 && image->colorspace == CMYKColorspace)
2925 q_indexes[x] = ClampToQuantum(result.index);
2928 { /* Channel 'Sync' Flag, and Alpha Channel enabled.
2929 ** Weight the color channels with Alpha Channel so that
2930 ** transparent pixels are not part of the results.
2933 alpha, /* alpha weighting of colors : kernel*alpha */
2934 gamma; /* divisor, sum of color weighting values */
2937 for (v=0; v < (ssize_t) kernel->height; v++) {
2938 for (u=0; u < (ssize_t) kernel->width; u++, k--) {
2939 if ( IsNan(*k) ) continue;
2940 alpha=(*k)*(QuantumScale*(QuantumRange-
2941 k_pixels[u].opacity));
2943 result.red += alpha*k_pixels[u].red;
2944 result.green += alpha*k_pixels[u].green;
2945 result.blue += alpha*k_pixels[u].blue;
2946 result.opacity += (*k)*k_pixels[u].opacity;
2947 if ( image->colorspace == CMYKColorspace)
2948 result.index += alpha*k_indexes[u];
2950 k_pixels += virt_width;
2951 k_indexes += virt_width;
2953 /* Sync'ed channels, all channels are modified */
2954 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
2955 q->red = ClampToQuantum(gamma*result.red);
2956 q->green = ClampToQuantum(gamma*result.green);
2957 q->blue = ClampToQuantum(gamma*result.blue);
2958 q->opacity = ClampToQuantum(result.opacity);
2959 if (image->colorspace == CMYKColorspace)
2960 q_indexes[x] = ClampToQuantum(gamma*result.index);
2964 case ErodeMorphology:
2965 /* Minimum Value within kernel neighbourhood
2967 ** NOTE that the kernel is not reflected for this operation!
2969 ** NOTE: in normal Greyscale Morphology, the kernel value should
2970 ** be added to the real value, this is currently not done, due to
2971 ** the nature of the boolean kernels being used.
2975 k_indexes = p_indexes;
2976 for (v=0; v < (ssize_t) kernel->height; v++) {
2977 for (u=0; u < (ssize_t) kernel->width; u++, k++) {
2978 if ( IsNan(*k) || (*k) < 0.5 ) continue;
2979 Minimize(min.red, (double) k_pixels[u].red);
2980 Minimize(min.green, (double) k_pixels[u].green);
2981 Minimize(min.blue, (double) k_pixels[u].blue);
2982 Minimize(min.opacity,
2983 QuantumRange-(double) k_pixels[u].opacity);
2984 if ( image->colorspace == CMYKColorspace)
2985 Minimize(min.index, (double) k_indexes[u]);
2987 k_pixels += virt_width;
2988 k_indexes += virt_width;
2992 case DilateMorphology:
2993 /* Maximum Value within kernel neighbourhood
2995 ** NOTE for correct working of this operation for asymetrical
2996 ** kernels, the kernel needs to be applied in its reflected form.
2997 ** That is its values needs to be reversed.
2999 ** NOTE: in normal Greyscale Morphology, the kernel value should
3000 ** be added to the real value, this is currently not done, due to
3001 ** the nature of the boolean kernels being used.
3004 k = &kernel->values[ kernel->width*kernel->height-1 ];
3006 k_indexes = p_indexes;
3007 for (v=0; v < (ssize_t) kernel->height; v++) {
3008 for (u=0; u < (ssize_t) kernel->width; u++, k--) {
3009 if ( IsNan(*k) || (*k) < 0.5 ) continue;
3010 Maximize(max.red, (double) k_pixels[u].red);
3011 Maximize(max.green, (double) k_pixels[u].green);
3012 Maximize(max.blue, (double) k_pixels[u].blue);
3013 Maximize(max.opacity,
3014 QuantumRange-(double) k_pixels[u].opacity);
3015 if ( image->colorspace == CMYKColorspace)
3016 Maximize(max.index, (double) k_indexes[u]);
3018 k_pixels += virt_width;
3019 k_indexes += virt_width;
3023 case HitAndMissMorphology:
3024 case ThinningMorphology:
3025 case ThickenMorphology:
3026 /* Minimum of Foreground Pixel minus Maxumum of Background Pixels
3028 ** NOTE that the kernel is not reflected for this operation,
3029 ** and consists of both foreground and background pixel
3030 ** neighbourhoods, 0.0 for background, and 1.0 for foreground
3031 ** with either Nan or 0.5 values for don't care.
3033 ** Note that this will never produce a meaningless negative
3034 ** result. Such results can cause Thinning/Thicken to not work
3035 ** correctly when used against a greyscale image.
3039 k_indexes = p_indexes;
3040 for (v=0; v < (ssize_t) kernel->height; v++) {
3041 for (u=0; u < (ssize_t) kernel->width; u++, k++) {
3042 if ( IsNan(*k) ) continue;
3044 { /* minimim of foreground pixels */
3045 Minimize(min.red, (double) k_pixels[u].red);
3046 Minimize(min.green, (double) k_pixels[u].green);
3047 Minimize(min.blue, (double) k_pixels[u].blue);
3048 Minimize(min.opacity,
3049 QuantumRange-(double) k_pixels[u].opacity);
3050 if ( image->colorspace == CMYKColorspace)
3051 Minimize(min.index, (double) k_indexes[u]);
3053 else if ( (*k) < 0.3 )
3054 { /* maximum of background pixels */
3055 Maximize(max.red, (double) k_pixels[u].red);
3056 Maximize(max.green, (double) k_pixels[u].green);
3057 Maximize(max.blue, (double) k_pixels[u].blue);
3058 Maximize(max.opacity,
3059 QuantumRange-(double) k_pixels[u].opacity);
3060 if ( image->colorspace == CMYKColorspace)
3061 Maximize(max.index, (double) k_indexes[u]);
3064 k_pixels += virt_width;
3065 k_indexes += virt_width;
3067 /* Pattern Match if difference is positive */
3068 min.red -= max.red; Maximize( min.red, 0.0 );
3069 min.green -= max.green; Maximize( min.green, 0.0 );
3070 min.blue -= max.blue; Maximize( min.blue, 0.0 );
3071 min.opacity -= max.opacity; Maximize( min.opacity, 0.0 );
3072 min.index -= max.index; Maximize( min.index, 0.0 );
3075 case ErodeIntensityMorphology:
3076 /* Select Pixel with Minimum Intensity within kernel neighbourhood
3078 ** WARNING: the intensity test fails for CMYK and does not
3079 ** take into account the moderating effect of the alpha channel
3080 ** on the intensity.
3082 ** NOTE that the kernel is not reflected for this operation!
3086 k_indexes = p_indexes;
3087 for (v=0; v < (ssize_t) kernel->height; v++) {
3088 for (u=0; u < (ssize_t) kernel->width; u++, k++) {
3089 if ( IsNan(*k) || (*k) < 0.5 ) continue;
3090 if ( result.red == 0.0 ||
3091 PixelIntensity(&(k_pixels[u])) < PixelIntensity(q) ) {
3092 /* copy the whole pixel - no channel selection */
3094 if ( result.red > 0.0 ) changed++;
3098 k_pixels += virt_width;
3099 k_indexes += virt_width;
3103 case DilateIntensityMorphology:
3104 /* Select Pixel with Maximum Intensity within kernel neighbourhood
3106 ** WARNING: the intensity test fails for CMYK and does not
3107 ** take into account the moderating effect of the alpha channel
3108 ** on the intensity (yet).
3110 ** NOTE for correct working of this operation for asymetrical
3111 ** kernels, the kernel needs to be applied in its reflected form.
3112 ** That is its values needs to be reversed.
3114 k = &kernel->values[ kernel->width*kernel->height-1 ];
3116 k_indexes = p_indexes;
3117 for (v=0; v < (ssize_t) kernel->height; v++) {
3118 for (u=0; u < (ssize_t) kernel->width; u++, k--) {
3119 if ( IsNan(*k) || (*k) < 0.5 ) continue; /* boolean kernel */
3120 if ( result.red == 0.0 ||
3121 PixelIntensity(&(k_pixels[u])) > PixelIntensity(q) ) {
3122 /* copy the whole pixel - no channel selection */
3124 if ( result.red > 0.0 ) changed++;
3128 k_pixels += virt_width;
3129 k_indexes += virt_width;
3133 This code has been obsoleted by the MorphologyPrimitiveDirect() function.
3134 However it is still (almost) correct coding for Grayscale Morphology.
3137 GrayErode is equivelent but with kernel values subtracted from pixels
3138 without the kernel rotation
3139 GreyDilate is equivelent but using Maximum() instead of Minimum()
3140 useing kernel rotation
3142 case DistanceMorphology:
3143 /* Add kernel Value and select the minimum value found.
3144 ** The result is a iterative distance from edge of image shape.
3146 ** All Distance Kernels are symetrical, but that may not always
3147 ** be the case. For example how about a distance from left edges?
3148 ** To work correctly with asymetrical kernels the reflected kernel
3149 ** needs to be applied.
3151 k = &kernel->values[ kernel->width*kernel->height-1 ];
3153 k_indexes = p_indexes;
3154 for (v=0; v < (ssize_t) kernel->height; v++) {
3155 for (u=0; u < (ssize_t) kernel->width; u++, k--) {
3156 if ( IsNan(*k) ) continue;
3157 Minimize(result.red, (*k)+k_pixels[u].red);
3158 Minimize(result.green, (*k)+k_pixels[u].green);
3159 Minimize(result.blue, (*k)+k_pixels[u].blue);
3160 Minimize(result.opacity, (*k)+QuantumRange-k_pixels[u].opacity);
3161 if ( image->colorspace == CMYKColorspace)
3162 Minimize(result.index, (*k)+k_indexes[u]);
3164 k_pixels += virt_width;
3165 k_indexes += virt_width;
3169 case UndefinedMorphology:
3171 break; /* Do nothing */
3173 /* Final mathematics of results (combine with original image?)
3175 ** NOTE: Difference Morphology operators Edge* and *Hat could also
3176 ** be done here but works better with iteration as a image difference
3177 ** in the controling function (below). Thicken and Thinning however
3178 ** should be done here so thay can be iterated correctly.
3181 case HitAndMissMorphology:
3182 case ErodeMorphology:
3183 result = min; /* minimum of neighbourhood */
3185 case DilateMorphology:
3186 result = max; /* maximum of neighbourhood */
3188 case ThinningMorphology:
3189 /* subtract pattern match from original */
3190 result.red -= min.red;
3191 result.green -= min.green;
3192 result.blue -= min.blue;
3193 result.opacity -= min.opacity;
3194 result.index -= min.index;
3196 case ThickenMorphology:
3197 /* Add the pattern matchs to the original */
3198 result.red += min.red;
3199 result.green += min.green;
3200 result.blue += min.blue;
3201 result.opacity += min.opacity;
3202 result.index += min.index;
3205 /* result directly calculated or assigned */
3208 /* Assign the resulting pixel values - Clamping Result */
3210 case UndefinedMorphology:
3211 case ConvolveMorphology:
3212 case DilateIntensityMorphology:
3213 case ErodeIntensityMorphology:
3214 break; /* full pixel was directly assigned - not a channel method */
3216 if ((channel & RedChannel) != 0)
3217 q->red = ClampToQuantum(result.red);
3218 if ((channel & GreenChannel) != 0)
3219 q->green = ClampToQuantum(result.green);
3220 if ((channel & BlueChannel) != 0)
3221 q->blue = ClampToQuantum(result.blue);
3222 if ((channel & OpacityChannel) != 0
3223 && image->matte == MagickTrue )
3224 q->opacity = ClampToQuantum(QuantumRange-result.opacity);
3225 if ((channel & IndexChannel) != 0
3226 && image->colorspace == CMYKColorspace)
3227 q_indexes[x] = ClampToQuantum(result.index);
3230 /* Count up changed pixels */
3231 if ( ( p[r].red != q->red )
3232 || ( p[r].green != q->green )
3233 || ( p[r].blue != q->blue )
3234 || ( p[r].opacity != q->opacity )
3235 || ( image->colorspace == CMYKColorspace &&
3236 p_indexes[r] != q_indexes[x] ) )
3237 changed++; /* The pixel was changed in some way! */
3241 if ( SyncCacheViewAuthenticPixels(q_view,exception) == MagickFalse)
3243 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3248 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3249 #pragma omp critical (MagickCore_MorphologyImage)
3251 proceed=SetImageProgress(image,MorphologyTag,progress++,image->rows);
3252 if (proceed == MagickFalse)
3256 q_view=DestroyCacheView(q_view);
3257 p_view=DestroyCacheView(p_view);
3258 return(status ? (ssize_t)changed : -1);
3261 /* This is almost identical to the MorphologyPrimative() function above,
3262 ** but will apply the primitive directly to the image in two passes.
3264 ** That is after each row is 'Sync'ed' into the image, the next row will
3265 ** make use of those values as part of the calculation of the next row.
3266 ** It then repeats, but going in the oppisite (bottom-up) direction.
3268 ** Because of this 'iterative' handling this function can not make use
3269 ** of multi-threaded, parellel processing.
3271 static ssize_t MorphologyPrimitiveDirect(Image *image,
3272 const MorphologyMethod method, const ChannelType channel,
3273 const KernelInfo *kernel,ExceptionInfo *exception)
3296 assert(image != (Image *) NULL);
3297 assert(image->signature == MagickSignature);
3298 assert(kernel != (KernelInfo *) NULL);
3299 assert(kernel->signature == MagickSignature);
3300 assert(exception != (ExceptionInfo *) NULL);
3301 assert(exception->signature == MagickSignature);
3303 /* Some methods (including convolve) needs use a reflected kernel.
3304 * Adjust 'origin' offsets to loop though kernel as a reflection.
3309 case DistanceMorphology:
3310 case VoronoiMorphology:
3311 /* kernel needs to used with reflection about origin */
3312 offx = (ssize_t) kernel->width-offx-1;
3313 offy = (ssize_t) kernel->height-offy-1;
3316 case ?????Morphology:
3317 /* kernel is used as is, without reflection */
3321 assert("Not a PrimativeDirect Morphology Method" != (char *) NULL);
3325 /* DO NOT THREAD THIS CODE! */
3326 /* two views into same image (virtual, and actual) */
3327 virt_view=AcquireCacheView(image);
3328 auth_view=AcquireCacheView(image);
3329 virt_width=image->columns+kernel->width-1;
3331 for (y=0; y < (ssize_t) image->rows; y++)
3333 register const PixelPacket
3336 register const IndexPacket
3337 *restrict p_indexes;
3339 register PixelPacket
3342 register IndexPacket
3343 *restrict q_indexes;
3351 /* NOTE read virtual pixels, and authentic pixels, from the same image!
3352 ** we read using virtual to get virtual pixel handling, but write back
3353 ** into the same image.
3355 ** Only top half of kernel is processed as we do a single pass downward
3356 ** through the image iterating the distance function as we go.
3358 if (status == MagickFalse)
3360 p=GetCacheViewVirtualPixels(virt_view, -offx, y-offy, virt_width, (size_t) offy+1,
3362 q=GetCacheViewAuthenticPixels(auth_view, 0, y, image->columns, 1,
3364 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
3366 if (status == MagickFalse)
3368 p_indexes=GetCacheViewVirtualIndexQueue(virt_view);
3369 q_indexes=GetCacheViewAuthenticIndexQueue(auth_view);
3371 /* offset to origin in 'p'. while 'q' points to it directly */
3372 r = (ssize_t) virt_width*offy + offx;
3374 for (x=0; x < (ssize_t) image->columns; x++)
3382 register const double
3385 register const PixelPacket
3388 register const IndexPacket
3389 *restrict k_indexes;
3394 /* Starting Defaults */
3395 SetMagickPixelPacket(image,q,q_indexes,&result);
3396 if ( method != VoronoiMorphology )
3397 result.opacity = QuantumRange - result.opacity;
3400 case DistanceMorphology:
3401 /* Add kernel Value and select the minimum value found. */
3402 k = &kernel->values[ kernel->width*kernel->height-1 ];
3404 k_indexes = p_indexes;
3405 for (v=0; v <= (ssize_t) offy; v++) {
3406 for (u=0; u < (ssize_t) kernel->width; u++, k--) {
3407 if ( IsNan(*k) ) continue;
3408 Minimize(result.red, (*k)+k_pixels[u].red);
3409 Minimize(result.green, (*k)+k_pixels[u].green);
3410 Minimize(result.blue, (*k)+k_pixels[u].blue);
3411 Minimize(result.opacity, (*k)+QuantumRange-k_pixels[u].opacity);
3412 if ( image->colorspace == CMYKColorspace)
3413 Minimize(result.index, (*k)+k_indexes[u]);
3415 k_pixels += virt_width;
3416 k_indexes += virt_width;
3418 /* repeat with the just processed pixels of this row */
3419 k = &kernel->values[ kernel->width*(kernel->y+1)-1 ];
3421 k_indexes = q_indexes-offx;
3422 for (u=0; u < (ssize_t) offx; u++, k--) {
3423 if ( x+u-offx < 0 ) continue; /* off the edge! */
3424 if ( IsNan(*k) ) continue;
3425 Minimize(result.red, (*k)+k_pixels[u].red);
3426 Minimize(result.green, (*k)+k_pixels[u].green);
3427 Minimize(result.blue, (*k)+k_pixels[u].blue);
3428 Minimize(result.opacity, (*k)+QuantumRange-k_pixels[u].opacity);
3429 if ( image->colorspace == CMYKColorspace)
3430 Minimize(result.index, (*k)+k_indexes[u]);
3433 case VoronoiMorphology:
3434 /* Apply Distance to 'Matte' channel, coping the closest color.
3436 ** This is experimental, and realy the 'alpha' component should
3437 ** be completely separate 'masking' channel.
3439 k = &kernel->values[ kernel->width*kernel->height-1 ];
3441 k_indexes = p_indexes;
3442 for (v=0; v <= (ssize_t) offy; v++) {
3443 for (u=0; u < (ssize_t) kernel->width; u++, k--) {
3444 if ( IsNan(*k) ) continue;
3445 if( result.opacity > (*k)+k_pixels[u].opacity )
3447 SetMagickPixelPacket(image,&k_pixels[u],&k_indexes[u],
3449 result.opacity += *k;
3452 k_pixels += virt_width;
3453 k_indexes += virt_width;
3455 /* repeat with the just processed pixels of this row */
3456 k = &kernel->values[ kernel->width*(kernel->y+1)-1 ];
3458 k_indexes = q_indexes-offx;
3459 for (u=0; u < (ssize_t) offx; u++, k--) {
3460 if ( x+u-offx < 0 ) continue; /* off the edge! */
3461 if ( IsNan(*k) ) continue;
3462 if( result.opacity > (*k)+k_pixels[u].opacity )
3464 SetMagickPixelPacket(image,&k_pixels[u],&k_indexes[u],
3466 result.opacity += *k;
3471 /* result directly calculated or assigned */
3474 /* Assign the resulting pixel values - Clamping Result */
3476 case VoronoiMorphology:
3477 SetPixelPacket(image,&result,q,q_indexes);
3480 if ((channel & RedChannel) != 0)
3481 q->red = ClampToQuantum(result.red);
3482 if ((channel & GreenChannel) != 0)
3483 q->green = ClampToQuantum(result.green);
3484 if ((channel & BlueChannel) != 0)
3485 q->blue = ClampToQuantum(result.blue);
3486 if ((channel & OpacityChannel) != 0 && image->matte == MagickTrue )
3487 q->opacity = ClampToQuantum(QuantumRange-result.opacity);
3488 if ((channel & IndexChannel) != 0
3489 && image->colorspace == CMYKColorspace)
3490 q_indexes[x] = ClampToQuantum(result.index);
3493 /* Count up changed pixels */
3494 if ( ( p[r].red != q->red )
3495 || ( p[r].green != q->green )
3496 || ( p[r].blue != q->blue )
3497 || ( p[r].opacity != q->opacity )
3498 || ( image->colorspace == CMYKColorspace &&
3499 p_indexes[r] != q_indexes[x] ) )
3500 changed++; /* The pixel was changed in some way! */
3502 p++; /* increment pixel buffers */
3506 if ( SyncCacheViewAuthenticPixels(auth_view,exception) == MagickFalse)
3508 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3509 if ( SetImageProgress(image,MorphologyTag,progress++,image->rows)
3515 /* Do the reversed pass through the image */
3516 for (y=(ssize_t)image->rows-1; y >= 0; y--)
3518 register const PixelPacket
3521 register const IndexPacket
3522 *restrict p_indexes;
3524 register PixelPacket
3527 register IndexPacket
3528 *restrict q_indexes;
3536 if (status == MagickFalse)
3538 /* NOTE read virtual pixels, and authentic pixels, from the same image!
3539 ** we read using virtual to get virtual pixel handling, but write back
3540 ** into the same image.
3542 ** Only the bottom half of the kernel will be processes as we
3545 p=GetCacheViewVirtualPixels(virt_view, -offx, y, virt_width, (size_t) kernel->y+1,
3547 q=GetCacheViewAuthenticPixels(auth_view, 0, y, image->columns, 1,
3549 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
3551 if (status == MagickFalse)
3553 p_indexes=GetCacheViewVirtualIndexQueue(virt_view);
3554 q_indexes=GetCacheViewAuthenticIndexQueue(auth_view);
3556 /* adjust positions to end of row */
3557 p += image->columns-1;
3558 q += image->columns-1;
3560 /* offset to origin in 'p'. while 'q' points to it directly */
3563 for (x=(ssize_t)image->columns-1; x >= 0; x--)
3571 register const double
3574 register const PixelPacket
3577 register const IndexPacket
3578 *restrict k_indexes;
3583 /* Default - previously modified pixel */
3584 SetMagickPixelPacket(image,q,q_indexes,&result);
3585 if ( method != VoronoiMorphology )
3586 result.opacity = QuantumRange - result.opacity;
3589 case DistanceMorphology:
3590 /* Add kernel Value and select the minimum value found. */
3591 k = &kernel->values[ kernel->width*(kernel->y+1)-1 ];
3593 k_indexes = p_indexes;
3594 for (v=offy; v < (ssize_t) kernel->height; v++) {
3595 for (u=0; u < (ssize_t) kernel->width; u++, k--) {
3596 if ( IsNan(*k) ) continue;
3597 Minimize(result.red, (*k)+k_pixels[u].red);
3598 Minimize(result.green, (*k)+k_pixels[u].green);
3599 Minimize(result.blue, (*k)+k_pixels[u].blue);
3600 Minimize(result.opacity, (*k)+QuantumRange-k_pixels[u].opacity);
3601 if ( image->colorspace == CMYKColorspace)
3602 Minimize(result.index, (*k)+k_indexes[u]);
3604 k_pixels += virt_width;
3605 k_indexes += virt_width;
3607 /* repeat with the just processed pixels of this row */
3608 k = &kernel->values[ kernel->width*(kernel->y)+kernel->x-1 ];
3610 k_indexes = q_indexes-offx;
3611 for (u=offx+1; u < (ssize_t) kernel->width; u++, k--) {
3612 if ( (x+u-offx) >= (ssize_t)image->columns ) continue;
3613 if ( IsNan(*k) ) continue;
3614 Minimize(result.red, (*k)+k_pixels[u].red);
3615 Minimize(result.green, (*k)+k_pixels[u].green);
3616 Minimize(result.blue, (*k)+k_pixels[u].blue);
3617 Minimize(result.opacity, (*k)+QuantumRange-k_pixels[u].opacity);
3618 if ( image->colorspace == CMYKColorspace)
3619 Minimize(result.index, (*k)+k_indexes[u]);
3622 case VoronoiMorphology:
3623 /* Apply Distance to 'Matte' channel, coping the closest color.
3625 ** This is experimental, and realy the 'alpha' component should
3626 ** be completely separate 'masking' channel.
3628 k = &kernel->values[ kernel->width*(kernel->y+1)-1 ];
3630 k_indexes = p_indexes;
3631 for (v=offy; v < (ssize_t) kernel->height; v++) {
3632 for (u=0; u < (ssize_t) kernel->width; u++, k--) {
3633 if ( IsNan(*k) ) continue;
3634 if( result.opacity > (*k)+k_pixels[u].opacity )
3636 SetMagickPixelPacket(image,&k_pixels[u],&k_indexes[u],
3638 result.opacity += *k;
3641 k_pixels += virt_width;
3642 k_indexes += virt_width;
3644 /* repeat with the just processed pixels of this row */
3645 k = &kernel->values[ kernel->width*(kernel->y)+kernel->x-1 ];
3647 k_indexes = q_indexes-offx;
3648 for (u=offx+1; u < (ssize_t) kernel->width; u++, k--) {
3649 if ( (x+u-offx) >= (ssize_t)image->columns ) continue;
3650 if ( IsNan(*k) ) continue;
3651 if( result.opacity > (*k)+k_pixels[u].opacity )
3653 SetMagickPixelPacket(image,&k_pixels[u],&k_indexes[u],
3655 result.opacity += *k;
3660 /* result directly calculated or assigned */
3663 /* Assign the resulting pixel values - Clamping Result */
3665 case VoronoiMorphology:
3666 SetPixelPacket(image,&result,q,q_indexes);
3669 if ((channel & RedChannel) != 0)
3670 q->red = ClampToQuantum(result.red);
3671 if ((channel & GreenChannel) != 0)
3672 q->green = ClampToQuantum(result.green);
3673 if ((channel & BlueChannel) != 0)
3674 q->blue = ClampToQuantum(result.blue);
3675 if ((channel & OpacityChannel) != 0 && image->matte == MagickTrue )
3676 q->opacity = ClampToQuantum(QuantumRange-result.opacity);
3677 if ((channel & IndexChannel) != 0
3678 && image->colorspace == CMYKColorspace)
3679 q_indexes[x] = ClampToQuantum(result.index);
3682 /* Count up changed pixels */
3683 if ( ( p[r].red != q->red )
3684 || ( p[r].green != q->green )
3685 || ( p[r].blue != q->blue )
3686 || ( p[r].opacity != q->opacity )
3687 || ( image->colorspace == CMYKColorspace &&
3688 p_indexes[r] != q_indexes[x] ) )
3689 changed++; /* The pixel was changed in some way! */
3691 p--; /* go backward through pixel buffers */
3694 if ( SyncCacheViewAuthenticPixels(auth_view,exception) == MagickFalse)
3696 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3697 if ( SetImageProgress(image,MorphologyTag,progress++,image->rows)
3703 auth_view=DestroyCacheView(auth_view);
3704 virt_view=DestroyCacheView(virt_view);
3705 return(status ? (ssize_t) changed : -1);
3708 /* Apply a Morphology by calling theabove low level primitive application
3709 ** functions. This function handles any iteration loops, composition or
3710 ** re-iteration of results, and compound morphology methods that is based
3711 ** on multiple low-level (staged) morphology methods.
3713 ** Basically this provides the complex grue between the requested morphology
3714 ** method and raw low-level implementation (above).
3716 MagickExport Image *MorphologyApply(const Image *image, const ChannelType
3717 channel,const MorphologyMethod method, const ssize_t iterations,
3718 const KernelInfo *kernel, const CompositeOperator compose,
3719 const double bias, ExceptionInfo *exception)
3725 *curr_image, /* Image we are working with or iterating */
3726 *work_image, /* secondary image for primitive iteration */
3727 *save_image, /* saved image - for 'edge' method only */
3728 *rslt_image; /* resultant image - after multi-kernel handling */
3731 *reflected_kernel, /* A reflected copy of the kernel (if needed) */
3732 *norm_kernel, /* the current normal un-reflected kernel */
3733 *rflt_kernel, /* the current reflected kernel (if needed) */
3734 *this_kernel; /* the kernel being applied */
3737 primitive; /* the current morphology primitive being applied */
3740 rslt_compose; /* multi-kernel compose method for results to use */
3743 special, /* do we use a direct modify function? */
3744 verbose; /* verbose output of results */
3747 method_loop, /* Loop 1: number of compound method iterations (norm 1) */
3748 method_limit, /* maximum number of compound method iterations */
3749 kernel_number, /* Loop 2: the kernel number being applied */
3750 stage_loop, /* Loop 3: primitive loop for compound morphology */
3751 stage_limit, /* how many primitives are in this compound */
3752 kernel_loop, /* Loop 4: iterate the kernel over image */
3753 kernel_limit, /* number of times to iterate kernel */
3754 count, /* total count of primitive steps applied */
3755 kernel_changed, /* total count of changed using iterated kernel */
3756 method_changed; /* total count of changed over method iteration */
3759 changed; /* number pixels changed by last primitive operation */
3764 assert(image != (Image *) NULL);
3765 assert(image->signature == MagickSignature);
3766 assert(kernel != (KernelInfo *) NULL);
3767 assert(kernel->signature == MagickSignature);
3768 assert(exception != (ExceptionInfo *) NULL);
3769 assert(exception->signature == MagickSignature);
3771 count = 0; /* number of low-level morphology primitives performed */
3772 if ( iterations == 0 )
3773 return((Image *)NULL); /* null operation - nothing to do! */
3775 kernel_limit = (size_t) iterations;
3776 if ( iterations < 0 ) /* negative interations = infinite (well alomst) */
3777 kernel_limit = image->columns>image->rows ? image->columns : image->rows;
3779 verbose = IsMagickTrue(GetImageArtifact(image,"verbose"));
3781 /* initialise for cleanup */
3782 curr_image = (Image *) image;
3783 curr_compose = image->compose;
3784 (void) curr_compose;
3785 work_image = save_image = rslt_image = (Image *) NULL;
3786 reflected_kernel = (KernelInfo *) NULL;
3788 /* Initialize specific methods
3789 * + which loop should use the given iteratations
3790 * + how many primitives make up the compound morphology
3791 * + multi-kernel compose method to use (by default)
3793 method_limit = 1; /* just do method once, unless otherwise set */
3794 stage_limit = 1; /* assume method is not a compound */
3795 special = MagickFalse; /* assume it is NOT a direct modify primative */
3796 rslt_compose = compose; /* and we are composing multi-kernels as given */
3798 case SmoothMorphology: /* 4 primitive compound morphology */
3801 case OpenMorphology: /* 2 primitive compound morphology */
3802 case OpenIntensityMorphology:
3803 case TopHatMorphology:
3804 case CloseMorphology:
3805 case CloseIntensityMorphology:
3806 case BottomHatMorphology:
3807 case EdgeMorphology:
3810 case HitAndMissMorphology:
3811 rslt_compose = LightenCompositeOp; /* Union of multi-kernel results */
3813 case ThinningMorphology:
3814 case ThickenMorphology:
3815 method_limit = kernel_limit; /* iterate the whole method */
3816 kernel_limit = 1; /* do not do kernel iteration */
3818 case DistanceMorphology:
3819 case VoronoiMorphology:
3820 special = MagickTrue;
3826 /* Apply special methods with special requirments
3827 ** For example, single run only, or post-processing requirements
3829 if ( special == MagickTrue )
3831 rslt_image=CloneImage(image,0,0,MagickTrue,exception);
3832 if (rslt_image == (Image *) NULL)
3834 if (SetImageStorageClass(rslt_image,DirectClass) == MagickFalse)
3836 InheritException(exception,&rslt_image->exception);
3840 changed = MorphologyPrimitiveDirect(rslt_image, method,
3841 channel, kernel, exception);
3843 if ( verbose == MagickTrue )
3844 (void) fprintf(stderr, "%s:%.20g.%.20g #%.20g => Changed %.20g\n",
3845 MagickOptionToMnemonic(MagickMorphologyOptions, method),
3846 1.0,0.0,1.0, (double) changed);
3851 if ( method == VoronoiMorphology ) {
3852 /* Preserve the alpha channel of input image - but turned off */
3853 SetImageAlphaChannel(rslt_image, DeactivateAlphaChannel);
3854 (void) CompositeImageChannel(rslt_image, DefaultChannels,
3855 CopyOpacityCompositeOp, image, 0, 0);
3856 SetImageAlphaChannel(rslt_image, DeactivateAlphaChannel);
3861 /* Handle user (caller) specified multi-kernel composition method */
3862 if ( compose != UndefinedCompositeOp )
3863 rslt_compose = compose; /* override default composition for method */
3864 if ( rslt_compose == UndefinedCompositeOp )
3865 rslt_compose = NoCompositeOp; /* still not defined! Then re-iterate */
3867 /* Some methods require a reflected kernel to use with primitives.
3868 * Create the reflected kernel for those methods. */
3870 case CorrelateMorphology:
3871 case CloseMorphology:
3872 case CloseIntensityMorphology:
3873 case BottomHatMorphology:
3874 case SmoothMorphology:
3875 reflected_kernel = CloneKernelInfo(kernel);
3876 if (reflected_kernel == (KernelInfo *) NULL)
3878 RotateKernelInfo(reflected_kernel,180);
3884 /* Loops around more primitive morpholgy methods
3885 ** erose, dilate, open, close, smooth, edge, etc...
3887 /* Loop 1: iterate the compound method */
3890 while ( method_loop < method_limit && method_changed > 0 ) {
3894 /* Loop 2: iterate over each kernel in a multi-kernel list */
3895 norm_kernel = (KernelInfo *) kernel;
3896 this_kernel = (KernelInfo *) kernel;
3897 rflt_kernel = reflected_kernel;
3900 while ( norm_kernel != NULL ) {
3902 /* Loop 3: Compound Morphology Staging - Select Primative to apply */
3903 stage_loop = 0; /* the compound morphology stage number */
3904 while ( stage_loop < stage_limit ) {
3905 stage_loop++; /* The stage of the compound morphology */
3907 /* Select primitive morphology for this stage of compound method */
3908 this_kernel = norm_kernel; /* default use unreflected kernel */
3909 primitive = method; /* Assume method is a primitive */
3911 case ErodeMorphology: /* just erode */
3912 case EdgeInMorphology: /* erode and image difference */
3913 primitive = ErodeMorphology;
3915 case DilateMorphology: /* just dilate */
3916 case EdgeOutMorphology: /* dilate and image difference */
3917 primitive = DilateMorphology;
3919 case OpenMorphology: /* erode then dialate */
3920 case TopHatMorphology: /* open and image difference */
3921 primitive = ErodeMorphology;
3922 if ( stage_loop == 2 )
3923 primitive = DilateMorphology;
3925 case OpenIntensityMorphology:
3926 primitive = ErodeIntensityMorphology;
3927 if ( stage_loop == 2 )
3928 primitive = DilateIntensityMorphology;
3930 case CloseMorphology: /* dilate, then erode */
3931 case BottomHatMorphology: /* close and image difference */
3932 this_kernel = rflt_kernel; /* use the reflected kernel */
3933 primitive = DilateMorphology;
3934 if ( stage_loop == 2 )
3935 primitive = ErodeMorphology;
3937 case CloseIntensityMorphology:
3938 this_kernel = rflt_kernel; /* use the reflected kernel */
3939 primitive = DilateIntensityMorphology;
3940 if ( stage_loop == 2 )
3941 primitive = ErodeIntensityMorphology;
3943 case SmoothMorphology: /* open, close */
3944 switch ( stage_loop ) {
3945 case 1: /* start an open method, which starts with Erode */
3946 primitive = ErodeMorphology;
3948 case 2: /* now Dilate the Erode */
3949 primitive = DilateMorphology;
3951 case 3: /* Reflect kernel a close */
3952 this_kernel = rflt_kernel; /* use the reflected kernel */
3953 primitive = DilateMorphology;
3955 case 4: /* Finish the Close */
3956 this_kernel = rflt_kernel; /* use the reflected kernel */
3957 primitive = ErodeMorphology;
3961 case EdgeMorphology: /* dilate and erode difference */
3962 primitive = DilateMorphology;
3963 if ( stage_loop == 2 ) {
3964 save_image = curr_image; /* save the image difference */
3965 curr_image = (Image *) image;
3966 primitive = ErodeMorphology;
3969 case CorrelateMorphology:
3970 /* A Correlation is a Convolution with a reflected kernel.
3971 ** However a Convolution is a weighted sum using a reflected
3972 ** kernel. It may seem stange to convert a Correlation into a
3973 ** Convolution as the Correlation is the simplier method, but
3974 ** Convolution is much more commonly used, and it makes sense to
3975 ** implement it directly so as to avoid the need to duplicate the
3976 ** kernel when it is not required (which is typically the
3979 this_kernel = rflt_kernel; /* use the reflected kernel */
3980 primitive = ConvolveMorphology;
3985 assert( this_kernel != (KernelInfo *) NULL );
3987 /* Extra information for debugging compound operations */
3988 if ( verbose == MagickTrue ) {
3989 if ( stage_limit > 1 )
3990 (void) FormatMagickString(v_info,MaxTextExtent,"%s:%.20g.%.20g -> ",
3991 MagickOptionToMnemonic(MagickMorphologyOptions,method),(double)
3992 method_loop,(double) stage_loop);
3993 else if ( primitive != method )
3994 (void) FormatMagickString(v_info, MaxTextExtent, "%s:%.20g -> ",
3995 MagickOptionToMnemonic(MagickMorphologyOptions, method),(double)
4001 /* Loop 4: Iterate the kernel with primitive */
4005 while ( kernel_loop < kernel_limit && changed > 0 ) {
4006 kernel_loop++; /* the iteration of this kernel */
4008 /* Create a clone as the destination image, if not yet defined */
4009 if ( work_image == (Image *) NULL )
4011 work_image=CloneImage(image,0,0,MagickTrue,exception);
4012 if (work_image == (Image *) NULL)
4014 if (SetImageStorageClass(work_image,DirectClass) == MagickFalse)
4016 InheritException(exception,&work_image->exception);
4019 /* work_image->type=image->type; ??? */
4022 /* APPLY THE MORPHOLOGICAL PRIMITIVE (curr -> work) */
4024 changed = MorphologyPrimitive(curr_image, work_image, primitive,
4025 channel, this_kernel, bias, exception);
4027 if ( verbose == MagickTrue ) {
4028 if ( kernel_loop > 1 )
4029 fprintf(stderr, "\n"); /* add end-of-line from previous */
4030 (void) fprintf(stderr, "%s%s%s:%.20g.%.20g #%.20g => Changed %.20g",
4031 v_info,MagickOptionToMnemonic(MagickMorphologyOptions,
4032 primitive),(this_kernel == rflt_kernel ) ? "*" : "",
4033 (double) (method_loop+kernel_loop-1),(double) kernel_number,
4034 (double) count,(double) changed);
4038 kernel_changed += changed;
4039 method_changed += changed;
4041 /* prepare next loop */
4042 { Image *tmp = work_image; /* swap images for iteration */
4043 work_image = curr_image;
4046 if ( work_image == image )
4047 work_image = (Image *) NULL; /* replace input 'image' */
4049 } /* End Loop 4: Iterate the kernel with primitive */
4051 if ( verbose == MagickTrue && kernel_changed != (size_t)changed )
4052 fprintf(stderr, " Total %.20g",(double) kernel_changed);
4053 if ( verbose == MagickTrue && stage_loop < stage_limit )
4054 fprintf(stderr, "\n"); /* add end-of-line before looping */
4057 fprintf(stderr, "--E-- image=0x%lx\n", (unsigned long)image);
4058 fprintf(stderr, " curr =0x%lx\n", (unsigned long)curr_image);
4059 fprintf(stderr, " work =0x%lx\n", (unsigned long)work_image);
4060 fprintf(stderr, " save =0x%lx\n", (unsigned long)save_image);
4061 fprintf(stderr, " union=0x%lx\n", (unsigned long)rslt_image);
4064 } /* End Loop 3: Primative (staging) Loop for Coumpound Methods */
4066 /* Final Post-processing for some Compound Methods
4068 ** The removal of any 'Sync' channel flag in the Image Compositon
4069 ** below ensures the methematical compose method is applied in a
4070 ** purely mathematical way, and only to the selected channels.
4071 ** Turn off SVG composition 'alpha blending'.
4074 case EdgeOutMorphology:
4075 case EdgeInMorphology:
4076 case TopHatMorphology:
4077 case BottomHatMorphology:
4078 if ( verbose == MagickTrue )
4079 fprintf(stderr, "\n%s: Difference with original image",
4080 MagickOptionToMnemonic(MagickMorphologyOptions, method) );
4081 (void) CompositeImageChannel(curr_image,
4082 (ChannelType) (channel & ~SyncChannels),
4083 DifferenceCompositeOp, image, 0, 0);
4085 case EdgeMorphology:
4086 if ( verbose == MagickTrue )
4087 fprintf(stderr, "\n%s: Difference of Dilate and Erode",
4088 MagickOptionToMnemonic(MagickMorphologyOptions, method) );
4089 (void) CompositeImageChannel(curr_image,
4090 (ChannelType) (channel & ~SyncChannels),
4091 DifferenceCompositeOp, save_image, 0, 0);
4092 save_image = DestroyImage(save_image); /* finished with save image */
4098 /* multi-kernel handling: re-iterate, or compose results */
4099 if ( kernel->next == (KernelInfo *) NULL )
4100 rslt_image = curr_image; /* just return the resulting image */
4101 else if ( rslt_compose == NoCompositeOp )
4102 { if ( verbose == MagickTrue ) {
4103 if ( this_kernel->next != (KernelInfo *) NULL )
4104 fprintf(stderr, " (re-iterate)");
4106 fprintf(stderr, " (done)");
4108 rslt_image = curr_image; /* return result, and re-iterate */
4110 else if ( rslt_image == (Image *) NULL)
4111 { if ( verbose == MagickTrue )
4112 fprintf(stderr, " (save for compose)");
4113 rslt_image = curr_image;
4114 curr_image = (Image *) image; /* continue with original image */
4117 { /* Add the new 'current' result to the composition
4119 ** The removal of any 'Sync' channel flag in the Image Compositon
4120 ** below ensures the methematical compose method is applied in a
4121 ** purely mathematical way, and only to the selected channels.
4122 ** IE: Turn off SVG composition 'alpha blending'.
4124 if ( verbose == MagickTrue )
4125 fprintf(stderr, " (compose \"%s\")",
4126 MagickOptionToMnemonic(MagickComposeOptions, rslt_compose) );
4127 (void) CompositeImageChannel(rslt_image,
4128 (ChannelType) (channel & ~SyncChannels), rslt_compose,
4130 curr_image = DestroyImage(curr_image);
4131 curr_image = (Image *) image; /* continue with original image */
4133 if ( verbose == MagickTrue )
4134 fprintf(stderr, "\n");
4136 /* loop to the next kernel in a multi-kernel list */
4137 norm_kernel = norm_kernel->next;
4138 if ( rflt_kernel != (KernelInfo *) NULL )
4139 rflt_kernel = rflt_kernel->next;
4141 } /* End Loop 2: Loop over each kernel */
4143 } /* End Loop 1: compound method interation */
4147 /* Yes goto's are bad, but it makes cleanup lot more efficient */
4149 if ( curr_image == rslt_image )
4150 curr_image = (Image *) NULL;
4151 if ( rslt_image != (Image *) NULL )
4152 rslt_image = DestroyImage(rslt_image);
4154 if ( curr_image == rslt_image || curr_image == image )
4155 curr_image = (Image *) NULL;
4156 if ( curr_image != (Image *) NULL )
4157 curr_image = DestroyImage(curr_image);
4158 if ( work_image != (Image *) NULL )
4159 work_image = DestroyImage(work_image);
4160 if ( save_image != (Image *) NULL )
4161 save_image = DestroyImage(save_image);
4162 if ( reflected_kernel != (KernelInfo *) NULL )
4163 reflected_kernel = DestroyKernelInfo(reflected_kernel);
4169 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4173 % M o r p h o l o g y I m a g e C h a n n e l %
4177 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4179 % MorphologyImageChannel() applies a user supplied kernel to the image
4180 % according to the given mophology method.
4182 % This function applies any and all user defined settings before calling
4183 % the above internal function MorphologyApply().
4185 % User defined settings include...
4186 % * Output Bias for Convolution and correlation ("-bias")
4187 % * Kernel Scale/normalize settings ("-set 'option:convolve:scale'")
4188 % This can also includes the addition of a scaled unity kernel.
4189 % * Show Kernel being applied ("-set option:showkernel 1")
4191 % The format of the MorphologyImage method is:
4193 % Image *MorphologyImage(const Image *image,MorphologyMethod method,
4194 % const ssize_t iterations,KernelInfo *kernel,ExceptionInfo *exception)
4196 % Image *MorphologyImageChannel(const Image *image, const ChannelType
4197 % channel,MorphologyMethod method,const ssize_t iterations,
4198 % KernelInfo *kernel,ExceptionInfo *exception)
4200 % A description of each parameter follows:
4202 % o image: the image.
4204 % o method: the morphology method to be applied.
4206 % o iterations: apply the operation this many times (or no change).
4207 % A value of -1 means loop until no change found.
4208 % How this is applied may depend on the morphology method.
4209 % Typically this is a value of 1.
4211 % o channel: the channel type.
4213 % o kernel: An array of double representing the morphology kernel.
4214 % Warning: kernel may be normalized for the Convolve method.
4216 % o exception: return any errors or warnings in this structure.
4220 MagickExport Image *MorphologyImageChannel(const Image *image,
4221 const ChannelType channel,const MorphologyMethod method,
4222 const ssize_t iterations,const KernelInfo *kernel,ExceptionInfo *exception)
4234 /* Apply Convolve/Correlate Normalization and Scaling Factors.
4235 * This is done BEFORE the ShowKernelInfo() function is called so that
4236 * users can see the results of the 'option:convolve:scale' option.
4238 curr_kernel = (KernelInfo *) kernel;
4239 if ( method == ConvolveMorphology || method == CorrelateMorphology )
4243 artifact = GetImageArtifact(image,"convolve:scale");
4244 if ( artifact != (const char *)NULL ) {
4245 if ( curr_kernel == kernel )
4246 curr_kernel = CloneKernelInfo(kernel);
4247 if (curr_kernel == (KernelInfo *) NULL) {
4248 curr_kernel=DestroyKernelInfo(curr_kernel);
4249 return((Image *) NULL);
4251 ScaleGeometryKernelInfo(curr_kernel, artifact);
4255 /* display the (normalized) kernel via stderr */
4256 if ( IsMagickTrue(GetImageArtifact(image,"showkernel"))
4257 || IsMagickTrue(GetImageArtifact(image,"convolve:showkernel"))
4258 || IsMagickTrue(GetImageArtifact(image,"morphology:showkernel")) )
4259 ShowKernelInfo(curr_kernel);
4261 /* Override the default handling of multi-kernel morphology results
4262 * If 'Undefined' use the default method
4263 * If 'None' (default for 'Convolve') re-iterate previous result
4264 * Otherwise merge resulting images using compose method given.
4265 * Default for 'HitAndMiss' is 'Lighten'.
4269 artifact = GetImageArtifact(image,"morphology:compose");
4270 compose = UndefinedCompositeOp; /* use default for method */
4271 if ( artifact != (const char *) NULL)
4272 compose = (CompositeOperator) ParseMagickOption(
4273 MagickComposeOptions,MagickFalse,artifact);
4275 /* Apply the Morphology */
4276 morphology_image = MorphologyApply(image, channel, method, iterations,
4277 curr_kernel, compose, image->bias, exception);
4279 /* Cleanup and Exit */
4280 if ( curr_kernel != kernel )
4281 curr_kernel=DestroyKernelInfo(curr_kernel);
4282 return(morphology_image);
4285 MagickExport Image *MorphologyImage(const Image *image, const MorphologyMethod
4286 method, const ssize_t iterations,const KernelInfo *kernel, ExceptionInfo
4292 morphology_image=MorphologyImageChannel(image,DefaultChannels,method,
4293 iterations,kernel,exception);
4294 return(morphology_image);
4298 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4302 + R o t a t e K e r n e l I n f o %
4306 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4308 % RotateKernelInfo() rotates the kernel by the angle given.
4310 % Currently it is restricted to 90 degree angles, of either 1D kernels
4311 % or square kernels. And 'circular' rotations of 45 degrees for 3x3 kernels.
4312 % It will ignore usless rotations for specific 'named' built-in kernels.
4314 % The format of the RotateKernelInfo method is:
4316 % void RotateKernelInfo(KernelInfo *kernel, double angle)
4318 % A description of each parameter follows:
4320 % o kernel: the Morphology/Convolution kernel
4322 % o angle: angle to rotate in degrees
4324 % This function is currently internal to this module only, but can be exported
4325 % to other modules if needed.
4327 static void RotateKernelInfo(KernelInfo *kernel, double angle)
4329 /* angle the lower kernels first */
4330 if ( kernel->next != (KernelInfo *) NULL)
4331 RotateKernelInfo(kernel->next, angle);
4333 /* WARNING: Currently assumes the kernel (rightly) is horizontally symetrical
4335 ** TODO: expand beyond simple 90 degree rotates, flips and flops
4338 /* Modulus the angle */
4339 angle = fmod(angle, 360.0);
4343 if ( 337.5 < angle || angle <= 22.5 )
4344 return; /* Near zero angle - no change! - At least not at this time */
4346 /* Handle special cases */
4347 switch (kernel->type) {
4348 /* These built-in kernels are cylindrical kernels, rotating is useless */
4349 case GaussianKernel:
4354 case LaplacianKernel:
4355 case ChebyshevKernel:
4356 case ManhattanKernel:
4357 case EuclideanKernel:
4360 /* These may be rotatable at non-90 angles in the future */
4361 /* but simply rotating them in multiples of 90 degrees is useless */
4368 /* These only allows a +/-90 degree rotation (by transpose) */
4369 /* A 180 degree rotation is useless */
4371 case RectangleKernel:
4372 if ( 135.0 < angle && angle <= 225.0 )
4374 if ( 225.0 < angle && angle <= 315.0 )
4381 /* Attempt rotations by 45 degrees */
4382 if ( 22.5 < fmod(angle,90.0) && fmod(angle,90.0) <= 67.5 )
4384 if ( kernel->width == 3 && kernel->height == 3 )
4385 { /* Rotate a 3x3 square by 45 degree angle */
4386 MagickRealType t = kernel->values[0];
4387 kernel->values[0] = kernel->values[3];
4388 kernel->values[3] = kernel->values[6];
4389 kernel->values[6] = kernel->values[7];
4390 kernel->values[7] = kernel->values[8];
4391 kernel->values[8] = kernel->values[5];
4392 kernel->values[5] = kernel->values[2];
4393 kernel->values[2] = kernel->values[1];
4394 kernel->values[1] = t;
4395 /* rotate non-centered origin */
4396 if ( kernel->x != 1 || kernel->y != 1 ) {
4398 x = (ssize_t) kernel->x-1;
4399 y = (ssize_t) kernel->y-1;
4400 if ( x == y ) x = 0;
4401 else if ( x == 0 ) x = -y;
4402 else if ( x == -y ) y = 0;
4403 else if ( y == 0 ) y = x;
4404 kernel->x = (ssize_t) x+1;
4405 kernel->y = (ssize_t) y+1;
4407 angle = fmod(angle+315.0, 360.0); /* angle reduced 45 degrees */
4408 kernel->angle = fmod(kernel->angle+45.0, 360.0);
4411 perror("Unable to rotate non-3x3 kernel by 45 degrees");
4413 if ( 45.0 < fmod(angle, 180.0) && fmod(angle,180.0) <= 135.0 )
4415 if ( kernel->width == 1 || kernel->height == 1 )
4416 { /* Do a transpose of a 1 dimentional kernel,
4417 ** which results in a fast 90 degree rotation of some type.
4421 t = (ssize_t) kernel->width;
4422 kernel->width = kernel->height;
4423 kernel->height = (size_t) t;
4425 kernel->x = kernel->y;
4427 if ( kernel->width == 1 ) {
4428 angle = fmod(angle+270.0, 360.0); /* angle reduced 90 degrees */
4429 kernel->angle = fmod(kernel->angle+90.0, 360.0);
4431 angle = fmod(angle+90.0, 360.0); /* angle increased 90 degrees */
4432 kernel->angle = fmod(kernel->angle+270.0, 360.0);
4435 else if ( kernel->width == kernel->height )
4436 { /* Rotate a square array of values by 90 degrees */
4439 register MagickRealType
4442 for( i=0, x=kernel->width-1; i<=x; i++, x--)
4443 for( j=0, y=kernel->height-1; j<y; j++, y--)
4444 { t = k[i+j*kernel->width];
4445 k[i+j*kernel->width] = k[j+x*kernel->width];
4446 k[j+x*kernel->width] = k[x+y*kernel->width];
4447 k[x+y*kernel->width] = k[y+i*kernel->width];
4448 k[y+i*kernel->width] = t;
4451 /* rotate the origin - relative to center of array */
4452 { register ssize_t x,y;
4453 x = (ssize_t) (kernel->x*2-kernel->width+1);
4454 y = (ssize_t) (kernel->y*2-kernel->height+1);
4455 kernel->x = (ssize_t) ( -y +(ssize_t) kernel->width-1)/2;
4456 kernel->y = (ssize_t) ( +x +(ssize_t) kernel->height-1)/2;
4458 angle = fmod(angle+270.0, 360.0); /* angle reduced 90 degrees */
4459 kernel->angle = fmod(kernel->angle+90.0, 360.0);
4462 perror("Unable to rotate a non-square, non-linear kernel 90 degrees");
4464 if ( 135.0 < angle && angle <= 225.0 )
4466 /* For a 180 degree rotation - also know as a reflection
4467 * This is actually a very very common operation!
4468 * Basically all that is needed is a reversal of the kernel data!
4469 * And a reflection of the origon
4477 for ( i=0, j=kernel->width*kernel->height-1; i<j; i++, j--)
4478 t=k[i], k[i]=k[j], k[j]=t;
4480 kernel->x = (ssize_t) kernel->width - kernel->x - 1;
4481 kernel->y = (ssize_t) kernel->height - kernel->y - 1;
4482 angle = fmod(angle-180.0, 360.0); /* angle+180 degrees */
4483 kernel->angle = fmod(kernel->angle+180.0, 360.0);
4485 /* At this point angle should at least between -45 (315) and +45 degrees
4486 * In the future some form of non-orthogonal angled rotates could be
4487 * performed here, posibily with a linear kernel restriction.
4494 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4498 % S c a l e G e o m e t r y K e r n e l I n f o %
4502 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4504 % ScaleGeometryKernelInfo() takes a geometry argument string, typically
4505 % provided as a "-set option:convolve:scale {geometry}" user setting,
4506 % and modifies the kernel according to the parsed arguments of that setting.
4508 % The first argument (and any normalization flags) are passed to
4509 % ScaleKernelInfo() to scale/normalize the kernel. The second argument
4510 % is then passed to UnityAddKernelInfo() to add a scled unity kernel
4511 % into the scaled/normalized kernel.
4513 % The format of the ScaleGeometryKernelInfo method is:
4515 % void ScaleGeometryKernelInfo(KernelInfo *kernel,
4516 % const double scaling_factor,const MagickStatusType normalize_flags)
4518 % A description of each parameter follows:
4520 % o kernel: the Morphology/Convolution kernel to modify
4523 % The geometry string to parse, typically from the user provided
4524 % "-set option:convolve:scale {geometry}" setting.
4527 MagickExport void ScaleGeometryKernelInfo (KernelInfo *kernel,
4528 const char *geometry)
4535 SetGeometryInfo(&args);
4536 flags = (GeometryFlags) ParseGeometry(geometry, &args);
4539 /* For Debugging Geometry Input */
4540 fprintf(stderr, "Geometry = 0x%04X : %lg x %lg %+lg %+lg\n",
4541 flags, args.rho, args.sigma, args.xi, args.psi );
4544 if ( (flags & PercentValue) != 0 ) /* Handle Percentage flag*/
4545 args.rho *= 0.01, args.sigma *= 0.01;
4547 if ( (flags & RhoValue) == 0 ) /* Set Defaults for missing args */
4549 if ( (flags & SigmaValue) == 0 )
4552 /* Scale/Normalize the input kernel */
4553 ScaleKernelInfo(kernel, args.rho, flags);
4555 /* Add Unity Kernel, for blending with original */
4556 if ( (flags & SigmaValue) != 0 )
4557 UnityAddKernelInfo(kernel, args.sigma);
4562 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4566 % S c a l e K e r n e l I n f o %
4570 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4572 % ScaleKernelInfo() scales the given kernel list by the given amount, with or
4573 % without normalization of the sum of the kernel values (as per given flags).
4575 % By default (no flags given) the values within the kernel is scaled
4576 % directly using given scaling factor without change.
4578 % If either of the two 'normalize_flags' are given the kernel will first be
4579 % normalized and then further scaled by the scaling factor value given.
4581 % Kernel normalization ('normalize_flags' given) is designed to ensure that
4582 % any use of the kernel scaling factor with 'Convolve' or 'Correlate'
4583 % morphology methods will fall into -1.0 to +1.0 range. Note that for
4584 % non-HDRI versions of IM this may cause images to have any negative results
4585 % clipped, unless some 'bias' is used.
4587 % More specifically. Kernels which only contain positive values (such as a
4588 % 'Gaussian' kernel) will be scaled so that those values sum to +1.0,
4589 % ensuring a 0.0 to +1.0 output range for non-HDRI images.
4591 % For Kernels that contain some negative values, (such as 'Sharpen' kernels)
4592 % the kernel will be scaled by the absolute of the sum of kernel values, so
4593 % that it will generally fall within the +/- 1.0 range.
4595 % For kernels whose values sum to zero, (such as 'Laplician' kernels) kernel
4596 % will be scaled by just the sum of the postive values, so that its output
4597 % range will again fall into the +/- 1.0 range.
4599 % For special kernels designed for locating shapes using 'Correlate', (often
4600 % only containing +1 and -1 values, representing foreground/brackground
4601 % matching) a special normalization method is provided to scale the positive
4602 % values separately to those of the negative values, so the kernel will be
4603 % forced to become a zero-sum kernel better suited to such searches.
4605 % WARNING: Correct normalization of the kernel assumes that the '*_range'
4606 % attributes within the kernel structure have been correctly set during the
4609 % NOTE: The values used for 'normalize_flags' have been selected specifically
4610 % to match the use of geometry options, so that '!' means NormalizeValue, '^'
4611 % means CorrelateNormalizeValue. All other GeometryFlags values are ignored.
4613 % The format of the ScaleKernelInfo method is:
4615 % void ScaleKernelInfo(KernelInfo *kernel, const double scaling_factor,
4616 % const MagickStatusType normalize_flags )
4618 % A description of each parameter follows:
4620 % o kernel: the Morphology/Convolution kernel
4623 % multiply all values (after normalization) by this factor if not
4624 % zero. If the kernel is normalized regardless of any flags.
4626 % o normalize_flags:
4627 % GeometryFlags defining normalization method to use.
4628 % specifically: NormalizeValue, CorrelateNormalizeValue,
4629 % and/or PercentValue
4632 MagickExport void ScaleKernelInfo(KernelInfo *kernel,
4633 const double scaling_factor,const GeometryFlags normalize_flags)
4642 /* do the other kernels in a multi-kernel list first */
4643 if ( kernel->next != (KernelInfo *) NULL)
4644 ScaleKernelInfo(kernel->next, scaling_factor, normalize_flags);
4646 /* Normalization of Kernel */
4648 if ( (normalize_flags&NormalizeValue) != 0 ) {
4649 if ( fabs(kernel->positive_range + kernel->negative_range) > MagickEpsilon )
4650 /* non-zero-summing kernel (generally positive) */
4651 pos_scale = fabs(kernel->positive_range + kernel->negative_range);
4653 /* zero-summing kernel */
4654 pos_scale = kernel->positive_range;
4656 /* Force kernel into a normalized zero-summing kernel */
4657 if ( (normalize_flags&CorrelateNormalizeValue) != 0 ) {
4658 pos_scale = ( fabs(kernel->positive_range) > MagickEpsilon )
4659 ? kernel->positive_range : 1.0;
4660 neg_scale = ( fabs(kernel->negative_range) > MagickEpsilon )
4661 ? -kernel->negative_range : 1.0;
4664 neg_scale = pos_scale;
4666 /* finialize scaling_factor for positive and negative components */
4667 pos_scale = scaling_factor/pos_scale;
4668 neg_scale = scaling_factor/neg_scale;
4670 for (i=0; i < (ssize_t) (kernel->width*kernel->height); i++)
4671 if ( ! IsNan(kernel->values[i]) )
4672 kernel->values[i] *= (kernel->values[i] >= 0) ? pos_scale : neg_scale;
4674 /* convolution output range */
4675 kernel->positive_range *= pos_scale;
4676 kernel->negative_range *= neg_scale;
4677 /* maximum and minimum values in kernel */
4678 kernel->maximum *= (kernel->maximum >= 0.0) ? pos_scale : neg_scale;
4679 kernel->minimum *= (kernel->minimum >= 0.0) ? pos_scale : neg_scale;
4681 /* swap kernel settings if user's scaling factor is negative */
4682 if ( scaling_factor < MagickEpsilon ) {
4684 t = kernel->positive_range;
4685 kernel->positive_range = kernel->negative_range;
4686 kernel->negative_range = t;
4687 t = kernel->maximum;
4688 kernel->maximum = kernel->minimum;
4689 kernel->minimum = 1;
4696 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4700 % S h o w K e r n e l I n f o %
4704 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4706 % ShowKernelInfo() outputs the details of the given kernel defination to
4707 % standard error, generally due to a users 'showkernel' option request.
4709 % The format of the ShowKernel method is:
4711 % void ShowKernelInfo(KernelInfo *kernel)
4713 % A description of each parameter follows:
4715 % o kernel: the Morphology/Convolution kernel
4718 MagickExport void ShowKernelInfo(KernelInfo *kernel)
4726 for (c=0, k=kernel; k != (KernelInfo *) NULL; c++, k=k->next ) {
4728 fprintf(stderr, "Kernel");
4729 if ( kernel->next != (KernelInfo *) NULL )
4730 fprintf(stderr, " #%lu", (unsigned long) c );
4731 fprintf(stderr, " \"%s",
4732 MagickOptionToMnemonic(MagickKernelOptions, k->type) );
4733 if ( fabs(k->angle) > MagickEpsilon )
4734 fprintf(stderr, "@%lg", k->angle);
4735 fprintf(stderr, "\" of size %lux%lu%+ld%+ld",(unsigned long) k->width,
4736 (unsigned long) k->height,(long) k->x,(long) k->y);
4738 " with values from %.*lg to %.*lg\n",
4739 GetMagickPrecision(), k->minimum,
4740 GetMagickPrecision(), k->maximum);
4741 fprintf(stderr, "Forming a output range from %.*lg to %.*lg",
4742 GetMagickPrecision(), k->negative_range,
4743 GetMagickPrecision(), k->positive_range);
4744 if ( fabs(k->positive_range+k->negative_range) < MagickEpsilon )
4745 fprintf(stderr, " (Zero-Summing)\n");
4746 else if ( fabs(k->positive_range+k->negative_range-1.0) < MagickEpsilon )
4747 fprintf(stderr, " (Normalized)\n");
4749 fprintf(stderr, " (Sum %.*lg)\n",
4750 GetMagickPrecision(), k->positive_range+k->negative_range);
4751 for (i=v=0; v < k->height; v++) {
4752 fprintf(stderr, "%2lu:", (unsigned long) v );
4753 for (u=0; u < k->width; u++, i++)
4754 if ( IsNan(k->values[i]) )
4755 fprintf(stderr," %*s", GetMagickPrecision()+3, "nan");
4757 fprintf(stderr," %*.*lg", GetMagickPrecision()+3,
4758 GetMagickPrecision(), k->values[i]);
4759 fprintf(stderr,"\n");
4765 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4769 % U n i t y A d d K e r n a l I n f o %
4773 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4775 % UnityAddKernelInfo() Adds a given amount of the 'Unity' Convolution Kernel
4776 % to the given pre-scaled and normalized Kernel. This in effect adds that
4777 % amount of the original image into the resulting convolution kernel. This
4778 % value is usually provided by the user as a percentage value in the
4779 % 'convolve:scale' setting.
4781 % The resulting effect is to convert the defined kernels into blended
4782 % soft-blurs, unsharp kernels or into sharpening kernels.
4784 % The format of the UnityAdditionKernelInfo method is:
4786 % void UnityAdditionKernelInfo(KernelInfo *kernel, const double scale )
4788 % A description of each parameter follows:
4790 % o kernel: the Morphology/Convolution kernel
4793 % scaling factor for the unity kernel to be added to
4797 MagickExport void UnityAddKernelInfo(KernelInfo *kernel,
4800 /* do the other kernels in a multi-kernel list first */
4801 if ( kernel->next != (KernelInfo *) NULL)
4802 UnityAddKernelInfo(kernel->next, scale);
4804 /* Add the scaled unity kernel to the existing kernel */
4805 kernel->values[kernel->x+kernel->y*kernel->width] += scale;
4806 CalcKernelMetaData(kernel); /* recalculate the meta-data */
4812 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4816 % Z e r o K e r n e l N a n s %
4820 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4822 % ZeroKernelNans() replaces any special 'nan' value that may be present in
4823 % the kernel with a zero value. This is typically done when the kernel will
4824 % be used in special hardware (GPU) convolution processors, to simply
4827 % The format of the ZeroKernelNans method is:
4829 % void ZeroKernelNans (KernelInfo *kernel)
4831 % A description of each parameter follows:
4833 % o kernel: the Morphology/Convolution kernel
4836 MagickExport void ZeroKernelNans(KernelInfo *kernel)
4841 /* do the other kernels in a multi-kernel list first */
4842 if ( kernel->next != (KernelInfo *) NULL)
4843 ZeroKernelNans(kernel->next);
4845 for (i=0; i < (kernel->width*kernel->height); i++)
4846 if ( IsNan(kernel->values[i]) )
4847 kernel->values[i] = 0.0;