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-2010 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 kernals, 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/option.h"
69 #include "magick/pixel-private.h"
70 #include "magick/prepress.h"
71 #include "magick/quantize.h"
72 #include "magick/registry.h"
73 #include "magick/semaphore.h"
74 #include "magick/splay-tree.h"
75 #include "magick/statistic.h"
76 #include "magick/string_.h"
77 #include "magick/string-private.h"
78 #include "magick/token.h"
81 The following test is for special floating point numbers of value NaN (not
82 a number), that may be used within a Kernel Definition. NaN's are defined
83 as part of the IEEE standard for floating point number representation.
85 These are used a Kernel value of NaN means that that kernal position is not
86 part of the normal convolution or morphology process, and thus allowing the
87 use of 'shaped' kernels.
89 Special properities two NaN's are never equal, even if they are from the
90 same variable That is the IsNaN() macro is only true if the value is NaN.
92 #define IsNan(a) ((a)!=(a))
95 Other global definitions used by module.
97 static inline double MagickMin(const double x,const double y)
99 return( x < y ? x : y);
101 static inline double MagickMax(const double x,const double y)
103 return( x > y ? x : y);
105 #define Minimize(assign,value) assign=MagickMin(assign,value)
106 #define Maximize(assign,value) assign=MagickMax(assign,value)
108 /* Currently these are only internal to this module */
110 RotateKernelInfo(KernelInfo *, double);
113 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
117 % A c q u i r e K e r n e l I n f o %
121 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
123 % AcquireKernelInfo() takes the given string (generally supplied by the
124 % user) and converts it into a Morphology/Convolution Kernel. This allows
125 % users to specify a kernel from a number of pre-defined kernels, or to fully
126 % specify their own kernel for a specific Convolution or Morphology
129 % The kernel so generated can be any rectangular array of floating point
130 % values (doubles) with the 'control point' or 'pixel being affected'
131 % anywhere within that array of values.
133 % Previously IM was restricted to a square of odd size using the exact
134 % center as origin, this is no longer the case, and any rectangular kernel
135 % with any value being declared the origin. This in turn allows the use of
136 % highly asymmetrical kernels.
138 % The floating point values in the kernel can also include a special value
139 % known as 'nan' or 'not a number' to indicate that this value is not part
140 % of the kernel array. This allows you to shaped the kernel within its
141 % rectangular area. That is 'nan' values provide a 'mask' for the kernel
142 % shape. However at least one non-nan value must be provided for correct
143 % working of a kernel.
145 % The returned kernel should be free using the DestroyKernelInfo() when you
146 % are finished with it.
148 % Input kernel defintion strings can consist of any of three types.
151 % Select from one of the built in kernels, using the name and
152 % geometry arguments supplied. See AcquireKernelBuiltIn()
154 % "WxH[+X+Y]:num, num, num ..."
155 % a kernal of size W by H, with W*H floating point numbers following.
156 % the 'center' can be optionally be defined at +X+Y (such that +0+0
157 % is top left corner). If not defined the pixel in the center, for
158 % odd sizes, or to the immediate top or left of center for even sizes
159 % is automatically selected.
161 % "num, num, num, num, ..."
162 % list of floating point numbers defining an 'old style' odd sized
163 % square kernel. At least 9 values should be provided for a 3x3
164 % square kernel, 25 for a 5x5 square kernel, 49 for 7x7, etc.
165 % Values can be space or comma separated. This is not recommended.
167 % Note that 'name' kernels will start with an alphabetic character while the
168 % new kernel specification has a ':' character in its specification string.
169 % If neither is the case, it is assumed an old style of a simple list of
170 % numbers generating a odd-sized square kernel has been given.
172 % The format of the AcquireKernal method is:
174 % KernelInfo *AcquireKernelInfo(const char *kernel_string)
176 % A description of each parameter follows:
178 % o kernel_string: the Morphology/Convolution kernel wanted.
182 /* This was separated so that it could be used as a separate
183 ** array input handling function.
185 static KernelInfo *ParseArray(const char *kernel_string)
191 token[MaxTextExtent];
200 nan = sqrt((double)-1.0); /* Special Value : Not A Number */
202 kernel=(KernelInfo *) AcquireMagickMemory(sizeof(*kernel));
203 if (kernel == (KernelInfo *)NULL)
205 (void) ResetMagickMemory(kernel,0,sizeof(*kernel));
206 kernel->type = UserDefinedKernel;
207 kernel->signature = MagickSignature;
209 /* Has a ':' in argument - New user kernel specification */
210 p = strchr(kernel_string, ':');
211 if ( p != (char *) NULL)
219 /* ParseGeometry() needs the geometry separated! -- Arrgghh */
220 memcpy(token, kernel_string, (size_t) (p-kernel_string));
221 token[p-kernel_string] = '\0';
222 SetGeometryInfo(&args);
223 flags = ParseGeometry(token, &args);
225 /* Size handling and checks of geometry settings */
226 if ( (flags & WidthValue) == 0 ) /* if no width then */
227 args.rho = args.sigma; /* then width = height */
228 if ( args.rho < 1.0 ) /* if width too small */
229 args.rho = 1.0; /* then width = 1 */
230 if ( args.sigma < 1.0 ) /* if height too small */
231 args.sigma = args.rho; /* then height = width */
232 kernel->width = (unsigned long)args.rho;
233 kernel->height = (unsigned long)args.sigma;
235 /* Offset Handling and Checks */
236 if ( args.xi < 0.0 || args.psi < 0.0 )
237 return(DestroyKernelInfo(kernel));
238 kernel->x = ((flags & XValue)!=0) ? (long)args.xi
239 : (long) (kernel->width-1)/2;
240 kernel->y = ((flags & YValue)!=0) ? (long)args.psi
241 : (long) (kernel->height-1)/2;
242 if ( kernel->x >= (long) kernel->width ||
243 kernel->y >= (long) kernel->height )
244 return(DestroyKernelInfo(kernel));
246 p++; /* advance beyond the ':' */
249 { /* ELSE - Old old specification, forming odd-square kernel */
250 /* count up number of values given */
251 p=(const char *) kernel_string;
252 while ((isspace((int) ((unsigned char) *p)) != 0) || (*p == '\''))
253 p++; /* ignore "'" chars for convolve filter usage - Cristy */
254 for (i=0; *p != '\0'; i++)
256 GetMagickToken(p,&p,token);
258 GetMagickToken(p,&p,token);
260 /* set the size of the kernel - old sized square */
261 kernel->width = kernel->height= (unsigned long) sqrt((double) i+1.0);
262 kernel->x = kernel->y = (long) (kernel->width-1)/2;
263 p=(const char *) kernel_string;
264 while ((isspace((int) ((unsigned char) *p)) != 0) || (*p == '\''))
265 p++; /* ignore "'" chars for convolve filter usage - Cristy */
268 /* Read in the kernel values from rest of input string argument */
269 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
270 kernel->height*sizeof(double));
271 if (kernel->values == (double *) NULL)
272 return(DestroyKernelInfo(kernel));
274 kernel->minimum = +MagickHuge;
275 kernel->maximum = -MagickHuge;
276 kernel->negative_range = kernel->positive_range = 0.0;
278 for (i=0; (i < (long) (kernel->width*kernel->height)) && (*p != '\0'); i++)
280 GetMagickToken(p,&p,token);
282 GetMagickToken(p,&p,token);
283 if ( LocaleCompare("nan",token) == 0
284 || LocaleCompare("-",token) == 0 ) {
285 kernel->values[i] = nan; /* do not include this value in kernel */
288 kernel->values[i] = StringToDouble(token);
289 ( kernel->values[i] < 0)
290 ? ( kernel->negative_range += kernel->values[i] )
291 : ( kernel->positive_range += kernel->values[i] );
292 Minimize(kernel->minimum, kernel->values[i]);
293 Maximize(kernel->maximum, kernel->values[i]);
298 /* this was the old method of handling a incomplete kernel */
299 if ( i < (long) (kernel->width*kernel->height) ) {
300 Minimize(kernel->minimum, kernel->values[i]);
301 Maximize(kernel->maximum, kernel->values[i]);
302 for ( ; i < (long) (kernel->width*kernel->height); i++)
303 kernel->values[i]=0.0;
306 /* Number of values for kernel was not enough - Report Error */
307 if ( i < (long) (kernel->width*kernel->height) )
308 return(DestroyKernelInfo(kernel));
311 /* check that we recieved at least one real (non-nan) value! */
312 if ( kernel->minimum == MagickHuge )
313 return(DestroyKernelInfo(kernel));
319 MagickExport KernelInfo *AcquireKernelInfo(const char *kernel_string)
322 token[MaxTextExtent];
336 /* If it does not start with an alpha - user defined kernel */
337 GetMagickToken(kernel_string,&p,token);
338 if (isalpha((int) ((unsigned char) *token)) == 0)
339 return(ParseArray(kernel_string));
341 /* Parse special 'named' kernel */
342 type=ParseMagickOption(MagickKernelOptions,MagickFalse,token);
343 if ( type < 0 || type == UserDefinedKernel )
344 return((KernelInfo *)NULL);
346 while (((isspace((int) ((unsigned char) *p)) != 0) ||
347 (*p == ',') || (*p == ':' )) && (*p != '\0'))
349 SetGeometryInfo(&args);
350 flags = ParseGeometry(p, &args);
352 /* special handling of missing values in input string */
354 case RectangleKernel:
355 if ( (flags & WidthValue) == 0 ) /* if no width then */
356 args.rho = args.sigma; /* then width = height */
357 if ( args.rho < 1.0 ) /* if width too small */
358 args.rho = 3; /* then width = 3 */
359 if ( args.sigma < 1.0 ) /* if height too small */
360 args.sigma = args.rho; /* then height = width */
361 if ( (flags & XValue) == 0 ) /* center offset if not defined */
362 args.xi = (double)(((long)args.rho-1)/2);
363 if ( (flags & YValue) == 0 )
364 args.psi = (double)(((long)args.sigma-1)/2);
370 /* If no scale given (a 0 scale is valid! - set it to 1.0 */
371 if ( (flags & HeightValue) == 0 )
374 case ChebyshevKernel:
375 case ManhattenKernel:
376 case EuclideanKernel:
377 if ( (flags & HeightValue) == 0 )
378 args.sigma = 100.0; /* default distance scaling */
379 else if ( (flags & AspectValue ) != 0 ) /* '!' flag */
380 args.sigma = QuantumRange/args.sigma; /* maximum pixel distance */
381 else if ( (flags & PercentValue ) != 0 ) /* '%' flag */
382 args.sigma *= QuantumRange/100.0; /* percentage of color range */
388 return(AcquireKernelBuiltIn((KernelInfoType)type, &args));
393 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
397 % A c q u i r e K e r n e l B u i l t I n %
401 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
403 % AcquireKernelBuiltIn() returned one of the 'named' built-in types of
404 % kernels used for special purposes such as gaussian blurring, skeleton
405 % pruning, and edge distance determination.
407 % They take a KernelType, and a set of geometry style arguments, which were
408 % typically decoded from a user supplied string, or from a more complex
409 % Morphology Method that was requested.
411 % The format of the AcquireKernalBuiltIn method is:
413 % KernelInfo *AcquireKernelBuiltIn(const KernelInfoType type,
414 % const GeometryInfo args)
416 % A description of each parameter follows:
418 % o type: the pre-defined type of kernel wanted
420 % o args: arguments defining or modifying the kernel
422 % Convolution Kernels
424 % Gaussian "{radius},{sigma}"
425 % Generate a two-dimentional gaussian kernel, as used by -gaussian
426 % A sigma is required, (with the 'x'), due to historical reasons.
428 % NOTE: that the 'radius' is optional, but if provided can limit (clip)
429 % the final size of the resulting kernel to a square 2*radius+1 in size.
430 % The radius should be at least 2 times that of the sigma value, or
431 % sever clipping and aliasing may result. If not given or set to 0 the
432 % radius will be determined so as to produce the best minimal error
433 % result, which is usally much larger than is normally needed.
435 % Blur "{radius},{sigma},{angle}"
436 % As per Gaussian, but generates a 1 dimensional or linear gaussian
437 % blur, at the angle given (current restricted to orthogonal angles).
438 % If a 'radius' is given the kernel is clipped to a width of 2*radius+1.
440 % NOTE that two such blurs perpendicular to each other is equivelent to
441 % -blur and the previous gaussian, but is often 10 or more times faster.
443 % Comet "{width},{sigma},{angle}"
444 % Blur in one direction only, mush like how a bright object leaves
445 % a comet like trail. The Kernel is actually half a gaussian curve,
446 % Adding two such blurs in oppiste directions produces a Linear Blur.
448 % NOTE: that the first argument is the width of the kernel and not the
449 % radius of the kernel.
451 % # Still to be implemented...
453 % # Sharpen "{radius},{sigma}
454 % # Negated Gaussian (center zeroed and re-normalized),
455 % # with a 2 unit positive peak. -- Check On line documentation
457 % # Laplacian "{radius},{sigma}"
458 % # Laplacian (a mexican hat like) Function
460 % # LOG "{radius},{sigma1},{sigma2}
461 % # Laplacian of Gaussian
463 % # DOG "{radius},{sigma1},{sigma2}
464 % # Difference of two Gaussians
468 % # Set kernel values using a resize filter, and given scale (sigma)
469 % # Cylindrical or Linear. Is this posible with an image?
474 % Rectangle "{geometry}"
475 % Simply generate a rectangle of 1's with the size given. You can also
476 % specify the location of the 'control point', otherwise the closest
477 % pixel to the center of the rectangle is selected.
479 % Properly centered and odd sized rectangles work the best.
481 % Diamond "[{radius}[,{scale}]]"
482 % Generate a diamond shaped kernal with given radius to the points.
483 % Kernel size will again be radius*2+1 square and defaults to radius 1,
484 % generating a 3x3 kernel that is slightly larger than a square.
486 % Square "[{radius}[,{scale}]]"
487 % Generate a square shaped kernel of size radius*2+1, and defaulting
488 % to a 3x3 (radius 1).
490 % Note that using a larger radius for the "Square" or the "Diamond"
491 % is also equivelent to iterating the basic morphological method
492 % that many times. However However iterating with the smaller radius 1
493 % default is actually faster than using a larger kernel radius.
495 % Disk "[{radius}[,{scale}]]
496 % Generate a binary disk of the radius given, radius may be a float.
497 % Kernel size will be ceil(radius)*2+1 square.
498 % NOTE: Here are some disk shapes of specific interest
499 % "disk:1" => "diamond" or "cross:1"
500 % "disk:1.5" => "square"
501 % "disk:2" => "diamond:2"
502 % "disk:2.5" => a general disk shape of radius 2
503 % "disk:2.9" => "square:2"
504 % "disk:3.5" => default - octagonal/disk shape of radius 3
505 % "disk:4.2" => roughly octagonal shape of radius 4
506 % "disk:4.3" => a general disk shape of radius 4
507 % After this all the kernel shape becomes more and more circular.
509 % Because a "disk" is more circular when using a larger radius, using a
510 % larger radius is preferred over iterating the morphological operation.
512 % Plus "[{radius}[,{scale}]]"
513 % Generate a kernel in the shape of a 'plus' sign. The length of each
514 % arm is also the radius, which defaults to 2.
516 % This kernel is not a good general morphological kernel, but is used
517 % more for highlighting and marking any single pixels in an image using,
518 % a "Dilate" or "Erode" method as appropriate.
520 % NOTE: "plus:1" is equivelent to a "Diamond" kernel.
522 % Note that unlike other kernels iterating a plus does not produce the
523 % same result as using a larger radius for the cross.
525 % Distance Measuring Kernels
527 % Chebyshev "[{radius}][x{scale}[%!]]"
528 % Manhatten "[{radius}][x{scale}[%!]]"
529 % Euclidean "[{radius}][x{scale}[%!]]"
531 % Different types of distance measuring methods, which are used with the
532 % a 'Distance' morphology method for generating a gradient based on
533 % distance from an edge of a binary shape, though there is a technique
534 % for handling a anti-aliased shape.
536 % Chebyshev Distance (also known as Tchebychev Distance) is a value of
537 % one to any neighbour, orthogonal or diagonal. One why of thinking of
538 % it is the number of squares a 'King' or 'Queen' in chess needs to
539 % traverse reach any other position on a chess board. It results in a
540 % 'square' like distance function, but one where diagonals are closer
543 % Manhatten Distance (also known as Rectilinear Distance, or the Taxi
544 % Cab metric), is the distance needed when you can only travel in
545 % orthogonal (horizontal or vertical) only. It is the distance a 'Rook'
546 % in chess would travel. It results in a diamond like distances, where
547 % diagonals are further than expected.
549 % Euclidean Distance is the 'direct' or 'as the crow flys distance.
550 % However by default the kernel size only has a radius of 1, which
551 % limits the distance to 'Knight' like moves, with only orthogonal and
552 % diagonal measurements being correct. As such for the default kernel
553 % you will get octagonal like distance function, which is reasonally
556 % However if you use a larger radius such as "Euclidean:4" you will
557 % get a much smoother distance gradient from the edge of the shape.
558 % Of course a larger kernel is slower to use, and generally not needed.
560 % To allow the use of fractional distances that you get with diagonals
561 % the actual distance is scaled by a fixed value which the user can
562 % provide. This is not actually nessary for either ""Chebyshev" or
563 % "Manhatten" distance kernels, but is done for all three distance
564 % kernels. If no scale is provided it is set to a value of 100,
565 % allowing for a maximum distance measurement of 655 pixels using a Q16
566 % version of IM, from any edge. However for small images this can
567 % result in quite a dark gradient.
569 % See the 'Distance' Morphological Method, for information of how it is
572 % # Hit-n-Miss Kernel-Lists -- Still to be implemented
574 % # specifically for Pruning, Thinning, Thickening
578 MagickExport KernelInfo *AcquireKernelBuiltIn(const KernelInfoType type,
579 const GeometryInfo *args)
592 nan = sqrt((double)-1.0); /* Special Value : Not A Number */
594 kernel=(KernelInfo *) AcquireMagickMemory(sizeof(*kernel));
595 if (kernel == (KernelInfo *) NULL)
597 (void) ResetMagickMemory(kernel,0,sizeof(*kernel));
598 kernel->minimum = kernel->maximum = 0.0;
599 kernel->negative_range = kernel->positive_range = 0.0;
601 kernel->signature = MagickSignature;
604 /* Convolution Kernels */
607 sigma = fabs(args->sigma);
609 sigma = (sigma <= MagickEpsilon) ? 1.0 : sigma;
611 kernel->width = kernel->height =
612 GetOptimalKernelWidth2D(args->rho,sigma);
613 kernel->x = kernel->y = (long) (kernel->width-1)/2;
614 kernel->negative_range = kernel->positive_range = 0.0;
615 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
616 kernel->height*sizeof(double));
617 if (kernel->values == (double *) NULL)
618 return(DestroyKernelInfo(kernel));
620 sigma = 2.0*sigma*sigma; /* simplify the expression */
621 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
622 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
623 kernel->positive_range += (
625 exp(-((double)(u*u+v*v))/sigma)
626 /* / (MagickPI*sigma) */ );
628 kernel->maximum = kernel->values[
629 kernel->y*kernel->width+kernel->x ];
631 ScaleKernelInfo(kernel, 1.0, NormalizeValue); /* Normalize */
637 sigma = fabs(args->sigma);
639 sigma = (sigma <= MagickEpsilon) ? 1.0 : sigma;
641 kernel->width = GetOptimalKernelWidth1D(args->rho,sigma);
642 kernel->x = (long) (kernel->width-1)/2;
645 kernel->negative_range = kernel->positive_range = 0.0;
646 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
647 kernel->height*sizeof(double));
648 if (kernel->values == (double *) NULL)
649 return(DestroyKernelInfo(kernel));
653 /* Formula derived from GetBlurKernel() in "effect.c" (plus bug fix).
654 ** It generates a gaussian 3 times the width, and compresses it into
655 ** the expected range. This produces a closer normalization of the
656 ** resulting kernel, especially for very low sigma values.
657 ** As such while wierd it is prefered.
659 ** I am told this method originally came from Photoshop.
661 sigma *= KernelRank; /* simplify expanded curve */
662 v = (long) (kernel->width*KernelRank-1)/2; /* start/end points to fit range */
663 (void) ResetMagickMemory(kernel->values,0, (size_t)
664 kernel->width*sizeof(double));
665 for ( u=-v; u <= v; u++) {
666 kernel->values[(u+v)/KernelRank] +=
667 exp(-((double)(u*u))/(2.0*sigma*sigma))
668 /* / (MagickSQ2PI*sigma/KernelRank) */ ;
670 for (i=0; i < (long) kernel->width; i++)
671 kernel->positive_range += kernel->values[i];
673 for ( i=0, u=-kernel->x; i < kernel->width; i++, u++)
674 kernel->positive_range += (
676 exp(-((double)(u*u))/(2.0*sigma*sigma))
677 /* / (MagickSQ2PI*sigma) */ );
680 kernel->maximum = kernel->values[ kernel->x ];
681 /* Note that neither methods above generate a normalized kernel,
682 ** though it gets close. The kernel may be 'clipped' by a user defined
683 ** radius, producing a smaller (darker) kernel. Also for very small
684 ** sigma's (> 0.1) the central value becomes larger than one, and thus
685 ** producing a very bright kernel.
688 /* Normalize the 1D Gaussian Kernel
690 ** Because of this the divisor in the above kernel generator is
691 ** not needed, so is not done above.
693 ScaleKernelInfo(kernel, 1.0, NormalizeValue); /* Normalize */
695 /* rotate the kernel by given angle */
696 RotateKernelInfo(kernel, args->xi);
701 sigma = fabs(args->sigma);
703 sigma = (sigma <= MagickEpsilon) ? 1.0 : sigma;
705 if ( args->rho < 1.0 )
706 kernel->width = GetOptimalKernelWidth1D(args->rho,sigma);
708 kernel->width = (unsigned long)args->rho;
709 kernel->x = kernel->y = 0;
711 kernel->negative_range = kernel->positive_range = 0.0;
712 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
713 kernel->height*sizeof(double));
714 if (kernel->values == (double *) NULL)
715 return(DestroyKernelInfo(kernel));
717 /* A comet blur is half a gaussian curve, so that the object is
718 ** blurred in one direction only. This may not be quite the right
719 ** curve so may change in the future. The function must be normalised.
723 sigma *= KernelRank; /* simplify expanded curve */
724 v = (long) kernel->width*KernelRank; /* start/end points to fit range */
725 (void) ResetMagickMemory(kernel->values,0, (size_t)
726 kernel->width*sizeof(double));
727 for ( u=0; u < v; u++) {
728 kernel->values[u/KernelRank] +=
729 exp(-((double)(u*u))/(2.0*sigma*sigma))
730 /* / (MagickSQ2PI*sigma/KernelRank) */ ;
732 for (i=0; i < (long) kernel->width; i++)
733 kernel->positive_range += kernel->values[i];
735 for ( i=0; i < (long) kernel->width; i++)
736 kernel->positive_range += (
738 exp(-((double)(i*i))/(2.0*sigma*sigma))
739 /* / (MagickSQ2PI*sigma) */ );
742 kernel->maximum = kernel->values[0];
744 ScaleKernelInfo(kernel, 1.0, NormalizeValue); /* Normalize */
745 RotateKernelInfo(kernel, args->xi); /* Rotate by angle */
748 /* Boolean Kernels */
749 case RectangleKernel:
753 if ( type == SquareKernel )
756 kernel->width = kernel->height = 3; /* default radius = 1 */
758 kernel->width = kernel->height = (unsigned long) (2*args->rho+1);
759 kernel->x = kernel->y = (long) (kernel->width-1)/2;
763 /* NOTE: user defaults set in "AcquireKernelInfo()" */
764 if ( args->rho < 1.0 || args->sigma < 1.0 )
765 return(DestroyKernelInfo(kernel)); /* invalid args given */
766 kernel->width = (unsigned long)args->rho;
767 kernel->height = (unsigned long)args->sigma;
768 if ( args->xi < 0.0 || args->xi > (double)kernel->width ||
769 args->psi < 0.0 || args->psi > (double)kernel->height )
770 return(DestroyKernelInfo(kernel)); /* invalid args given */
771 kernel->x = (long) args->xi;
772 kernel->y = (long) args->psi;
775 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
776 kernel->height*sizeof(double));
777 if (kernel->values == (double *) NULL)
778 return(DestroyKernelInfo(kernel));
780 /* set all kernel values to 1.0 */
781 u=(long) kernel->width*kernel->height;
782 for ( i=0; i < u; i++)
783 kernel->values[i] = scale;
784 kernel->minimum = kernel->maximum = scale; /* a flat shape */
785 kernel->positive_range = scale*u;
791 kernel->width = kernel->height = 3; /* default radius = 1 */
793 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
794 kernel->x = kernel->y = (long) (kernel->width-1)/2;
796 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
797 kernel->height*sizeof(double));
798 if (kernel->values == (double *) NULL)
799 return(DestroyKernelInfo(kernel));
801 /* set all kernel values within diamond area to scale given */
802 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
803 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
804 if ((labs(u)+labs(v)) <= (long)kernel->x)
805 kernel->positive_range += kernel->values[i] = args->sigma;
807 kernel->values[i] = nan;
808 kernel->minimum = kernel->maximum = args->sigma; /* a flat shape */
816 limit = (long)(args->rho*args->rho);
817 if (args->rho < 0.1) /* default radius approx 3.5 */
818 kernel->width = kernel->height = 7L, limit = 10L;
820 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
821 kernel->x = kernel->y = (long) (kernel->width-1)/2;
823 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
824 kernel->height*sizeof(double));
825 if (kernel->values == (double *) NULL)
826 return(DestroyKernelInfo(kernel));
828 /* set all kernel values within disk area to 1.0 */
829 for ( i=0, v= -kernel->y; v <= (long)kernel->y; v++)
830 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
831 if ((u*u+v*v) <= limit)
832 kernel->positive_range += kernel->values[i] = args->sigma;
834 kernel->values[i] = nan;
835 kernel->minimum = kernel->maximum = args->sigma; /* a flat shape */
841 kernel->width = kernel->height = 5; /* default radius 2 */
843 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
844 kernel->x = kernel->y = (long) (kernel->width-1)/2;
846 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
847 kernel->height*sizeof(double));
848 if (kernel->values == (double *) NULL)
849 return(DestroyKernelInfo(kernel));
851 /* set all kernel values along axises to 1.0 */
852 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
853 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
854 kernel->values[i] = (u == 0 || v == 0) ? args->sigma : nan;
855 kernel->minimum = kernel->maximum = args->sigma; /* a flat shape */
856 kernel->positive_range = args->sigma*(kernel->width*2.0 - 1.0);
859 /* Distance Measuring Kernels */
860 case ChebyshevKernel:
863 kernel->width = kernel->height = 3; /* default radius = 1 */
865 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
866 kernel->x = kernel->y = (long) (kernel->width-1)/2;
868 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
869 kernel->height*sizeof(double));
870 if (kernel->values == (double *) NULL)
871 return(DestroyKernelInfo(kernel));
873 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
874 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
875 kernel->positive_range += ( kernel->values[i] =
876 args->sigma*((labs(u)>labs(v)) ? labs(u) : labs(v)) );
877 kernel->maximum = kernel->values[0];
880 case ManhattenKernel:
883 kernel->width = kernel->height = 3; /* default radius = 1 */
885 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
886 kernel->x = kernel->y = (long) (kernel->width-1)/2;
888 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
889 kernel->height*sizeof(double));
890 if (kernel->values == (double *) NULL)
891 return(DestroyKernelInfo(kernel));
893 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
894 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
895 kernel->positive_range += ( kernel->values[i] =
896 args->sigma*(labs(u)+labs(v)) );
897 kernel->maximum = kernel->values[0];
900 case EuclideanKernel:
903 kernel->width = kernel->height = 3; /* default radius = 1 */
905 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
906 kernel->x = kernel->y = (long) (kernel->width-1)/2;
908 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
909 kernel->height*sizeof(double));
910 if (kernel->values == (double *) NULL)
911 return(DestroyKernelInfo(kernel));
913 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
914 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
915 kernel->positive_range += ( kernel->values[i] =
916 args->sigma*sqrt((double)(u*u+v*v)) );
917 kernel->maximum = kernel->values[0];
920 /* Undefined Kernels */
921 case LaplacianKernel:
924 perror("Kernel Type has not been defined yet");
927 /* Generate a No-Op minimal kernel - 1x1 pixel */
928 kernel->values=(double *)AcquireQuantumMemory((size_t)1,sizeof(double));
929 if (kernel->values == (double *) NULL)
930 return(DestroyKernelInfo(kernel));
931 kernel->width = kernel->height = 1;
932 kernel->x = kernel->x = 0;
933 kernel->type = UndefinedKernel;
935 kernel->positive_range =
936 kernel->values[0] = 1.0; /* a flat single-point no-op kernel! */
944 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
948 % C l o n e K e r n e l I n f o %
952 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
954 % CloneKernelInfo() creates a new clone of the given Kernel so that its can
955 % be modified without effecting the original. The cloned kernel should be
956 % destroyed using DestoryKernelInfo() when no longer needed.
958 % The format of the CloneKernelInfo method is:
960 % KernelInfo *CloneKernelInfo(const KernelInfo *kernel)
962 % A description of each parameter follows:
964 % o kernel: the Morphology/Convolution kernel to be cloned
967 MagickExport KernelInfo *CloneKernelInfo(const KernelInfo *kernel)
975 assert(kernel != (KernelInfo *) NULL);
976 kernel_info=(KernelInfo *) AcquireMagickMemory(sizeof(*kernel));
977 if (kernel_info == (KernelInfo *) NULL)
979 *kernel_info=(*kernel); /* copy values in structure */
980 kernel_info->values=(double *) AcquireQuantumMemory(kernel->width,
981 kernel->height*sizeof(double));
982 if (kernel_info->values == (double *) NULL)
983 return(DestroyKernelInfo(kernel_info));
984 for (i=0; i < (long) (kernel->width*kernel->height); i++)
985 kernel_info->values[i]=kernel->values[i];
990 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
994 % D e s t r o y K e r n e l I n f o %
998 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1000 % DestroyKernelInfo() frees the memory used by a Convolution/Morphology
1003 % The format of the DestroyKernelInfo method is:
1005 % KernelInfo *DestroyKernelInfo(KernelInfo *kernel)
1007 % A description of each parameter follows:
1009 % o kernel: the Morphology/Convolution kernel to be destroyed
1013 MagickExport KernelInfo *DestroyKernelInfo(KernelInfo *kernel)
1015 assert(kernel != (KernelInfo *) NULL);
1017 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
1018 kernel->height*sizeof(double));
1019 kernel->values=(double *)RelinquishMagickMemory(kernel->values);
1020 kernel=(KernelInfo *) RelinquishMagickMemory(kernel);
1025 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1029 % M o r p h o l o g y I m a g e C h a n n e l %
1033 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1035 % MorphologyImageChannel() applies a user supplied kernel to the image
1036 % according to the given mophology method.
1038 % The given kernel is assumed to have been pre-scaled appropriatally, usally
1039 % by the kernel generator.
1041 % The format of the MorphologyImage method is:
1043 % Image *MorphologyImage(const Image *image,MorphologyMethod method,
1044 % const long iterations,KernelInfo *kernel,ExceptionInfo *exception)
1045 % Image *MorphologyImageChannel(const Image *image, const ChannelType
1046 % channel,MorphologyMethod method,const long iterations,
1047 % KernelInfo *kernel,ExceptionInfo *exception)
1049 % A description of each parameter follows:
1051 % o image: the image.
1053 % o method: the morphology method to be applied.
1055 % o iterations: apply the operation this many times (or no change).
1056 % A value of -1 means loop until no change found.
1057 % How this is applied may depend on the morphology method.
1058 % Typically this is a value of 1.
1060 % o channel: the channel type.
1062 % o kernel: An array of double representing the morphology kernel.
1063 % Warning: kernel may be normalized for the Convolve method.
1065 % o exception: return any errors or warnings in this structure.
1068 % TODO: bias and auto-scale handling of the kernel for convolution
1069 % The given kernel is assumed to have been pre-scaled appropriatally, usally
1070 % by the kernel generator.
1075 /* Internal function
1076 * Apply the Low-Level Morphology Method using the given Kernel
1077 * Returning the number of pixels that changed.
1078 * Two pre-created images must be provided, no image is created.
1080 static unsigned long MorphologyApply(const Image *image, Image
1081 *result_image, const MorphologyMethod method, const ChannelType channel,
1082 const KernelInfo *kernel, ExceptionInfo *exception)
1084 #define MorphologyTag "Morphology/Image"
1101 /* Only the most basic morphology is actually performed by this routine */
1104 Apply Basic Morphology to Image.
1110 GetMagickPixelPacket(image,&bias);
1111 SetMagickPixelPacketBias(image,&bias);
1112 /* Future: handle auto-bias from user, based on kernel input */
1114 p_view=AcquireCacheView(image);
1115 q_view=AcquireCacheView(result_image);
1117 /* Some methods (including convolve) needs use a reflected kernel.
1118 * Adjust 'origin' offsets for this reflected kernel.
1123 case ErodeMorphology:
1124 case ErodeIntensityMorphology:
1125 /* kernel is user as is, without reflection */
1127 case ConvolveMorphology:
1128 case DilateMorphology:
1129 case DilateIntensityMorphology:
1130 case DistanceMorphology:
1131 /* kernel needs to used with reflection */
1132 offx = (long) kernel->width-offx-1;
1133 offy = (long) kernel->height-offy-1;
1136 perror("Not a low level Morpholgy Method");
1140 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1141 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
1143 for (y=0; y < (long) image->rows; y++)
1148 register const PixelPacket
1151 register const IndexPacket
1152 *restrict p_indexes;
1154 register PixelPacket
1157 register IndexPacket
1158 *restrict q_indexes;
1166 if (status == MagickFalse)
1168 p=GetCacheViewVirtualPixels(p_view, -offx, y-offy,
1169 image->columns+kernel->width, kernel->height, exception);
1170 q=GetCacheViewAuthenticPixels(q_view,0,y,result_image->columns,1,
1172 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
1177 p_indexes=GetCacheViewVirtualIndexQueue(p_view);
1178 q_indexes=GetCacheViewAuthenticIndexQueue(q_view);
1179 r = (image->columns+kernel->width)*offy+offx; /* constant */
1181 for (x=0; x < (long) image->columns; x++)
1189 register const double
1192 register const PixelPacket
1195 register const IndexPacket
1196 *restrict k_indexes;
1201 /* Copy input to ouput image for unused channels
1202 * This removes need for 'cloning' a new image every iteration
1205 if (image->colorspace == CMYKColorspace)
1206 q_indexes[x] = p_indexes[r];
1208 result.green=(MagickRealType) 0;
1209 result.blue=(MagickRealType) 0;
1210 result.opacity=(MagickRealType) 0;
1211 result.index=(MagickRealType) 0;
1213 case ConvolveMorphology:
1214 /* Set the user defined bias of the weighted average output
1216 ** FUTURE: provide some way for internal functions to disable
1217 ** user defined bias and scaling effects.
1221 case DilateMorphology:
1226 result.index = -MagickHuge;
1228 case ErodeMorphology:
1233 result.index = +MagickHuge;
1235 case DilateIntensityMorphology:
1236 case ErodeIntensityMorphology:
1237 result.red = 0.0; /* flag indicating first match found */
1240 /* Otherwise just start with the original pixel value */
1241 result.red = (MagickRealType) p[r].red;
1242 result.green = (MagickRealType) p[r].green;
1243 result.blue = (MagickRealType) p[r].blue;
1244 result.opacity = QuantumRange - (MagickRealType) p[r].opacity;
1245 if ( image->colorspace == CMYKColorspace)
1246 result.index = (MagickRealType) p_indexes[r];
1251 case ConvolveMorphology:
1252 /* Weighted Average of pixels using reflected kernel
1254 ** NOTE for correct working of this operation for asymetrical
1255 ** kernels, the kernel needs to be applied in its reflected form.
1256 ** That is its values needs to be reversed.
1258 ** Correlation is actually the same as this but without reflecting
1259 ** the kernel, and thus 'lower-level' that Convolution. However
1260 ** as Convolution is the more common method used, and it does not
1261 ** really cost us much in terms of processing to use a reflected
1262 ** kernel it is Convolution that is implemented.
1264 ** Correlation will have its kernel reflected before calling
1265 ** this function to do a Convolve.
1267 ** For more details of Correlation vs Convolution see
1268 ** http://www.cs.umd.edu/~djacobs/CMSC426/Convolution.pdf
1270 if (((channel & OpacityChannel) == 0) ||
1271 (image->matte == MagickFalse))
1273 /* Convolution without transparency effects */
1274 k = &kernel->values[ kernel->width*kernel->height-1 ];
1276 k_indexes = p_indexes;
1277 for (v=0; v < (long) kernel->height; v++) {
1278 for (u=0; u < (long) kernel->width; u++, k--) {
1279 if ( IsNan(*k) ) continue;
1280 result.red += (*k)*k_pixels[u].red;
1281 result.green += (*k)*k_pixels[u].green;
1282 result.blue += (*k)*k_pixels[u].blue;
1283 /* result.opacity += not involved here */
1284 if ( image->colorspace == CMYKColorspace)
1285 result.index += (*k)*k_indexes[u];
1287 k_pixels += image->columns+kernel->width;
1288 k_indexes += image->columns+kernel->width;
1292 { /* Kernel & Alpha weighted Convolution */
1294 alpha, /* alpha value * kernel weighting */
1295 gamma; /* weighting divisor */
1298 k = &kernel->values[ kernel->width*kernel->height-1 ];
1300 k_indexes = p_indexes;
1301 for (v=0; v < (long) kernel->height; v++) {
1302 for (u=0; u < (long) kernel->width; u++, k--) {
1303 if ( IsNan(*k) ) continue;
1304 alpha=(*k)*(QuantumScale*(QuantumRange-
1305 k_pixels[u].opacity));
1307 result.red += alpha*k_pixels[u].red;
1308 result.green += alpha*k_pixels[u].green;
1309 result.blue += alpha*k_pixels[u].blue;
1310 result.opacity += (*k)*(QuantumRange-k_pixels[u].opacity);
1311 if ( image->colorspace == CMYKColorspace)
1312 result.index += alpha*k_indexes[u];
1314 k_pixels += image->columns+kernel->width;
1315 k_indexes += image->columns+kernel->width;
1317 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
1318 result.red *= gamma;
1319 result.green *= gamma;
1320 result.blue *= gamma;
1321 result.opacity *= gamma;
1322 result.index *= gamma;
1326 case ErodeMorphology:
1327 /* Minimize Value within kernel neighbourhood
1329 ** NOTE that the kernel is not reflected for this operation!
1331 ** NOTE: in normal Greyscale Morphology, the kernel value should
1332 ** be added to the real value, this is currently not done, due to
1333 ** the nature of the boolean kernels being used.
1337 k_indexes = p_indexes;
1338 for (v=0; v < (long) kernel->height; v++) {
1339 for (u=0; u < (long) kernel->width; u++, k++) {
1340 if ( IsNan(*k) || (*k) < 0.5 ) continue;
1341 Minimize(result.red, (double) k_pixels[u].red);
1342 Minimize(result.green, (double) k_pixels[u].green);
1343 Minimize(result.blue, (double) k_pixels[u].blue);
1344 Minimize(result.opacity,
1345 QuantumRange-(double) k_pixels[u].opacity);
1346 if ( image->colorspace == CMYKColorspace)
1347 Minimize(result.index, (double) k_indexes[u]);
1349 k_pixels += image->columns+kernel->width;
1350 k_indexes += image->columns+kernel->width;
1354 case DilateMorphology:
1355 /* Maximize Value within kernel neighbourhood
1357 ** NOTE for correct working of this operation for asymetrical
1358 ** kernels, the kernel needs to be applied in its reflected form.
1359 ** That is its values needs to be reversed.
1361 ** NOTE: in normal Greyscale Morphology, the kernel value should
1362 ** be added to the real value, this is currently not done, due to
1363 ** the nature of the boolean kernels being used.
1366 k = &kernel->values[ kernel->width*kernel->height-1 ];
1368 k_indexes = p_indexes;
1369 for (v=0; v < (long) kernel->height; v++) {
1370 for (u=0; u < (long) kernel->width; u++, k--) {
1371 if ( IsNan(*k) || (*k) < 0.5 ) continue;
1372 Maximize(result.red, (double) k_pixels[u].red);
1373 Maximize(result.green, (double) k_pixels[u].green);
1374 Maximize(result.blue, (double) k_pixels[u].blue);
1375 Maximize(result.opacity,
1376 QuantumRange-(double) k_pixels[u].opacity);
1377 if ( image->colorspace == CMYKColorspace)
1378 Maximize(result.index, (double) k_indexes[u]);
1380 k_pixels += image->columns+kernel->width;
1381 k_indexes += image->columns+kernel->width;
1385 case ErodeIntensityMorphology:
1386 /* Select Pixel with Minimum Intensity within kernel neighbourhood
1388 ** WARNING: the intensity test fails for CMYK and does not
1389 ** take into account the moderating effect of teh alpha channel
1390 ** on the intensity.
1392 ** NOTE that the kernel is not reflected for this operation!
1396 k_indexes = p_indexes;
1397 for (v=0; v < (long) kernel->height; v++) {
1398 for (u=0; u < (long) kernel->width; u++, k++) {
1399 if ( IsNan(*k) || (*k) < 0.5 ) continue;
1400 if ( result.red == 0.0 ||
1401 PixelIntensity(&(k_pixels[u])) < PixelIntensity(q) ) {
1402 /* copy the whole pixel - no channel selection */
1404 if ( result.red > 0.0 ) changed++;
1408 k_pixels += image->columns+kernel->width;
1409 k_indexes += image->columns+kernel->width;
1413 case DilateIntensityMorphology:
1414 /* Select Pixel with Maximum Intensity within kernel neighbourhood
1416 ** WARNING: the intensity test fails for CMYK and does not
1417 ** take into account the moderating effect of teh alpha channel
1418 ** on the intensity.
1420 ** NOTE for correct working of this operation for asymetrical
1421 ** kernels, the kernel needs to be applied in its reflected form.
1422 ** That is its values needs to be reversed.
1424 k = &kernel->values[ kernel->width*kernel->height-1 ];
1426 k_indexes = p_indexes;
1427 for (v=0; v < (long) kernel->height; v++) {
1428 for (u=0; u < (long) kernel->width; u++, k--) {
1429 if ( IsNan(*k) || (*k) < 0.5 ) continue; /* boolean kernel */
1430 if ( result.red == 0.0 ||
1431 PixelIntensity(&(k_pixels[u])) > PixelIntensity(q) ) {
1432 /* copy the whole pixel - no channel selection */
1434 if ( result.red > 0.0 ) changed++;
1438 k_pixels += image->columns+kernel->width;
1439 k_indexes += image->columns+kernel->width;
1443 case DistanceMorphology:
1444 /* Add kernel Value and select the minimum value found.
1445 ** The result is a iterative distance from edge of image shape.
1447 ** All Distance Kernels are symetrical, but that may not always
1448 ** be the case. For example how about a distance from left edges?
1449 ** To work correctly with asymetrical kernels the reflected kernel
1450 ** needs to be applied.
1453 /* No need to do distance morphology if original value is zero
1454 ** Unfortunatally I have not been able to get this right
1455 ** when channel selection also becomes involved. -- Arrgghhh
1457 if ( ((channel & RedChannel) == 0 && p[r].red == 0)
1458 || ((channel & GreenChannel) == 0 && p[r].green == 0)
1459 || ((channel & BlueChannel) == 0 && p[r].blue == 0)
1460 || ((channel & OpacityChannel) == 0 && p[r].opacity == 0)
1461 || (( (channel & IndexChannel) == 0
1462 || image->colorspace != CMYKColorspace
1463 ) && p_indexes[x] ==0 )
1467 k = &kernel->values[ kernel->width*kernel->height-1 ];
1469 k_indexes = p_indexes;
1470 for (v=0; v < (long) kernel->height; v++) {
1471 for (u=0; u < (long) kernel->width; u++, k--) {
1472 if ( IsNan(*k) ) continue;
1473 Minimize(result.red, (*k)+k_pixels[u].red);
1474 Minimize(result.green, (*k)+k_pixels[u].green);
1475 Minimize(result.blue, (*k)+k_pixels[u].blue);
1476 Minimize(result.opacity, (*k)+QuantumRange-k_pixels[u].opacity);
1477 if ( image->colorspace == CMYKColorspace)
1478 Minimize(result.index, (*k)+k_indexes[u]);
1480 k_pixels += image->columns+kernel->width;
1481 k_indexes += image->columns+kernel->width;
1485 case UndefinedMorphology:
1487 break; /* Do nothing */
1490 case UndefinedMorphology:
1491 case DilateIntensityMorphology:
1492 case ErodeIntensityMorphology:
1493 break; /* full pixel was directly assigned - not a channel method */
1495 /* Assign the results */
1496 if ((channel & RedChannel) != 0)
1497 q->red = ClampToQuantum(result.red);
1498 if ((channel & GreenChannel) != 0)
1499 q->green = ClampToQuantum(result.green);
1500 if ((channel & BlueChannel) != 0)
1501 q->blue = ClampToQuantum(result.blue);
1502 if ((channel & OpacityChannel) != 0
1503 && image->matte == MagickTrue )
1504 q->opacity = ClampToQuantum(QuantumRange-result.opacity);
1505 if ((channel & IndexChannel) != 0
1506 && image->colorspace == CMYKColorspace)
1507 q_indexes[x] = ClampToQuantum(result.index);
1510 if ( ( p[r].red != q->red )
1511 || ( p[r].green != q->green )
1512 || ( p[r].blue != q->blue )
1513 || ( p[r].opacity != q->opacity )
1514 || ( image->colorspace == CMYKColorspace &&
1515 p_indexes[r] != q_indexes[x] ) )
1516 changed++; /* The pixel had some value changed! */
1520 sync=SyncCacheViewAuthenticPixels(q_view,exception);
1521 if (sync == MagickFalse)
1523 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1528 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1529 #pragma omp critical (MagickCore_MorphologyImage)
1531 proceed=SetImageProgress(image,MorphologyTag,progress++,image->rows);
1532 if (proceed == MagickFalse)
1536 result_image->type=image->type;
1537 q_view=DestroyCacheView(q_view);
1538 p_view=DestroyCacheView(p_view);
1539 return(status ? (unsigned long) changed : 0);
1543 MagickExport Image *MorphologyImage(const Image *image, const MorphologyMethod
1544 method, const long iterations,const KernelInfo *kernel, ExceptionInfo
1550 morphology_image=MorphologyImageChannel(image,DefaultChannels,method,
1551 iterations,kernel,exception);
1552 return(morphology_image);
1556 MagickExport Image *MorphologyImageChannel(const Image *image,
1557 const ChannelType channel,const MorphologyMethod method,
1558 const long iterations,const KernelInfo *kernel,ExceptionInfo *exception)
1581 assert(image != (Image *) NULL);
1582 assert(image->signature == MagickSignature);
1583 assert(kernel != (KernelInfo *) NULL);
1584 assert(kernel->signature == MagickSignature);
1585 assert(exception != (ExceptionInfo *) NULL);
1586 assert(exception->signature == MagickSignature);
1588 if ( iterations == 0 )
1589 return((Image *)NULL); /* null operation - nothing to do! */
1591 /* kernel must be valid at this point
1592 * (except maybe for posible future morphology methods like "Prune"
1594 assert(kernel != (KernelInfo *)NULL);
1596 count = 0; /* interation count */
1597 changed = 1; /* if compound method assume image was changed */
1598 curr_kernel = (KernelInfo *)kernel; /* allow kernel and method */
1599 curr_method = method; /* to be changed as nessary */
1601 limit = (unsigned long) iterations;
1602 if ( iterations < 0 )
1603 limit = image->columns > image->rows ? image->columns : image->rows;
1605 /* Third-level morphology methods */
1606 grad_image=(Image *) NULL;
1607 switch( curr_method ) {
1608 case EdgeMorphology:
1609 grad_image = MorphologyImageChannel(image, channel,
1610 DilateMorphology, iterations, curr_kernel, exception);
1612 case EdgeInMorphology:
1613 curr_method = ErodeMorphology;
1615 case EdgeOutMorphology:
1616 curr_method = DilateMorphology;
1618 case TopHatMorphology:
1619 curr_method = OpenMorphology;
1621 case BottomHatMorphology:
1622 curr_method = CloseMorphology;
1625 break; /* not a third-level method */
1628 /* Second-level morphology methods */
1629 switch( curr_method ) {
1630 case OpenMorphology:
1631 /* Open is a Erode then a Dilate without reflection */
1632 new_image = MorphologyImageChannel(image, channel,
1633 ErodeMorphology, iterations, curr_kernel, exception);
1634 if (new_image == (Image *) NULL)
1635 return((Image *) NULL);
1636 curr_method = DilateMorphology;
1638 case OpenIntensityMorphology:
1639 new_image = MorphologyImageChannel(image, channel,
1640 ErodeIntensityMorphology, iterations, curr_kernel, exception);
1641 if (new_image == (Image *) NULL)
1642 return((Image *) NULL);
1643 curr_method = DilateIntensityMorphology;
1646 case CloseMorphology:
1647 /* Close is a Dilate then Erode using reflected kernel */
1648 /* A reflected kernel is needed for a Close */
1649 if ( curr_kernel == kernel )
1650 curr_kernel = CloneKernelInfo(kernel);
1651 RotateKernelInfo(curr_kernel,180);
1652 new_image = MorphologyImageChannel(image, channel,
1653 DilateMorphology, iterations, curr_kernel, exception);
1654 if (new_image == (Image *) NULL)
1655 return((Image *) NULL);
1656 curr_method = ErodeMorphology;
1658 case CloseIntensityMorphology:
1659 /* A reflected kernel is needed for a Close */
1660 if ( curr_kernel == kernel )
1661 curr_kernel = CloneKernelInfo(kernel);
1662 RotateKernelInfo(curr_kernel,180);
1663 new_image = MorphologyImageChannel(image, channel,
1664 DilateIntensityMorphology, iterations, curr_kernel, exception);
1665 if (new_image == (Image *) NULL)
1666 return((Image *) NULL);
1667 curr_method = ErodeIntensityMorphology;
1670 case CorrelateMorphology:
1671 /* A Correlation is actually a Convolution with a reflected kernel.
1672 ** However a Convolution is a weighted sum with a reflected kernel.
1673 ** It may seem stange to convert a Correlation into a Convolution
1674 ** as the Correleation is the simplier method, but Convolution is
1675 ** much more commonly used, and it makes sense to implement it directly
1676 ** so as to avoid the need to duplicate the kernel when it is not
1677 ** required (which is typically the default).
1679 if ( curr_kernel == kernel )
1680 curr_kernel = CloneKernelInfo(kernel);
1681 RotateKernelInfo(curr_kernel,180);
1682 curr_method = ConvolveMorphology;
1683 /* FALL-THRU into Correlate (weigthed sum without reflection) */
1685 case ConvolveMorphology:
1686 /* Scale or Normalize kernel, according to user wishes
1687 ** before using it for the Convolve/Correlate method.
1689 ** FUTURE: provide some way for internal functions to disable
1690 ** user bias and scaling effects.
1692 artifact = GetImageArtifact(image,"convolve:scale");
1693 if ( artifact != (char *)NULL ) {
1699 if ( curr_kernel == kernel )
1700 curr_kernel = CloneKernelInfo(kernel);
1703 flags = (GeometryFlags) ParseGeometry(artifact, &args);
1704 ScaleKernelInfo(curr_kernel, args.rho, flags);
1706 /* FALL-THRU to do the first, and typically the only iteration */
1709 /* Do a single iteration using the Low-Level Morphology method!
1710 ** This ensures a "new_image" has been generated, but allows us to skip
1711 ** the creation of 'old_image' if no more iterations are needed.
1713 ** The "curr_method" should also be set to a low-level method that is
1714 ** understood by the MorphologyApply() internal function.
1716 new_image=CloneImage(image,0,0,MagickTrue,exception);
1717 if (new_image == (Image *) NULL)
1718 return((Image *) NULL);
1719 if (SetImageStorageClass(new_image,DirectClass) == MagickFalse)
1721 InheritException(exception,&new_image->exception);
1722 new_image=DestroyImage(new_image);
1723 return((Image *) NULL);
1725 changed = MorphologyApply(image,new_image,curr_method,channel,curr_kernel,
1728 if ( GetImageArtifact(image,"verbose") != (const char *) NULL )
1729 fprintf(stderr, "Morphology %s:%ld => Changed %lu\n",
1730 MagickOptionToMnemonic(MagickMorphologyOptions, curr_method),
1735 /* At this point the "curr_method" should not only be set to a low-level
1736 ** method that is understood by the MorphologyApply() internal function,
1737 ** but "new_image" should now be defined, as the image to apply the
1738 ** "curr_method" to.
1741 /* Repeat the low-level morphology until count or no change reached */
1742 if ( count < (long) limit && changed > 0 ) {
1743 old_image = CloneImage(new_image,0,0,MagickTrue,exception);
1744 if (old_image == (Image *) NULL)
1745 return(DestroyImage(new_image));
1746 if (SetImageStorageClass(old_image,DirectClass) == MagickFalse)
1748 InheritException(exception,&old_image->exception);
1749 old_image=DestroyImage(old_image);
1750 return(DestroyImage(new_image));
1752 while( count < (long) limit && changed != 0 )
1754 Image *tmp = old_image;
1755 old_image = new_image;
1757 changed = MorphologyApply(old_image,new_image,curr_method,channel,
1758 curr_kernel, exception);
1760 if ( GetImageArtifact(image,"verbose") != (const char *) NULL )
1761 fprintf(stderr, "Morphology %s:%ld => Changed %lu\n",
1762 MagickOptionToMnemonic(MagickMorphologyOptions, curr_method),
1765 old_image=DestroyImage(old_image);
1768 /* We are finished with kernel - destroy it if we made a clone */
1769 if ( curr_kernel != kernel )
1770 curr_kernel=DestroyKernelInfo(curr_kernel);
1772 /* Third-level Subtractive methods post-processing
1774 ** The removal of any 'Sync' channel flag in the Image Compositon below
1775 ** ensures the compose method is applied in a purely mathematical way, only
1776 ** the selected channels, without any normal 'alpha blending' normally
1777 ** associated with the compose method.
1779 ** Note "method" here is the 'original' morphological method, and not the
1780 ** 'current' morphological method used above to generate "new_image".
1783 case EdgeOutMorphology:
1784 case EdgeInMorphology:
1785 case TopHatMorphology:
1786 case BottomHatMorphology:
1787 /* Get Difference relative to the original image */
1788 (void) CompositeImageChannel(new_image, (ChannelType) (channel & ~SyncChannels),
1789 DifferenceCompositeOp, image, 0, 0);
1791 case EdgeMorphology:
1792 /* Difference the Eroded image from the saved Dilated image */
1793 (void) CompositeImageChannel(new_image, (ChannelType) (channel & ~SyncChannels),
1794 DifferenceCompositeOp, grad_image, 0, 0);
1795 grad_image=DestroyImage(grad_image);
1805 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1809 + R o t a t e K e r n e l I n f o %
1813 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1815 % RotateKernelInfo() rotates the kernel by the angle given. Currently it is
1816 % restricted to 90 degree angles, but this may be improved in the future.
1818 % The format of the RotateKernelInfo method is:
1820 % void RotateKernelInfo(KernelInfo *kernel, double angle)
1822 % A description of each parameter follows:
1824 % o kernel: the Morphology/Convolution kernel
1826 % o angle: angle to rotate in degrees
1828 % This function is only internel to this module, as it is not finalized,
1829 % especially with regard to non-orthogonal angles, and rotation of larger
1832 static void RotateKernelInfo(KernelInfo *kernel, double angle)
1834 /* WARNING: Currently assumes the kernel (rightly) is horizontally symetrical
1836 ** TODO: expand beyond simple 90 degree rotates, flips and flops
1839 /* Modulus the angle */
1840 angle = fmod(angle, 360.0);
1844 if ( 315.0 < angle || angle <= 45.0 )
1845 return; /* no change! - At least at this time */
1847 switch (kernel->type) {
1848 /* These built-in kernels are cylindrical kernels, rotating is useless */
1849 case GaussianKernel:
1850 case LaplacianKernel:
1854 case ChebyshevKernel:
1855 case ManhattenKernel:
1856 case EuclideanKernel:
1859 /* These may be rotatable at non-90 angles in the future */
1860 /* but simply rotating them in multiples of 90 degrees is useless */
1866 /* These only allows a +/-90 degree rotation (by transpose) */
1867 /* A 180 degree rotation is useless */
1869 case RectangleKernel:
1870 if ( 135.0 < angle && angle <= 225.0 )
1872 if ( 225.0 < angle && angle <= 315.0 )
1876 /* these are freely rotatable in 90 degree units */
1878 case UndefinedKernel:
1879 case UserDefinedKernel:
1882 if ( 135.0 < angle && angle <= 225.0 )
1884 /* For a 180 degree rotation - also know as a reflection */
1885 /* This is actually a very very common operation! */
1886 /* Basically all that is needed is a reversal of the kernel data! */
1893 for ( i=0, j=kernel->width*kernel->height-1; i<j; i++, j--)
1894 t=k[i], k[i]=k[j], k[j]=t;
1896 kernel->x = (long) kernel->width - kernel->x - 1;
1897 kernel->y = (long) kernel->height - kernel->y - 1;
1898 angle = fmod(angle+180.0, 360.0);
1900 if ( 45.0 < angle && angle <= 135.0 )
1901 { /* Do a transpose and a flop, of the image, which results in a 90
1902 * degree rotation using two mirror operations.
1904 * WARNING: this assumes the original image was a 1 dimentional image
1905 * but currently that is the only built-ins it is applied to.
1909 t = (long) kernel->width;
1910 kernel->width = kernel->height;
1911 kernel->height = (unsigned long) t;
1913 kernel->x = kernel->y;
1915 angle = fmod(450.0 - angle, 360.0);
1917 /* At this point angle should be between -45 (315) and +45 degrees
1918 * In the future some form of non-orthogonal angled rotates could be
1919 * performed here, posibily with a linear kernel restriction.
1923 Not currently in use!
1924 { /* Do a flop, this assumes kernel is horizontally symetrical.
1925 * Each row of the kernel needs to be reversed!
1934 for ( y=0, k=kernel->values; y < kernel->height; y++, k+=kernel->width)
1935 for ( x=0, r=kernel->width-1; x<kernel->width/2; x++, r--)
1936 t=k[x], k[x]=k[r], k[r]=t;
1938 kernel->x = kernel->width - kernel->x - 1;
1939 angle = fmod(angle+180.0, 360.0);
1946 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1950 % S c a l e K e r n e l I n f o %
1954 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1956 % ScaleKernelInfo() scales the kernel by the given amount, with or without
1957 % normalization of the sum of the kernel values.
1959 % By default (no flags given) the values within the kernel is scaled
1960 % according the given scaling factor.
1962 % If any 'normalize_flags' are given the kernel will be normalized and then
1963 % further scaled by the scaleing factor value given. A 'PercentValue' flag
1964 % will cause the given scaling factor to be divided by one hundred percent.
1966 % Kernel normalization ('normalize_flags' given) is designed to ensure that
1967 % any use of the kernel scaling factor with 'Convolve' or 'Correlate'
1968 % morphology methods will fall into -1.0 to +1.0 range. Note however that
1969 % for non-HDRI versions of IM this may cause images to have any negative
1970 % results clipped, unless some 'clip' any negative output from 'Convolve'
1971 % with the use of some kernels.
1973 % More specifically. Kernels which only contain positive values (such as a
1974 % 'Gaussian' kernel) will be scaled so that those values sum to +1.0,
1975 % ensuring a 0.0 to +1.0 convolution output range for non-HDRI images.
1977 % For Kernels that contain some negative values, (such as 'Sharpen' kernels)
1978 % the kernel will be scaled by the absolute of the sum of kernel values, so
1979 % that it will generally fall within the +/- 1.0 range.
1981 % For kernels whose values sum to zero, (such as 'Laplician' kernels) kernel
1982 % will be scaled by just the sum of the postive values, so that its output
1983 % range will again fall into the +/- 1.0 range.
1985 % For special kernels designed for locating shapes using 'Correlate', (often
1986 % only containing +1 and -1 values, representing foreground/brackground
1987 % matching) a special normalization method is provided to scale the positive
1988 % values seperatally to those of the negative values, so the kernel will be
1989 % forced to become a zero-sum kernel better suited to such searches.
1991 % WARNING: Correct normalization of the kernal assumes that the '*_range'
1992 % attributes within the kernel structure have been correctly set during the
1995 % NOTE: The values used for 'normalize_flags' have been selected specifically
1996 % to match the use of geometry options, so that '!' means NormalizeValue, '^'
1997 % means CorrelateNormalizeValue, and '%' means PercentValue. All other
1998 % GeometryFlags values are ignored.
2000 % The format of the ScaleKernelInfo method is:
2002 % void ScaleKernelInfo(KernelInfo *kernel, const double scaling_factor,
2003 % const MagickStatusType normalize_flags )
2005 % A description of each parameter follows:
2007 % o kernel: the Morphology/Convolution kernel
2010 % multiply all values (after normalization) by this factor if not
2011 % zero. If the kernel is normalized regardless of any flags.
2013 % o normalize_flags:
2014 % GeometryFlags defining normalization method to use.
2015 % specifically: NormalizeValue, CorrelateNormalizeValue,
2016 % and/or PercentValue
2018 % This function is internal to this module only at this time, but can be
2019 % exported to other modules if needed.
2021 MagickExport void ScaleKernelInfo(KernelInfo *kernel,
2022 const double scaling_factor,const GeometryFlags normalize_flags)
2032 if ( (normalize_flags&NormalizeValue) != 0 ) {
2033 /* normalize kernel appropriately */
2034 if ( fabs(kernel->positive_range + kernel->negative_range) > MagickEpsilon )
2035 pos_scale = fabs(kernel->positive_range + kernel->negative_range);
2037 pos_scale = kernel->positive_range; /* special zero-summing kernel */
2039 /* force kernel into being a normalized zero-summing kernel */
2040 if ( (normalize_flags&CorrelateNormalizeValue) != 0 ) {
2041 pos_scale = ( fabs(kernel->positive_range) > MagickEpsilon )
2042 ? kernel->positive_range : 1.0;
2043 neg_scale = ( fabs(kernel->negative_range) > MagickEpsilon )
2044 ? -kernel->negative_range : 1.0;
2047 neg_scale = pos_scale;
2049 /* finialize scaling_factor for positive and negative components */
2050 pos_scale = scaling_factor/pos_scale;
2051 neg_scale = scaling_factor/neg_scale;
2052 if ( (normalize_flags&PercentValue) != 0 ) {
2057 for (i=0; i < (long) (kernel->width*kernel->height); i++)
2058 if ( ! IsNan(kernel->values[i]) )
2059 kernel->values[i] *= (kernel->values[i] >= 0) ? pos_scale : neg_scale;
2061 /* convolution output range */
2062 kernel->positive_range *= pos_scale;
2063 kernel->negative_range *= neg_scale;
2064 /* maximum and minimum values in kernel */
2065 kernel->maximum *= (kernel->maximum >= 0.0) ? pos_scale : neg_scale;
2066 kernel->minimum *= (kernel->minimum >= 0.0) ? pos_scale : neg_scale;
2068 /* swap kernel settings if user scaling factor is negative */
2069 if ( scaling_factor < MagickEpsilon ) {
2071 t = kernel->positive_range;
2072 kernel->positive_range = kernel->negative_range;
2073 kernel->negative_range = t;
2074 t = kernel->maximum;
2075 kernel->maximum = kernel->minimum;
2076 kernel->minimum = 1;
2083 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2087 + S h o w K e r n e l I n f o %
2091 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2093 % ShowKernelInfo() outputs the details of the given kernel defination to
2094 % standard error, generally due to a users 'showkernel' option request.
2096 % The format of the ShowKernel method is:
2098 % void ShowKernelInfo(KernelInfo *kernel)
2100 % A description of each parameter follows:
2102 % o kernel: the Morphology/Convolution kernel
2104 % This function is internal to this module only at this time. That may change
2107 MagickExport void ShowKernelInfo(KernelInfo *kernel)
2113 "Kernel \"%s\" of size %lux%lu%+ld%+ld with values from %.*lg to %.*lg\n",
2114 MagickOptionToMnemonic(MagickKernelOptions, kernel->type),
2115 kernel->width, kernel->height,
2116 kernel->x, kernel->y,
2117 GetMagickPrecision(), kernel->minimum,
2118 GetMagickPrecision(), kernel->maximum);
2119 fprintf(stderr, "Forming convolution output range from %.*lg to %.*lg%s\n",
2120 GetMagickPrecision(), kernel->negative_range,
2121 GetMagickPrecision(), kernel->positive_range,
2122 /*kernel->normalized == MagickTrue ? " (normalized)" : */ "" );
2123 for (i=v=0; v < (long) kernel->height; v++) {
2124 fprintf(stderr,"%2ld:",v);
2125 for (u=0; u < (long) kernel->width; u++, i++)
2126 if ( IsNan(kernel->values[i]) )
2127 fprintf(stderr," %*s", GetMagickPrecision()+2, "nan");
2129 fprintf(stderr," %*.*lg", GetMagickPrecision()+2,
2130 GetMagickPrecision(), kernel->values[i]);
2131 fprintf(stderr,"\n");
2136 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2140 + Z e r o K e r n e l N a n s %
2144 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2146 % ZeroKernelNans() replaces any special 'nan' value that may be present in
2147 % the kernel with a zero value. This is typically done when the kernel will
2148 % be used in special hardware (GPU) convolution processors, to simply
2151 % The format of the ZeroKernelNans method is:
2153 % voidZeroKernelNans (KernelInfo *kernel)
2155 % A description of each parameter follows:
2157 % o kernel: the Morphology/Convolution kernel
2159 % FUTURE: return the information in a string for API usage.
2161 MagickExport void ZeroKernelNans(KernelInfo *kernel)
2166 for (i=0; i < (long) (kernel->width*kernel->height); i++)
2167 if ( IsNan(kernel->values[i]) )
2168 kernel->values[i] = 0.0;