MagickCore  7.1.2-29
Convert, Edit, Or Compose Bitmap Images
morphology.c
1 /*
2 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3 % %
4 % %
5 % %
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 %
11 % %
12 % %
13 % MagickCore Morphology Methods %
14 % %
15 % Software Design %
16 % Anthony Thyssen %
17 % January 2010 %
18 % %
19 % %
20 % Copyright @ 1999 ImageMagick Studio LLC, a non-profit organization %
21 % dedicated to making software imaging solutions freely available. %
22 % %
23 % You may not use this file except in compliance with the License. You may %
24 % obtain a copy of the License at %
25 % %
26 % https://imagemagick.org/license/ %
27 % %
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. %
33 % %
34 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
35 %
36 % Morphology is the application of various kernels, of any size or shape, to an
37 % image in various ways (typically binary, but not always).
38 %
39 % Convolution (weighted sum or average) is just one specific type of
40 % morphology. Just one that is very common for image blurring and sharpening
41 % effects. Not only 2D Gaussian blurring, but also 2-pass 1D Blurring.
42 %
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.
47 */
48 ␌
49 /*
50  Include declarations.
51 */
52 #include "MagickCore/studio.h"
53 #include "MagickCore/artifact.h"
54 #include "MagickCore/cache-view.h"
55 #include "MagickCore/channel.h"
56 #include "MagickCore/color-private.h"
57 #include "MagickCore/enhance.h"
58 #include "MagickCore/exception.h"
59 #include "MagickCore/exception-private.h"
60 #include "MagickCore/gem.h"
61 #include "MagickCore/gem-private.h"
62 #include "MagickCore/image.h"
63 #include "MagickCore/image-private.h"
64 #include "MagickCore/linked-list.h"
65 #include "MagickCore/list.h"
66 #include "MagickCore/magick.h"
67 #include "MagickCore/memory_.h"
68 #include "MagickCore/memory-private.h"
69 #include "MagickCore/monitor-private.h"
70 #include "MagickCore/morphology.h"
71 #include "MagickCore/morphology-private.h"
72 #include "MagickCore/option.h"
73 #include "MagickCore/pixel-accessor.h"
74 #include "MagickCore/prepress.h"
75 #include "MagickCore/quantize.h"
76 #include "MagickCore/resource_.h"
77 #include "MagickCore/registry.h"
78 #include "MagickCore/semaphore.h"
79 #include "MagickCore/splay-tree.h"
80 #include "MagickCore/statistic.h"
81 #include "MagickCore/string_.h"
82 #include "MagickCore/string-private.h"
83 #include "MagickCore/thread-private.h"
84 #include "MagickCore/token.h"
85 #include "MagickCore/utility.h"
86 #include "MagickCore/utility-private.h"
87 ␌
88 /*
89  Other global definitions used by module.
90 */
91 #define Minimize(assign,value) assign=MagickMin(assign,value)
92 #define Maximize(assign,value) assign=MagickMax(assign,value)
93 
94 /* Integer Factorial Function - for a Binomial kernel */
95 static inline size_t fact(size_t n)
96 {
97  size_t f,l;
98  for(f=1, l=2; l <= n; f=f*l, l++);
99  return(f);
100 }
101 
102 /* Currently these are only internal to this module */
103 static void
104  CalcKernelMetaData(KernelInfo *),
105  ExpandMirrorKernelInfo(KernelInfo *),
106  ExpandRotateKernelInfo(KernelInfo *, const double),
107  RotateKernelInfo(KernelInfo *, double);
108 ␌
109 
110 /* Quick function to find last kernel in a kernel list */
111 static inline KernelInfo *LastKernelInfo(KernelInfo *kernel)
112 {
113  while (kernel->next != (KernelInfo *) NULL)
114  kernel=kernel->next;
115  return(kernel);
116 }
117 
118 /*
119 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
120 % %
121 % %
122 % %
123 % A c q u i r e K e r n e l I n f o %
124 % %
125 % %
126 % %
127 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
128 %
129 % AcquireKernelInfo() takes the given string (generally supplied by the
130 % user) and converts it into a Morphology/Convolution Kernel. This allows
131 % users to specify a kernel from a number of pre-defined kernels, or to fully
132 % specify their own kernel for a specific Convolution or Morphology
133 % Operation.
134 %
135 % The kernel so generated can be any rectangular array of floating point
136 % values (doubles) with the 'control point' or 'pixel being affected'
137 % anywhere within that array of values.
138 %
139 % Previously IM was restricted to a square of odd size using the exact
140 % center as origin, this is no longer the case, and any rectangular kernel
141 % with any value being declared the origin. This in turn allows the use of
142 % highly asymmetrical kernels.
143 %
144 % The floating point values in the kernel can also include a special value
145 % known as 'nan' or 'not a number' to indicate that this value is not part
146 % of the kernel array. This allows you to shaped the kernel within its
147 % rectangular area. That is 'nan' values provide a 'mask' for the kernel
148 % shape. However at least one non-nan value must be provided for correct
149 % working of a kernel.
150 %
151 % The returned kernel should be freed using the DestroyKernelInfo() when you
152 % are finished with it. Do not free this memory yourself.
153 %
154 % Input kernel definition strings can consist of any of three types.
155 %
156 % "name:args[[@><]"
157 % Select from one of the built in kernels, using the name and
158 % geometry arguments supplied. See AcquireKernelBuiltIn()
159 %
160 % "WxH[+X+Y][@><]:num, num, num ..."
161 % a kernel of size W by H, with W*H floating point numbers following.
162 % the 'center' can be optionally be defined at +X+Y (such that +0+0
163 % is top left corner). If not defined the pixel in the center, for
164 % odd sizes, or to the immediate top or left of center for even sizes
165 % is automatically selected.
166 %
167 % "num, num, num, num, ..."
168 % list of floating point numbers defining an 'old style' odd sized
169 % square kernel. At least 9 values should be provided for a 3x3
170 % square kernel, 25 for a 5x5 square kernel, 49 for 7x7, etc.
171 % Values can be space or comma separated. This is not recommended.
172 %
173 % You can define a 'list of kernels' which can be used by some morphology
174 % operators A list is defined as a semi-colon separated list kernels.
175 %
176 % " kernel ; kernel ; kernel ; "
177 %
178 % Any extra ';' characters, at start, end or between kernel definitions are
179 % simply ignored.
180 %
181 % The special flags will expand a single kernel, into a list of rotated
182 % kernels. A '@' flag will expand a 3x3 kernel into a list of 45-degree
183 % cyclic rotations, while a '>' will generate a list of 90-degree rotations.
184 % The '<' also expands using 90-degree rotates, but giving a 180-degree
185 % reflected kernel before the +/- 90-degree rotations, which can be important
186 % for Thinning operations.
187 %
188 % Note that 'name' kernels will start with an alphabetic character while the
189 % new kernel specification has a ':' character in its specification string.
190 % If neither is the case, it is assumed an old style of a simple list of
191 % numbers generating a odd-sized square kernel has been given.
192 %
193 % The format of the AcquireKernel method is:
194 %
195 % KernelInfo *AcquireKernelInfo(const char *kernel_string)
196 %
197 % A description of each parameter follows:
198 %
199 % o kernel_string: the Morphology/Convolution kernel wanted.
200 %
201 */
202 
203 static inline MagickBooleanType AcquireKernelValues(KernelInfo *kernel)
204 {
205  size_t
206  elements;
207 
208  kernel->values=(MagickRealType *) NULL;
209  if (HeapOverflowSanityCheckGetSize(kernel->width,kernel->height,&elements) != MagickFalse)
210  return(MagickFalse);
211  kernel->values=(MagickRealType *) MagickAssumeAligned(AcquireAlignedMemory(
212  elements,sizeof(*kernel->values)));
213  return(kernel->values == (MagickRealType *) NULL ? MagickFalse : MagickTrue);
214 }
215 
216 /* This was separated so that it could be used as a separate
217 ** array input handling function, such as for -color-matrix
218 */
219 static KernelInfo *ParseKernelArray(const char *kernel_string)
220 {
221  KernelInfo
222  *kernel;
223 
224  char
225  token[MagickPathExtent];
226 
227  const char
228  *p,
229  *end;
230 
231  ssize_t
232  i;
233 
234  double
235  nan = sqrt(-1.0); /* Special Value : Not A Number */
236 
237  MagickStatusType
238  flags;
239 
241  args;
242 
243  size_t
244  length;
245 
246  kernel=(KernelInfo *) AcquireMagickMemory(sizeof(*kernel));
247  if (kernel == (KernelInfo *) NULL)
248  return(kernel);
249  (void) memset(kernel,0,sizeof(*kernel));
250  kernel->minimum = kernel->maximum = kernel->angle = 0.0;
251  kernel->negative_range = kernel->positive_range = 0.0;
252  kernel->type = UserDefinedKernel;
253  kernel->next = (KernelInfo *) NULL;
254  kernel->signature=MagickCoreSignature;
255  if (kernel_string == (const char *) NULL)
256  return(kernel);
257 
258  /* find end of this specific kernel definition string */
259  end = strchr(kernel_string, ';');
260  if ( end == (char *) NULL )
261  end = strchr(kernel_string, '\0');
262 
263  /* clear flags - for Expanding kernel lists through rotations */
264  flags = NoValue;
265 
266  /* Has a ':' in argument - New user kernel specification
267  FUTURE: this split on ':' could be done by StringToken()
268  */
269  p = strchr(kernel_string, ':');
270  if ( p != (char *) NULL && p < end)
271  {
272  /* ParseGeometry() needs the geometry separated! -- Arrgghh */
273  length=MagickMin((size_t) (p-kernel_string),sizeof(token)-1);
274  (void) memcpy(token, kernel_string, length);
275  token[length] = '\0';
276  SetGeometryInfo(&args);
277  flags = ParseGeometry(token, &args);
278 
279  /* Size handling and checks of geometry settings */
280  if ( (flags & WidthValue) == 0 ) /* if no width then */
281  args.rho = args.sigma; /* then width = height */
282  if ( args.rho < 1.0 ) /* if width too small */
283  args.rho = 1.0; /* then width = 1 */
284  if ( args.sigma < 1.0 ) /* if height too small */
285  args.sigma = args.rho; /* then height = width */
286  kernel->width = CastDoubleToSizeT(args.rho);
287  kernel->height = CastDoubleToSizeT(args.sigma);
288 
289  /* Offset Handling and Checks */
290  if ( args.xi < 0.0 || args.psi < 0.0 )
291  return(DestroyKernelInfo(kernel));
292  kernel->x = ((flags & XValue)!=0) ? (ssize_t)args.xi
293  : (ssize_t) (kernel->width-1)/2;
294  kernel->y = ((flags & YValue)!=0) ? (ssize_t)args.psi
295  : (ssize_t) (kernel->height-1)/2;
296  if ( kernel->x >= (ssize_t) kernel->width ||
297  kernel->y >= (ssize_t) kernel->height )
298  return(DestroyKernelInfo(kernel));
299 
300  p++; /* advance beyond the ':' */
301  }
302  else
303  { /* ELSE - Old old specification, forming odd-square kernel */
304  /* count up number of values given */
305  p=(const char *) kernel_string;
306  while ((isspace((int) ((unsigned char) *p)) != 0) || (*p == '\''))
307  p++; /* ignore "'" chars for convolve filter usage - Cristy */
308  for (i=0; p < end; i++)
309  {
310  (void) GetNextToken(p,&p,MagickPathExtent,token);
311  if (*token == ',')
312  (void) GetNextToken(p,&p,MagickPathExtent,token);
313  }
314  /* set the size of the kernel - old sized square */
315  kernel->width = kernel->height=CastDoubleToSizeT(sqrt((double) i+1.0));
316  kernel->x = kernel->y = (ssize_t) (kernel->width-1)/2;
317  p=(const char *) kernel_string;
318  while ((isspace((int) ((unsigned char) *p)) != 0) || (*p == '\''))
319  p++; /* ignore "'" chars for convolve filter usage - Cristy */
320  }
321 
322  /* Read in the kernel values from rest of input string argument */
323  if (AcquireKernelValues(kernel) == MagickFalse)
324  return(DestroyKernelInfo(kernel));
325  kernel->minimum=MagickMaximumValue;
326  kernel->maximum=(-MagickMaximumValue);
327  kernel->negative_range = kernel->positive_range = 0.0;
328  for (i=0; (i < (ssize_t) (kernel->width*kernel->height)) && (p < end); i++)
329  {
330  (void) GetNextToken(p,&p,MagickPathExtent,token);
331  if (*token == ',')
332  (void) GetNextToken(p,&p,MagickPathExtent,token);
333  if ( LocaleCompare("nan",token) == 0
334  || LocaleCompare("-",token) == 0 ) {
335  kernel->values[i] = nan; /* this value is not part of neighbourhood */
336  }
337  else {
338  kernel->values[i] = StringToDouble(token,(char **) NULL);
339  ( kernel->values[i] < 0)
340  ? ( kernel->negative_range += kernel->values[i] )
341  : ( kernel->positive_range += kernel->values[i] );
342  Minimize(kernel->minimum, kernel->values[i]);
343  Maximize(kernel->maximum, kernel->values[i]);
344  }
345  }
346 
347  /* sanity check -- no more values in kernel definition */
348  (void) GetNextToken(p,&p,MagickPathExtent,token);
349  if ( *token != '\0' && *token != ';' && *token != '\'' )
350  return(DestroyKernelInfo(kernel));
351 
352 #if 0
353  /* this was the old method of handling a incomplete kernel */
354  if ( i < (ssize_t) (kernel->width*kernel->height) ) {
355  Minimize(kernel->minimum, kernel->values[i]);
356  Maximize(kernel->maximum, kernel->values[i]);
357  for ( ; i < (ssize_t) (kernel->width*kernel->height); i++)
358  kernel->values[i]=0.0;
359  }
360 #else
361  /* Number of values for kernel was not enough - Report Error */
362  if ( i < (ssize_t) (kernel->width*kernel->height) )
363  return(DestroyKernelInfo(kernel));
364 #endif
365 
366  /* check that we received at least one real (non-nan) value! */
367  if (kernel->minimum == MagickMaximumValue)
368  return(DestroyKernelInfo(kernel));
369 
370  if ( (flags & AreaValue) != 0 ) /* '@' symbol in kernel size */
371  ExpandRotateKernelInfo(kernel, 45.0); /* cyclic rotate 3x3 kernels */
372  else if ( (flags & GreaterValue) != 0 ) /* '>' symbol in kernel args */
373  ExpandRotateKernelInfo(kernel, 90.0); /* 90 degree rotate of kernel */
374  else if ( (flags & LessValue) != 0 ) /* '<' symbol in kernel args */
375  ExpandMirrorKernelInfo(kernel); /* 90 degree mirror rotate */
376 
377  return(kernel);
378 }
379 
380 static KernelInfo *ParseKernelName(const char *kernel_string,
381  ExceptionInfo *exception)
382 {
383  char
384  token[MagickPathExtent] = "";
385 
386  const char
387  *p,
388  *end;
389 
391  args;
392 
393  KernelInfo
394  *kernel;
395 
396  MagickStatusType
397  flags;
398 
399  size_t
400  length;
401 
402  ssize_t
403  type;
404 
405  /* Parse special 'named' kernel */
406  (void) GetNextToken(kernel_string,&p,MagickPathExtent,token);
407  type=ParseCommandOption(MagickKernelOptions,MagickFalse,token);
408  if ( type < 0 || type == UserDefinedKernel )
409  return((KernelInfo *) NULL); /* not a valid named kernel */
410 
411  while (((isspace((int) ((unsigned char) *p)) != 0) ||
412  (*p == ',') || (*p == ':' )) && (*p != '\0') && (*p != ';'))
413  p++;
414 
415  end = strchr(p, ';'); /* end of this kernel definition */
416  if ( end == (char *) NULL )
417  end = strchr(p, '\0');
418 
419  /* ParseGeometry() needs the geometry separated! -- Arrgghh */
420  length=MagickMin((size_t) (end-p),sizeof(token)-1);
421  (void) memcpy(token, p, length);
422  token[length] = '\0';
423  SetGeometryInfo(&args);
424  flags = ParseGeometry(token, &args);
425 
426 #if 0
427  /* For Debugging Geometry Input */
428  (void) FormatLocaleFile(stderr, "Geometry = 0x%04X : %lg x %lg %+lg %+lg\n",
429  flags, args.rho, args.sigma, args.xi, args.psi );
430 #endif
431 
432  /* special handling of missing values in input string */
433  switch( type ) {
434  /* Shape Kernel Defaults */
435  case UnityKernel:
436  if ( (flags & WidthValue) == 0 )
437  args.rho = 1.0; /* Default scale = 1.0, zero is valid */
438  break;
439  case SquareKernel:
440  case DiamondKernel:
441  case OctagonKernel:
442  case DiskKernel:
443  case PlusKernel:
444  case CrossKernel:
445  if ( (flags & HeightValue) == 0 )
446  args.sigma = 1.0; /* Default scale = 1.0, zero is valid */
447  break;
448  case RingKernel:
449  if ( (flags & XValue) == 0 )
450  args.xi = 1.0; /* Default scale = 1.0, zero is valid */
451  break;
452  case RectangleKernel: /* Rectangle - set size defaults */
453  if ( (flags & WidthValue) == 0 ) /* if no width then */
454  args.rho = args.sigma; /* then width = height */
455  if ( args.rho < 1.0 ) /* if width too small */
456  args.rho = 3; /* then width = 3 */
457  if ( args.sigma < 1.0 ) /* if height too small */
458  args.sigma = args.rho; /* then height = width */
459  if ( (flags & XValue) == 0 ) /* center offset if not defined */
460  args.xi = (double)(((ssize_t)args.rho-1)/2);
461  if ( (flags & YValue) == 0 )
462  args.psi = (double)(((ssize_t)args.sigma-1)/2);
463  break;
464  /* Distance Kernel Defaults */
465  case ChebyshevKernel:
466  case ManhattanKernel:
467  case OctagonalKernel:
468  case EuclideanKernel:
469  if ( (flags & HeightValue) == 0 ) /* no distance scale */
470  args.sigma = 100.0; /* default distance scaling */
471  else if ( (flags & AspectValue ) != 0 ) /* '!' flag */
472  args.sigma = (double) QuantumRange/(args.sigma+1); /* maximum pixel distance */
473  else if ( (flags & PercentValue ) != 0 ) /* '%' flag */
474  args.sigma *= (double) QuantumRange/100.0; /* percentage of color range */
475  break;
476  default:
477  break;
478  }
479 
480  kernel = AcquireKernelBuiltIn((KernelInfoType)type, &args, exception);
481  if ( kernel == (KernelInfo *) NULL )
482  return(kernel);
483 
484  /* global expand to rotated kernel list - only for single kernels */
485  if ( kernel->next == (KernelInfo *) NULL ) {
486  if ( (flags & AreaValue) != 0 ) /* '@' symbol in kernel args */
487  ExpandRotateKernelInfo(kernel, 45.0);
488  else if ( (flags & GreaterValue) != 0 ) /* '>' symbol in kernel args */
489  ExpandRotateKernelInfo(kernel, 90.0);
490  else if ( (flags & LessValue) != 0 ) /* '<' symbol in kernel args */
491  ExpandMirrorKernelInfo(kernel);
492  }
493 
494  return(kernel);
495 }
496 
497 MagickExport KernelInfo *AcquireKernelInfo(const char *kernel_string,
498  ExceptionInfo *exception)
499 {
500  KernelInfo
501  *kernel,
502  *new_kernel;
503 
504  char
505  *kernel_cache,
506  token[MagickPathExtent];
507 
508  const char
509  *p;
510 
511  if (kernel_string == (const char *) NULL)
512  return(ParseKernelArray(kernel_string));
513  p=kernel_string;
514  kernel_cache=(char *) NULL;
515  if (*kernel_string == '@')
516  {
517  kernel_cache=FileToString(kernel_string,~0UL,exception);
518  if (kernel_cache == (char *) NULL)
519  return((KernelInfo *) NULL);
520  p=(const char *) kernel_cache;
521  }
522  kernel=NULL;
523  while (GetNextToken(p,(const char **) NULL,MagickPathExtent,token), *token != '\0')
524  {
525  /* ignore extra or multiple ';' kernel separators */
526  if (*token != ';')
527  {
528  /* tokens starting with alpha is a Named kernel */
529  if (isalpha((int) ((unsigned char) *token)) != 0)
530  new_kernel=ParseKernelName(p,exception);
531  else /* otherwise a user defined kernel array */
532  new_kernel=ParseKernelArray(p);
533 
534  /* Error handling -- this is not proper error handling! */
535  if (new_kernel == (KernelInfo *) NULL)
536  {
537  if (kernel != (KernelInfo *) NULL)
538  kernel=DestroyKernelInfo(kernel);
539  return((KernelInfo *) NULL);
540  }
541 
542  /* initialise or append the kernel list */
543  if (kernel == (KernelInfo *) NULL)
544  kernel=new_kernel;
545  else
546  LastKernelInfo(kernel)->next=new_kernel;
547  }
548 
549  /* look for the next kernel in list */
550  p=strchr(p,';');
551  if (p == (char *) NULL)
552  break;
553  p++;
554  }
555  if (kernel_cache != (char *) NULL)
556  kernel_cache=DestroyString(kernel_cache);
557  return(kernel);
558 }
559 ␌
560 /*
561 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
562 % %
563 % %
564 % %
565 % A c q u i r e K e r n e l B u i l t I n %
566 % %
567 % %
568 % %
569 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
570 %
571 % AcquireKernelBuiltIn() returned one of the 'named' built-in types of
572 % kernels used for special purposes such as gaussian blurring, skeleton
573 % pruning, and edge distance determination.
574 %
575 % They take a KernelType, and a set of geometry style arguments, which were
576 % typically decoded from a user supplied string, or from a more complex
577 % Morphology Method that was requested.
578 %
579 % The format of the AcquireKernelBuiltIn method is:
580 %
581 % KernelInfo *AcquireKernelBuiltIn(const KernelInfoType type,
582 % const GeometryInfo args)
583 %
584 % A description of each parameter follows:
585 %
586 % o type: the pre-defined type of kernel wanted
587 %
588 % o args: arguments defining or modifying the kernel
589 %
590 % Convolution Kernels
591 %
592 % Unity
593 % The a No-Op or Scaling single element kernel.
594 %
595 % Gaussian:{radius},{sigma}
596 % Generate a two-dimensional gaussian kernel, as used by -gaussian.
597 % The sigma for the curve is required. The resulting kernel is
598 % normalized,
599 %
600 % If 'sigma' is zero, you get a single pixel on a field of zeros.
601 %
602 % NOTE: that the 'radius' is optional, but if provided can limit (clip)
603 % the final size of the resulting kernel to a square 2*radius+1 in size.
604 % The radius should be at least 2 times that of the sigma value, or
605 % sever clipping and aliasing may result. If not given or set to 0 the
606 % radius will be determined so as to produce the best minimal error
607 % result, which is usually much larger than is normally needed.
608 %
609 % LoG:{radius},{sigma}
610 % "Laplacian of a Gaussian" or "Mexican Hat" Kernel.
611 % The supposed ideal edge detection, zero-summing kernel.
612 %
613 % An alternative to this kernel is to use a "DoG" with a sigma ratio of
614 % approx 1.6 (according to wikipedia).
615 %
616 % DoG:{radius},{sigma1},{sigma2}
617 % "Difference of Gaussians" Kernel.
618 % As "Gaussian" but with a gaussian produced by 'sigma2' subtracted
619 % from the gaussian produced by 'sigma1'. Typically sigma2 > sigma1.
620 % The result is a zero-summing kernel.
621 %
622 % Blur:{radius},{sigma}[,{angle}]
623 % Generates a 1 dimensional or linear gaussian blur, at the angle given
624 % (current restricted to orthogonal angles). If a 'radius' is given the
625 % kernel is clipped to a width of 2*radius+1. Kernel can be rotated
626 % by a 90 degree angle.
627 %
628 % If 'sigma' is zero, you get a single pixel on a field of zeros.
629 %
630 % Note that two convolutions with two "Blur" kernels perpendicular to
631 % each other, is equivalent to a far larger "Gaussian" kernel with the
632 % same sigma value, However it is much faster to apply. This is how the
633 % "-blur" operator actually works.
634 %
635 % Comet:{width},{sigma},{angle}
636 % Blur in one direction only, much like how a bright object leaves
637 % a comet like trail. The Kernel is actually half a gaussian curve,
638 % Adding two such blurs in opposite directions produces a Blur Kernel.
639 % Angle can be rotated in multiples of 90 degrees.
640 %
641 % Note that the first argument is the width of the kernel and not the
642 % radius of the kernel.
643 %
644 % Binomial:[{radius}]
645 % Generate a discrete kernel using a 2 dimensional Pascal's Triangle
646 % of values. Used for special forma of image filters.
647 %
648 % # Still to be implemented...
649 % #
650 % # Filter2D
651 % # Filter1D
652 % # Set kernel values using a resize filter, and given scale (sigma)
653 % # Cylindrical or Linear. Is this possible with an image?
654 % #
655 %
656 % Named Constant Convolution Kernels
657 %
658 % All these are unscaled, zero-summing kernels by default. As such for
659 % non-HDRI version of ImageMagick some form of normalization, user scaling,
660 % and biasing the results is recommended, to prevent the resulting image
661 % being 'clipped'.
662 %
663 % The 3x3 kernels (most of these) can be circularly rotated in multiples of
664 % 45 degrees to generate the 8 angled variants of each of the kernels.
665 %
666 % Laplacian:{type}
667 % Discrete Laplacian Kernels, (without normalization)
668 % Type 0 : 3x3 with center:8 surrounded by -1 (8 neighbourhood)
669 % Type 1 : 3x3 with center:4 edge:-1 corner:0 (4 neighbourhood)
670 % Type 2 : 3x3 with center:4 edge:1 corner:-2
671 % Type 3 : 3x3 with center:4 edge:-2 corner:1
672 % Type 5 : 5x5 laplacian
673 % Type 7 : 7x7 laplacian
674 % Type 15 : 5x5 LoG (sigma approx 1.4)
675 % Type 19 : 9x9 LoG (sigma approx 1.4)
676 %
677 % Sobel:{angle}
678 % Sobel 'Edge' convolution kernel (3x3)
679 % | -1, 0, 1 |
680 % | -2, 0,-2 |
681 % | -1, 0, 1 |
682 %
683 % Roberts:{angle}
684 % Roberts convolution kernel (3x3)
685 % | 0, 0, 0 |
686 % | -1, 1, 0 |
687 % | 0, 0, 0 |
688 %
689 % Prewitt:{angle}
690 % Prewitt Edge convolution kernel (3x3)
691 % | -1, 0, 1 |
692 % | -1, 0, 1 |
693 % | -1, 0, 1 |
694 %
695 % Compass:{angle}
696 % Prewitt's "Compass" convolution kernel (3x3)
697 % | -1, 1, 1 |
698 % | -1,-2, 1 |
699 % | -1, 1, 1 |
700 %
701 % Kirsch:{angle}
702 % Kirsch's "Compass" convolution kernel (3x3)
703 % | -3,-3, 5 |
704 % | -3, 0, 5 |
705 % | -3,-3, 5 |
706 %
707 % FreiChen:{angle}
708 % Frei-Chen Edge Detector is based on a kernel that is similar to
709 % the Sobel Kernel, but is designed to be isotropic. That is it takes
710 % into account the distance of the diagonal in the kernel.
711 %
712 % | 1, 0, -1 |
713 % | sqrt(2), 0, -sqrt(2) |
714 % | 1, 0, -1 |
715 %
716 % FreiChen:{type},{angle}
717 %
718 % Frei-Chen Pre-weighted kernels...
719 %
720 % Type 0: default un-normalized version shown above.
721 %
722 % Type 1: Orthogonal Kernel (same as type 11 below)
723 % | 1, 0, -1 |
724 % | sqrt(2), 0, -sqrt(2) | / 2*sqrt(2)
725 % | 1, 0, -1 |
726 %
727 % Type 2: Diagonal form of Kernel...
728 % | 1, sqrt(2), 0 |
729 % | sqrt(2), 0, -sqrt(2) | / 2*sqrt(2)
730 % | 0, -sqrt(2) -1 |
731 %
732 % However this kernel is als at the heart of the FreiChen Edge Detection
733 % Process which uses a set of 9 specially weighted kernel. These 9
734 % kernels not be normalized, but directly applied to the image. The
735 % results is then added together, to produce the intensity of an edge in
736 % a specific direction. The square root of the pixel value can then be
737 % taken as the cosine of the edge, and at least 2 such runs at 90 degrees
738 % from each other, both the direction and the strength of the edge can be
739 % determined.
740 %
741 % Type 10: All 9 of the following pre-weighted kernels...
742 %
743 % Type 11: | 1, 0, -1 |
744 % | sqrt(2), 0, -sqrt(2) | / 2*sqrt(2)
745 % | 1, 0, -1 |
746 %
747 % Type 12: | 1, sqrt(2), 1 |
748 % | 0, 0, 0 | / 2*sqrt(2)
749 % | 1, sqrt(2), 1 |
750 %
751 % Type 13: | sqrt(2), -1, 0 |
752 % | -1, 0, 1 | / 2*sqrt(2)
753 % | 0, 1, -sqrt(2) |
754 %
755 % Type 14: | 0, 1, -sqrt(2) |
756 % | -1, 0, 1 | / 2*sqrt(2)
757 % | sqrt(2), -1, 0 |
758 %
759 % Type 15: | 0, -1, 0 |
760 % | 1, 0, 1 | / 2
761 % | 0, -1, 0 |
762 %
763 % Type 16: | 1, 0, -1 |
764 % | 0, 0, 0 | / 2
765 % | -1, 0, 1 |
766 %
767 % Type 17: | 1, -2, 1 |
768 % | -2, 4, -2 | / 6
769 % | -1, -2, 1 |
770 %
771 % Type 18: | -2, 1, -2 |
772 % | 1, 4, 1 | / 6
773 % | -2, 1, -2 |
774 %
775 % Type 19: | 1, 1, 1 |
776 % | 1, 1, 1 | / 3
777 % | 1, 1, 1 |
778 %
779 % The first 4 are for edge detection, the next 4 are for line detection
780 % and the last is to add a average component to the results.
781 %
782 % Using a special type of '-1' will return all 9 pre-weighted kernels
783 % as a multi-kernel list, so that you can use them directly (without
784 % normalization) with the special "-set option:morphology:compose Plus"
785 % setting to apply the full FreiChen Edge Detection Technique.
786 %
787 % If 'type' is large it will be taken to be an actual rotation angle for
788 % the default FreiChen (type 0) kernel. As such FreiChen:45 will look
789 % like a Sobel:45 but with 'sqrt(2)' instead of '2' values.
790 %
791 % WARNING: The above was layed out as per
792 % http://www.math.tau.ac.il/~turkel/notes/edge_detectors.pdf
793 % But rotated 90 degrees so direction is from left rather than the top.
794 % I have yet to find any secondary confirmation of the above. The only
795 % other source found was actual source code at
796 % http://ltswww.epfl.ch/~courstiv/exos_labos/sol3.pdf
797 % Neither paper defines the kernels in a way that looks logical or
798 % correct when taken as a whole.
799 %
800 % Boolean Kernels
801 %
802 % Diamond:[{radius}[,{scale}]]
803 % Generate a diamond shaped kernel with given radius to the points.
804 % Kernel size will again be radius*2+1 square and defaults to radius 1,
805 % generating a 3x3 kernel that is slightly larger than a square.
806 %
807 % Square:[{radius}[,{scale}]]
808 % Generate a square shaped kernel of size radius*2+1, and defaulting
809 % to a 3x3 (radius 1).
810 %
811 % Octagon:[{radius}[,{scale}]]
812 % Generate octagonal shaped kernel of given radius and constant scale.
813 % Default radius is 3 producing a 7x7 kernel. A radius of 1 will result
814 % in "Diamond" kernel.
815 %
816 % Disk:[{radius}[,{scale}]]
817 % Generate a binary disk, thresholded at the radius given, the radius
818 % may be a float-point value. Final Kernel size is floor(radius)*2+1
819 % square. A radius of 5.3 is the default.
820 %
821 % NOTE: That a low radii Disk kernels produce the same results as
822 % many of the previously defined kernels, but differ greatly at larger
823 % radii. Here is a table of equivalences...
824 % "Disk:1" => "Diamond", "Octagon:1", or "Cross:1"
825 % "Disk:1.5" => "Square"
826 % "Disk:2" => "Diamond:2"
827 % "Disk:2.5" => "Octagon"
828 % "Disk:2.9" => "Square:2"
829 % "Disk:3.5" => "Octagon:3"
830 % "Disk:4.5" => "Octagon:4"
831 % "Disk:5.4" => "Octagon:5"
832 % "Disk:6.4" => "Octagon:6"
833 % All other Disk shapes are unique to this kernel, but because a "Disk"
834 % is more circular when using a larger radius, using a larger radius is
835 % preferred over iterating the morphological operation.
836 %
837 % Rectangle:{geometry}
838 % Simply generate a rectangle of 1's with the size given. You can also
839 % specify the location of the 'control point', otherwise the closest
840 % pixel to the center of the rectangle is selected.
841 %
842 % Properly centered and odd sized rectangles work the best.
843 %
844 % Symbol Dilation Kernels
845 %
846 % These kernel is not a good general morphological kernel, but is used
847 % more for highlighting and marking any single pixels in an image using,
848 % a "Dilate" method as appropriate.
849 %
850 % For the same reasons iterating these kernels does not produce the
851 % same result as using a larger radius for the symbol.
852 %
853 % Plus:[{radius}[,{scale}]]
854 % Cross:[{radius}[,{scale}]]
855 % Generate a kernel in the shape of a 'plus' or a 'cross' with
856 % a each arm the length of the given radius (default 2).
857 %
858 % NOTE: "plus:1" is equivalent to a "Diamond" kernel.
859 %
860 % Ring:{radius1},{radius2}[,{scale}]
861 % A ring of the values given that falls between the two radii.
862 % Defaults to a ring of approximately 3 radius in a 7x7 kernel.
863 % This is the 'edge' pixels of the default "Disk" kernel,
864 % More specifically, "Ring" -> "Ring:2.5,3.5,1.0"
865 %
866 % Hit and Miss Kernels
867 %
868 % Peak:radius1,radius2
869 % Find any peak larger than the pixels the fall between the two radii.
870 % The default ring of pixels is as per "Ring".
871 % Edges
872 % Find flat orthogonal edges of a binary shape
873 % Corners
874 % Find 90 degree corners of a binary shape
875 % Diagonals:type
876 % A special kernel to thin the 'outside' of diagonals
877 % LineEnds:type
878 % Find end points of lines (for pruning a skeleton)
879 % Two types of lines ends (default to both) can be searched for
880 % Type 0: All line ends
881 % Type 1: single kernel for 4-connected line ends
882 % Type 2: single kernel for simple line ends
883 % LineJunctions
884 % Find three line junctions (within a skeleton)
885 % Type 0: all line junctions
886 % Type 1: Y Junction kernel
887 % Type 2: Diagonal T Junction kernel
888 % Type 3: Orthogonal T Junction kernel
889 % Type 4: Diagonal X Junction kernel
890 % Type 5: Orthogonal + Junction kernel
891 % Ridges:type
892 % Find single pixel ridges or thin lines
893 % Type 1: Fine single pixel thick lines and ridges
894 % Type 2: Find two pixel thick lines and ridges
895 % ConvexHull
896 % Octagonal Thickening Kernel, to generate convex hulls of 45 degrees
897 % Skeleton:type
898 % Traditional skeleton generating kernels.
899 % Type 1: Traditional Skeleton kernel (4 connected skeleton)
900 % Type 2: HIPR2 Skeleton kernel (8 connected skeleton)
901 % Type 3: Thinning skeleton based on a research paper by
902 % Dan S. Bloomberg (Default Type)
903 % ThinSE:type
904 % A huge variety of Thinning Kernels designed to preserve connectivity.
905 % many other kernel sets use these kernels as source definitions.
906 % Type numbers are 41-49, 81-89, 481, and 482 which are based on
907 % the super and sub notations used in the source research paper.
908 %
909 % Distance Measuring Kernels
910 %
911 % Different types of distance measuring methods, which are used with the
912 % a 'Distance' morphology method for generating a gradient based on
913 % distance from an edge of a binary shape, though there is a technique
914 % for handling a anti-aliased shape.
915 %
916 % See the 'Distance' Morphological Method, for information of how it is
917 % applied.
918 %
919 % Chebyshev:[{radius}][x{scale}[%!]]
920 % Chebyshev Distance (also known as Tchebychev or Chessboard distance)
921 % is a value of one to any neighbour, orthogonal or diagonal. One why
922 % of thinking of it is the number of squares a 'King' or 'Queen' in
923 % chess needs to traverse reach any other position on a chess board.
924 % It results in a 'square' like distance function, but one where
925 % diagonals are given a value that is closer than expected.
926 %
927 % Manhattan:[{radius}][x{scale}[%!]]
928 % Manhattan Distance (also known as Rectilinear, City Block, or the Taxi
929 % Cab distance metric), it is the distance needed when you can only
930 % travel in horizontal or vertical directions only. It is the
931 % distance a 'Rook' in chess would have to travel, and results in a
932 % diamond like distances, where diagonals are further than expected.
933 %
934 % Octagonal:[{radius}][x{scale}[%!]]
935 % An interleaving of Manhattan and Chebyshev metrics producing an
936 % increasing octagonally shaped distance. Distances matches those of
937 % the "Octagon" shaped kernel of the same radius. The minimum radius
938 % and default is 2, producing a 5x5 kernel.
939 %
940 % Euclidean:[{radius}][x{scale}[%!]]
941 % Euclidean distance is the 'direct' or 'as the crow flys' distance.
942 % However by default the kernel size only has a radius of 1, which
943 % limits the distance to 'Knight' like moves, with only orthogonal and
944 % diagonal measurements being correct. As such for the default kernel
945 % you will get octagonal like distance function.
946 %
947 % However using a larger radius such as "Euclidean:4" you will get a
948 % much smoother distance gradient from the edge of the shape. Especially
949 % if the image is pre-processed to include any anti-aliasing pixels.
950 % Of course a larger kernel is slower to use, and not always needed.
951 %
952 % The first three Distance Measuring Kernels will only generate distances
953 % of exact multiples of {scale} in binary images. As such you can use a
954 % scale of 1 without loosing any information. However you also need some
955 % scaling when handling non-binary anti-aliased shapes.
956 %
957 % The "Euclidean" Distance Kernel however does generate a non-integer
958 % fractional results, and as such scaling is vital even for binary shapes.
959 %
960 */
961 
962 MagickExport KernelInfo *AcquireKernelBuiltIn(const KernelInfoType type,
963  const GeometryInfo *args,ExceptionInfo *exception)
964 {
965  KernelInfo
966  *kernel;
967 
968  ssize_t
969  i;
970 
971  ssize_t
972  u,
973  v;
974 
975  double
976  nan = sqrt(-1.0); /* Special Value : Not A Number */
977 
978  /* Generate a new empty kernel if needed */
979  kernel=(KernelInfo *) NULL;
980  switch(type) {
981  case UndefinedKernel: /* These should not call this function */
982  case UserDefinedKernel:
983  (void) ThrowMagickException(exception,GetMagickModule(),OptionWarning,
984  "InvalidOption","`%s'","Should not call this function");
985  return((KernelInfo *) NULL);
986  case LaplacianKernel: /* Named Discrete Convolution Kernels */
987  case SobelKernel: /* these are defined using other kernels */
988  case RobertsKernel:
989  case PrewittKernel:
990  case CompassKernel:
991  case KirschKernel:
992  case FreiChenKernel:
993  case EdgesKernel: /* Hit and Miss kernels */
994  case CornersKernel:
995  case DiagonalsKernel:
996  case LineEndsKernel:
997  case LineJunctionsKernel:
998  case RidgesKernel:
999  case ConvexHullKernel:
1000  case SkeletonKernel:
1001  case ThinSEKernel:
1002  break; /* A pre-generated kernel is not needed */
1003 #if 0
1004  /* set to 1 to do a compile-time check that we haven't missed anything */
1005  case UnityKernel:
1006  case GaussianKernel:
1007  case DoGKernel:
1008  case LoGKernel:
1009  case BlurKernel:
1010  case CometKernel:
1011  case BinomialKernel:
1012  case DiamondKernel:
1013  case SquareKernel:
1014  case RectangleKernel:
1015  case OctagonKernel:
1016  case DiskKernel:
1017  case PlusKernel:
1018  case CrossKernel:
1019  case RingKernel:
1020  case PeaksKernel:
1021  case ChebyshevKernel:
1022  case ManhattanKernel:
1023  case OctagonalKernel:
1024  case EuclideanKernel:
1025 #else
1026  default:
1027 #endif
1028  /* Generate the base Kernel Structure */
1029  kernel=(KernelInfo *) AcquireMagickMemory(sizeof(*kernel));
1030  if (kernel == (KernelInfo *) NULL)
1031  return(kernel);
1032  (void) memset(kernel,0,sizeof(*kernel));
1033  kernel->minimum = kernel->maximum = kernel->angle = 0.0;
1034  kernel->negative_range = kernel->positive_range = 0.0;
1035  kernel->type = type;
1036  kernel->next = (KernelInfo *) NULL;
1037  kernel->signature=MagickCoreSignature;
1038  break;
1039  }
1040 
1041  switch(type) {
1042  /*
1043  Convolution Kernels
1044  */
1045  case UnityKernel:
1046  {
1047  kernel->height = kernel->width = (size_t) 1;
1048  kernel->x = kernel->y = (ssize_t) 0;
1049  kernel->values=(MagickRealType *) MagickAssumeAligned(
1050  AcquireAlignedMemory(1,sizeof(*kernel->values)));
1051  if (kernel->values == (MagickRealType *) NULL)
1052  return(DestroyKernelInfo(kernel));
1053  kernel->maximum = kernel->values[0] = args->rho;
1054  break;
1055  }
1056  break;
1057  case GaussianKernel:
1058  case DoGKernel:
1059  case LoGKernel:
1060  { double
1061  sigma = fabs(args->sigma),
1062  sigma2 = fabs(args->xi),
1063  A, B, R;
1064 
1065  if ( args->rho >= 1.0 )
1066  kernel->width = CastDoubleToSizeT(args->rho)*2+1;
1067  else if ( (type != DoGKernel) || (sigma >= sigma2) )
1068  kernel->width = GetOptimalKernelWidth2D(args->rho,sigma);
1069  else
1070  kernel->width = GetOptimalKernelWidth2D(args->rho,sigma2);
1071  kernel->height = kernel->width;
1072  kernel->x = kernel->y = (ssize_t) (kernel->width-1)/2;
1073  if (AcquireKernelValues(kernel) == MagickFalse)
1074  return(DestroyKernelInfo(kernel));
1075 
1076  /* WARNING: The following generates a 'sampled gaussian' kernel.
1077  * What we really want is a 'discrete gaussian' kernel.
1078  *
1079  * How to do this is I don't know, but appears to be basied on the
1080  * Error Function 'erf()' (integral of a gaussian)
1081  */
1082 
1083  if ( type == GaussianKernel || type == DoGKernel )
1084  { /* Calculate a Gaussian, OR positive half of a DoG */
1085  if ( sigma > MagickEpsilon )
1086  { A = 1.0/(2.0*sigma*sigma); /* simplify loop expressions */
1087  B = (double) (1.0/(Magick2PI*sigma*sigma));
1088  for ( i=0, v=-kernel->y; v <= (ssize_t)kernel->y; v++)
1089  for ( u=-kernel->x; u <= (ssize_t)kernel->x; u++, i++)
1090  kernel->values[i] = exp(-((double)(u*u+v*v))*A)*B;
1091  }
1092  else /* limiting case - a unity (normalized Dirac) kernel */
1093  { (void) memset(kernel->values,0, (size_t)
1094  kernel->width*kernel->height*sizeof(*kernel->values));
1095  kernel->values[kernel->x+kernel->y*(ssize_t) kernel->width] = 1.0;
1096  }
1097  }
1098 
1099  if ( type == DoGKernel )
1100  { /* Subtract a Negative Gaussian for "Difference of Gaussian" */
1101  if ( sigma2 > MagickEpsilon )
1102  { sigma = sigma2; /* simplify loop expressions */
1103  A = 1.0/(2.0*sigma*sigma);
1104  B = (double) (1.0/(Magick2PI*sigma*sigma));
1105  for ( i=0, v=-kernel->y; v <= (ssize_t)kernel->y; v++)
1106  for ( u=-kernel->x; u <= (ssize_t)kernel->x; u++, i++)
1107  kernel->values[i] -= exp(-((double)(u*u+v*v))*A)*B;
1108  }
1109  else /* limiting case - a unity (normalized Dirac) kernel */
1110  kernel->values[kernel->x+kernel->y*(ssize_t) kernel->width] -= 1.0;
1111  }
1112 
1113  if ( type == LoGKernel )
1114  { /* Calculate a Laplacian of a Gaussian - Or Mexican Hat */
1115  if ( sigma > MagickEpsilon )
1116  { A = 1.0/(2.0*sigma*sigma); /* simplify loop expressions */
1117  B = (double) (1.0/(MagickPI*sigma*sigma*sigma*sigma));
1118  for ( i=0, v=-kernel->y; v <= (ssize_t)kernel->y; v++)
1119  for ( u=-kernel->x; u <= (ssize_t)kernel->x; u++, i++)
1120  { R = ((double)(u*u+v*v))*A;
1121  kernel->values[i] = (1-R)*exp(-R)*B;
1122  }
1123  }
1124  else /* special case - generate a unity kernel */
1125  { (void) memset(kernel->values,0, (size_t)
1126  kernel->width*kernel->height*sizeof(*kernel->values));
1127  kernel->values[kernel->x+kernel->y*(ssize_t) kernel->width] = 1.0;
1128  }
1129  }
1130 
1131  /* Note the above kernels may have been 'clipped' by a user defined
1132  ** radius, producing a smaller (darker) kernel. Also for very small
1133  ** sigma's (> 0.1) the central value becomes larger than one, and thus
1134  ** producing a very bright kernel.
1135  **
1136  ** Normalization will still be needed.
1137  */
1138 
1139  /* Normalize the 2D Gaussian Kernel
1140  **
1141  ** NB: a CorrelateNormalize performs a normal Normalize if
1142  ** there are no negative values.
1143  */
1144  CalcKernelMetaData(kernel); /* the other kernel meta-data */
1145  ScaleKernelInfo(kernel, 1.0, CorrelateNormalizeValue);
1146 
1147  break;
1148  }
1149  case BlurKernel:
1150  { double
1151  sigma = fabs(args->sigma),
1152  alpha, beta;
1153 
1154  if ( args->rho >= 1.0 )
1155  kernel->width = CastDoubleToSizeT(args->rho)*2+1;
1156  else
1157  kernel->width = GetOptimalKernelWidth1D(args->rho,sigma);
1158  kernel->height = 1;
1159  kernel->x = (ssize_t) (kernel->width-1)/2;
1160  kernel->y = 0;
1161  kernel->negative_range = kernel->positive_range = 0.0;
1162  if (AcquireKernelValues(kernel) == MagickFalse)
1163  return(DestroyKernelInfo(kernel));
1164 
1165 #if 1
1166 #define KernelRank 3
1167  /* Formula derived from GetBlurKernel() in "effect.c" (plus bug fix).
1168  ** It generates a gaussian 3 times the width, and compresses it into
1169  ** the expected range. This produces a closer normalization of the
1170  ** resulting kernel, especially for very low sigma values.
1171  ** As such while wierd it is prefered.
1172  **
1173  ** I am told this method originally came from Photoshop.
1174  **
1175  ** A properly normalized curve is generated (apart from edge clipping)
1176  ** even though we later normalize the result (for edge clipping)
1177  ** to allow the correct generation of a "Difference of Blurs".
1178  */
1179 
1180  /* initialize */
1181  v = (ssize_t) (kernel->width*KernelRank-1)/2; /* start/end points to fit range */
1182  (void) memset(kernel->values,0, (size_t)
1183  kernel->width*kernel->height*sizeof(*kernel->values));
1184  /* Calculate a Positive 1D Gaussian */
1185  if ( sigma > MagickEpsilon )
1186  { sigma *= KernelRank; /* simplify loop expressions */
1187  alpha = 1.0/(2.0*sigma*sigma);
1188  beta= (double) (1.0/(MagickSQ2PI*sigma ));
1189  for ( u=-v; u <= v; u++) {
1190  kernel->values[(u+v)/KernelRank] +=
1191  exp(-((double)(u*u))*alpha)*beta;
1192  }
1193  }
1194  else /* special case - generate a unity kernel */
1195  kernel->values[kernel->x+kernel->y*(ssize_t) kernel->width] = 1.0;
1196 #else
1197  /* Direct calculation without curve averaging
1198  This is equivalent to a KernelRank of 1 */
1199 
1200  /* Calculate a Positive Gaussian */
1201  if ( sigma > MagickEpsilon )
1202  { alpha = 1.0/(2.0*sigma*sigma); /* simplify loop expressions */
1203  beta = 1.0/(MagickSQ2PI*sigma);
1204  for ( i=0, u=-kernel->x; u <= (ssize_t)kernel->x; u++, i++)
1205  kernel->values[i] = exp(-((double)(u*u))*alpha)*beta;
1206  }
1207  else /* special case - generate a unity kernel */
1208  { (void) memset(kernel->values,0, (size_t)
1209  kernel->width*kernel->height*sizeof(*kernel->values));
1210  kernel->values[kernel->x+kernel->y*kernel->width] = 1.0;
1211  }
1212 #endif
1213  /* Note the above kernel may have been 'clipped' by a user defined
1214  ** radius, producing a smaller (darker) kernel. Also for very small
1215  ** sigma's (> 0.1) the central value becomes larger than one, as a
1216  ** result of not generating a actual 'discrete' kernel, and thus
1217  ** producing a very bright 'impulse'.
1218  **
1219  ** Because of these two factors Normalization is required!
1220  */
1221 
1222  /* Normalize the 1D Gaussian Kernel
1223  **
1224  ** NB: a CorrelateNormalize performs a normal Normalize if
1225  ** there are no negative values.
1226  */
1227  CalcKernelMetaData(kernel); /* the other kernel meta-data */
1228  ScaleKernelInfo(kernel, 1.0, CorrelateNormalizeValue);
1229 
1230  /* rotate the 1D kernel by given angle */
1231  RotateKernelInfo(kernel, args->xi );
1232  break;
1233  }
1234  case CometKernel:
1235  { double
1236  sigma = fabs(args->sigma),
1237  A;
1238 
1239  if ( args->rho < 1.0 )
1240  kernel->width = (GetOptimalKernelWidth1D(args->rho,sigma)-1)/2+1;
1241  else
1242  kernel->width = CastDoubleToSizeT(args->rho);
1243  kernel->x = kernel->y = 0;
1244  kernel->height = 1;
1245  kernel->negative_range = kernel->positive_range = 0.0;
1246  if (AcquireKernelValues(kernel) == MagickFalse)
1247  return(DestroyKernelInfo(kernel));
1248 
1249  /* A comet blur is half a 1D gaussian curve, so that the object is
1250  ** blurred in one direction only. This may not be quite the right
1251  ** curve to use so may change in the future. The function must be
1252  ** normalised after generation, which also resolves any clipping.
1253  **
1254  ** As we are normalizing and not subtracting gaussians,
1255  ** there is no need for a divisor in the gaussian formula
1256  **
1257  ** It is less complex
1258  */
1259  if ( sigma > MagickEpsilon )
1260  {
1261 #if 1
1262 #define KernelRank 3
1263  v = (ssize_t) kernel->width*KernelRank; /* start/end points */
1264  (void) memset(kernel->values,0, (size_t)
1265  kernel->width*sizeof(*kernel->values));
1266  sigma *= KernelRank; /* simplify the loop expression */
1267  A = 1.0/(2.0*sigma*sigma);
1268  /* B = 1.0/(MagickSQ2PI*sigma); */
1269  for ( u=0; u < v; u++) {
1270  kernel->values[u/KernelRank] +=
1271  exp(-((double)(u*u))*A);
1272  /* exp(-((double)(i*i))/2.0*sigma*sigma)/(MagickSQ2PI*sigma); */
1273  }
1274  for (i=0; i < (ssize_t) kernel->width; i++)
1275  kernel->positive_range += kernel->values[i];
1276 #else
1277  A = 1.0/(2.0*sigma*sigma); /* simplify the loop expression */
1278  /* B = 1.0/(MagickSQ2PI*sigma); */
1279  for ( i=0; i < (ssize_t) kernel->width; i++)
1280  kernel->positive_range +=
1281  kernel->values[i] = exp(-((double)(i*i))*A);
1282  /* exp(-((double)(i*i))/2.0*sigma*sigma)/(MagickSQ2PI*sigma); */
1283 #endif
1284  }
1285  else /* special case - generate a unity kernel */
1286  { (void) memset(kernel->values,0, (size_t)
1287  kernel->width*kernel->height*sizeof(*kernel->values));
1288  kernel->values[kernel->x+kernel->y*(ssize_t) kernel->width] = 1.0;
1289  kernel->positive_range = 1.0;
1290  }
1291 
1292  kernel->minimum = 0.0;
1293  kernel->maximum = kernel->values[0];
1294  kernel->negative_range = 0.0;
1295 
1296  ScaleKernelInfo(kernel, 1.0, NormalizeValue); /* Normalize */
1297  RotateKernelInfo(kernel, args->xi); /* Rotate by angle */
1298  break;
1299  }
1300  case BinomialKernel:
1301  {
1302  const size_t
1303  max_order = (sizeof(size_t) > 4) ? 20 : 12;
1304 
1305  size_t
1306  order_f;
1307 
1308  if (args->rho < 1.0)
1309  kernel->width = kernel->height = 3; /* default radius = 1 */
1310  else
1311  kernel->width = kernel->height = CastDoubleToSizeT(args->rho)*2+1;
1312  kernel->x = kernel->y = (ssize_t) (kernel->width-1)/2;
1313 
1314  /* Check if kernel order (width-1) would overflow fact() */
1315  if ((kernel->width-1) > max_order)
1316  return(DestroyKernelInfo(kernel));
1317 
1318  order_f = fact(kernel->width-1);
1319 
1320  if (AcquireKernelValues(kernel) == MagickFalse)
1321  return(DestroyKernelInfo(kernel));
1322 
1323  /* set all kernel values within diamond area to scale given */
1324  for ( i=0, v=0; v < (ssize_t)kernel->height; v++)
1325  {
1326  size_t
1327  alpha = order_f / ( fact((size_t) v) * fact(kernel->height-(size_t) v-1) );
1328 
1329  for ( u=0; u < (ssize_t)kernel->width; u++, i++)
1330  kernel->positive_range += kernel->values[i] = (double)
1331  (alpha * order_f / ( fact((size_t) u) * fact(kernel->height-(size_t) u-1) ));
1332  }
1333  kernel->minimum = 1.0;
1334  kernel->maximum = kernel->values[kernel->x+kernel->y*(ssize_t) kernel->width];
1335  kernel->negative_range = 0.0;
1336  break;
1337  }
1338 
1339  /*
1340  Convolution Kernels - Well Known Named Constant Kernels
1341  */
1342  case LaplacianKernel:
1343  { switch ( (int) args->rho ) {
1344  case 0:
1345  default: /* laplacian square filter -- default */
1346  kernel=ParseKernelArray("3: -1,-1,-1 -1,8,-1 -1,-1,-1");
1347  break;
1348  case 1: /* laplacian diamond filter */
1349  kernel=ParseKernelArray("3: 0,-1,0 -1,4,-1 0,-1,0");
1350  break;
1351  case 2:
1352  kernel=ParseKernelArray("3: -2,1,-2 1,4,1 -2,1,-2");
1353  break;
1354  case 3:
1355  kernel=ParseKernelArray("3: 1,-2,1 -2,4,-2 1,-2,1");
1356  break;
1357  case 5: /* a 5x5 laplacian */
1358  kernel=ParseKernelArray(
1359  "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");
1360  break;
1361  case 7: /* a 7x7 laplacian */
1362  kernel=ParseKernelArray(
1363  "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" );
1364  break;
1365  case 15: /* a 5x5 LoG (sigma approx 1.4) */
1366  kernel=ParseKernelArray(
1367  "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");
1368  break;
1369  case 19: /* a 9x9 LoG (sigma approx 1.4) */
1370  /* http://www.cscjournals.org/csc/manuscript/Journals/IJIP/volume3/Issue1/IJIP-15.pdf */
1371  kernel=ParseKernelArray(
1372  "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");
1373  break;
1374  }
1375  if (kernel == (KernelInfo *) NULL)
1376  return(kernel);
1377  kernel->type = type;
1378  break;
1379  }
1380  case SobelKernel:
1381  { /* Simple Sobel Kernel */
1382  kernel=ParseKernelArray("3: 1,0,-1 2,0,-2 1,0,-1");
1383  if (kernel == (KernelInfo *) NULL)
1384  return(kernel);
1385  kernel->type = type;
1386  RotateKernelInfo(kernel, args->rho);
1387  break;
1388  }
1389  case RobertsKernel:
1390  {
1391  kernel=ParseKernelArray("3: 0,0,0 1,-1,0 0,0,0");
1392  if (kernel == (KernelInfo *) NULL)
1393  return(kernel);
1394  kernel->type = type;
1395  RotateKernelInfo(kernel, args->rho);
1396  break;
1397  }
1398  case PrewittKernel:
1399  {
1400  kernel=ParseKernelArray("3: 1,0,-1 1,0,-1 1,0,-1");
1401  if (kernel == (KernelInfo *) NULL)
1402  return(kernel);
1403  kernel->type = type;
1404  RotateKernelInfo(kernel, args->rho);
1405  break;
1406  }
1407  case CompassKernel:
1408  {
1409  kernel=ParseKernelArray("3: 1,1,-1 1,-2,-1 1,1,-1");
1410  if (kernel == (KernelInfo *) NULL)
1411  return(kernel);
1412  kernel->type = type;
1413  RotateKernelInfo(kernel, args->rho);
1414  break;
1415  }
1416  case KirschKernel:
1417  {
1418  kernel=ParseKernelArray("3: 5,-3,-3 5,0,-3 5,-3,-3");
1419  if (kernel == (KernelInfo *) NULL)
1420  return(kernel);
1421  kernel->type = type;
1422  RotateKernelInfo(kernel, args->rho);
1423  break;
1424  }
1425  case FreiChenKernel:
1426  /* Direction is set to be left to right positive */
1427  /* http://www.math.tau.ac.il/~turkel/notes/edge_detectors.pdf -- RIGHT? */
1428  /* http://ltswww.epfl.ch/~courstiv/exos_labos/sol3.pdf -- WRONG? */
1429  { switch ( (int) args->rho ) {
1430  default:
1431  case 0:
1432  kernel=ParseKernelArray("3: 1,0,-1 2,0,-2 1,0,-1");
1433  if (kernel == (KernelInfo *) NULL)
1434  return(kernel);
1435  kernel->type = type;
1436  kernel->values[3] = +(MagickRealType) MagickSQ2;
1437  kernel->values[5] = -(MagickRealType) MagickSQ2;
1438  CalcKernelMetaData(kernel); /* recalculate meta-data */
1439  break;
1440  case 2:
1441  kernel=ParseKernelArray("3: 1,2,0 2,0,-2 0,-2,-1");
1442  if (kernel == (KernelInfo *) NULL)
1443  return(kernel);
1444  kernel->type = type;
1445  kernel->values[1] = kernel->values[3]= +(MagickRealType) MagickSQ2;
1446  kernel->values[5] = kernel->values[7]= -(MagickRealType) MagickSQ2;
1447  CalcKernelMetaData(kernel); /* recalculate meta-data */
1448  ScaleKernelInfo(kernel, (double) (1.0/2.0*MagickSQ2), NoValue);
1449  break;
1450  case 10:
1451  {
1452  kernel=AcquireKernelInfo("FreiChen:11;FreiChen:12;FreiChen:13;FreiChen:14;FreiChen:15;FreiChen:16;FreiChen:17;FreiChen:18;FreiChen:19",exception);
1453  if (kernel == (KernelInfo *) NULL)
1454  return(kernel);
1455  break;
1456  }
1457  case 1:
1458  case 11:
1459  kernel=ParseKernelArray("3: 1,0,-1 2,0,-2 1,0,-1");
1460  if (kernel == (KernelInfo *) NULL)
1461  return(kernel);
1462  kernel->type = type;
1463  kernel->values[3] = +(MagickRealType) MagickSQ2;
1464  kernel->values[5] = -(MagickRealType) MagickSQ2;
1465  CalcKernelMetaData(kernel); /* recalculate meta-data */
1466  ScaleKernelInfo(kernel, (double) (1.0/2.0*MagickSQ2), NoValue);
1467  break;
1468  case 12:
1469  kernel=ParseKernelArray("3: 1,2,1 0,0,0 1,2,1");
1470  if (kernel == (KernelInfo *) NULL)
1471  return(kernel);
1472  kernel->type = type;
1473  kernel->values[1] = +(MagickRealType) MagickSQ2;
1474  kernel->values[7] = +(MagickRealType) MagickSQ2;
1475  CalcKernelMetaData(kernel);
1476  ScaleKernelInfo(kernel, (double) (1.0/2.0*MagickSQ2), NoValue);
1477  break;
1478  case 13:
1479  kernel=ParseKernelArray("3: 2,-1,0 -1,0,1 0,1,-2");
1480  if (kernel == (KernelInfo *) NULL)
1481  return(kernel);
1482  kernel->type = type;
1483  kernel->values[0] = +(MagickRealType) MagickSQ2;
1484  kernel->values[8] = -(MagickRealType) MagickSQ2;
1485  CalcKernelMetaData(kernel);
1486  ScaleKernelInfo(kernel, (double) (1.0/2.0*MagickSQ2), NoValue);
1487  break;
1488  case 14:
1489  kernel=ParseKernelArray("3: 0,1,-2 -1,0,1 2,-1,0");
1490  if (kernel == (KernelInfo *) NULL)
1491  return(kernel);
1492  kernel->type = type;
1493  kernel->values[2] = -(MagickRealType) MagickSQ2;
1494  kernel->values[6] = +(MagickRealType) MagickSQ2;
1495  CalcKernelMetaData(kernel);
1496  ScaleKernelInfo(kernel, (double) (1.0/2.0*MagickSQ2), NoValue);
1497  break;
1498  case 15:
1499  kernel=ParseKernelArray("3: 0,-1,0 1,0,1 0,-1,0");
1500  if (kernel == (KernelInfo *) NULL)
1501  return(kernel);
1502  kernel->type = type;
1503  ScaleKernelInfo(kernel, 1.0/2.0, NoValue);
1504  break;
1505  case 16:
1506  kernel=ParseKernelArray("3: 1,0,-1 0,0,0 -1,0,1");
1507  if (kernel == (KernelInfo *) NULL)
1508  return(kernel);
1509  kernel->type = type;
1510  ScaleKernelInfo(kernel, 1.0/2.0, NoValue);
1511  break;
1512  case 17:
1513  kernel=ParseKernelArray("3: 1,-2,1 -2,4,-2 -1,-2,1");
1514  if (kernel == (KernelInfo *) NULL)
1515  return(kernel);
1516  kernel->type = type;
1517  ScaleKernelInfo(kernel, 1.0/6.0, NoValue);
1518  break;
1519  case 18:
1520  kernel=ParseKernelArray("3: -2,1,-2 1,4,1 -2,1,-2");
1521  if (kernel == (KernelInfo *) NULL)
1522  return(kernel);
1523  kernel->type = type;
1524  ScaleKernelInfo(kernel, 1.0/6.0, NoValue);
1525  break;
1526  case 19:
1527  kernel=ParseKernelArray("3: 1,1,1 1,1,1 1,1,1");
1528  if (kernel == (KernelInfo *) NULL)
1529  return(kernel);
1530  kernel->type = type;
1531  ScaleKernelInfo(kernel, 1.0/3.0, NoValue);
1532  break;
1533  }
1534  if ( fabs(args->sigma) >= MagickEpsilon )
1535  /* Rotate by correctly supplied 'angle' */
1536  RotateKernelInfo(kernel, args->sigma);
1537  else if ( args->rho > 30.0 || args->rho < -30.0 )
1538  /* Rotate by out of bounds 'type' */
1539  RotateKernelInfo(kernel, args->rho);
1540  break;
1541  }
1542 
1543  /*
1544  Boolean or Shaped Kernels
1545  */
1546  case DiamondKernel:
1547  {
1548  if (args->rho < 1.0)
1549  kernel->width = kernel->height = 3; /* default radius = 1 */
1550  else
1551  kernel->width = kernel->height = CastDoubleToSizeT(args->rho)*2+1;
1552  kernel->x = kernel->y = (ssize_t) (kernel->width-1)/2;
1553 
1554  if (AcquireKernelValues(kernel) == MagickFalse)
1555  return(DestroyKernelInfo(kernel));
1556 
1557  /* set all kernel values within diamond area to scale given */
1558  for ( i=0, v=-kernel->y; v <= (ssize_t)kernel->y; v++)
1559  for ( u=-kernel->x; u <= (ssize_t)kernel->x; u++, i++)
1560  if ( (labs((long) u)+labs((long) v)) <= (long) kernel->x)
1561  kernel->positive_range += kernel->values[i] = args->sigma;
1562  else
1563  kernel->values[i] = nan;
1564  kernel->minimum = kernel->maximum = args->sigma; /* a flat shape */
1565  break;
1566  }
1567  case SquareKernel:
1568  case RectangleKernel:
1569  { double
1570  scale;
1571  if ( type == SquareKernel )
1572  {
1573  if (args->rho < 1.0)
1574  kernel->width = kernel->height = 3; /* default radius = 1 */
1575  else
1576  kernel->width = kernel->height = CastDoubleToSizeT(args->rho*2+1);
1577  kernel->x = kernel->y = (ssize_t) (kernel->width-1)/2;
1578  scale = args->sigma;
1579  }
1580  else {
1581  /* NOTE: user defaults set in "AcquireKernelInfo()" */
1582  if ( args->rho < 1.0 || args->sigma < 1.0 )
1583  return(DestroyKernelInfo(kernel)); /* invalid args given */
1584  kernel->width = CastDoubleToSizeT(args->rho);
1585  kernel->height = CastDoubleToSizeT(args->sigma);
1586  if ((args->xi < 0.0) || (args->xi >= (double) kernel->width) ||
1587  (args->psi < 0.0) || (args->psi >= (double) kernel->height))
1588  return(DestroyKernelInfo(kernel)); /* invalid args given */
1589  kernel->x = (ssize_t) args->xi;
1590  kernel->y = (ssize_t) args->psi;
1591  scale = 1.0;
1592  }
1593  if (AcquireKernelValues(kernel) == MagickFalse)
1594  return(DestroyKernelInfo(kernel));
1595 
1596  /* set all kernel values to scale given */
1597  u=(ssize_t) (kernel->width*kernel->height);
1598  for ( i=0; i < u; i++)
1599  kernel->values[i] = scale;
1600  kernel->minimum = kernel->maximum = scale; /* a flat shape */
1601  kernel->positive_range = scale*u;
1602  break;
1603  }
1604  case OctagonKernel:
1605  {
1606  if (args->rho < 1.0)
1607  kernel->width = kernel->height = 5; /* default radius = 2 */
1608  else
1609  kernel->width = kernel->height = CastDoubleToSizeT(args->rho)*2+1;
1610  kernel->x = kernel->y = (ssize_t) (kernel->width-1)/2;
1611 
1612  if (AcquireKernelValues(kernel) == MagickFalse)
1613  return(DestroyKernelInfo(kernel));
1614 
1615  for ( i=0, v=-kernel->y; v <= (ssize_t)kernel->y; v++)
1616  for ( u=-kernel->x; u <= (ssize_t)kernel->x; u++, i++)
1617  if ( (labs((long) u)+labs((long) v)) <=
1618  ((long)kernel->x + (long)(kernel->x/2)) )
1619  kernel->positive_range += kernel->values[i] = args->sigma;
1620  else
1621  kernel->values[i] = nan;
1622  kernel->minimum = kernel->maximum = args->sigma; /* a flat shape */
1623  break;
1624  }
1625  case DiskKernel:
1626  {
1627  ssize_t
1628  limit = (ssize_t)(args->rho*args->rho);
1629 
1630  if (args->rho < 0.4) /* default radius approx 4.3 */
1631  kernel->width = kernel->height = 9L, limit = 18L;
1632  else
1633  kernel->width = kernel->height = CastDoubleToSizeT(fabs(args->rho))*2+1;
1634  kernel->x = kernel->y = (ssize_t) (kernel->width-1)/2;
1635 
1636  if (AcquireKernelValues(kernel) == MagickFalse)
1637  return(DestroyKernelInfo(kernel));
1638 
1639  for ( i=0, v=-kernel->y; v <= (ssize_t)kernel->y; v++)
1640  for ( u=-kernel->x; u <= (ssize_t)kernel->x; u++, i++)
1641  if ((u*u+v*v) <= limit)
1642  kernel->positive_range += kernel->values[i] = args->sigma;
1643  else
1644  kernel->values[i] = nan;
1645  kernel->minimum = kernel->maximum = args->sigma; /* a flat shape */
1646  break;
1647  }
1648  case PlusKernel:
1649  {
1650  if (args->rho < 1.0)
1651  kernel->width = kernel->height = 5; /* default radius 2 */
1652  else
1653  kernel->width = kernel->height = CastDoubleToSizeT(args->rho)*2+1;
1654  kernel->x = kernel->y = (ssize_t) (kernel->width-1)/2;
1655 
1656  if (AcquireKernelValues(kernel) == MagickFalse)
1657  return(DestroyKernelInfo(kernel));
1658 
1659  /* set all kernel values along axises to given scale */
1660  for ( i=0, v=-kernel->y; v <= (ssize_t)kernel->y; v++)
1661  for ( u=-kernel->x; u <= (ssize_t)kernel->x; u++, i++)
1662  kernel->values[i] = (u == 0 || v == 0) ? args->sigma : nan;
1663  kernel->minimum = kernel->maximum = args->sigma; /* a flat shape */
1664  kernel->positive_range = args->sigma*(kernel->width*2.0 - 1.0);
1665  break;
1666  }
1667  case CrossKernel:
1668  {
1669  if (args->rho < 1.0)
1670  kernel->width = kernel->height = 5; /* default radius 2 */
1671  else
1672  kernel->width = kernel->height = CastDoubleToSizeT(args->rho)*2+1;
1673  kernel->x = kernel->y = (ssize_t) (kernel->width-1)/2;
1674 
1675  if (AcquireKernelValues(kernel) == MagickFalse)
1676  return(DestroyKernelInfo(kernel));
1677 
1678  /* set all kernel values along axises to given scale */
1679  for ( i=0, v=-kernel->y; v <= (ssize_t)kernel->y; v++)
1680  for ( u=-kernel->x; u <= (ssize_t)kernel->x; u++, i++)
1681  kernel->values[i] = (u == v || u == -v) ? args->sigma : nan;
1682  kernel->minimum = kernel->maximum = args->sigma; /* a flat shape */
1683  kernel->positive_range = args->sigma*(kernel->width*2.0 - 1.0);
1684  break;
1685  }
1686  /*
1687  HitAndMiss Kernels
1688  */
1689  case RingKernel:
1690  case PeaksKernel:
1691  {
1692  ssize_t
1693  limit1,
1694  limit2,
1695  scale;
1696 
1697  if (args->rho < args->sigma)
1698  {
1699  kernel->width = CastDoubleToSizeT(args->sigma)*2+1;
1700  limit1 = (ssize_t)(args->rho*args->rho);
1701  limit2 = (ssize_t)(args->sigma*args->sigma);
1702  }
1703  else
1704  {
1705  kernel->width = CastDoubleToSizeT(args->rho)*2+1;
1706  limit1 = (ssize_t)(args->sigma*args->sigma);
1707  limit2 = (ssize_t)(args->rho*args->rho);
1708  }
1709  if ( limit2 <= 0 )
1710  kernel->width = 7L, limit1 = 7L, limit2 = 11L;
1711 
1712  kernel->height = kernel->width;
1713  kernel->x = kernel->y = (ssize_t) (kernel->width-1)/2;
1714  if (AcquireKernelValues(kernel) == MagickFalse)
1715  return(DestroyKernelInfo(kernel));
1716 
1717  /* set a ring of points of 'scale' ( 0.0 for PeaksKernel ) */
1718  scale = (ssize_t) (( type == PeaksKernel) ? 0.0 : args->xi);
1719  for ( i=0, v= -kernel->y; v <= (ssize_t)kernel->y; v++)
1720  for ( u=-kernel->x; u <= (ssize_t)kernel->x; u++, i++)
1721  { ssize_t radius=u*u+v*v;
1722  if (limit1 < radius && radius <= limit2)
1723  kernel->positive_range += kernel->values[i] = (double) scale;
1724  else
1725  kernel->values[i] = nan;
1726  }
1727  kernel->minimum = kernel->maximum = (double) scale;
1728  if ( type == PeaksKernel ) {
1729  /* set the central point in the middle */
1730  kernel->values[kernel->x+kernel->y*(ssize_t) kernel->width] = 1.0;
1731  kernel->positive_range = 1.0;
1732  kernel->maximum = 1.0;
1733  }
1734  break;
1735  }
1736  case EdgesKernel:
1737  {
1738  kernel=AcquireKernelInfo("ThinSE:482",exception);
1739  if (kernel == (KernelInfo *) NULL)
1740  return(kernel);
1741  kernel->type = type;
1742  ExpandMirrorKernelInfo(kernel); /* mirror expansion of kernels */
1743  break;
1744  }
1745  case CornersKernel:
1746  {
1747  kernel=AcquireKernelInfo("ThinSE:87",exception);
1748  if (kernel == (KernelInfo *) NULL)
1749  return(kernel);
1750  kernel->type = type;
1751  ExpandRotateKernelInfo(kernel, 90.0); /* Expand 90 degree rotations */
1752  break;
1753  }
1754  case DiagonalsKernel:
1755  {
1756  switch ( (int) args->rho ) {
1757  case 0:
1758  default:
1759  { KernelInfo
1760  *new_kernel;
1761  kernel=ParseKernelArray("3: 0,0,0 0,-,1 1,1,-");
1762  if (kernel == (KernelInfo *) NULL)
1763  return(kernel);
1764  kernel->type = type;
1765  new_kernel=ParseKernelArray("3: 0,0,1 0,-,1 0,1,-");
1766  if (new_kernel == (KernelInfo *) NULL)
1767  return(DestroyKernelInfo(kernel));
1768  new_kernel->type = type;
1769  LastKernelInfo(kernel)->next = new_kernel;
1770  ExpandMirrorKernelInfo(kernel);
1771  return(kernel);
1772  }
1773  case 1:
1774  kernel=ParseKernelArray("3: 0,0,0 0,-,1 1,1,-");
1775  break;
1776  case 2:
1777  kernel=ParseKernelArray("3: 0,0,1 0,-,1 0,1,-");
1778  break;
1779  }
1780  if (kernel == (KernelInfo *) NULL)
1781  return(kernel);
1782  kernel->type = type;
1783  RotateKernelInfo(kernel, args->sigma);
1784  break;
1785  }
1786  case LineEndsKernel:
1787  { /* Kernels for finding the end of thin lines */
1788  switch ( (int) args->rho ) {
1789  case 0:
1790  default:
1791  /* set of kernels to find all end of lines */
1792  return(AcquireKernelInfo("LineEnds:1>;LineEnds:2>",exception));
1793  case 1:
1794  /* kernel for 4-connected line ends - no rotation */
1795  kernel=ParseKernelArray("3: 0,0,- 0,1,1 0,0,-");
1796  break;
1797  case 2:
1798  /* kernel to add for 8-connected lines - no rotation */
1799  kernel=ParseKernelArray("3: 0,0,0 0,1,0 0,0,1");
1800  break;
1801  case 3:
1802  /* kernel to add for orthogonal line ends - does not find corners */
1803  kernel=ParseKernelArray("3: 0,0,0 0,1,1 0,0,0");
1804  break;
1805  case 4:
1806  /* traditional line end - fails on last T end */
1807  kernel=ParseKernelArray("3: 0,0,0 0,1,- 0,0,-");
1808  break;
1809  }
1810  if (kernel == (KernelInfo *) NULL)
1811  return(kernel);
1812  kernel->type = type;
1813  RotateKernelInfo(kernel, args->sigma);
1814  break;
1815  }
1816  case LineJunctionsKernel:
1817  { /* kernels for finding the junctions of multiple lines */
1818  switch ( (int) args->rho ) {
1819  case 0:
1820  default:
1821  /* set of kernels to find all line junctions */
1822  return(AcquireKernelInfo("LineJunctions:1@;LineJunctions:2>",exception));
1823  case 1:
1824  /* Y Junction */
1825  kernel=ParseKernelArray("3: 1,-,1 -,1,- -,1,-");
1826  break;
1827  case 2:
1828  /* Diagonal T Junctions */
1829  kernel=ParseKernelArray("3: 1,-,- -,1,- 1,-,1");
1830  break;
1831  case 3:
1832  /* Orthogonal T Junctions */
1833  kernel=ParseKernelArray("3: -,-,- 1,1,1 -,1,-");
1834  break;
1835  case 4:
1836  /* Diagonal X Junctions */
1837  kernel=ParseKernelArray("3: 1,-,1 -,1,- 1,-,1");
1838  break;
1839  case 5:
1840  /* Orthogonal X Junctions - minimal diamond kernel */
1841  kernel=ParseKernelArray("3: -,1,- 1,1,1 -,1,-");
1842  break;
1843  }
1844  if (kernel == (KernelInfo *) NULL)
1845  return(kernel);
1846  kernel->type = type;
1847  RotateKernelInfo(kernel, args->sigma);
1848  break;
1849  }
1850  case RidgesKernel:
1851  { /* Ridges - Ridge finding kernels */
1852  KernelInfo
1853  *new_kernel;
1854  switch ( (int) args->rho ) {
1855  case 1:
1856  default:
1857  kernel=ParseKernelArray("3x1:0,1,0");
1858  if (kernel == (KernelInfo *) NULL)
1859  return(kernel);
1860  kernel->type = type;
1861  ExpandRotateKernelInfo(kernel, 90.0); /* 2 rotated kernels (symmetrical) */
1862  break;
1863  case 2:
1864  kernel=ParseKernelArray("4x1:0,1,1,0");
1865  if (kernel == (KernelInfo *) NULL)
1866  return(kernel);
1867  kernel->type = type;
1868  ExpandRotateKernelInfo(kernel, 90.0); /* 4 rotated kernels */
1869 
1870  /* Kernels to find a stepped 'thick' line, 4 rotates + mirrors */
1871  /* Unfortunately we can not yet rotate a non-square kernel */
1872  /* But then we can't flip a non-symmetrical kernel either */
1873  new_kernel=ParseKernelArray("4x3+1+1:0,1,1,- -,1,1,- -,1,1,0");
1874  if (new_kernel == (KernelInfo *) NULL)
1875  return(DestroyKernelInfo(kernel));
1876  new_kernel->type = type;
1877  LastKernelInfo(kernel)->next = new_kernel;
1878  new_kernel=ParseKernelArray("4x3+2+1:0,1,1,- -,1,1,- -,1,1,0");
1879  if (new_kernel == (KernelInfo *) NULL)
1880  return(DestroyKernelInfo(kernel));
1881  new_kernel->type = type;
1882  LastKernelInfo(kernel)->next = new_kernel;
1883  new_kernel=ParseKernelArray("4x3+1+1:-,1,1,0 -,1,1,- 0,1,1,-");
1884  if (new_kernel == (KernelInfo *) NULL)
1885  return(DestroyKernelInfo(kernel));
1886  new_kernel->type = type;
1887  LastKernelInfo(kernel)->next = new_kernel;
1888  new_kernel=ParseKernelArray("4x3+2+1:-,1,1,0 -,1,1,- 0,1,1,-");
1889  if (new_kernel == (KernelInfo *) NULL)
1890  return(DestroyKernelInfo(kernel));
1891  new_kernel->type = type;
1892  LastKernelInfo(kernel)->next = new_kernel;
1893  new_kernel=ParseKernelArray("3x4+1+1:0,-,- 1,1,1 1,1,1 -,-,0");
1894  if (new_kernel == (KernelInfo *) NULL)
1895  return(DestroyKernelInfo(kernel));
1896  new_kernel->type = type;
1897  LastKernelInfo(kernel)->next = new_kernel;
1898  new_kernel=ParseKernelArray("3x4+1+2:0,-,- 1,1,1 1,1,1 -,-,0");
1899  if (new_kernel == (KernelInfo *) NULL)
1900  return(DestroyKernelInfo(kernel));
1901  new_kernel->type = type;
1902  LastKernelInfo(kernel)->next = new_kernel;
1903  new_kernel=ParseKernelArray("3x4+1+1:-,-,0 1,1,1 1,1,1 0,-,-");
1904  if (new_kernel == (KernelInfo *) NULL)
1905  return(DestroyKernelInfo(kernel));
1906  new_kernel->type = type;
1907  LastKernelInfo(kernel)->next = new_kernel;
1908  new_kernel=ParseKernelArray("3x4+1+2:-,-,0 1,1,1 1,1,1 0,-,-");
1909  if (new_kernel == (KernelInfo *) NULL)
1910  return(DestroyKernelInfo(kernel));
1911  new_kernel->type = type;
1912  LastKernelInfo(kernel)->next = new_kernel;
1913  break;
1914  }
1915  break;
1916  }
1917  case ConvexHullKernel:
1918  {
1919  KernelInfo
1920  *new_kernel;
1921  /* first set of 8 kernels */
1922  kernel=ParseKernelArray("3: 1,1,- 1,0,- 1,-,0");
1923  if (kernel == (KernelInfo *) NULL)
1924  return(kernel);
1925  kernel->type = type;
1926  ExpandRotateKernelInfo(kernel, 90.0);
1927  /* append the mirror versions too - no flip function yet */
1928  new_kernel=ParseKernelArray("3: 1,1,1 1,0,- -,-,0");
1929  if (new_kernel == (KernelInfo *) NULL)
1930  return(DestroyKernelInfo(kernel));
1931  new_kernel->type = type;
1932  ExpandRotateKernelInfo(new_kernel, 90.0);
1933  LastKernelInfo(kernel)->next = new_kernel;
1934  break;
1935  }
1936  case SkeletonKernel:
1937  {
1938  switch ( (int) args->rho ) {
1939  case 1:
1940  default:
1941  /* Traditional Skeleton...
1942  ** A cyclically rotated single kernel
1943  */
1944  kernel=AcquireKernelInfo("ThinSE:482",exception);
1945  if (kernel == (KernelInfo *) NULL)
1946  return(kernel);
1947  kernel->type = type;
1948  ExpandRotateKernelInfo(kernel, 45.0); /* 8 rotations */
1949  break;
1950  case 2:
1951  /* HIPR Variation of the cyclic skeleton
1952  ** Corners of the traditional method made more forgiving,
1953  ** but the retain the same cyclic order.
1954  */
1955  kernel=AcquireKernelInfo("ThinSE:482; ThinSE:87x90;",exception);
1956  if (kernel == (KernelInfo *) NULL)
1957  return(kernel);
1958  if (kernel->next == (KernelInfo *) NULL)
1959  return(DestroyKernelInfo(kernel));
1960  kernel->type = type;
1961  kernel->next->type = type;
1962  ExpandRotateKernelInfo(kernel, 90.0); /* 4 rotations of the 2 kernels */
1963  break;
1964  case 3:
1965  /* Dan Bloomberg Skeleton, from his paper on 3x3 thinning SE's
1966  ** "Connectivity-Preserving Morphological Image Transformations"
1967  ** by Dan S. Bloomberg, available on Leptonica, Selected Papers,
1968  ** http://www.leptonica.com/papers/conn.pdf
1969  */
1970  kernel=AcquireKernelInfo("ThinSE:41; ThinSE:42; ThinSE:43",
1971  exception);
1972  if (kernel == (KernelInfo *) NULL)
1973  return(kernel);
1974  if (kernel->next == (KernelInfo *) NULL)
1975  return(DestroyKernelInfo(kernel));
1976  if (kernel->next->next == (KernelInfo *) NULL)
1977  return(DestroyKernelInfo(kernel));
1978  kernel->type = type;
1979  kernel->next->type = type;
1980  kernel->next->next->type = type;
1981  ExpandMirrorKernelInfo(kernel); /* 12 kernels total */
1982  break;
1983  }
1984  break;
1985  }
1986  case ThinSEKernel:
1987  { /* Special kernels for general thinning, while preserving connections
1988  ** "Connectivity-Preserving Morphological Image Transformations"
1989  ** by Dan S. Bloomberg, available on Leptonica, Selected Papers,
1990  ** http://www.leptonica.com/papers/conn.pdf
1991  ** And
1992  ** http://tpgit.github.com/Leptonica/ccthin_8c_source.html
1993  **
1994  ** Note kernels do not specify the origin pixel, allowing them
1995  ** to be used for both thickening and thinning operations.
1996  */
1997  switch ( (int) args->rho ) {
1998  /* SE for 4-connected thinning */
1999  case 41: /* SE_4_1 */
2000  kernel=ParseKernelArray("3: -,-,1 0,-,1 -,-,1");
2001  break;
2002  case 42: /* SE_4_2 */
2003  kernel=ParseKernelArray("3: -,-,1 0,-,1 -,0,-");
2004  break;
2005  case 43: /* SE_4_3 */
2006  kernel=ParseKernelArray("3: -,0,- 0,-,1 -,-,1");
2007  break;
2008  case 44: /* SE_4_4 */
2009  kernel=ParseKernelArray("3: -,0,- 0,-,1 -,0,-");
2010  break;
2011  case 45: /* SE_4_5 */
2012  kernel=ParseKernelArray("3: -,0,1 0,-,1 -,0,-");
2013  break;
2014  case 46: /* SE_4_6 */
2015  kernel=ParseKernelArray("3: -,0,- 0,-,1 -,0,1");
2016  break;
2017  case 47: /* SE_4_7 */
2018  kernel=ParseKernelArray("3: -,1,1 0,-,1 -,0,-");
2019  break;
2020  case 48: /* SE_4_8 */
2021  kernel=ParseKernelArray("3: -,-,1 0,-,1 0,-,1");
2022  break;
2023  case 49: /* SE_4_9 */
2024  kernel=ParseKernelArray("3: 0,-,1 0,-,1 -,-,1");
2025  break;
2026  /* SE for 8-connected thinning - negatives of the above */
2027  case 81: /* SE_8_0 */
2028  kernel=ParseKernelArray("3: -,1,- 0,-,1 -,1,-");
2029  break;
2030  case 82: /* SE_8_2 */
2031  kernel=ParseKernelArray("3: -,1,- 0,-,1 0,-,-");
2032  break;
2033  case 83: /* SE_8_3 */
2034  kernel=ParseKernelArray("3: 0,-,- 0,-,1 -,1,-");
2035  break;
2036  case 84: /* SE_8_4 */
2037  kernel=ParseKernelArray("3: 0,-,- 0,-,1 0,-,-");
2038  break;
2039  case 85: /* SE_8_5 */
2040  kernel=ParseKernelArray("3: 0,-,1 0,-,1 0,-,-");
2041  break;
2042  case 86: /* SE_8_6 */
2043  kernel=ParseKernelArray("3: 0,-,- 0,-,1 0,-,1");
2044  break;
2045  case 87: /* SE_8_7 */
2046  kernel=ParseKernelArray("3: -,1,- 0,-,1 0,0,-");
2047  break;
2048  case 88: /* SE_8_8 */
2049  kernel=ParseKernelArray("3: -,1,- 0,-,1 0,1,-");
2050  break;
2051  case 89: /* SE_8_9 */
2052  kernel=ParseKernelArray("3: 0,1,- 0,-,1 -,1,-");
2053  break;
2054  /* Special combined SE kernels */
2055  case 423: /* SE_4_2 , SE_4_3 Combined Kernel */
2056  kernel=ParseKernelArray("3: -,-,1 0,-,- -,0,-");
2057  break;
2058  case 823: /* SE_8_2 , SE_8_3 Combined Kernel */
2059  kernel=ParseKernelArray("3: -,1,- -,-,1 0,-,-");
2060  break;
2061  case 481: /* SE_48_1 - General Connected Corner Kernel */
2062  kernel=ParseKernelArray("3: -,1,1 0,-,1 0,0,-");
2063  break;
2064  default:
2065  case 482: /* SE_48_2 - General Edge Kernel */
2066  kernel=ParseKernelArray("3: 0,-,1 0,-,1 0,-,1");
2067  break;
2068  }
2069  if (kernel == (KernelInfo *) NULL)
2070  return(kernel);
2071  kernel->type = type;
2072  RotateKernelInfo(kernel, args->sigma);
2073  break;
2074  }
2075  /*
2076  Distance Measuring Kernels
2077  */
2078  case ChebyshevKernel:
2079  {
2080  if (args->rho < 1.0)
2081  kernel->width = kernel->height = 3; /* default radius = 1 */
2082  else
2083  kernel->width = kernel->height = CastDoubleToSizeT(args->rho)*2+1;
2084  kernel->x = kernel->y = (ssize_t) (kernel->width-1)/2;
2085 
2086  if (AcquireKernelValues(kernel) == MagickFalse)
2087  return(DestroyKernelInfo(kernel));
2088 
2089  for ( i=0, v=-kernel->y; v <= (ssize_t)kernel->y; v++)
2090  for ( u=-kernel->x; u <= (ssize_t)kernel->x; u++, i++)
2091  kernel->positive_range += ( kernel->values[i] =
2092  args->sigma*MagickMax(fabs((double)u),fabs((double)v)) );
2093  kernel->maximum = kernel->values[0];
2094  break;
2095  }
2096  case ManhattanKernel:
2097  {
2098  if (args->rho < 1.0)
2099  kernel->width = kernel->height = 3; /* default radius = 1 */
2100  else
2101  kernel->width = kernel->height = CastDoubleToSizeT(args->rho)*2+1;
2102  kernel->x = kernel->y = (ssize_t) (kernel->width-1)/2;
2103 
2104  if (AcquireKernelValues(kernel) == MagickFalse)
2105  return(DestroyKernelInfo(kernel));
2106 
2107  for ( i=0, v=-kernel->y; v <= (ssize_t)kernel->y; v++)
2108  for ( u=-kernel->x; u <= (ssize_t)kernel->x; u++, i++)
2109  kernel->positive_range += ( kernel->values[i] =
2110  args->sigma*(labs((long) u)+labs((long) v)) );
2111  kernel->maximum = kernel->values[0];
2112  break;
2113  }
2114  case OctagonalKernel:
2115  {
2116  if (args->rho < 2.0)
2117  kernel->width = kernel->height = 5; /* default/minimum radius = 2 */
2118  else
2119  kernel->width = kernel->height = CastDoubleToSizeT(args->rho)*2+1;
2120  kernel->x = kernel->y = (ssize_t) (kernel->width-1)/2;
2121 
2122  if (AcquireKernelValues(kernel) == MagickFalse)
2123  return(DestroyKernelInfo(kernel));
2124 
2125  for ( i=0, v=-kernel->y; v <= (ssize_t)kernel->y; v++)
2126  for ( u=-kernel->x; u <= (ssize_t)kernel->x; u++, i++)
2127  {
2128  double
2129  r1 = MagickMax(fabs((double)u),fabs((double)v)),
2130  r2 = floor((double)(labs((long)u)+labs((long)v)+1)/1.5);
2131  kernel->positive_range += kernel->values[i] =
2132  args->sigma*MagickMax(r1,r2);
2133  }
2134  kernel->maximum = kernel->values[0];
2135  break;
2136  }
2137  case EuclideanKernel:
2138  {
2139  if (args->rho < 1.0)
2140  kernel->width = kernel->height = 3; /* default radius = 1 */
2141  else
2142  kernel->width = kernel->height = CastDoubleToSizeT(args->rho)*2+1;
2143  kernel->x = kernel->y = (ssize_t) (kernel->width-1)/2;
2144 
2145  if (AcquireKernelValues(kernel) == MagickFalse)
2146  return(DestroyKernelInfo(kernel));
2147 
2148  for ( i=0, v=-kernel->y; v <= (ssize_t)kernel->y; v++)
2149  for ( u=-kernel->x; u <= (ssize_t)kernel->x; u++, i++)
2150  kernel->positive_range += ( kernel->values[i] =
2151  args->sigma*sqrt((double) (u*u+v*v)) );
2152  kernel->maximum = kernel->values[0];
2153  break;
2154  }
2155  default:
2156  {
2157  /* No-Op Kernel - Basically just a single pixel on its own */
2158  kernel=ParseKernelArray("1:1");
2159  if (kernel == (KernelInfo *) NULL)
2160  return(kernel);
2161  kernel->type = UndefinedKernel;
2162  break;
2163  }
2164  break;
2165  }
2166  return(kernel);
2167 }
2168 ␌
2169 /*
2170 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2171 % %
2172 % %
2173 % %
2174 % C l o n e K e r n e l I n f o %
2175 % %
2176 % %
2177 % %
2178 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2179 %
2180 % CloneKernelInfo() creates a new clone of the given Kernel List so that its
2181 % can be modified without effecting the original. The cloned kernel should
2182 % be destroyed using DestroyKernelInfo() when no longer needed.
2183 %
2184 % The format of the CloneKernelInfo method is:
2185 %
2186 % KernelInfo *CloneKernelInfo(const KernelInfo *kernel)
2187 %
2188 % A description of each parameter follows:
2189 %
2190 % o kernel: the Morphology/Convolution kernel to be cloned
2191 %
2192 */
2193 MagickExport KernelInfo *CloneKernelInfo(const KernelInfo *kernel)
2194 {
2195  ssize_t
2196  i;
2197 
2198  KernelInfo
2199  *new_kernel;
2200 
2201  assert(kernel != (KernelInfo *) NULL);
2202  new_kernel=(KernelInfo *) AcquireMagickMemory(sizeof(*kernel));
2203  if (new_kernel == (KernelInfo *) NULL)
2204  return(new_kernel);
2205  *new_kernel=(*kernel); /* copy values in structure */
2206 
2207  /* replace the values with a copy of the values */
2208  if (AcquireKernelValues(new_kernel) == MagickFalse)
2209  return(DestroyKernelInfo(new_kernel));
2210  for (i=0; i < (ssize_t) (kernel->width*kernel->height); i++)
2211  new_kernel->values[i]=kernel->values[i];
2212 
2213  /* Also clone the next kernel in the kernel list */
2214  if ( kernel->next != (KernelInfo *) NULL ) {
2215  new_kernel->next = CloneKernelInfo(kernel->next);
2216  if ( new_kernel->next == (KernelInfo *) NULL )
2217  return(DestroyKernelInfo(new_kernel));
2218  }
2219 
2220  return(new_kernel);
2221 }
2222 ␌
2223 /*
2224 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2225 % %
2226 % %
2227 % %
2228 % D e s t r o y K e r n e l I n f o %
2229 % %
2230 % %
2231 % %
2232 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2233 %
2234 % DestroyKernelInfo() frees the memory used by a Convolution/Morphology
2235 % kernel.
2236 %
2237 % The format of the DestroyKernelInfo method is:
2238 %
2239 % KernelInfo *DestroyKernelInfo(KernelInfo *kernel)
2240 %
2241 % A description of each parameter follows:
2242 %
2243 % o kernel: the Morphology/Convolution kernel to be destroyed
2244 %
2245 */
2246 MagickExport KernelInfo *DestroyKernelInfo(KernelInfo *kernel)
2247 {
2248  assert(kernel != (KernelInfo *) NULL);
2249  if (kernel->next != (KernelInfo *) NULL)
2250  kernel->next=DestroyKernelInfo(kernel->next);
2251  kernel->values=(MagickRealType *) RelinquishAlignedMemory(kernel->values);
2252  kernel=(KernelInfo *) RelinquishMagickMemory(kernel);
2253  return(kernel);
2254 }
2255 ␌
2256 /*
2257 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2258 % %
2259 % %
2260 % %
2261 + E x p a n d M i r r o r K e r n e l I n f o %
2262 % %
2263 % %
2264 % %
2265 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2266 %
2267 % ExpandMirrorKernelInfo() takes a single kernel, and expands it into a
2268 % sequence of 90-degree rotated kernels but providing a reflected 180
2269 % rotation, before the -/+ 90-degree rotations.
2270 %
2271 % This special rotation order produces a better, more symmetrical thinning of
2272 % objects.
2273 %
2274 % The format of the ExpandMirrorKernelInfo method is:
2275 %
2276 % void ExpandMirrorKernelInfo(KernelInfo *kernel)
2277 %
2278 % A description of each parameter follows:
2279 %
2280 % o kernel: the Morphology/Convolution kernel
2281 %
2282 % This function is only internal to this module, as it is not finalized,
2283 % especially with regard to non-orthogonal angles, and rotation of larger
2284 % 2D kernels.
2285 */
2286 
2287 #if 0
2288 static void FlopKernelInfo(KernelInfo *kernel)
2289  { /* Do a Flop by reversing each row. */
2290  size_t
2291  y;
2292  ssize_t
2293  x,r;
2294  double
2295  *k,t;
2296 
2297  for ( y=0, k=kernel->values; y < kernel->height; y++, k+=kernel->width)
2298  for ( x=0, r=kernel->width-1; x<kernel->width/2; x++, r--)
2299  t=k[x], k[x]=k[r], k[r]=t;
2300 
2301  kernel->x = kernel->width - kernel->x - 1;
2302  angle = fmod(angle+180.0, 360.0);
2303  }
2304 #endif
2305 
2306 static void ExpandMirrorKernelInfo(KernelInfo *kernel)
2307 {
2308  KernelInfo
2309  *clone,
2310  *last;
2311 
2312  last = kernel;
2313 
2314  clone = CloneKernelInfo(last);
2315  if (clone == (KernelInfo *) NULL)
2316  return;
2317  RotateKernelInfo(clone, 180); /* flip */
2318  LastKernelInfo(last)->next = clone;
2319  last = clone;
2320 
2321  clone = CloneKernelInfo(last);
2322  if (clone == (KernelInfo *) NULL)
2323  return;
2324  RotateKernelInfo(clone, 90); /* transpose */
2325  LastKernelInfo(last)->next = clone;
2326  last = clone;
2327 
2328  clone = CloneKernelInfo(last);
2329  if (clone == (KernelInfo *) NULL)
2330  return;
2331  RotateKernelInfo(clone, 180); /* flop */
2332  LastKernelInfo(last)->next = clone;
2333 
2334  return;
2335 }
2336 ␌
2337 /*
2338 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2339 % %
2340 % %
2341 % %
2342 + E x p a n d R o t a t e K e r n e l I n f o %
2343 % %
2344 % %
2345 % %
2346 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2347 %
2348 % ExpandRotateKernelInfo() takes a kernel list, and expands it by rotating
2349 % incrementally by the angle given, until the kernel repeats.
2350 %
2351 % WARNING: 45 degree rotations only works for 3x3 kernels.
2352 % While 90 degree rotations only works for linear and square kernels
2353 %
2354 % The format of the ExpandRotateKernelInfo method is:
2355 %
2356 % void ExpandRotateKernelInfo(KernelInfo *kernel, double angle)
2357 %
2358 % A description of each parameter follows:
2359 %
2360 % o kernel: the Morphology/Convolution kernel
2361 %
2362 % o angle: angle to rotate in degrees
2363 %
2364 % This function is only internal to this module, as it is not finalized,
2365 % especially with regard to non-orthogonal angles, and rotation of larger
2366 % 2D kernels.
2367 */
2368 
2369 /* Internal Routine - Return true if two kernels are the same */
2370 static MagickBooleanType SameKernelInfo(const KernelInfo *kernel1,
2371  const KernelInfo *kernel2)
2372 {
2373  size_t
2374  i;
2375 
2376  /* check size and origin location */
2377  if ( kernel1->width != kernel2->width
2378  || kernel1->height != kernel2->height
2379  || kernel1->x != kernel2->x
2380  || kernel1->y != kernel2->y )
2381  return MagickFalse;
2382 
2383  /* check actual kernel values */
2384  for (i=0; i < (kernel1->width*kernel1->height); i++) {
2385  /* Test for Nan equivalence */
2386  if ( IsNaN(kernel1->values[i]) && !IsNaN(kernel2->values[i]) )
2387  return MagickFalse;
2388  if ( IsNaN(kernel2->values[i]) && !IsNaN(kernel1->values[i]) )
2389  return MagickFalse;
2390  /* Test actual values are equivalent */
2391  if ( fabs(kernel1->values[i] - kernel2->values[i]) >= MagickEpsilon )
2392  return MagickFalse;
2393  }
2394 
2395  return MagickTrue;
2396 }
2397 
2398 static void ExpandRotateKernelInfo(KernelInfo *kernel,const double angle)
2399 {
2400  KernelInfo
2401  *clone_info,
2402  *last;
2403 
2404  clone_info=(KernelInfo *) NULL;
2405  last=kernel;
2406 DisableMSCWarning(4127)
2407  while (1) {
2408 RestoreMSCWarning
2409  clone_info=CloneKernelInfo(last);
2410  if (clone_info == (KernelInfo *) NULL)
2411  break;
2412  RotateKernelInfo(clone_info,angle);
2413  if (SameKernelInfo(kernel,clone_info) != MagickFalse)
2414  break;
2415  LastKernelInfo(last)->next=clone_info;
2416  last=clone_info;
2417  }
2418  if (clone_info != (KernelInfo *) NULL)
2419  clone_info=DestroyKernelInfo(clone_info); /* kernel repeated - junk */
2420  return;
2421 }
2422 ␌
2423 /*
2424 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2425 % %
2426 % %
2427 % %
2428 + C a l c M e t a K e r n a l I n f o %
2429 % %
2430 % %
2431 % %
2432 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2433 %
2434 % CalcKernelMetaData() recalculate the KernelInfo meta-data of this kernel only,
2435 % using the kernel values. This should only ne used if it is not possible to
2436 % calculate that meta-data in some easier way.
2437 %
2438 % It is important that the meta-data is correct before ScaleKernelInfo() is
2439 % used to perform kernel normalization.
2440 %
2441 % The format of the CalcKernelMetaData method is:
2442 %
2443 % void CalcKernelMetaData(KernelInfo *kernel, const double scale )
2444 %
2445 % A description of each parameter follows:
2446 %
2447 % o kernel: the Morphology/Convolution kernel to modify
2448 %
2449 % WARNING: Minimum and Maximum values are assumed to include zero, even if
2450 % zero is not part of the kernel (as in Gaussian Derived kernels). This
2451 % however is not true for flat-shaped morphological kernels.
2452 %
2453 % WARNING: Only the specific kernel pointed to is modified, not a list of
2454 % multiple kernels.
2455 %
2456 % This is an internal function and not expected to be useful outside this
2457 % module. This could change however.
2458 */
2459 static void CalcKernelMetaData(KernelInfo *kernel)
2460 {
2461  size_t
2462  i;
2463 
2464  kernel->minimum = kernel->maximum = 0.0;
2465  kernel->negative_range = kernel->positive_range = 0.0;
2466  for (i=0; i < (kernel->width*kernel->height); i++)
2467  {
2468  if ( fabs(kernel->values[i]) < MagickEpsilon )
2469  kernel->values[i] = 0.0;
2470  ( kernel->values[i] < 0)
2471  ? ( kernel->negative_range += kernel->values[i] )
2472  : ( kernel->positive_range += kernel->values[i] );
2473  Minimize(kernel->minimum, kernel->values[i]);
2474  Maximize(kernel->maximum, kernel->values[i]);
2475  }
2476 
2477  return;
2478 }
2479 ␌
2480 /*
2481 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2482 % %
2483 % %
2484 % %
2485 % M o r p h o l o g y A p p l y %
2486 % %
2487 % %
2488 % %
2489 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2490 %
2491 % MorphologyApply() applies a morphological method, multiple times using
2492 % a list of multiple kernels. This is the method that should be called by
2493 % other 'operators' that internally use morphology operations as part of
2494 % their processing.
2495 %
2496 % It is basically equivalent to as MorphologyImage() (see below) but without
2497 % any user controls. This allows internal programs to use this method to
2498 % perform a specific task without possible interference by any API user
2499 % supplied settings.
2500 %
2501 % It is MorphologyImage() task to extract any such user controls, and
2502 % pass them to this function for processing.
2503 %
2504 % More specifically all given kernels should already be scaled, normalised,
2505 % and blended appropriately before being parred to this routine. The
2506 % appropriate bias, and compose (typically 'UndefinedComposeOp') given.
2507 %
2508 % The format of the MorphologyApply method is:
2509 %
2510 % Image *MorphologyApply(const Image *image,MorphologyMethod method,
2511 % const ssize_t iterations,const KernelInfo *kernel,
2512 % const CompositeMethod compose,const double bias,
2513 % ExceptionInfo *exception)
2514 %
2515 % A description of each parameter follows:
2516 %
2517 % o image: the source image
2518 %
2519 % o method: the morphology method to be applied.
2520 %
2521 % o iterations: apply the operation this many times (or no change).
2522 % A value of -1 means loop until no change found.
2523 % How this is applied may depend on the morphology method.
2524 % Typically this is a value of 1.
2525 %
2526 % o channel: the channel type.
2527 %
2528 % o kernel: An array of double representing the morphology kernel.
2529 %
2530 % o compose: How to handle or merge multi-kernel results.
2531 % If 'UndefinedCompositeOp' use default for the Morphology method.
2532 % If 'NoCompositeOp' force image to be re-iterated by each kernel.
2533 % Otherwise merge the results using the compose method given.
2534 %
2535 % o bias: Convolution Output Bias.
2536 %
2537 % o exception: return any errors or warnings in this structure.
2538 %
2539 */
2540 static ssize_t MorphologyPrimitive(const Image *image,Image *morphology_image,
2541  const MorphologyMethod method,const KernelInfo *kernel,const double bias,
2542  ExceptionInfo *exception)
2543 {
2544 #define MorphologyTag "Morphology/Image"
2545 
2546  CacheView
2547  *image_view,
2548  *morphology_view;
2549 
2550  MagickBooleanType
2551  status;
2552 
2553  MagickOffsetType
2554  progress;
2555 
2556  OffsetInfo
2557  offset;
2558 
2559  ssize_t
2560  j,
2561  y;
2562 
2563  size_t
2564  changed,
2565  *changes,
2566  width;
2567 
2568  /*
2569  Some methods (including convolve) needs to use a reflected kernel.
2570  Adjust 'origin' offsets to loop though kernel as a reflection.
2571  */
2572  assert(image != (Image *) NULL);
2573  assert(image->signature == MagickCoreSignature);
2574  assert(morphology_image != (Image *) NULL);
2575  assert(morphology_image->signature == MagickCoreSignature);
2576  assert(kernel != (KernelInfo *) NULL);
2577  assert(kernel->signature == MagickCoreSignature);
2578  assert(exception != (ExceptionInfo *) NULL);
2579  assert(exception->signature == MagickCoreSignature);
2580  status=MagickTrue;
2581  progress=0;
2582  image_view=AcquireVirtualCacheView(image,exception);
2583  morphology_view=AcquireAuthenticCacheView(morphology_image,exception);
2584  width=image->columns+kernel->width-1;
2585  offset.x=0;
2586  offset.y=0;
2587  switch (method)
2588  {
2589  case ConvolveMorphology:
2590  case DilateMorphology:
2591  case DilateIntensityMorphology:
2592  case IterativeDistanceMorphology:
2593  {
2594  /*
2595  Kernel needs to use a reflection about origin.
2596  */
2597  offset.x=(ssize_t) kernel->width-kernel->x-1;
2598  offset.y=(ssize_t) kernel->height-kernel->y-1;
2599  break;
2600  }
2601  case ErodeMorphology:
2602  case ErodeIntensityMorphology:
2603  case HitAndMissMorphology:
2604  case ThinningMorphology:
2605  case ThickenMorphology:
2606  {
2607  /*
2608  Use kernel as is, not reflection required.
2609  */
2610  offset.x=kernel->x;
2611  offset.y=kernel->y;
2612  break;
2613  }
2614  default:
2615  {
2616  (void) ThrowMagickException(exception,GetMagickModule(),OptionWarning,
2617  "InvalidOption","`%s'","not a primitive morphology method");
2618  break;
2619  }
2620  }
2621  changed=0;
2622  changes=(size_t *) AcquireQuantumMemory(GetOpenMPMaximumThreads(),
2623  sizeof(*changes));
2624  if (changes == (size_t *) NULL)
2625  ThrowFatalException(ResourceLimitFatalError,"MemoryAllocationFailed");
2626  for (j=0; j < (ssize_t) GetOpenMPMaximumThreads(); j++)
2627  changes[j]=0;
2628  if ((method == ConvolveMorphology) && (kernel->width == 1))
2629  {
2630  ssize_t
2631  x;
2632 
2633  /*
2634  Special handling (for speed) of vertical (blur) kernels. This performs
2635  its handling in columns rather than in rows. This is only done
2636  for convolve as it is the only method that generates very large 1-D
2637  vertical kernels (such as a 'BlurKernel')
2638  */
2639 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2640  #pragma omp parallel for schedule(static) shared(progress,status) \
2641  magick_number_threads(image,morphology_image,image->columns,1)
2642 #endif
2643  for (x=0; x < (ssize_t) image->columns; x++)
2644  {
2645  const int
2646  id = GetOpenMPThreadId();
2647 
2648  const Quantum
2649  *magick_restrict p;
2650 
2651  Quantum
2652  *magick_restrict q;
2653 
2654  ssize_t
2655  center,
2656  r;
2657 
2658  if (status == MagickFalse)
2659  continue;
2660  p=GetCacheViewVirtualPixels(image_view,x,-offset.y,1,image->rows+
2661  kernel->height-1,exception);
2662  q=GetCacheViewAuthenticPixels(morphology_view,x,0,1,
2663  morphology_image->rows,exception);
2664  if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
2665  {
2666  status=MagickFalse;
2667  continue;
2668  }
2669  center=(ssize_t) GetPixelChannels(image)*offset.y;
2670  for (r=0; r < (ssize_t) image->rows; r++)
2671  {
2672  ssize_t
2673  i;
2674 
2675  for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
2676  {
2677  double
2678  alpha,
2679  gamma,
2680  pixel;
2681 
2682  PixelChannel
2683  channel;
2684 
2685  PixelTrait
2686  morphology_traits,
2687  traits;
2688 
2689  const MagickRealType
2690  *magick_restrict k;
2691 
2692  const Quantum
2693  *magick_restrict pixels;
2694 
2695  ssize_t
2696  v;
2697 
2698  size_t
2699  count;
2700 
2701  channel=GetPixelChannelChannel(image,i);
2702  traits=GetPixelChannelTraits(image,channel);
2703  morphology_traits=GetPixelChannelTraits(morphology_image,channel);
2704  if ((traits == UndefinedPixelTrait) ||
2705  (morphology_traits == UndefinedPixelTrait))
2706  continue;
2707  if ((traits & CopyPixelTrait) != 0)
2708  {
2709  SetPixelChannel(morphology_image,channel,p[center+i],q);
2710  continue;
2711  }
2712  k=(&kernel->values[kernel->height-1]);
2713  pixels=p;
2714  pixel=bias;
2715  gamma=1.0;
2716  count=0;
2717  if (((image->alpha_trait & BlendPixelTrait) == 0) ||
2718  ((morphology_traits & BlendPixelTrait) == 0))
2719  for (v=0; v < (ssize_t) kernel->height; v++)
2720  {
2721  if (!IsNaN(*k))
2722  {
2723  pixel+=(*k)*(double) pixels[i];
2724  count++;
2725  }
2726  k--;
2727  pixels+=(ptrdiff_t) GetPixelChannels(image);
2728  }
2729  else
2730  {
2731  gamma=0.0;
2732  for (v=0; v < (ssize_t) kernel->height; v++)
2733  {
2734  if (!IsNaN(*k))
2735  {
2736  alpha=(double) (QuantumScale*(double)
2737  GetPixelAlpha(image,pixels));
2738  pixel+=alpha*(*k)*(double) pixels[i];
2739  gamma+=alpha*(*k);
2740  count++;
2741  }
2742  k--;
2743  pixels+=(ptrdiff_t) GetPixelChannels(image);
2744  }
2745  }
2746  if (fabs(pixel-(double) p[center+i]) >= MagickEpsilon)
2747  changes[id]++;
2748  gamma=MagickSafeReciprocal(gamma);
2749  if (count != 0)
2750  gamma*=(double) kernel->height/count;
2751  SetPixelChannel(morphology_image,channel,ClampToQuantum(gamma*
2752  pixel),q);
2753  }
2754  p+=(ptrdiff_t) GetPixelChannels(image);
2755  q+=(ptrdiff_t) GetPixelChannels(morphology_image);
2756  }
2757  if (SyncCacheViewAuthenticPixels(morphology_view,exception) == MagickFalse)
2758  status=MagickFalse;
2759  if (image->progress_monitor != (MagickProgressMonitor) NULL)
2760  {
2761  MagickBooleanType
2762  proceed;
2763 
2764 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2765  #pragma omp atomic
2766 #endif
2767  progress++;
2768  proceed=SetImageProgress(image,MorphologyTag,progress,
2769  image->columns);
2770  if (proceed == MagickFalse)
2771  status=MagickFalse;
2772  }
2773  }
2774  morphology_image->type=image->type;
2775  morphology_view=DestroyCacheView(morphology_view);
2776  image_view=DestroyCacheView(image_view);
2777  for (j=0; j < (ssize_t) GetOpenMPMaximumThreads(); j++)
2778  changed+=changes[j];
2779  changes=(size_t *) RelinquishMagickMemory(changes);
2780  return(status ? (ssize_t) (changed/GetImageChannels(image)) : 0);
2781  }
2782  /*
2783  Normal handling of horizontal or rectangular kernels (row by row).
2784  */
2785 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2786  #pragma omp parallel for schedule(static) shared(progress,status) \
2787  magick_number_threads(image,morphology_image,image->rows,1)
2788 #endif
2789  for (y=0; y < (ssize_t) image->rows; y++)
2790  {
2791  const int
2792  id = GetOpenMPThreadId();
2793 
2794  const Quantum
2795  *magick_restrict p;
2796 
2797  Quantum
2798  *magick_restrict q;
2799 
2800  ssize_t
2801  x;
2802 
2803  ssize_t
2804  center;
2805 
2806  if (status == MagickFalse)
2807  continue;
2808  p=GetCacheViewVirtualPixels(image_view,-offset.x,y-offset.y,width,
2809  kernel->height,exception);
2810  q=GetCacheViewAuthenticPixels(morphology_view,0,y,morphology_image->columns,
2811  1,exception);
2812  if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
2813  {
2814  status=MagickFalse;
2815  continue;
2816  }
2817  center=(ssize_t) ((ssize_t) GetPixelChannels(image)*(ssize_t) width*
2818  offset.y+(ssize_t) GetPixelChannels(image)*offset.x);
2819  for (x=0; x < (ssize_t) image->columns; x++)
2820  {
2821  ssize_t
2822  i;
2823 
2824  for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
2825  {
2826  double
2827  alpha,
2828  gamma,
2829  intensity,
2830  maximum,
2831  minimum,
2832  pixel;
2833 
2834  PixelChannel
2835  channel;
2836 
2837  PixelTrait
2838  morphology_traits,
2839  traits;
2840 
2841  const MagickRealType
2842  *magick_restrict k;
2843 
2844  const Quantum
2845  *magick_restrict pixels,
2846  *magick_restrict quantum_pixels;
2847 
2848  ssize_t
2849  u;
2850 
2851  ssize_t
2852  v;
2853 
2854  channel=GetPixelChannelChannel(image,i);
2855  traits=GetPixelChannelTraits(image,channel);
2856  morphology_traits=GetPixelChannelTraits(morphology_image,channel);
2857  if ((traits == UndefinedPixelTrait) ||
2858  (morphology_traits == UndefinedPixelTrait))
2859  continue;
2860  if ((traits & CopyPixelTrait) != 0)
2861  {
2862  SetPixelChannel(morphology_image,channel,p[center+i],q);
2863  continue;
2864  }
2865  pixels=p;
2866  quantum_pixels=(const Quantum *) NULL;
2867  maximum=0.0;
2868  minimum=(double) QuantumRange;
2869  switch (method)
2870  {
2871  case ConvolveMorphology:
2872  {
2873  pixel=bias;
2874  break;
2875  }
2876  case DilateMorphology:
2877  case ErodeIntensityMorphology:
2878  {
2879  pixel=0.0;
2880  break;
2881  }
2882  default:
2883  {
2884  pixel=(double) p[center+i];
2885  break;
2886  }
2887  }
2888  gamma=1.0;
2889  switch (method)
2890  {
2891  case ConvolveMorphology:
2892  {
2893  /*
2894  Weighted Average of pixels using reflected kernel
2895 
2896  For correct working of this operation for asymmetrical kernels,
2897  the kernel needs to be applied in its reflected form. That is
2898  its values needs to be reversed.
2899 
2900  Correlation is actually the same as this but without reflecting
2901  the kernel, and thus 'lower-level' that Convolution. However as
2902  Convolution is the more common method used, and it does not
2903  really cost us much in terms of processing to use a reflected
2904  kernel, so it is Convolution that is implemented.
2905 
2906  Correlation will have its kernel reflected before calling this
2907  function to do a Convolve.
2908 
2909  For more details of Correlation vs Convolution see
2910  http://www.cs.umd.edu/~djacobs/CMSC426/Convolution.pdf
2911  */
2912  k=(&kernel->values[kernel->width*kernel->height-1]);
2913  if (((image->alpha_trait & BlendPixelTrait) == 0) ||
2914  ((morphology_traits & BlendPixelTrait) == 0))
2915  {
2916  /*
2917  No alpha blending.
2918  */
2919  for (v=0; v < (ssize_t) kernel->height; v++)
2920  {
2921  for (u=0; u < (ssize_t) kernel->width; u++)
2922  {
2923  if (!IsNaN(*k))
2924  pixel+=(*k)*(double) pixels[i];
2925  k--;
2926  pixels+=(ptrdiff_t) GetPixelChannels(image);
2927  }
2928  pixels+=(image->columns-1)*GetPixelChannels(image);
2929  }
2930  break;
2931  }
2932  /*
2933  Alpha blending.
2934  */
2935  gamma=0.0;
2936  for (v=0; v < (ssize_t) kernel->height; v++)
2937  {
2938  for (u=0; u < (ssize_t) kernel->width; u++)
2939  {
2940  if (!IsNaN(*k))
2941  {
2942  alpha=(double) (QuantumScale*(double)
2943  GetPixelAlpha(image,pixels));
2944  pixel+=alpha*(*k)*(double) pixels[i];
2945  gamma+=alpha*(*k);
2946  }
2947  k--;
2948  pixels+=(ptrdiff_t) GetPixelChannels(image);
2949  }
2950  pixels+=(image->columns-1)*GetPixelChannels(image);
2951  }
2952  break;
2953  }
2954  case ErodeMorphology:
2955  {
2956  /*
2957  Minimum value within kernel neighbourhood.
2958 
2959  The kernel is not reflected for this operation. In normal
2960  Greyscale Morphology, the kernel value should be added
2961  to the real value, this is currently not done, due to the
2962  nature of the boolean kernels being used.
2963  */
2964  k=kernel->values;
2965  for (v=0; v < (ssize_t) kernel->height; v++)
2966  {
2967  for (u=0; u < (ssize_t) kernel->width; u++)
2968  {
2969  if (!IsNaN(*k) && (*k >= 0.5))
2970  {
2971  if ((double) pixels[i] < pixel)
2972  pixel=(double) pixels[i];
2973  }
2974  k++;
2975  pixels+=(ptrdiff_t) GetPixelChannels(image);
2976  }
2977  pixels+=(image->columns-1)*GetPixelChannels(image);
2978  }
2979  break;
2980  }
2981  case DilateMorphology:
2982  {
2983  /*
2984  Maximum value within kernel neighbourhood.
2985 
2986  For correct working of this operation for asymmetrical kernels,
2987  the kernel needs to be applied in its reflected form. That is
2988  its values needs to be reversed.
2989 
2990  In normal Greyscale Morphology, the kernel value should be
2991  added to the real value, this is currently not done, due to the
2992  nature of the boolean kernels being used.
2993  */
2994  k=(&kernel->values[kernel->width*kernel->height-1]);
2995  for (v=0; v < (ssize_t) kernel->height; v++)
2996  {
2997  for (u=0; u < (ssize_t) kernel->width; u++)
2998  {
2999  if (!IsNaN(*k) && (*k > 0.5))
3000  {
3001  if ((double) pixels[i] > pixel)
3002  pixel=(double) pixels[i];
3003  }
3004  k--;
3005  pixels+=(ptrdiff_t) GetPixelChannels(image);
3006  }
3007  pixels+=(image->columns-1)*GetPixelChannels(image);
3008  }
3009  break;
3010  }
3011  case HitAndMissMorphology:
3012  case ThinningMorphology:
3013  case ThickenMorphology:
3014  {
3015  /*
3016  Minimum of foreground pixel minus maximum of background pixels.
3017 
3018  The kernel is not reflected for this operation, and consists
3019  of both foreground and background pixel neighbourhoods, 0.0 for
3020  background, and 1.0 for foreground with either Nan or 0.5 values
3021  for don't care.
3022 
3023  This never produces a meaningless negative result. Such results
3024  cause Thinning/Thicken to not work correctly when used against a
3025  greyscale image.
3026  */
3027  k=kernel->values;
3028  for (v=0; v < (ssize_t) kernel->height; v++)
3029  {
3030  for (u=0; u < (ssize_t) kernel->width; u++)
3031  {
3032  if (!IsNaN(*k))
3033  {
3034  if (*k > 0.7)
3035  {
3036  if ((double) pixels[i] < minimum)
3037  minimum=(double) pixels[i];
3038  }
3039  else
3040  if (*k < 0.3)
3041  {
3042  if ((double) pixels[i] > maximum)
3043  maximum=(double) pixels[i];
3044  }
3045  }
3046  k++;
3047  pixels+=(ptrdiff_t) GetPixelChannels(image);
3048  }
3049  pixels+=(image->columns-1)*GetPixelChannels(image);
3050  }
3051  minimum-=maximum;
3052  if (minimum < 0.0)
3053  minimum=0.0;
3054  pixel=minimum;
3055  if (method == ThinningMorphology)
3056  pixel=(double) p[center+i]-minimum;
3057  else
3058  if (method == ThickenMorphology)
3059  pixel=(double) p[center+i]+minimum;
3060  break;
3061  }
3062  case ErodeIntensityMorphology:
3063  {
3064  /*
3065  Select pixel with minimum intensity within kernel neighbourhood.
3066 
3067  The kernel is not reflected for this operation.
3068  */
3069  k=kernel->values;
3070  for (v=0; v < (ssize_t) kernel->height; v++)
3071  {
3072  for (u=0; u < (ssize_t) kernel->width; u++)
3073  {
3074  if (!IsNaN(*k) && (*k >= 0.5))
3075  {
3076  intensity=(double) GetPixelIntensity(image,pixels);
3077  if (intensity < minimum)
3078  {
3079  quantum_pixels=pixels;
3080  pixel=(double) pixels[i];
3081  minimum=intensity;
3082  }
3083  }
3084  k++;
3085  pixels+=(ptrdiff_t) GetPixelChannels(image);
3086  }
3087  pixels+=(image->columns-1)*GetPixelChannels(image);
3088  }
3089  break;
3090  }
3091  case DilateIntensityMorphology:
3092  {
3093  /*
3094  Select pixel with maximum intensity within kernel neighbourhood.
3095 
3096  The kernel is not reflected for this operation.
3097  */
3098  k=(&kernel->values[kernel->width*kernel->height-1]);
3099  for (v=0; v < (ssize_t) kernel->height; v++)
3100  {
3101  for (u=0; u < (ssize_t) kernel->width; u++)
3102  {
3103  if (!IsNaN(*k) && (*k >= 0.5))
3104  {
3105  intensity=(double) GetPixelIntensity(image,pixels);
3106  if (intensity > maximum)
3107  {
3108  pixel=(double) pixels[i];
3109  quantum_pixels=pixels;
3110  maximum=intensity;
3111  }
3112  }
3113  k--;
3114  pixels+=(ptrdiff_t) GetPixelChannels(image);
3115  }
3116  pixels+=(image->columns-1)*GetPixelChannels(image);
3117  }
3118  break;
3119  }
3120  case IterativeDistanceMorphology:
3121  {
3122  /*
3123  Compute th iterative distance from black edge of a white image
3124  shape. Essentially white values are decreased to the smallest
3125  'distance from edge' it can find.
3126 
3127  It works by adding kernel values to the neighbourhood, and
3128  select the minimum value found. The kernel is rotated before
3129  use, so kernel distances match resulting distances, when a user
3130  provided asymmetric kernel is applied.
3131 
3132  This code is nearly identical to True GrayScale Morphology but
3133  not quite.
3134 
3135  GreyDilate Kernel values added, maximum value found Kernel is
3136  rotated before use.
3137 
3138  GrayErode: Kernel values subtracted and minimum value found No
3139  kernel rotation used.
3140 
3141  Note the Iterative Distance method is essentially a
3142  GrayErode, but with negative kernel values, and kernel rotation
3143  applied.
3144  */
3145  k=(&kernel->values[kernel->width*kernel->height-1]);
3146  for (v=0; v < (ssize_t) kernel->height; v++)
3147  {
3148  for (u=0; u < (ssize_t) kernel->width; u++)
3149  {
3150  if (!IsNaN(*k))
3151  {
3152  if (((double) pixels[i]+(*k)) < pixel)
3153  pixel=(double) pixels[i]+(*k);
3154  }
3155  k--;
3156  pixels+=(ptrdiff_t) GetPixelChannels(image);
3157  }
3158  pixels+=(image->columns-1)*GetPixelChannels(image);
3159  }
3160  break;
3161  }
3162  case UndefinedMorphology:
3163  default:
3164  break;
3165  }
3166  if (quantum_pixels != (const Quantum *) NULL)
3167  {
3168  SetPixelChannel(morphology_image,channel,quantum_pixels[i],q);
3169  continue;
3170  }
3171  gamma=MagickSafeReciprocal(gamma);
3172  SetPixelChannel(morphology_image,channel,ClampToQuantum(gamma*pixel),q);
3173  if (fabs(pixel-(double) p[center+i]) >= MagickEpsilon)
3174  changes[id]++;
3175  }
3176  p+=(ptrdiff_t) GetPixelChannels(image);
3177  q+=(ptrdiff_t) GetPixelChannels(morphology_image);
3178  }
3179  if (SyncCacheViewAuthenticPixels(morphology_view,exception) == MagickFalse)
3180  status=MagickFalse;
3181  if (image->progress_monitor != (MagickProgressMonitor) NULL)
3182  {
3183  MagickBooleanType
3184  proceed;
3185 
3186 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3187  #pragma omp atomic
3188 #endif
3189  progress++;
3190  proceed=SetImageProgress(image,MorphologyTag,progress,image->rows);
3191  if (proceed == MagickFalse)
3192  status=MagickFalse;
3193  }
3194  }
3195  morphology_view=DestroyCacheView(morphology_view);
3196  image_view=DestroyCacheView(image_view);
3197  for (j=0; j < (ssize_t) GetOpenMPMaximumThreads(); j++)
3198  changed+=changes[j];
3199  changes=(size_t *) RelinquishMagickMemory(changes);
3200  return(status ? (ssize_t) (changed/GetImageChannels(image)) : -1);
3201 }
3202 
3203 /*
3204  This is almost identical to the MorphologyPrimitive() function above, but
3205  applies the primitive directly to the actual image using two passes, once in
3206  each direction, with the results of the previous (and current) row being
3207  re-used.
3208 
3209  That is after each row is 'Sync'ed' into the image, the next row makes use of
3210  those values as part of the calculation of the next row. It repeats, but
3211  going in the opposite (bottom-up) direction.
3212 
3213  Because of this 're-use of results' this function can not make use of multi-
3214  threaded, parallel processing.
3215 */
3216 static ssize_t MorphologyPrimitiveDirect(Image *image,
3217  const MorphologyMethod method,const KernelInfo *kernel,
3218  ExceptionInfo *exception)
3219 {
3220  CacheView
3221  *morphology_view,
3222  *image_view;
3223 
3224  MagickBooleanType
3225  status;
3226 
3227  MagickOffsetType
3228  progress;
3229 
3230  OffsetInfo
3231  offset;
3232 
3233  size_t
3234  width,
3235  changed;
3236 
3237  ssize_t
3238  y;
3239 
3240  assert(image != (Image *) NULL);
3241  assert(image->signature == MagickCoreSignature);
3242  assert(kernel != (KernelInfo *) NULL);
3243  assert(kernel->signature == MagickCoreSignature);
3244  assert(exception != (ExceptionInfo *) NULL);
3245  assert(exception->signature == MagickCoreSignature);
3246  status=MagickTrue;
3247  changed=0;
3248  progress=0;
3249  switch(method)
3250  {
3251  case DistanceMorphology:
3252  case VoronoiMorphology:
3253  {
3254  /*
3255  Kernel reflected about origin.
3256  */
3257  offset.x=(ssize_t) kernel->width-kernel->x-1;
3258  offset.y=(ssize_t) kernel->height-kernel->y-1;
3259  break;
3260  }
3261  default:
3262  {
3263  offset.x=kernel->x;
3264  offset.y=kernel->y;
3265  break;
3266  }
3267  }
3268  /*
3269  Two views into same image, do not thread.
3270  */
3271  image_view=AcquireVirtualCacheView(image,exception);
3272  morphology_view=AcquireAuthenticCacheView(image,exception);
3273  width=image->columns+kernel->width-1;
3274  for (y=0; y < (ssize_t) image->rows; y++)
3275  {
3276  const Quantum
3277  *magick_restrict p;
3278 
3279  Quantum
3280  *magick_restrict q;
3281 
3282  ssize_t
3283  x;
3284 
3285  /*
3286  Read virtual pixels, and authentic pixels, from the same image! We read
3287  using virtual to get virtual pixel handling, but write back into the same
3288  image.
3289 
3290  Only top half of kernel is processed as we do a single pass downward
3291  through the image iterating the distance function as we go.
3292  */
3293  if (status == MagickFalse)
3294  continue;
3295  p=GetCacheViewVirtualPixels(image_view,-offset.x,y-offset.y,width,(size_t)
3296  offset.y+1,exception);
3297  q=GetCacheViewAuthenticPixels(morphology_view,0,y,image->columns,1,
3298  exception);
3299  if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
3300  {
3301  status=MagickFalse;
3302  continue;
3303  }
3304  for (x=0; x < (ssize_t) image->columns; x++)
3305  {
3306  ssize_t
3307  i;
3308 
3309  for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
3310  {
3311  double
3312  pixel;
3313 
3314  PixelChannel
3315  channel;
3316 
3317  PixelTrait
3318  traits;
3319 
3320  const MagickRealType
3321  *magick_restrict k;
3322 
3323  const Quantum
3324  *magick_restrict pixels;
3325 
3326  ssize_t
3327  u;
3328 
3329  ssize_t
3330  v;
3331 
3332  channel=GetPixelChannelChannel(image,i);
3333  traits=GetPixelChannelTraits(image,channel);
3334  if (traits == UndefinedPixelTrait)
3335  continue;
3336  if ((traits & CopyPixelTrait) != 0)
3337  continue;
3338  pixels=p;
3339  pixel=(double) QuantumRange;
3340  switch (method)
3341  {
3342  case DistanceMorphology:
3343  {
3344  k=(&kernel->values[kernel->width*kernel->height-1]);
3345  for (v=0; v <= offset.y; v++)
3346  {
3347  for (u=0; u < (ssize_t) kernel->width; u++)
3348  {
3349  if (!IsNaN(*k))
3350  {
3351  if (((double) pixels[i]+(*k)) < pixel)
3352  pixel=(double) pixels[i]+(*k);
3353  }
3354  k--;
3355  pixels+=(ptrdiff_t) GetPixelChannels(image);
3356  }
3357  pixels+=(image->columns-1)*GetPixelChannels(image);
3358  }
3359  k=(&kernel->values[(ssize_t) kernel->width*(kernel->y+1)-1]);
3360  pixels=q-offset.x*(ssize_t) GetPixelChannels(image);
3361  for (u=0; u < offset.x; u++)
3362  {
3363  if (!IsNaN(*k) && ((x+u-offset.x) >= 0))
3364  {
3365  if (((double) pixels[i]+(*k)) < pixel)
3366  pixel=(double) pixels[i]+(*k);
3367  }
3368  k--;
3369  pixels+=(ptrdiff_t) GetPixelChannels(image);
3370  }
3371  break;
3372  }
3373  case VoronoiMorphology:
3374  {
3375  k=(&kernel->values[kernel->width*kernel->height-1]);
3376  for (v=0; v < offset.y; v++)
3377  {
3378  for (u=0; u < (ssize_t) kernel->width; u++)
3379  {
3380  if (!IsNaN(*k))
3381  {
3382  if (((double) pixels[i]+(*k)) < pixel)
3383  pixel=(double) pixels[i]+(*k);
3384  }
3385  k--;
3386  pixels+=(ptrdiff_t) GetPixelChannels(image);
3387  }
3388  pixels+=(image->columns-1)*GetPixelChannels(image);
3389  }
3390  k=(&kernel->values[(ssize_t) kernel->width*(kernel->y+1)-1]);
3391  pixels=q-offset.x*(ssize_t) GetPixelChannels(image);
3392  for (u=0; u < offset.x; u++)
3393  {
3394  if (!IsNaN(*k) && ((x+u-offset.x) >= 0))
3395  {
3396  if (((double) pixels[i]+(*k)) < pixel)
3397  pixel=(double) pixels[i]+(*k);
3398  }
3399  k--;
3400  pixels+=(ptrdiff_t) GetPixelChannels(image);
3401  }
3402  break;
3403  }
3404  default:
3405  break;
3406  }
3407  if (fabs(pixel-(double) q[i]) > MagickEpsilon)
3408  changed++;
3409  q[i]=ClampToQuantum(pixel);
3410  }
3411  p+=(ptrdiff_t) GetPixelChannels(image);
3412  q+=(ptrdiff_t) GetPixelChannels(image);
3413  }
3414  if (SyncCacheViewAuthenticPixels(morphology_view,exception) == MagickFalse)
3415  status=MagickFalse;
3416  if (image->progress_monitor != (MagickProgressMonitor) NULL)
3417  {
3418  MagickBooleanType
3419  proceed;
3420 
3421 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3422  #pragma omp atomic
3423 #endif
3424  progress++;
3425  proceed=SetImageProgress(image,MorphologyTag,progress,2*image->rows);
3426  if (proceed == MagickFalse)
3427  status=MagickFalse;
3428  }
3429  }
3430  morphology_view=DestroyCacheView(morphology_view);
3431  image_view=DestroyCacheView(image_view);
3432  /*
3433  Do the reverse pass through the image.
3434  */
3435  image_view=AcquireVirtualCacheView(image,exception);
3436  morphology_view=AcquireAuthenticCacheView(image,exception);
3437  for (y=(ssize_t) image->rows-1; y >= 0; y--)
3438  {
3439  const Quantum
3440  *magick_restrict p;
3441 
3442  Quantum
3443  *magick_restrict q;
3444 
3445  ssize_t
3446  x;
3447 
3448  /*
3449  Read virtual pixels, and authentic pixels, from the same image. We
3450  read using virtual to get virtual pixel handling, but write back
3451  into the same image.
3452 
3453  Only the bottom half of the kernel is processed as we up the image.
3454  */
3455  if (status == MagickFalse)
3456  continue;
3457  p=GetCacheViewVirtualPixels(image_view,-offset.x,y,width,(size_t)
3458  kernel->y+1,exception);
3459  q=GetCacheViewAuthenticPixels(morphology_view,0,y,image->columns,1,
3460  exception);
3461  if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
3462  {
3463  status=MagickFalse;
3464  continue;
3465  }
3466  p+=(ptrdiff_t) (image->columns-1)*GetPixelChannels(image);
3467  q+=(ptrdiff_t) (image->columns-1)*GetPixelChannels(image);
3468  for (x=(ssize_t) image->columns-1; x >= 0; x--)
3469  {
3470  ssize_t
3471  i;
3472 
3473  for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
3474  {
3475  double
3476  pixel;
3477 
3478  PixelChannel
3479  channel;
3480 
3481  PixelTrait
3482  traits;
3483 
3484  const MagickRealType
3485  *magick_restrict k;
3486 
3487  const Quantum
3488  *magick_restrict pixels;
3489 
3490  ssize_t
3491  u;
3492 
3493  ssize_t
3494  v;
3495 
3496  channel=GetPixelChannelChannel(image,i);
3497  traits=GetPixelChannelTraits(image,channel);
3498  if (traits == UndefinedPixelTrait)
3499  continue;
3500  if ((traits & CopyPixelTrait) != 0)
3501  continue;
3502  pixels=p;
3503  pixel=(double) QuantumRange;
3504  switch (method)
3505  {
3506  case DistanceMorphology:
3507  {
3508  k=(&kernel->values[(ssize_t) kernel->width*(kernel->y+1)-1]);
3509  for (v=offset.y; v < (ssize_t) kernel->height; v++)
3510  {
3511  for (u=0; u < (ssize_t) kernel->width; u++)
3512  {
3513  if (!IsNaN(*k))
3514  {
3515  if (((double) pixels[i]+(*k)) < pixel)
3516  pixel=(double) pixels[i]+(*k);
3517  }
3518  k--;
3519  pixels+=(ptrdiff_t) GetPixelChannels(image);
3520  }
3521  pixels+=(image->columns-1)*GetPixelChannels(image);
3522  }
3523  k=(&kernel->values[(ssize_t) kernel->width*kernel->y+kernel->x-1]);
3524  pixels=q;
3525  for (u=offset.x+1; u < (ssize_t) kernel->width; u++)
3526  {
3527  pixels+=(ptrdiff_t) GetPixelChannels(image);
3528  if (!IsNaN(*k) && ((x+u-offset.x) < (ssize_t) image->columns))
3529  {
3530  if (((double) pixels[i]+(*k)) < pixel)
3531  pixel=(double) pixels[i]+(*k);
3532  }
3533  k--;
3534  }
3535  break;
3536  }
3537  case VoronoiMorphology:
3538  {
3539  k=(&kernel->values[(ssize_t) kernel->width*(kernel->y+1)-1]);
3540  for (v=offset.y; v < (ssize_t) kernel->height; v++)
3541  {
3542  for (u=0; u < (ssize_t) kernel->width; u++)
3543  {
3544  if (!IsNaN(*k))
3545  {
3546  if (((double) pixels[i]+(*k)) < pixel)
3547  pixel=(double) pixels[i]+(*k);
3548  }
3549  k--;
3550  pixels+=(ptrdiff_t) GetPixelChannels(image);
3551  }
3552  pixels+=(image->columns-1)*GetPixelChannels(image);
3553  }
3554  k=(&kernel->values[(ssize_t) kernel->width*(kernel->y+1)-1]);
3555  pixels=q;
3556  for (u=offset.x+1; u < (ssize_t) kernel->width; u++)
3557  {
3558  pixels+=(ptrdiff_t) GetPixelChannels(image);
3559  if (!IsNaN(*k) && ((x+u-offset.x) < (ssize_t) image->columns))
3560  {
3561  if (((double) pixels[i]+(*k)) < pixel)
3562  pixel=(double) pixels[i]+(*k);
3563  }
3564  k--;
3565  }
3566  break;
3567  }
3568  default:
3569  break;
3570  }
3571  if (fabs(pixel-(double) q[i]) > MagickEpsilon)
3572  changed++;
3573  q[i]=ClampToQuantum(pixel);
3574  }
3575  p-=(ptrdiff_t)GetPixelChannels(image);
3576  q-=GetPixelChannels(image);
3577  }
3578  if (SyncCacheViewAuthenticPixels(morphology_view,exception) == MagickFalse)
3579  status=MagickFalse;
3580  if (image->progress_monitor != (MagickProgressMonitor) NULL)
3581  {
3582  MagickBooleanType
3583  proceed;
3584 
3585 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3586  #pragma omp atomic
3587 #endif
3588  progress++;
3589  proceed=SetImageProgress(image,MorphologyTag,progress,2*image->rows);
3590  if (proceed == MagickFalse)
3591  status=MagickFalse;
3592  }
3593  }
3594  morphology_view=DestroyCacheView(morphology_view);
3595  image_view=DestroyCacheView(image_view);
3596  return(status ? (ssize_t) (changed/GetImageChannels(image)) : -1);
3597 }
3598 
3599 /*
3600  Apply a Morphology by calling one of the above low level primitive
3601  application functions. This function handles any iteration loops,
3602  composition or re-iteration of results, and compound morphology methods that
3603  is based on multiple low-level (staged) morphology methods.
3604 
3605  Basically this provides the complex glue between the requested morphology
3606  method and raw low-level implementation (above).
3607 */
3608 MagickPrivate Image *MorphologyApply(const Image *image,
3609  const MorphologyMethod method, const ssize_t iterations,
3610  const KernelInfo *kernel, const CompositeOperator compose,const double bias,
3611  ExceptionInfo *exception)
3612 {
3613  CompositeOperator
3614  curr_compose;
3615 
3616  Image
3617  *curr_image, /* Image we are working with or iterating */
3618  *work_image, /* secondary image for primitive iteration */
3619  *save_image, /* saved image - for 'edge' method only */
3620  *rslt_image; /* resultant image - after multi-kernel handling */
3621 
3622  KernelInfo
3623  *reflected_kernel, /* A reflected copy of the kernel (if needed) */
3624  *norm_kernel, /* the current normal un-reflected kernel */
3625  *rflt_kernel, /* the current reflected kernel (if needed) */
3626  *this_kernel; /* the kernel being applied */
3627 
3628  MorphologyMethod
3629  primitive; /* the current morphology primitive being applied */
3630 
3631  CompositeOperator
3632  rslt_compose; /* multi-kernel compose method for results to use */
3633 
3634  MagickBooleanType
3635  special, /* do we use a direct modify function? */
3636  verbose; /* verbose output of results */
3637 
3638  size_t
3639  method_loop, /* Loop 1: number of compound method iterations (norm 1) */
3640  method_limit, /* maximum number of compound method iterations */
3641  kernel_number, /* Loop 2: the kernel number being applied */
3642  stage_loop, /* Loop 3: primitive loop for compound morphology */
3643  stage_limit, /* how many primitives are in this compound */
3644  kernel_loop, /* Loop 4: iterate the kernel over image */
3645  kernel_limit, /* number of times to iterate kernel */
3646  count, /* total count of primitive steps applied */
3647  kernel_changed, /* total count of changed using iterated kernel */
3648  method_changed; /* total count of changed over method iteration */
3649 
3650  ssize_t
3651  changed; /* number pixels changed by last primitive operation */
3652 
3653  char
3654  v_info[MagickPathExtent];
3655 
3656  assert(image != (Image *) NULL);
3657  assert(image->signature == MagickCoreSignature);
3658  assert(kernel != (KernelInfo *) NULL);
3659  assert(kernel->signature == MagickCoreSignature);
3660  assert(exception != (ExceptionInfo *) NULL);
3661  assert(exception->signature == MagickCoreSignature);
3662 
3663  count = 0; /* number of low-level morphology primitives performed */
3664  if ( iterations == 0 )
3665  return((Image *) NULL); /* null operation - nothing to do! */
3666 
3667  kernel_limit = (size_t) iterations;
3668  if ( iterations < 0 ) /* negative interactions = infinite (well almost) */
3669  kernel_limit = image->columns>image->rows ? image->columns : image->rows;
3670 
3671  verbose = IsStringTrue(GetImageArtifact(image,"debug"));
3672 
3673  /* initialise for cleanup */
3674  curr_image = (Image *) image;
3675  curr_compose = image->compose;
3676  (void) curr_compose;
3677  work_image = save_image = rslt_image = (Image *) NULL;
3678  reflected_kernel = (KernelInfo *) NULL;
3679 
3680  /* Initialize specific methods
3681  * + which loop should use the given iterations
3682  * + how many primitives make up the compound morphology
3683  * + multi-kernel compose method to use (by default)
3684  */
3685  method_limit = 1; /* just do method once, unless otherwise set */
3686  stage_limit = 1; /* assume method is not a compound */
3687  special = MagickFalse; /* assume it is NOT a direct modify primitive */
3688  rslt_compose = compose; /* and we are composing multi-kernels as given */
3689  switch( method ) {
3690  case SmoothMorphology: /* 4 primitive compound morphology */
3691  stage_limit = 4;
3692  break;
3693  case OpenMorphology: /* 2 primitive compound morphology */
3694  case OpenIntensityMorphology:
3695  case TopHatMorphology:
3696  case CloseMorphology:
3697  case CloseIntensityMorphology:
3698  case BottomHatMorphology:
3699  case EdgeMorphology:
3700  stage_limit = 2;
3701  break;
3702  case HitAndMissMorphology:
3703  rslt_compose = LightenCompositeOp; /* Union of multi-kernel results */
3704  magick_fallthrough;
3705  case ThinningMorphology:
3706  case ThickenMorphology:
3707  method_limit = kernel_limit; /* iterate the whole method */
3708  kernel_limit = 1; /* do not do kernel iteration */
3709  break;
3710  case DistanceMorphology:
3711  case VoronoiMorphology:
3712  special = MagickTrue; /* use special direct primitive */
3713  break;
3714  default:
3715  break;
3716  }
3717 
3718  /* Apply special methods with special requirements
3719  ** For example, single run only, or post-processing requirements
3720  */
3721  if ( special != MagickFalse )
3722  {
3723  rslt_image=CloneImage(image,0,0,MagickTrue,exception);
3724  if (rslt_image == (Image *) NULL)
3725  goto error_cleanup;
3726  if (SetImageStorageClass(rslt_image,DirectClass,exception) == MagickFalse)
3727  goto error_cleanup;
3728 
3729  changed=MorphologyPrimitiveDirect(rslt_image,method,kernel,exception);
3730 
3731  if (verbose != MagickFalse)
3732  (void) (void) FormatLocaleFile(stderr,
3733  "%s:%.17g.%.17g #%.17g => Changed %.17g\n",
3734  CommandOptionToMnemonic(MagickMorphologyOptions, method),
3735  1.0,0.0,1.0, (double) changed);
3736 
3737  if ( changed < 0 )
3738  goto error_cleanup;
3739 
3740  if ( method == VoronoiMorphology ) {
3741  /* Preserve the alpha channel of input image - but turned it off */
3742  (void) SetImageAlphaChannel(rslt_image, DeactivateAlphaChannel,
3743  exception);
3744  (void) CompositeImage(rslt_image,image,CopyAlphaCompositeOp,
3745  MagickTrue,0,0,exception);
3746  (void) SetImageAlphaChannel(rslt_image, DeactivateAlphaChannel,
3747  exception);
3748  }
3749  goto exit_cleanup;
3750  }
3751 
3752  /* Handle user (caller) specified multi-kernel composition method */
3753  if ( compose != UndefinedCompositeOp )
3754  rslt_compose = compose; /* override default composition for method */
3755  if ( rslt_compose == UndefinedCompositeOp )
3756  rslt_compose = NoCompositeOp; /* still not defined! Then re-iterate */
3757 
3758  /* Some methods require a reflected kernel to use with primitives.
3759  * Create the reflected kernel for those methods. */
3760  switch ( method ) {
3761  case CorrelateMorphology:
3762  case CloseMorphology:
3763  case CloseIntensityMorphology:
3764  case BottomHatMorphology:
3765  case SmoothMorphology:
3766  reflected_kernel = CloneKernelInfo(kernel);
3767  if (reflected_kernel == (KernelInfo *) NULL)
3768  goto error_cleanup;
3769  RotateKernelInfo(reflected_kernel,180);
3770  break;
3771  default:
3772  break;
3773  }
3774 
3775  /* Loops around more primitive morphology methods
3776  ** erose, dilate, open, close, smooth, edge, etc...
3777  */
3778  /* Loop 1: iterate the compound method */
3779  method_loop = 0;
3780  method_changed = 1;
3781  while ( method_loop < method_limit && method_changed > 0 ) {
3782  method_loop++;
3783  method_changed = 0;
3784 
3785  /* Loop 2: iterate over each kernel in a multi-kernel list */
3786  norm_kernel = (KernelInfo *) kernel;
3787  this_kernel = (KernelInfo *) kernel;
3788  rflt_kernel = reflected_kernel;
3789 
3790  kernel_number = 0;
3791  while ( norm_kernel != NULL ) {
3792 
3793  /* Loop 3: Compound Morphology Staging - Select Primitive to apply */
3794  stage_loop = 0; /* the compound morphology stage number */
3795  while ( stage_loop < stage_limit ) {
3796  stage_loop++; /* The stage of the compound morphology */
3797 
3798  /* Select primitive morphology for this stage of compound method */
3799  this_kernel = norm_kernel; /* default use unreflected kernel */
3800  primitive = method; /* Assume method is a primitive */
3801  switch( method ) {
3802  case ErodeMorphology: /* just erode */
3803  case EdgeInMorphology: /* erode and image difference */
3804  primitive = ErodeMorphology;
3805  break;
3806  case DilateMorphology: /* just dilate */
3807  case EdgeOutMorphology: /* dilate and image difference */
3808  primitive = DilateMorphology;
3809  break;
3810  case OpenMorphology: /* erode then dilate */
3811  case TopHatMorphology: /* open and image difference */
3812  primitive = ErodeMorphology;
3813  if ( stage_loop == 2 )
3814  primitive = DilateMorphology;
3815  break;
3816  case OpenIntensityMorphology:
3817  primitive = ErodeIntensityMorphology;
3818  if ( stage_loop == 2 )
3819  primitive = DilateIntensityMorphology;
3820  break;
3821  case CloseMorphology: /* dilate, then erode */
3822  case BottomHatMorphology: /* close and image difference */
3823  this_kernel = rflt_kernel; /* use the reflected kernel */
3824  primitive = DilateMorphology;
3825  if ( stage_loop == 2 )
3826  primitive = ErodeMorphology;
3827  break;
3828  case CloseIntensityMorphology:
3829  this_kernel = rflt_kernel; /* use the reflected kernel */
3830  primitive = DilateIntensityMorphology;
3831  if ( stage_loop == 2 )
3832  primitive = ErodeIntensityMorphology;
3833  break;
3834  case SmoothMorphology: /* open, close */
3835  switch ( stage_loop ) {
3836  case 1: /* start an open method, which starts with Erode */
3837  primitive = ErodeMorphology;
3838  break;
3839  case 2: /* now Dilate the Erode */
3840  primitive = DilateMorphology;
3841  break;
3842  case 3: /* Reflect kernel a close */
3843  this_kernel = rflt_kernel; /* use the reflected kernel */
3844  primitive = DilateMorphology;
3845  break;
3846  case 4: /* Finish the Close */
3847  this_kernel = rflt_kernel; /* use the reflected kernel */
3848  primitive = ErodeMorphology;
3849  break;
3850  }
3851  break;
3852  case EdgeMorphology: /* dilate and erode difference */
3853  primitive = DilateMorphology;
3854  if ( stage_loop == 2 ) {
3855  save_image = curr_image; /* save the image difference */
3856  curr_image = (Image *) image;
3857  primitive = ErodeMorphology;
3858  }
3859  break;
3860  case CorrelateMorphology:
3861  /* A Correlation is a Convolution with a reflected kernel.
3862  ** However a Convolution is a weighted sum using a reflected
3863  ** kernel. It may seem strange to convert a Correlation into a
3864  ** Convolution as the Correlation is the simpler method, but
3865  ** Convolution is much more commonly used, and it makes sense to
3866  ** implement it directly so as to avoid the need to duplicate the
3867  ** kernel when it is not required (which is typically the
3868  ** default).
3869  */
3870  this_kernel = rflt_kernel; /* use the reflected kernel */
3871  primitive = ConvolveMorphology;
3872  break;
3873  default:
3874  break;
3875  }
3876  assert( this_kernel != (KernelInfo *) NULL );
3877 
3878  /* Extra information for debugging compound operations */
3879  if (verbose != MagickFalse) {
3880  if ( stage_limit > 1 )
3881  (void) FormatLocaleString(v_info,MagickPathExtent,"%s:%.17g.%.17g -> ",
3882  CommandOptionToMnemonic(MagickMorphologyOptions,method),(double)
3883  method_loop,(double) stage_loop);
3884  else if ( primitive != method )
3885  (void) FormatLocaleString(v_info, MagickPathExtent, "%s:%.17g -> ",
3886  CommandOptionToMnemonic(MagickMorphologyOptions, method),(double)
3887  method_loop);
3888  else
3889  v_info[0] = '\0';
3890  }
3891 
3892  /* Loop 4: Iterate the kernel with primitive */
3893  kernel_loop = 0;
3894  kernel_changed = 0;
3895  changed = 1;
3896  while ( kernel_loop < kernel_limit && changed > 0 ) {
3897  kernel_loop++; /* the iteration of this kernel */
3898 
3899  /* Create a clone as the destination image, if not yet defined */
3900  if ( work_image == (Image *) NULL )
3901  {
3902  work_image=CloneImage(image,0,0,MagickTrue,exception);
3903  if (work_image == (Image *) NULL)
3904  goto error_cleanup;
3905  if (SetImageStorageClass(work_image,DirectClass,exception) == MagickFalse)
3906  goto error_cleanup;
3907  }
3908 
3909  /* APPLY THE MORPHOLOGICAL PRIMITIVE (curr -> work) */
3910  count++;
3911  changed = MorphologyPrimitive(curr_image, work_image, primitive,
3912  this_kernel, bias, exception);
3913  if (verbose != MagickFalse) {
3914  if ( kernel_loop > 1 )
3915  (void) FormatLocaleFile(stderr, "\n"); /* add end-of-line from previous */
3916  (void) (void) FormatLocaleFile(stderr,
3917  "%s%s%s:%.17g.%.17g #%.17g => Changed %.17g",
3918  v_info,CommandOptionToMnemonic(MagickMorphologyOptions,
3919  primitive),(this_kernel == rflt_kernel ) ? "*" : "",
3920  (double) (method_loop+kernel_loop-1),(double) kernel_number,
3921  (double) count,(double) changed);
3922  }
3923  if ( changed < 0 )
3924  goto error_cleanup;
3925  kernel_changed = (size_t) ((ssize_t) kernel_changed+changed);
3926  method_changed = (size_t) ((ssize_t) method_changed+changed);
3927 
3928  /* prepare next loop */
3929  { Image *tmp = work_image; /* swap images for iteration */
3930  work_image = curr_image;
3931  curr_image = tmp;
3932  }
3933  if ( work_image == image )
3934  work_image = (Image *) NULL; /* replace input 'image' */
3935 
3936  } /* End Loop 4: Iterate the kernel with primitive */
3937 
3938  if (verbose != MagickFalse && kernel_changed != (size_t)changed)
3939  (void) FormatLocaleFile(stderr, " Total %.17g",(double) kernel_changed);
3940  if (verbose != MagickFalse && stage_loop < stage_limit)
3941  (void) FormatLocaleFile(stderr, "\n"); /* add end-of-line before looping */
3942 
3943 #if 0
3944  (void) FormatLocaleFile(stderr, "--E-- image=0x%lx\n", (unsigned long)image);
3945  (void) FormatLocaleFile(stderr, " curr =0x%lx\n", (unsigned long)curr_image);
3946  (void) FormatLocaleFile(stderr, " work =0x%lx\n", (unsigned long)work_image);
3947  (void) FormatLocaleFile(stderr, " save =0x%lx\n", (unsigned long)save_image);
3948  (void) FormatLocaleFile(stderr, " union=0x%lx\n", (unsigned long)rslt_image);
3949 #endif
3950 
3951  } /* End Loop 3: Primitive (staging) Loop for Compound Methods */
3952 
3953  /* Final Post-processing for some Compound Methods
3954  **
3955  ** The removal of any 'Sync' channel flag in the Image Composition
3956  ** below ensures the mathematical compose method is applied in a
3957  ** purely mathematical way, and only to the selected channels.
3958  ** Turn off SVG composition 'alpha blending'.
3959  */
3960  switch( method ) {
3961  case EdgeOutMorphology:
3962  case EdgeInMorphology:
3963  case TopHatMorphology:
3964  case BottomHatMorphology:
3965  if (verbose != MagickFalse)
3966  (void) FormatLocaleFile(stderr,
3967  "\n%s: Difference with original image",CommandOptionToMnemonic(
3968  MagickMorphologyOptions, method) );
3969  (void) CompositeImage(curr_image,image,DifferenceCompositeOp,
3970  MagickTrue,0,0,exception);
3971  break;
3972  case EdgeMorphology:
3973  if (verbose != MagickFalse)
3974  (void) FormatLocaleFile(stderr,
3975  "\n%s: Difference of Dilate and Erode",CommandOptionToMnemonic(
3976  MagickMorphologyOptions, method) );
3977  (void) CompositeImage(curr_image,save_image,DifferenceCompositeOp,
3978  MagickTrue,0,0,exception);
3979  save_image = DestroyImage(save_image); /* finished with save image */
3980  break;
3981  default:
3982  break;
3983  }
3984 
3985  /* multi-kernel handling: re-iterate, or compose results */
3986  if ( kernel->next == (KernelInfo *) NULL )
3987  rslt_image = curr_image; /* just return the resulting image */
3988  else if ( rslt_compose == NoCompositeOp )
3989  { if (verbose != MagickFalse) {
3990  if ( this_kernel->next != (KernelInfo *) NULL )
3991  (void) FormatLocaleFile(stderr, " (re-iterate)");
3992  else
3993  (void) FormatLocaleFile(stderr, " (done)");
3994  }
3995  rslt_image = curr_image; /* return result, and re-iterate */
3996  }
3997  else if ( rslt_image == (Image *) NULL)
3998  { if (verbose != MagickFalse)
3999  (void) FormatLocaleFile(stderr, " (save for compose)");
4000  rslt_image = curr_image;
4001  curr_image = (Image *) image; /* continue with original image */
4002  }
4003  else
4004  { /* Add the new 'current' result to the composition
4005  **
4006  ** The removal of any 'Sync' channel flag in the Image Composition
4007  ** below ensures the mathematical compose method is applied in a
4008  ** purely mathematical way, and only to the selected channels.
4009  ** IE: Turn off SVG composition 'alpha blending'.
4010  */
4011  if (verbose != MagickFalse)
4012  (void) FormatLocaleFile(stderr, " (compose \"%s\")",
4013  CommandOptionToMnemonic(MagickComposeOptions, rslt_compose) );
4014  (void) CompositeImage(rslt_image,curr_image,rslt_compose,MagickTrue,
4015  0,0,exception);
4016  curr_image = DestroyImage(curr_image);
4017  curr_image = (Image *) image; /* continue with original image */
4018  }
4019  if (verbose != MagickFalse)
4020  (void) FormatLocaleFile(stderr, "\n");
4021 
4022  /* loop to the next kernel in a multi-kernel list */
4023  norm_kernel = norm_kernel->next;
4024  if ( rflt_kernel != (KernelInfo *) NULL )
4025  rflt_kernel = rflt_kernel->next;
4026  kernel_number++;
4027  } /* End Loop 2: Loop over each kernel */
4028 
4029  } /* End Loop 1: compound method interaction */
4030 
4031  goto exit_cleanup;
4032 
4033  /* Yes goto's are bad, but it makes cleanup lot more efficient */
4034 error_cleanup:
4035  if ( curr_image == rslt_image )
4036  curr_image = (Image *) NULL;
4037  if ( rslt_image != (Image *) NULL )
4038  rslt_image = DestroyImage(rslt_image);
4039 exit_cleanup:
4040  if ( curr_image == rslt_image || curr_image == image )
4041  curr_image = (Image *) NULL;
4042  if ( curr_image != (Image *) NULL )
4043  curr_image = DestroyImage(curr_image);
4044  if ( work_image != (Image *) NULL )
4045  work_image = DestroyImage(work_image);
4046  if ( save_image != (Image *) NULL )
4047  save_image = DestroyImage(save_image);
4048  if ( reflected_kernel != (KernelInfo *) NULL )
4049  reflected_kernel = DestroyKernelInfo(reflected_kernel);
4050  return(rslt_image);
4051 }
4052 
4053 ␌
4054 /*
4055 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4056 % %
4057 % %
4058 % %
4059 % M o r p h o l o g y I m a g e %
4060 % %
4061 % %
4062 % %
4063 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4064 %
4065 % MorphologyImage() applies a user supplied kernel to the image according to
4066 % the given morphology method.
4067 %
4068 % This function applies any and all user defined settings before calling
4069 % the above internal function MorphologyApply().
4070 %
4071 % User defined settings include...
4072 % * Output Bias for Convolution and correlation ("-define convolve:bias=??")
4073 % * Kernel Scale/normalize settings ("-define convolve:scale=??")
4074 % This can also includes the addition of a scaled unity kernel.
4075 % * Show Kernel being applied ("-define morphology:showKernel=1")
4076 %
4077 % Other operators that do not want user supplied options interfering,
4078 % especially "convolve:bias" and "morphology:showKernel" should use
4079 % MorphologyApply() directly.
4080 %
4081 % The format of the MorphologyImage method is:
4082 %
4083 % Image *MorphologyImage(const Image *image,MorphologyMethod method,
4084 % const ssize_t iterations,KernelInfo *kernel,ExceptionInfo *exception)
4085 %
4086 % A description of each parameter follows:
4087 %
4088 % o image: the image.
4089 %
4090 % o method: the morphology method to be applied.
4091 %
4092 % o iterations: apply the operation this many times (or no change).
4093 % A value of -1 means loop until no change found.
4094 % How this is applied may depend on the morphology method.
4095 % Typically this is a value of 1.
4096 %
4097 % o kernel: An array of double representing the morphology kernel.
4098 % Warning: kernel may be normalized for the Convolve method.
4099 %
4100 % o exception: return any errors or warnings in this structure.
4101 %
4102 */
4103 MagickExport Image *MorphologyImage(const Image *image,
4104  const MorphologyMethod method,const ssize_t iterations,
4105  const KernelInfo *kernel,ExceptionInfo *exception)
4106 {
4107  const char
4108  *artifact;
4109 
4110  CompositeOperator
4111  compose;
4112 
4113  double
4114  bias;
4115 
4116  Image
4117  *morphology_image;
4118 
4119  KernelInfo
4120  *curr_kernel;
4121 
4122  assert(image != (const Image *) NULL);
4123  assert(image->signature == MagickCoreSignature);
4124  assert(exception != (ExceptionInfo *) NULL);
4125  assert(exception->signature == MagickCoreSignature);
4126  if (IsEventLogging() != MagickFalse)
4127  (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
4128  curr_kernel = (KernelInfo *) kernel;
4129  bias=0.0;
4130  compose = UndefinedCompositeOp; /* use default for method */
4131 
4132  /* Apply Convolve/Correlate Normalization and Scaling Factors.
4133  * This is done BEFORE the ShowKernelInfo() function is called so that
4134  * users can see the results of the 'option:convolve:scale' option.
4135  */
4136  if ( method == ConvolveMorphology || method == CorrelateMorphology ) {
4137  /* Get the bias value as it will be needed */
4138  artifact = GetImageArtifact(image,"convolve:bias");
4139  if ( artifact != (const char *) NULL) {
4140  if (IsGeometry(artifact) == MagickFalse)
4141  (void) ThrowMagickException(exception,GetMagickModule(),
4142  OptionWarning,"InvalidSetting","'%s' '%s'",
4143  "convolve:bias",artifact);
4144  else
4145  bias=StringToDoubleInterval(artifact,(double) QuantumRange+1.0);
4146  }
4147 
4148  /* Scale kernel according to user wishes */
4149  artifact = GetImageArtifact(image,"convolve:scale");
4150  if ( artifact != (const char *) NULL ) {
4151  if (IsGeometry(artifact) == MagickFalse)
4152  (void) ThrowMagickException(exception,GetMagickModule(),
4153  OptionWarning,"InvalidSetting","'%s' '%s'",
4154  "convolve:scale",artifact);
4155  else {
4156  if ( curr_kernel == kernel )
4157  curr_kernel = CloneKernelInfo(kernel);
4158  if (curr_kernel == (KernelInfo *) NULL)
4159  return((Image *) NULL);
4160  ScaleGeometryKernelInfo(curr_kernel, artifact);
4161  }
4162  }
4163  }
4164 
4165  /* display the (normalized) kernel via stderr */
4166  artifact=GetImageArtifact(image,"morphology:showKernel");
4167  if (IsStringTrue(artifact) != MagickFalse)
4168  ShowKernelInfo(curr_kernel);
4169 
4170  /* Override the default handling of multi-kernel morphology results
4171  * If 'Undefined' use the default method
4172  * If 'None' (default for 'Convolve') re-iterate previous result
4173  * Otherwise merge resulting images using compose method given.
4174  * Default for 'HitAndMiss' is 'Lighten'.
4175  */
4176  {
4177  ssize_t
4178  parse;
4179 
4180  artifact = GetImageArtifact(image,"morphology:compose");
4181  if ( artifact != (const char *) NULL) {
4182  parse=ParseCommandOption(MagickComposeOptions,
4183  MagickFalse,artifact);
4184  if ( parse < 0 )
4185  (void) ThrowMagickException(exception,GetMagickModule(),
4186  OptionWarning,"UnrecognizedComposeOperator","'%s' '%s'",
4187  "morphology:compose",artifact);
4188  else
4189  compose=(CompositeOperator)parse;
4190  }
4191  }
4192  /* Apply the Morphology */
4193  morphology_image = MorphologyApply(image,method,iterations,
4194  curr_kernel,compose,bias,exception);
4195 
4196  /* Cleanup and Exit */
4197  if ( curr_kernel != kernel )
4198  curr_kernel=DestroyKernelInfo(curr_kernel);
4199  return(morphology_image);
4200 }
4201 ␌
4202 /*
4203 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4204 % %
4205 % %
4206 % %
4207 + R o t a t e K e r n e l I n f o %
4208 % %
4209 % %
4210 % %
4211 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4212 %
4213 % RotateKernelInfo() rotates the kernel by the angle given.
4214 %
4215 % Currently it is restricted to 90 degree angles, of either 1D kernels
4216 % or square kernels. And 'circular' rotations of 45 degrees for 3x3 kernels.
4217 % It will ignore useless rotations for specific 'named' built-in kernels.
4218 %
4219 % The format of the RotateKernelInfo method is:
4220 %
4221 % void RotateKernelInfo(KernelInfo *kernel, double angle)
4222 %
4223 % A description of each parameter follows:
4224 %
4225 % o kernel: the Morphology/Convolution kernel
4226 %
4227 % o angle: angle to rotate in degrees
4228 %
4229 % This function is currently internal to this module only, but can be exported
4230 % to other modules if needed.
4231 */
4232 static void RotateKernelInfo(KernelInfo *kernel, double angle)
4233 {
4234  /* angle the lower kernels first */
4235  if ( kernel->next != (KernelInfo *) NULL)
4236  RotateKernelInfo(kernel->next, angle);
4237 
4238  /* WARNING: Currently assumes the kernel (rightly) is horizontally symmetrical
4239  **
4240  ** TODO: expand beyond simple 90 degree rotates, flips and flops
4241  */
4242 
4243  /* Modulus the angle */
4244  angle = fmod(angle, 360.0);
4245  if ( angle < 0 )
4246  angle += 360.0;
4247 
4248  if ( 337.5 < angle || angle <= 22.5 )
4249  return; /* Near zero angle - no change! - At least not at this time */
4250 
4251  /* Handle special cases */
4252  switch (kernel->type) {
4253  /* These built-in kernels are cylindrical kernels, rotating is useless */
4254  case GaussianKernel:
4255  case DoGKernel:
4256  case LoGKernel:
4257  case DiskKernel:
4258  case PeaksKernel:
4259  case LaplacianKernel:
4260  case ChebyshevKernel:
4261  case ManhattanKernel:
4262  case EuclideanKernel:
4263  return;
4264 
4265  /* These may be rotatable at non-90 angles in the future */
4266  /* but simply rotating them in multiples of 90 degrees is useless */
4267  case SquareKernel:
4268  case DiamondKernel:
4269  case PlusKernel:
4270  case CrossKernel:
4271  return;
4272 
4273  /* These only allows a +/-90 degree rotation (by transpose) */
4274  /* A 180 degree rotation is useless */
4275  case BlurKernel:
4276  if ( 135.0 < angle && angle <= 225.0 )
4277  return;
4278  if ( 225.0 < angle && angle <= 315.0 )
4279  angle -= 180;
4280  break;
4281 
4282  default:
4283  break;
4284  }
4285  /* Attempt rotations by 45 degrees -- 3x3 kernels only */
4286  if ( 22.5 < fmod(angle,90.0) && fmod(angle,90.0) <= 67.5 )
4287  {
4288  if ( kernel->width == 3 && kernel->height == 3 )
4289  { /* Rotate a 3x3 square by 45 degree angle */
4290  double t = kernel->values[0];
4291  kernel->values[0] = kernel->values[3];
4292  kernel->values[3] = kernel->values[6];
4293  kernel->values[6] = kernel->values[7];
4294  kernel->values[7] = kernel->values[8];
4295  kernel->values[8] = kernel->values[5];
4296  kernel->values[5] = kernel->values[2];
4297  kernel->values[2] = kernel->values[1];
4298  kernel->values[1] = t;
4299  /* rotate non-centered origin */
4300  if ( kernel->x != 1 || kernel->y != 1 ) {
4301  ssize_t x,y;
4302  x = (ssize_t) kernel->x-1;
4303  y = (ssize_t) kernel->y-1;
4304  if ( x == y ) x = 0;
4305  else if ( x == 0 ) x = -y;
4306  else if ( x == -y ) y = 0;
4307  else if ( y == 0 ) y = x;
4308  kernel->x = (ssize_t) x+1;
4309  kernel->y = (ssize_t) y+1;
4310  }
4311  angle = fmod(angle+315.0, 360.0); /* angle reduced 45 degrees */
4312  kernel->angle = fmod(kernel->angle+45.0, 360.0);
4313  }
4314  else
4315  perror("Unable to rotate non-3x3 kernel by 45 degrees");
4316  }
4317  if ( 45.0 < fmod(angle, 180.0) && fmod(angle,180.0) <= 135.0 )
4318  {
4319  if ( kernel->width == 1 || kernel->height == 1 )
4320  { /* Do a transpose of a 1 dimensional kernel,
4321  ** which results in a fast 90 degree rotation of some type.
4322  */
4323  ssize_t
4324  t;
4325  t = (ssize_t) kernel->width;
4326  kernel->width = kernel->height;
4327  kernel->height = (size_t) t;
4328  t = kernel->x;
4329  kernel->x = kernel->y;
4330  kernel->y = t;
4331  if ( kernel->width == 1 ) {
4332  angle = fmod(angle+270.0, 360.0); /* angle reduced 90 degrees */
4333  kernel->angle = fmod(kernel->angle+90.0, 360.0);
4334  } else {
4335  angle = fmod(angle+90.0, 360.0); /* angle increased 90 degrees */
4336  kernel->angle = fmod(kernel->angle+270.0, 360.0);
4337  }
4338  }
4339  else if ( kernel->width == kernel->height )
4340  { /* Rotate a square array of values by 90 degrees */
4341  { ssize_t
4342  i,j,x,y;
4343 
4344  MagickRealType
4345  *k,t;
4346 
4347  k=kernel->values;
4348  for( i=0, x=(ssize_t) kernel->width-1; i<=x; i++, x--)
4349  for( j=0, y=(ssize_t) kernel->height-1; j<y; j++, y--)
4350  { t = k[i+j*(ssize_t) kernel->width];
4351  k[i+j*(ssize_t) kernel->width] = k[j+x*(ssize_t) kernel->width];
4352  k[j+x*(ssize_t) kernel->width] = k[x+y*(ssize_t) kernel->width];
4353  k[x+y*(ssize_t) kernel->width] = k[y+i*(ssize_t) kernel->width];
4354  k[y+i*(ssize_t) kernel->width] = t;
4355  }
4356  }
4357  /* rotate the origin - relative to center of array */
4358  { ssize_t x,y;
4359  x = (ssize_t) (kernel->x*2-(ssize_t) kernel->width+1);
4360  y = (ssize_t) (kernel->y*2-(ssize_t) kernel->height+1);
4361  kernel->x = (ssize_t) ( -y +(ssize_t) kernel->width-1)/2;
4362  kernel->y = (ssize_t) ( +x +(ssize_t) kernel->height-1)/2;
4363  }
4364  angle = fmod(angle+270.0, 360.0); /* angle reduced 90 degrees */
4365  kernel->angle = fmod(kernel->angle+90.0, 360.0);
4366  }
4367  else
4368  perror("Unable to rotate a non-square, non-linear kernel 90 degrees");
4369  }
4370  if ( 135.0 < angle && angle <= 225.0 )
4371  {
4372  /* For a 180 degree rotation - also know as a reflection
4373  * This is actually a very very common operation!
4374  * Basically all that is needed is a reversal of the kernel data!
4375  * And a reflection of the origin
4376  */
4377  MagickRealType
4378  t;
4379 
4380  MagickRealType
4381  *k;
4382 
4383  ssize_t
4384  i,
4385  j;
4386 
4387  k=kernel->values;
4388  j=(ssize_t) (kernel->width*kernel->height-1);
4389  for (i=0; i < j; i++, j--)
4390  t=k[i], k[i]=k[j], k[j]=t;
4391 
4392  kernel->x = (ssize_t) kernel->width - kernel->x - 1;
4393  kernel->y = (ssize_t) kernel->height - kernel->y - 1;
4394  angle = fmod(angle-180.0, 360.0); /* angle+180 degrees */
4395  kernel->angle = fmod(kernel->angle+180.0, 360.0);
4396  }
4397  /* At this point angle should at least between -45 (315) and +45 degrees
4398  * In the future some form of non-orthogonal angled rotates could be
4399  * performed here, possibly with a linear kernel restriction.
4400  */
4401 
4402  return;
4403 }
4404 ␌
4405 /*
4406 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4407 % %
4408 % %
4409 % %
4410 % S c a l e G e o m e t r y K e r n e l I n f o %
4411 % %
4412 % %
4413 % %
4414 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4415 %
4416 % ScaleGeometryKernelInfo() takes a geometry argument string, typically
4417 % provided as a "-set option:convolve:scale {geometry}" user setting,
4418 % and modifies the kernel according to the parsed arguments of that setting.
4419 %
4420 % The first argument (and any normalization flags) are passed to
4421 % ScaleKernelInfo() to scale/normalize the kernel. The second argument
4422 % is then passed to UnityAddKernelInfo() to add a scaled unity kernel
4423 % into the scaled/normalized kernel.
4424 %
4425 % The format of the ScaleGeometryKernelInfo method is:
4426 %
4427 % void ScaleGeometryKernelInfo(KernelInfo *kernel,
4428 % const double scaling_factor,const MagickStatusType normalize_flags)
4429 %
4430 % A description of each parameter follows:
4431 %
4432 % o kernel: the Morphology/Convolution kernel to modify
4433 %
4434 % o geometry:
4435 % The geometry string to parse, typically from the user provided
4436 % "-set option:convolve:scale {geometry}" setting.
4437 %
4438 */
4439 MagickExport void ScaleGeometryKernelInfo (KernelInfo *kernel,
4440  const char *geometry)
4441 {
4442  MagickStatusType
4443  flags;
4444 
4445  GeometryInfo
4446  args;
4447 
4448  SetGeometryInfo(&args);
4449  flags = ParseGeometry(geometry, &args);
4450 
4451 #if 0
4452  /* For Debugging Geometry Input */
4453  (void) FormatLocaleFile(stderr, "Geometry = 0x%04X : %lg x %lg %+lg %+lg\n",
4454  flags, args.rho, args.sigma, args.xi, args.psi );
4455 #endif
4456 
4457  if ( (flags & PercentValue) != 0 ) /* Handle Percentage flag*/
4458  args.rho *= 0.01, args.sigma *= 0.01;
4459 
4460  if ( (flags & RhoValue) == 0 ) /* Set Defaults for missing args */
4461  args.rho = 1.0;
4462  if ( (flags & SigmaValue) == 0 )
4463  args.sigma = 0.0;
4464 
4465  /* Scale/Normalize the input kernel */
4466  ScaleKernelInfo(kernel, args.rho, (GeometryFlags) flags);
4467 
4468  /* Add Unity Kernel, for blending with original */
4469  if ( (flags & SigmaValue) != 0 )
4470  UnityAddKernelInfo(kernel, args.sigma);
4471 
4472  return;
4473 }
4474 /*
4475 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4476 % %
4477 % %
4478 % %
4479 % S c a l e K e r n e l I n f o %
4480 % %
4481 % %
4482 % %
4483 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4484 %
4485 % ScaleKernelInfo() scales the given kernel list by the given amount, with or
4486 % without normalization of the sum of the kernel values (as per given flags).
4487 %
4488 % By default (no flags given) the values within the kernel is scaled
4489 % directly using given scaling factor without change.
4490 %
4491 % If either of the two 'normalize_flags' are given the kernel will first be
4492 % normalized and then further scaled by the scaling factor value given.
4493 %
4494 % Kernel normalization ('normalize_flags' given) is designed to ensure that
4495 % any use of the kernel scaling factor with 'Convolve' or 'Correlate'
4496 % morphology methods will fall into -1.0 to +1.0 range. Note that for
4497 % non-HDRI versions of IM this may cause images to have any negative results
4498 % clipped, unless some 'bias' is used.
4499 %
4500 % More specifically. Kernels which only contain positive values (such as a
4501 % 'Gaussian' kernel) will be scaled so that those values sum to +1.0,
4502 % ensuring a 0.0 to +1.0 output range for non-HDRI images.
4503 %
4504 % For Kernels that contain some negative values, (such as 'Sharpen' kernels)
4505 % the kernel will be scaled by the absolute of the sum of kernel values, so
4506 % that it will generally fall within the +/- 1.0 range.
4507 %
4508 % For kernels whose values sum to zero, (such as 'Laplacian' kernels) kernel
4509 % will be scaled by just the sum of the positive values, so that its output
4510 % range will again fall into the +/- 1.0 range.
4511 %
4512 % For special kernels designed for locating shapes using 'Correlate', (often
4513 % only containing +1 and -1 values, representing foreground/background
4514 % matching) a special normalization method is provided to scale the positive
4515 % values separately to those of the negative values, so the kernel will be
4516 % forced to become a zero-sum kernel better suited to such searches.
4517 %
4518 % WARNING: Correct normalization of the kernel assumes that the '*_range'
4519 % attributes within the kernel structure have been correctly set during the
4520 % kernels creation.
4521 %
4522 % NOTE: The values used for 'normalize_flags' have been selected specifically
4523 % to match the use of geometry options, so that '!' means NormalizeValue, '^'
4524 % means CorrelateNormalizeValue. All other GeometryFlags values are ignored.
4525 %
4526 % The format of the ScaleKernelInfo method is:
4527 %
4528 % void ScaleKernelInfo(KernelInfo *kernel, const double scaling_factor,
4529 % const MagickStatusType normalize_flags )
4530 %
4531 % A description of each parameter follows:
4532 %
4533 % o kernel: the Morphology/Convolution kernel
4534 %
4535 % o scaling_factor:
4536 % multiply all values (after normalization) by this factor if not
4537 % zero. If the kernel is normalized regardless of any flags.
4538 %
4539 % o normalize_flags:
4540 % GeometryFlags defining normalization method to use.
4541 % specifically: NormalizeValue, CorrelateNormalizeValue,
4542 % and/or PercentValue
4543 %
4544 */
4545 MagickExport void ScaleKernelInfo(KernelInfo *kernel,
4546  const double scaling_factor,const GeometryFlags normalize_flags)
4547 {
4548  double
4549  pos_scale,
4550  neg_scale;
4551 
4552  ssize_t
4553  i;
4554 
4555  /* do the other kernels in a multi-kernel list first */
4556  if ( kernel->next != (KernelInfo *) NULL)
4557  ScaleKernelInfo(kernel->next, scaling_factor, normalize_flags);
4558 
4559  /* Normalization of Kernel */
4560  pos_scale = 1.0;
4561  if ( (normalize_flags&NormalizeValue) != 0 ) {
4562  if ( fabs(kernel->positive_range + kernel->negative_range) >= MagickEpsilon )
4563  /* non-zero-summing kernel (generally positive) */
4564  pos_scale = fabs(kernel->positive_range + kernel->negative_range);
4565  else
4566  /* zero-summing kernel */
4567  pos_scale = kernel->positive_range;
4568  }
4569  /* Force kernel into a normalized zero-summing kernel */
4570  if ( (normalize_flags&CorrelateNormalizeValue) != 0 ) {
4571  pos_scale = ( fabs(kernel->positive_range) >= MagickEpsilon )
4572  ? kernel->positive_range : 1.0;
4573  neg_scale = ( fabs(kernel->negative_range) >= MagickEpsilon )
4574  ? -kernel->negative_range : 1.0;
4575  }
4576  else
4577  neg_scale = pos_scale;
4578 
4579  /* finalize scaling_factor for positive and negative components */
4580  pos_scale = scaling_factor/pos_scale;
4581  neg_scale = scaling_factor/neg_scale;
4582 
4583  for (i=0; i < (ssize_t) (kernel->width*kernel->height); i++)
4584  if (!IsNaN(kernel->values[i]))
4585  kernel->values[i] *= (kernel->values[i] >= 0) ? pos_scale : neg_scale;
4586 
4587  /* convolution output range */
4588  kernel->positive_range *= pos_scale;
4589  kernel->negative_range *= neg_scale;
4590  /* maximum and minimum values in kernel */
4591  kernel->maximum *= (kernel->maximum >= 0.0) ? pos_scale : neg_scale;
4592  kernel->minimum *= (kernel->minimum >= 0.0) ? pos_scale : neg_scale;
4593 
4594  /* swap kernel settings if user's scaling factor is negative */
4595  if ( scaling_factor < MagickEpsilon ) {
4596  double t;
4597  t = kernel->positive_range;
4598  kernel->positive_range = kernel->negative_range;
4599  kernel->negative_range = t;
4600  t = kernel->maximum;
4601  kernel->maximum = kernel->minimum;
4602  kernel->minimum = 1;
4603  }
4604 
4605  return;
4606 }
4607 ␌
4608 /*
4609 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4610 % %
4611 % %
4612 % %
4613 % S h o w K e r n e l I n f o %
4614 % %
4615 % %
4616 % %
4617 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4618 %
4619 % ShowKernelInfo() outputs the details of the given kernel definition to
4620 % standard error, generally due to a users 'morphology:showKernel' option
4621 % request.
4622 %
4623 % The format of the ShowKernel method is:
4624 %
4625 % void ShowKernelInfo(const KernelInfo *kernel)
4626 %
4627 % A description of each parameter follows:
4628 %
4629 % o kernel: the Morphology/Convolution kernel
4630 %
4631 */
4632 MagickPrivate void ShowKernelInfo(const KernelInfo *kernel)
4633 {
4634  const KernelInfo
4635  *k;
4636 
4637  size_t
4638  c, i, u, v;
4639 
4640  for (c=0, k=kernel; k != (KernelInfo *) NULL; c++, k=k->next ) {
4641 
4642  (void) FormatLocaleFile(stderr, "Kernel");
4643  if ( kernel->next != (KernelInfo *) NULL )
4644  (void) FormatLocaleFile(stderr, " #%lu", (unsigned long) c );
4645  (void) FormatLocaleFile(stderr, " \"%s",
4646  CommandOptionToMnemonic(MagickKernelOptions, k->type) );
4647  if ( fabs(k->angle) >= MagickEpsilon )
4648  (void) FormatLocaleFile(stderr, "@%lg", k->angle);
4649  (void) FormatLocaleFile(stderr, "\" of size %lux%lu%+ld%+ld",(unsigned long)
4650  k->width,(unsigned long) k->height,(long) k->x,(long) k->y);
4651  (void) FormatLocaleFile(stderr,
4652  " with values from %.*lg to %.*lg\n",
4653  GetMagickPrecision(), k->minimum,
4654  GetMagickPrecision(), k->maximum);
4655  (void) FormatLocaleFile(stderr, "Forming a output range from %.*lg to %.*lg",
4656  GetMagickPrecision(), k->negative_range,
4657  GetMagickPrecision(), k->positive_range);
4658  if ( fabs(k->positive_range+k->negative_range) < MagickEpsilon )
4659  (void) FormatLocaleFile(stderr, " (Zero-Summing)\n");
4660  else if ( fabs(k->positive_range+k->negative_range-1.0) < MagickEpsilon )
4661  (void) FormatLocaleFile(stderr, " (Normalized)\n");
4662  else
4663  (void) FormatLocaleFile(stderr, " (Sum %.*lg)\n",
4664  GetMagickPrecision(), k->positive_range+k->negative_range);
4665  for (i=v=0; v < k->height; v++) {
4666  (void) FormatLocaleFile(stderr, "%2lu:", (unsigned long) v );
4667  for (u=0; u < k->width; u++, i++)
4668  if (IsNaN(k->values[i]))
4669  (void) FormatLocaleFile(stderr," %*s", GetMagickPrecision()+3, "nan");
4670  else
4671  (void) FormatLocaleFile(stderr," %*.*lg", GetMagickPrecision()+3,
4672  GetMagickPrecision(), (double) k->values[i]);
4673  (void) FormatLocaleFile(stderr,"\n");
4674  }
4675  }
4676 }
4677 ␌
4678 /*
4679 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4680 % %
4681 % %
4682 % %
4683 % U n i t y A d d K e r n a l I n f o %
4684 % %
4685 % %
4686 % %
4687 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4688 %
4689 % UnityAddKernelInfo() Adds a given amount of the 'Unity' Convolution Kernel
4690 % to the given pre-scaled and normalized Kernel. This in effect adds that
4691 % amount of the original image into the resulting convolution kernel. This
4692 % value is usually provided by the user as a percentage value in the
4693 % 'convolve:scale' setting.
4694 %
4695 % The resulting effect is to convert the defined kernels into blended
4696 % soft-blurs, unsharp kernels or into sharpening kernels.
4697 %
4698 % The format of the UnityAdditionKernelInfo method is:
4699 %
4700 % void UnityAdditionKernelInfo(KernelInfo *kernel, const double scale )
4701 %
4702 % A description of each parameter follows:
4703 %
4704 % o kernel: the Morphology/Convolution kernel
4705 %
4706 % o scale:
4707 % scaling factor for the unity kernel to be added to
4708 % the given kernel.
4709 %
4710 */
4711 MagickExport void UnityAddKernelInfo(KernelInfo *kernel,
4712  const double scale)
4713 {
4714  /* do the other kernels in a multi-kernel list first */
4715  if ( kernel->next != (KernelInfo *) NULL)
4716  UnityAddKernelInfo(kernel->next, scale);
4717 
4718  /* Add the scaled unity kernel to the existing kernel */
4719  kernel->values[kernel->x+kernel->y*(ssize_t) kernel->width] += scale;
4720  CalcKernelMetaData(kernel); /* recalculate the meta-data */
4721 
4722  return;
4723 }
4724 ␌
4725 /*
4726 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4727 % %
4728 % %
4729 % %
4730 % Z e r o K e r n e l N a n s %
4731 % %
4732 % %
4733 % %
4734 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4735 %
4736 % ZeroKernelNans() replaces any special 'nan' value that may be present in
4737 % the kernel with a zero value. This is typically done when the kernel will
4738 % be used in special hardware (GPU) convolution processors, to simply
4739 % matters.
4740 %
4741 % The format of the ZeroKernelNans method is:
4742 %
4743 % void ZeroKernelNans (KernelInfo *kernel)
4744 %
4745 % A description of each parameter follows:
4746 %
4747 % o kernel: the Morphology/Convolution kernel
4748 %
4749 */
4750 MagickPrivate void ZeroKernelNans(KernelInfo *kernel)
4751 {
4752  size_t
4753  i;
4754 
4755  /* do the other kernels in a multi-kernel list first */
4756  if (kernel->next != (KernelInfo *) NULL)
4757  ZeroKernelNans(kernel->next);
4758 
4759  for (i=0; i < (kernel->width*kernel->height); i++)
4760  if (IsNaN(kernel->values[i]))
4761  kernel->values[i]=0.0;
4762 
4763  return;
4764 }
Definition: image.h:132