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