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