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