MagickCore 7.1.1-43
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/script/license.php %
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{
222 *kernel;
223
224 char
225 token[MagickPathExtent];
226
227 const char
228 *p,
229 *end;
230
231 ssize_t
232 i;
233
234 double
235 nan = sqrt(-1.0); /* Special Value : Not A Number */
236
237 MagickStatusType
238 flags;
239
241 args;
242
243 size_t
244 length;
245
246 kernel=(KernelInfo *) AcquireMagickMemory(sizeof(*kernel));
247 if (kernel == (KernelInfo *) NULL)
248 return(kernel);
249 (void) memset(kernel,0,sizeof(*kernel));
250 kernel->minimum = kernel->maximum = kernel->angle = 0.0;
251 kernel->negative_range = kernel->positive_range = 0.0;
252 kernel->type = UserDefinedKernel;
253 kernel->next = (KernelInfo *) NULL;
254 kernel->signature=MagickCoreSignature;
255 if (kernel_string == (const char *) NULL)
256 return(kernel);
257
258 /* find end of this specific kernel definition string */
259 end = strchr(kernel_string, ';');
260 if ( end == (char *) NULL )
261 end = strchr(kernel_string, '\0');
262
263 /* clear flags - for Expanding kernel lists through rotations */
264 flags = NoValue;
265
266 /* Has a ':' in argument - New user kernel specification
267 FUTURE: this split on ':' could be done by StringToken()
268 */
269 p = strchr(kernel_string, ':');
270 if ( p != (char *) NULL && p < end)
271 {
272 /* ParseGeometry() needs the geometry separated! -- Arrgghh */
273 length=MagickMin((size_t) (p-kernel_string),sizeof(token)-1);
274 (void) memcpy(token, kernel_string, length);
275 token[length] = '\0';
276 SetGeometryInfo(&args);
277 flags = ParseGeometry(token, &args);
278
279 /* Size handling and checks of geometry settings */
280 if ( (flags & WidthValue) == 0 ) /* if no width then */
281 args.rho = args.sigma; /* then width = height */
282 if ( args.rho < 1.0 ) /* if width too small */
283 args.rho = 1.0; /* then width = 1 */
284 if ( args.sigma < 1.0 ) /* if height too small */
285 args.sigma = args.rho; /* then height = width */
286 kernel->width = (size_t)args.rho;
287 kernel->height = (size_t)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= (size_t) 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
391 args;
392
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{
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{
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 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 = (size_t)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 = (size_t)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 = (size_t)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 = ((size_t)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 { size_t
1326 alpha = order_f / ( fact((size_t) v) * fact(kernel->height-(size_t) v-1) );
1327 for ( u=0; u < (ssize_t)kernel->width; u++, i++)
1328 kernel->positive_range += kernel->values[i] = (double)
1329 (alpha * order_f / ( fact((size_t) u) * fact(kernel->height-(size_t) u-1) ));
1330 }
1331 kernel->minimum = 1.0;
1332 kernel->maximum = kernel->values[kernel->x+kernel->y*(ssize_t) kernel->width];
1333 kernel->negative_range = 0.0;
1334 break;
1335 }
1336
1337 /*
1338 Convolution Kernels - Well Known Named Constant Kernels
1339 */
1340 case LaplacianKernel:
1341 { switch ( (int) args->rho ) {
1342 case 0:
1343 default: /* laplacian square filter -- default */
1344 kernel=ParseKernelArray("3: -1,-1,-1 -1,8,-1 -1,-1,-1");
1345 break;
1346 case 1: /* laplacian diamond filter */
1347 kernel=ParseKernelArray("3: 0,-1,0 -1,4,-1 0,-1,0");
1348 break;
1349 case 2:
1350 kernel=ParseKernelArray("3: -2,1,-2 1,4,1 -2,1,-2");
1351 break;
1352 case 3:
1353 kernel=ParseKernelArray("3: 1,-2,1 -2,4,-2 1,-2,1");
1354 break;
1355 case 5: /* a 5x5 laplacian */
1356 kernel=ParseKernelArray(
1357 "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");
1358 break;
1359 case 7: /* a 7x7 laplacian */
1360 kernel=ParseKernelArray(
1361 "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" );
1362 break;
1363 case 15: /* a 5x5 LoG (sigma approx 1.4) */
1364 kernel=ParseKernelArray(
1365 "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");
1366 break;
1367 case 19: /* a 9x9 LoG (sigma approx 1.4) */
1368 /* http://www.cscjournals.org/csc/manuscript/Journals/IJIP/volume3/Issue1/IJIP-15.pdf */
1369 kernel=ParseKernelArray(
1370 "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");
1371 break;
1372 }
1373 if (kernel == (KernelInfo *) NULL)
1374 return(kernel);
1375 kernel->type = type;
1376 break;
1377 }
1378 case SobelKernel:
1379 { /* Simple Sobel Kernel */
1380 kernel=ParseKernelArray("3: 1,0,-1 2,0,-2 1,0,-1");
1381 if (kernel == (KernelInfo *) NULL)
1382 return(kernel);
1383 kernel->type = type;
1384 RotateKernelInfo(kernel, args->rho);
1385 break;
1386 }
1387 case RobertsKernel:
1388 {
1389 kernel=ParseKernelArray("3: 0,0,0 1,-1,0 0,0,0");
1390 if (kernel == (KernelInfo *) NULL)
1391 return(kernel);
1392 kernel->type = type;
1393 RotateKernelInfo(kernel, args->rho);
1394 break;
1395 }
1396 case PrewittKernel:
1397 {
1398 kernel=ParseKernelArray("3: 1,0,-1 1,0,-1 1,0,-1");
1399 if (kernel == (KernelInfo *) NULL)
1400 return(kernel);
1401 kernel->type = type;
1402 RotateKernelInfo(kernel, args->rho);
1403 break;
1404 }
1405 case CompassKernel:
1406 {
1407 kernel=ParseKernelArray("3: 1,1,-1 1,-2,-1 1,1,-1");
1408 if (kernel == (KernelInfo *) NULL)
1409 return(kernel);
1410 kernel->type = type;
1411 RotateKernelInfo(kernel, args->rho);
1412 break;
1413 }
1414 case KirschKernel:
1415 {
1416 kernel=ParseKernelArray("3: 5,-3,-3 5,0,-3 5,-3,-3");
1417 if (kernel == (KernelInfo *) NULL)
1418 return(kernel);
1419 kernel->type = type;
1420 RotateKernelInfo(kernel, args->rho);
1421 break;
1422 }
1423 case FreiChenKernel:
1424 /* Direction is set to be left to right positive */
1425 /* http://www.math.tau.ac.il/~turkel/notes/edge_detectors.pdf -- RIGHT? */
1426 /* http://ltswww.epfl.ch/~courstiv/exos_labos/sol3.pdf -- WRONG? */
1427 { switch ( (int) args->rho ) {
1428 default:
1429 case 0:
1430 kernel=ParseKernelArray("3: 1,0,-1 2,0,-2 1,0,-1");
1431 if (kernel == (KernelInfo *) NULL)
1432 return(kernel);
1433 kernel->type = type;
1434 kernel->values[3] = +(MagickRealType) MagickSQ2;
1435 kernel->values[5] = -(MagickRealType) MagickSQ2;
1436 CalcKernelMetaData(kernel); /* recalculate meta-data */
1437 break;
1438 case 2:
1439 kernel=ParseKernelArray("3: 1,2,0 2,0,-2 0,-2,-1");
1440 if (kernel == (KernelInfo *) NULL)
1441 return(kernel);
1442 kernel->type = type;
1443 kernel->values[1] = kernel->values[3]= +(MagickRealType) MagickSQ2;
1444 kernel->values[5] = kernel->values[7]= -(MagickRealType) MagickSQ2;
1445 CalcKernelMetaData(kernel); /* recalculate meta-data */
1446 ScaleKernelInfo(kernel, (double) (1.0/2.0*MagickSQ2), NoValue);
1447 break;
1448 case 10:
1449 {
1450 kernel=AcquireKernelInfo("FreiChen:11;FreiChen:12;FreiChen:13;FreiChen:14;FreiChen:15;FreiChen:16;FreiChen:17;FreiChen:18;FreiChen:19",exception);
1451 if (kernel == (KernelInfo *) NULL)
1452 return(kernel);
1453 break;
1454 }
1455 case 1:
1456 case 11:
1457 kernel=ParseKernelArray("3: 1,0,-1 2,0,-2 1,0,-1");
1458 if (kernel == (KernelInfo *) NULL)
1459 return(kernel);
1460 kernel->type = type;
1461 kernel->values[3] = +(MagickRealType) MagickSQ2;
1462 kernel->values[5] = -(MagickRealType) MagickSQ2;
1463 CalcKernelMetaData(kernel); /* recalculate meta-data */
1464 ScaleKernelInfo(kernel, (double) (1.0/2.0*MagickSQ2), NoValue);
1465 break;
1466 case 12:
1467 kernel=ParseKernelArray("3: 1,2,1 0,0,0 1,2,1");
1468 if (kernel == (KernelInfo *) NULL)
1469 return(kernel);
1470 kernel->type = type;
1471 kernel->values[1] = +(MagickRealType) MagickSQ2;
1472 kernel->values[7] = +(MagickRealType) MagickSQ2;
1473 CalcKernelMetaData(kernel);
1474 ScaleKernelInfo(kernel, (double) (1.0/2.0*MagickSQ2), NoValue);
1475 break;
1476 case 13:
1477 kernel=ParseKernelArray("3: 2,-1,0 -1,0,1 0,1,-2");
1478 if (kernel == (KernelInfo *) NULL)
1479 return(kernel);
1480 kernel->type = type;
1481 kernel->values[0] = +(MagickRealType) MagickSQ2;
1482 kernel->values[8] = -(MagickRealType) MagickSQ2;
1483 CalcKernelMetaData(kernel);
1484 ScaleKernelInfo(kernel, (double) (1.0/2.0*MagickSQ2), NoValue);
1485 break;
1486 case 14:
1487 kernel=ParseKernelArray("3: 0,1,-2 -1,0,1 2,-1,0");
1488 if (kernel == (KernelInfo *) NULL)
1489 return(kernel);
1490 kernel->type = type;
1491 kernel->values[2] = -(MagickRealType) MagickSQ2;
1492 kernel->values[6] = +(MagickRealType) MagickSQ2;
1493 CalcKernelMetaData(kernel);
1494 ScaleKernelInfo(kernel, (double) (1.0/2.0*MagickSQ2), NoValue);
1495 break;
1496 case 15:
1497 kernel=ParseKernelArray("3: 0,-1,0 1,0,1 0,-1,0");
1498 if (kernel == (KernelInfo *) NULL)
1499 return(kernel);
1500 kernel->type = type;
1501 ScaleKernelInfo(kernel, 1.0/2.0, NoValue);
1502 break;
1503 case 16:
1504 kernel=ParseKernelArray("3: 1,0,-1 0,0,0 -1,0,1");
1505 if (kernel == (KernelInfo *) NULL)
1506 return(kernel);
1507 kernel->type = type;
1508 ScaleKernelInfo(kernel, 1.0/2.0, NoValue);
1509 break;
1510 case 17:
1511 kernel=ParseKernelArray("3: 1,-2,1 -2,4,-2 -1,-2,1");
1512 if (kernel == (KernelInfo *) NULL)
1513 return(kernel);
1514 kernel->type = type;
1515 ScaleKernelInfo(kernel, 1.0/6.0, NoValue);
1516 break;
1517 case 18:
1518 kernel=ParseKernelArray("3: -2,1,-2 1,4,1 -2,1,-2");
1519 if (kernel == (KernelInfo *) NULL)
1520 return(kernel);
1521 kernel->type = type;
1522 ScaleKernelInfo(kernel, 1.0/6.0, NoValue);
1523 break;
1524 case 19:
1525 kernel=ParseKernelArray("3: 1,1,1 1,1,1 1,1,1");
1526 if (kernel == (KernelInfo *) NULL)
1527 return(kernel);
1528 kernel->type = type;
1529 ScaleKernelInfo(kernel, 1.0/3.0, NoValue);
1530 break;
1531 }
1532 if ( fabs(args->sigma) >= MagickEpsilon )
1533 /* Rotate by correctly supplied 'angle' */
1534 RotateKernelInfo(kernel, args->sigma);
1535 else if ( args->rho > 30.0 || args->rho < -30.0 )
1536 /* Rotate by out of bounds 'type' */
1537 RotateKernelInfo(kernel, args->rho);
1538 break;
1539 }
1540
1541 /*
1542 Boolean or Shaped Kernels
1543 */
1544 case DiamondKernel:
1545 {
1546 if (args->rho < 1.0)
1547 kernel->width = kernel->height = 3; /* default radius = 1 */
1548 else
1549 kernel->width = kernel->height = ((size_t)args->rho)*2+1;
1550 kernel->x = kernel->y = (ssize_t) (kernel->width-1)/2;
1551
1552 if (AcquireKernelValues(kernel) == MagickFalse)
1553 return(DestroyKernelInfo(kernel));
1554
1555 /* set all kernel values within diamond area to scale given */
1556 for ( i=0, v=-kernel->y; v <= (ssize_t)kernel->y; v++)
1557 for ( u=-kernel->x; u <= (ssize_t)kernel->x; u++, i++)
1558 if ( (labs((long) u)+labs((long) v)) <= (long) kernel->x)
1559 kernel->positive_range += kernel->values[i] = args->sigma;
1560 else
1561 kernel->values[i] = nan;
1562 kernel->minimum = kernel->maximum = args->sigma; /* a flat shape */
1563 break;
1564 }
1565 case SquareKernel:
1566 case RectangleKernel:
1567 { double
1568 scale;
1569 if ( type == SquareKernel )
1570 {
1571 if (args->rho < 1.0)
1572 kernel->width = kernel->height = 3; /* default radius = 1 */
1573 else
1574 kernel->width = kernel->height = (size_t) (2*args->rho+1);
1575 kernel->x = kernel->y = (ssize_t) (kernel->width-1)/2;
1576 scale = args->sigma;
1577 }
1578 else {
1579 /* NOTE: user defaults set in "AcquireKernelInfo()" */
1580 if ( args->rho < 1.0 || args->sigma < 1.0 )
1581 return(DestroyKernelInfo(kernel)); /* invalid args given */
1582 kernel->width = CastDoubleToSizeT(args->rho);
1583 kernel->height = CastDoubleToSizeT(args->sigma);
1584 if ((args->xi < 0.0) || (args->xi >= (double) kernel->width) ||
1585 (args->psi < 0.0) || (args->psi >= (double) kernel->height))
1586 return(DestroyKernelInfo(kernel)); /* invalid args given */
1587 kernel->x = (ssize_t) args->xi;
1588 kernel->y = (ssize_t) args->psi;
1589 scale = 1.0;
1590 }
1591 if (AcquireKernelValues(kernel) == MagickFalse)
1592 return(DestroyKernelInfo(kernel));
1593
1594 /* set all kernel values to scale given */
1595 u=(ssize_t) (kernel->width*kernel->height);
1596 for ( i=0; i < u; i++)
1597 kernel->values[i] = scale;
1598 kernel->minimum = kernel->maximum = scale; /* a flat shape */
1599 kernel->positive_range = scale*u;
1600 break;
1601 }
1602 case OctagonKernel:
1603 {
1604 if (args->rho < 1.0)
1605 kernel->width = kernel->height = 5; /* default radius = 2 */
1606 else
1607 kernel->width = kernel->height = ((size_t)args->rho)*2+1;
1608 kernel->x = kernel->y = (ssize_t) (kernel->width-1)/2;
1609
1610 if (AcquireKernelValues(kernel) == MagickFalse)
1611 return(DestroyKernelInfo(kernel));
1612
1613 for ( i=0, v=-kernel->y; v <= (ssize_t)kernel->y; v++)
1614 for ( u=-kernel->x; u <= (ssize_t)kernel->x; u++, i++)
1615 if ( (labs((long) u)+labs((long) v)) <=
1616 ((long)kernel->x + (long)(kernel->x/2)) )
1617 kernel->positive_range += kernel->values[i] = args->sigma;
1618 else
1619 kernel->values[i] = nan;
1620 kernel->minimum = kernel->maximum = args->sigma; /* a flat shape */
1621 break;
1622 }
1623 case DiskKernel:
1624 {
1625 ssize_t
1626 limit = (ssize_t)(args->rho*args->rho);
1627
1628 if (args->rho < 0.4) /* default radius approx 4.3 */
1629 kernel->width = kernel->height = 9L, limit = 18L;
1630 else
1631 kernel->width = kernel->height = (size_t)fabs(args->rho)*2+1;
1632 kernel->x = kernel->y = (ssize_t) (kernel->width-1)/2;
1633
1634 if (AcquireKernelValues(kernel) == MagickFalse)
1635 return(DestroyKernelInfo(kernel));
1636
1637 for ( i=0, v=-kernel->y; v <= (ssize_t)kernel->y; v++)
1638 for ( u=-kernel->x; u <= (ssize_t)kernel->x; u++, i++)
1639 if ((u*u+v*v) <= limit)
1640 kernel->positive_range += kernel->values[i] = args->sigma;
1641 else
1642 kernel->values[i] = nan;
1643 kernel->minimum = kernel->maximum = args->sigma; /* a flat shape */
1644 break;
1645 }
1646 case PlusKernel:
1647 {
1648 if (args->rho < 1.0)
1649 kernel->width = kernel->height = 5; /* default radius 2 */
1650 else
1651 kernel->width = kernel->height = ((size_t)args->rho)*2+1;
1652 kernel->x = kernel->y = (ssize_t) (kernel->width-1)/2;
1653
1654 if (AcquireKernelValues(kernel) == MagickFalse)
1655 return(DestroyKernelInfo(kernel));
1656
1657 /* set all kernel values along axises to given scale */
1658 for ( i=0, v=-kernel->y; v <= (ssize_t)kernel->y; v++)
1659 for ( u=-kernel->x; u <= (ssize_t)kernel->x; u++, i++)
1660 kernel->values[i] = (u == 0 || v == 0) ? args->sigma : nan;
1661 kernel->minimum = kernel->maximum = args->sigma; /* a flat shape */
1662 kernel->positive_range = args->sigma*(kernel->width*2.0 - 1.0);
1663 break;
1664 }
1665 case CrossKernel:
1666 {
1667 if (args->rho < 1.0)
1668 kernel->width = kernel->height = 5; /* default radius 2 */
1669 else
1670 kernel->width = kernel->height = ((size_t)args->rho)*2+1;
1671 kernel->x = kernel->y = (ssize_t) (kernel->width-1)/2;
1672
1673 if (AcquireKernelValues(kernel) == MagickFalse)
1674 return(DestroyKernelInfo(kernel));
1675
1676 /* set all kernel values along axises to given scale */
1677 for ( i=0, v=-kernel->y; v <= (ssize_t)kernel->y; v++)
1678 for ( u=-kernel->x; u <= (ssize_t)kernel->x; u++, i++)
1679 kernel->values[i] = (u == v || u == -v) ? args->sigma : nan;
1680 kernel->minimum = kernel->maximum = args->sigma; /* a flat shape */
1681 kernel->positive_range = args->sigma*(kernel->width*2.0 - 1.0);
1682 break;
1683 }
1684 /*
1685 HitAndMiss Kernels
1686 */
1687 case RingKernel:
1688 case PeaksKernel:
1689 {
1690 ssize_t
1691 limit1,
1692 limit2,
1693 scale;
1694
1695 if (args->rho < args->sigma)
1696 {
1697 kernel->width = ((size_t)args->sigma)*2+1;
1698 limit1 = (ssize_t)(args->rho*args->rho);
1699 limit2 = (ssize_t)(args->sigma*args->sigma);
1700 }
1701 else
1702 {
1703 kernel->width = ((size_t)args->rho)*2+1;
1704 limit1 = (ssize_t)(args->sigma*args->sigma);
1705 limit2 = (ssize_t)(args->rho*args->rho);
1706 }
1707 if ( limit2 <= 0 )
1708 kernel->width = 7L, limit1 = 7L, limit2 = 11L;
1709
1710 kernel->height = kernel->width;
1711 kernel->x = kernel->y = (ssize_t) (kernel->width-1)/2;
1712 if (AcquireKernelValues(kernel) == MagickFalse)
1713 return(DestroyKernelInfo(kernel));
1714
1715 /* set a ring of points of 'scale' ( 0.0 for PeaksKernel ) */
1716 scale = (ssize_t) (( type == PeaksKernel) ? 0.0 : args->xi);
1717 for ( i=0, v= -kernel->y; v <= (ssize_t)kernel->y; v++)
1718 for ( u=-kernel->x; u <= (ssize_t)kernel->x; u++, i++)
1719 { ssize_t radius=u*u+v*v;
1720 if (limit1 < radius && radius <= limit2)
1721 kernel->positive_range += kernel->values[i] = (double) scale;
1722 else
1723 kernel->values[i] = nan;
1724 }
1725 kernel->minimum = kernel->maximum = (double) scale;
1726 if ( type == PeaksKernel ) {
1727 /* set the central point in the middle */
1728 kernel->values[kernel->x+kernel->y*(ssize_t) kernel->width] = 1.0;
1729 kernel->positive_range = 1.0;
1730 kernel->maximum = 1.0;
1731 }
1732 break;
1733 }
1734 case EdgesKernel:
1735 {
1736 kernel=AcquireKernelInfo("ThinSE:482",exception);
1737 if (kernel == (KernelInfo *) NULL)
1738 return(kernel);
1739 kernel->type = type;
1740 ExpandMirrorKernelInfo(kernel); /* mirror expansion of kernels */
1741 break;
1742 }
1743 case CornersKernel:
1744 {
1745 kernel=AcquireKernelInfo("ThinSE:87",exception);
1746 if (kernel == (KernelInfo *) NULL)
1747 return(kernel);
1748 kernel->type = type;
1749 ExpandRotateKernelInfo(kernel, 90.0); /* Expand 90 degree rotations */
1750 break;
1751 }
1752 case DiagonalsKernel:
1753 {
1754 switch ( (int) args->rho ) {
1755 case 0:
1756 default:
1757 { KernelInfo
1758 *new_kernel;
1759 kernel=ParseKernelArray("3: 0,0,0 0,-,1 1,1,-");
1760 if (kernel == (KernelInfo *) NULL)
1761 return(kernel);
1762 kernel->type = type;
1763 new_kernel=ParseKernelArray("3: 0,0,1 0,-,1 0,1,-");
1764 if (new_kernel == (KernelInfo *) NULL)
1765 return(DestroyKernelInfo(kernel));
1766 new_kernel->type = type;
1767 LastKernelInfo(kernel)->next = new_kernel;
1768 ExpandMirrorKernelInfo(kernel);
1769 return(kernel);
1770 }
1771 case 1:
1772 kernel=ParseKernelArray("3: 0,0,0 0,-,1 1,1,-");
1773 break;
1774 case 2:
1775 kernel=ParseKernelArray("3: 0,0,1 0,-,1 0,1,-");
1776 break;
1777 }
1778 if (kernel == (KernelInfo *) NULL)
1779 return(kernel);
1780 kernel->type = type;
1781 RotateKernelInfo(kernel, args->sigma);
1782 break;
1783 }
1784 case LineEndsKernel:
1785 { /* Kernels for finding the end of thin lines */
1786 switch ( (int) args->rho ) {
1787 case 0:
1788 default:
1789 /* set of kernels to find all end of lines */
1790 return(AcquireKernelInfo("LineEnds:1>;LineEnds:2>",exception));
1791 case 1:
1792 /* kernel for 4-connected line ends - no rotation */
1793 kernel=ParseKernelArray("3: 0,0,- 0,1,1 0,0,-");
1794 break;
1795 case 2:
1796 /* kernel to add for 8-connected lines - no rotation */
1797 kernel=ParseKernelArray("3: 0,0,0 0,1,0 0,0,1");
1798 break;
1799 case 3:
1800 /* kernel to add for orthogonal line ends - does not find corners */
1801 kernel=ParseKernelArray("3: 0,0,0 0,1,1 0,0,0");
1802 break;
1803 case 4:
1804 /* traditional line end - fails on last T end */
1805 kernel=ParseKernelArray("3: 0,0,0 0,1,- 0,0,-");
1806 break;
1807 }
1808 if (kernel == (KernelInfo *) NULL)
1809 return(kernel);
1810 kernel->type = type;
1811 RotateKernelInfo(kernel, args->sigma);
1812 break;
1813 }
1814 case LineJunctionsKernel:
1815 { /* kernels for finding the junctions of multiple lines */
1816 switch ( (int) args->rho ) {
1817 case 0:
1818 default:
1819 /* set of kernels to find all line junctions */
1820 return(AcquireKernelInfo("LineJunctions:1@;LineJunctions:2>",exception));
1821 case 1:
1822 /* Y Junction */
1823 kernel=ParseKernelArray("3: 1,-,1 -,1,- -,1,-");
1824 break;
1825 case 2:
1826 /* Diagonal T Junctions */
1827 kernel=ParseKernelArray("3: 1,-,- -,1,- 1,-,1");
1828 break;
1829 case 3:
1830 /* Orthogonal T Junctions */
1831 kernel=ParseKernelArray("3: -,-,- 1,1,1 -,1,-");
1832 break;
1833 case 4:
1834 /* Diagonal X Junctions */
1835 kernel=ParseKernelArray("3: 1,-,1 -,1,- 1,-,1");
1836 break;
1837 case 5:
1838 /* Orthogonal X Junctions - minimal diamond kernel */
1839 kernel=ParseKernelArray("3: -,1,- 1,1,1 -,1,-");
1840 break;
1841 }
1842 if (kernel == (KernelInfo *) NULL)
1843 return(kernel);
1844 kernel->type = type;
1845 RotateKernelInfo(kernel, args->sigma);
1846 break;
1847 }
1848 case RidgesKernel:
1849 { /* Ridges - Ridge finding kernels */
1851 *new_kernel;
1852 switch ( (int) args->rho ) {
1853 case 1:
1854 default:
1855 kernel=ParseKernelArray("3x1:0,1,0");
1856 if (kernel == (KernelInfo *) NULL)
1857 return(kernel);
1858 kernel->type = type;
1859 ExpandRotateKernelInfo(kernel, 90.0); /* 2 rotated kernels (symmetrical) */
1860 break;
1861 case 2:
1862 kernel=ParseKernelArray("4x1:0,1,1,0");
1863 if (kernel == (KernelInfo *) NULL)
1864 return(kernel);
1865 kernel->type = type;
1866 ExpandRotateKernelInfo(kernel, 90.0); /* 4 rotated kernels */
1867
1868 /* Kernels to find a stepped 'thick' line, 4 rotates + mirrors */
1869 /* Unfortunately we can not yet rotate a non-square kernel */
1870 /* But then we can't flip a non-symmetrical kernel either */
1871 new_kernel=ParseKernelArray("4x3+1+1:0,1,1,- -,1,1,- -,1,1,0");
1872 if (new_kernel == (KernelInfo *) NULL)
1873 return(DestroyKernelInfo(kernel));
1874 new_kernel->type = type;
1875 LastKernelInfo(kernel)->next = new_kernel;
1876 new_kernel=ParseKernelArray("4x3+2+1:0,1,1,- -,1,1,- -,1,1,0");
1877 if (new_kernel == (KernelInfo *) NULL)
1878 return(DestroyKernelInfo(kernel));
1879 new_kernel->type = type;
1880 LastKernelInfo(kernel)->next = new_kernel;
1881 new_kernel=ParseKernelArray("4x3+1+1:-,1,1,0 -,1,1,- 0,1,1,-");
1882 if (new_kernel == (KernelInfo *) NULL)
1883 return(DestroyKernelInfo(kernel));
1884 new_kernel->type = type;
1885 LastKernelInfo(kernel)->next = new_kernel;
1886 new_kernel=ParseKernelArray("4x3+2+1:-,1,1,0 -,1,1,- 0,1,1,-");
1887 if (new_kernel == (KernelInfo *) NULL)
1888 return(DestroyKernelInfo(kernel));
1889 new_kernel->type = type;
1890 LastKernelInfo(kernel)->next = new_kernel;
1891 new_kernel=ParseKernelArray("3x4+1+1:0,-,- 1,1,1 1,1,1 -,-,0");
1892 if (new_kernel == (KernelInfo *) NULL)
1893 return(DestroyKernelInfo(kernel));
1894 new_kernel->type = type;
1895 LastKernelInfo(kernel)->next = new_kernel;
1896 new_kernel=ParseKernelArray("3x4+1+2:0,-,- 1,1,1 1,1,1 -,-,0");
1897 if (new_kernel == (KernelInfo *) NULL)
1898 return(DestroyKernelInfo(kernel));
1899 new_kernel->type = type;
1900 LastKernelInfo(kernel)->next = new_kernel;
1901 new_kernel=ParseKernelArray("3x4+1+1:-,-,0 1,1,1 1,1,1 0,-,-");
1902 if (new_kernel == (KernelInfo *) NULL)
1903 return(DestroyKernelInfo(kernel));
1904 new_kernel->type = type;
1905 LastKernelInfo(kernel)->next = new_kernel;
1906 new_kernel=ParseKernelArray("3x4+1+2:-,-,0 1,1,1 1,1,1 0,-,-");
1907 if (new_kernel == (KernelInfo *) NULL)
1908 return(DestroyKernelInfo(kernel));
1909 new_kernel->type = type;
1910 LastKernelInfo(kernel)->next = new_kernel;
1911 break;
1912 }
1913 break;
1914 }
1915 case ConvexHullKernel:
1916 {
1918 *new_kernel;
1919 /* first set of 8 kernels */
1920 kernel=ParseKernelArray("3: 1,1,- 1,0,- 1,-,0");
1921 if (kernel == (KernelInfo *) NULL)
1922 return(kernel);
1923 kernel->type = type;
1924 ExpandRotateKernelInfo(kernel, 90.0);
1925 /* append the mirror versions too - no flip function yet */
1926 new_kernel=ParseKernelArray("3: 1,1,1 1,0,- -,-,0");
1927 if (new_kernel == (KernelInfo *) NULL)
1928 return(DestroyKernelInfo(kernel));
1929 new_kernel->type = type;
1930 ExpandRotateKernelInfo(new_kernel, 90.0);
1931 LastKernelInfo(kernel)->next = new_kernel;
1932 break;
1933 }
1934 case SkeletonKernel:
1935 {
1936 switch ( (int) args->rho ) {
1937 case 1:
1938 default:
1939 /* Traditional Skeleton...
1940 ** A cyclically rotated single kernel
1941 */
1942 kernel=AcquireKernelInfo("ThinSE:482",exception);
1943 if (kernel == (KernelInfo *) NULL)
1944 return(kernel);
1945 kernel->type = type;
1946 ExpandRotateKernelInfo(kernel, 45.0); /* 8 rotations */
1947 break;
1948 case 2:
1949 /* HIPR Variation of the cyclic skeleton
1950 ** Corners of the traditional method made more forgiving,
1951 ** but the retain the same cyclic order.
1952 */
1953 kernel=AcquireKernelInfo("ThinSE:482; ThinSE:87x90;",exception);
1954 if (kernel == (KernelInfo *) NULL)
1955 return(kernel);
1956 if (kernel->next == (KernelInfo *) NULL)
1957 return(DestroyKernelInfo(kernel));
1958 kernel->type = type;
1959 kernel->next->type = type;
1960 ExpandRotateKernelInfo(kernel, 90.0); /* 4 rotations of the 2 kernels */
1961 break;
1962 case 3:
1963 /* Dan Bloomberg Skeleton, from his paper on 3x3 thinning SE's
1964 ** "Connectivity-Preserving Morphological Image Transformations"
1965 ** by Dan S. Bloomberg, available on Leptonica, Selected Papers,
1966 ** http://www.leptonica.com/papers/conn.pdf
1967 */
1968 kernel=AcquireKernelInfo("ThinSE:41; ThinSE:42; ThinSE:43",
1969 exception);
1970 if (kernel == (KernelInfo *) NULL)
1971 return(kernel);
1972 if (kernel->next == (KernelInfo *) NULL)
1973 return(DestroyKernelInfo(kernel));
1974 if (kernel->next->next == (KernelInfo *) NULL)
1975 return(DestroyKernelInfo(kernel));
1976 kernel->type = type;
1977 kernel->next->type = type;
1978 kernel->next->next->type = type;
1979 ExpandMirrorKernelInfo(kernel); /* 12 kernels total */
1980 break;
1981 }
1982 break;
1983 }
1984 case ThinSEKernel:
1985 { /* Special kernels for general thinning, while preserving connections
1986 ** "Connectivity-Preserving Morphological Image Transformations"
1987 ** by Dan S. Bloomberg, available on Leptonica, Selected Papers,
1988 ** http://www.leptonica.com/papers/conn.pdf
1989 ** And
1990 ** http://tpgit.github.com/Leptonica/ccthin_8c_source.html
1991 **
1992 ** Note kernels do not specify the origin pixel, allowing them
1993 ** to be used for both thickening and thinning operations.
1994 */
1995 switch ( (int) args->rho ) {
1996 /* SE for 4-connected thinning */
1997 case 41: /* SE_4_1 */
1998 kernel=ParseKernelArray("3: -,-,1 0,-,1 -,-,1");
1999 break;
2000 case 42: /* SE_4_2 */
2001 kernel=ParseKernelArray("3: -,-,1 0,-,1 -,0,-");
2002 break;
2003 case 43: /* SE_4_3 */
2004 kernel=ParseKernelArray("3: -,0,- 0,-,1 -,-,1");
2005 break;
2006 case 44: /* SE_4_4 */
2007 kernel=ParseKernelArray("3: -,0,- 0,-,1 -,0,-");
2008 break;
2009 case 45: /* SE_4_5 */
2010 kernel=ParseKernelArray("3: -,0,1 0,-,1 -,0,-");
2011 break;
2012 case 46: /* SE_4_6 */
2013 kernel=ParseKernelArray("3: -,0,- 0,-,1 -,0,1");
2014 break;
2015 case 47: /* SE_4_7 */
2016 kernel=ParseKernelArray("3: -,1,1 0,-,1 -,0,-");
2017 break;
2018 case 48: /* SE_4_8 */
2019 kernel=ParseKernelArray("3: -,-,1 0,-,1 0,-,1");
2020 break;
2021 case 49: /* SE_4_9 */
2022 kernel=ParseKernelArray("3: 0,-,1 0,-,1 -,-,1");
2023 break;
2024 /* SE for 8-connected thinning - negatives of the above */
2025 case 81: /* SE_8_0 */
2026 kernel=ParseKernelArray("3: -,1,- 0,-,1 -,1,-");
2027 break;
2028 case 82: /* SE_8_2 */
2029 kernel=ParseKernelArray("3: -,1,- 0,-,1 0,-,-");
2030 break;
2031 case 83: /* SE_8_3 */
2032 kernel=ParseKernelArray("3: 0,-,- 0,-,1 -,1,-");
2033 break;
2034 case 84: /* SE_8_4 */
2035 kernel=ParseKernelArray("3: 0,-,- 0,-,1 0,-,-");
2036 break;
2037 case 85: /* SE_8_5 */
2038 kernel=ParseKernelArray("3: 0,-,1 0,-,1 0,-,-");
2039 break;
2040 case 86: /* SE_8_6 */
2041 kernel=ParseKernelArray("3: 0,-,- 0,-,1 0,-,1");
2042 break;
2043 case 87: /* SE_8_7 */
2044 kernel=ParseKernelArray("3: -,1,- 0,-,1 0,0,-");
2045 break;
2046 case 88: /* SE_8_8 */
2047 kernel=ParseKernelArray("3: -,1,- 0,-,1 0,1,-");
2048 break;
2049 case 89: /* SE_8_9 */
2050 kernel=ParseKernelArray("3: 0,1,- 0,-,1 -,1,-");
2051 break;
2052 /* Special combined SE kernels */
2053 case 423: /* SE_4_2 , SE_4_3 Combined Kernel */
2054 kernel=ParseKernelArray("3: -,-,1 0,-,- -,0,-");
2055 break;
2056 case 823: /* SE_8_2 , SE_8_3 Combined Kernel */
2057 kernel=ParseKernelArray("3: -,1,- -,-,1 0,-,-");
2058 break;
2059 case 481: /* SE_48_1 - General Connected Corner Kernel */
2060 kernel=ParseKernelArray("3: -,1,1 0,-,1 0,0,-");
2061 break;
2062 default:
2063 case 482: /* SE_48_2 - General Edge Kernel */
2064 kernel=ParseKernelArray("3: 0,-,1 0,-,1 0,-,1");
2065 break;
2066 }
2067 if (kernel == (KernelInfo *) NULL)
2068 return(kernel);
2069 kernel->type = type;
2070 RotateKernelInfo(kernel, args->sigma);
2071 break;
2072 }
2073 /*
2074 Distance Measuring Kernels
2075 */
2076 case ChebyshevKernel:
2077 {
2078 if (args->rho < 1.0)
2079 kernel->width = kernel->height = 3; /* default radius = 1 */
2080 else
2081 kernel->width = kernel->height = ((size_t)args->rho)*2+1;
2082 kernel->x = kernel->y = (ssize_t) (kernel->width-1)/2;
2083
2084 if (AcquireKernelValues(kernel) == MagickFalse)
2085 return(DestroyKernelInfo(kernel));
2086
2087 for ( i=0, v=-kernel->y; v <= (ssize_t)kernel->y; v++)
2088 for ( u=-kernel->x; u <= (ssize_t)kernel->x; u++, i++)
2089 kernel->positive_range += ( kernel->values[i] =
2090 args->sigma*MagickMax(fabs((double)u),fabs((double)v)) );
2091 kernel->maximum = kernel->values[0];
2092 break;
2093 }
2094 case ManhattanKernel:
2095 {
2096 if (args->rho < 1.0)
2097 kernel->width = kernel->height = 3; /* default radius = 1 */
2098 else
2099 kernel->width = kernel->height = ((size_t)args->rho)*2+1;
2100 kernel->x = kernel->y = (ssize_t) (kernel->width-1)/2;
2101
2102 if (AcquireKernelValues(kernel) == MagickFalse)
2103 return(DestroyKernelInfo(kernel));
2104
2105 for ( i=0, v=-kernel->y; v <= (ssize_t)kernel->y; v++)
2106 for ( u=-kernel->x; u <= (ssize_t)kernel->x; u++, i++)
2107 kernel->positive_range += ( kernel->values[i] =
2108 args->sigma*(labs((long) u)+labs((long) v)) );
2109 kernel->maximum = kernel->values[0];
2110 break;
2111 }
2112 case OctagonalKernel:
2113 {
2114 if (args->rho < 2.0)
2115 kernel->width = kernel->height = 5; /* default/minimum radius = 2 */
2116 else
2117 kernel->width = kernel->height = ((size_t)args->rho)*2+1;
2118 kernel->x = kernel->y = (ssize_t) (kernel->width-1)/2;
2119
2120 if (AcquireKernelValues(kernel) == MagickFalse)
2121 return(DestroyKernelInfo(kernel));
2122
2123 for ( i=0, v=-kernel->y; v <= (ssize_t)kernel->y; v++)
2124 for ( u=-kernel->x; u <= (ssize_t)kernel->x; u++, i++)
2125 {
2126 double
2127 r1 = MagickMax(fabs((double)u),fabs((double)v)),
2128 r2 = floor((double)(labs((long)u)+labs((long)v)+1)/1.5);
2129 kernel->positive_range += kernel->values[i] =
2130 args->sigma*MagickMax(r1,r2);
2131 }
2132 kernel->maximum = kernel->values[0];
2133 break;
2134 }
2135 case EuclideanKernel:
2136 {
2137 if (args->rho < 1.0)
2138 kernel->width = kernel->height = 3; /* default radius = 1 */
2139 else
2140 kernel->width = kernel->height = ((size_t)args->rho)*2+1;
2141 kernel->x = kernel->y = (ssize_t) (kernel->width-1)/2;
2142
2143 if (AcquireKernelValues(kernel) == MagickFalse)
2144 return(DestroyKernelInfo(kernel));
2145
2146 for ( i=0, v=-kernel->y; v <= (ssize_t)kernel->y; v++)
2147 for ( u=-kernel->x; u <= (ssize_t)kernel->x; u++, i++)
2148 kernel->positive_range += ( kernel->values[i] =
2149 args->sigma*sqrt((double) (u*u+v*v)) );
2150 kernel->maximum = kernel->values[0];
2151 break;
2152 }
2153 default:
2154 {
2155 /* No-Op Kernel - Basically just a single pixel on its own */
2156 kernel=ParseKernelArray("1:1");
2157 if (kernel == (KernelInfo *) NULL)
2158 return(kernel);
2159 kernel->type = UndefinedKernel;
2160 break;
2161 }
2162 break;
2163 }
2164 return(kernel);
2165}
2166␌
2167/*
2168%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2169% %
2170% %
2171% %
2172% C l o n e K e r n e l I n f o %
2173% %
2174% %
2175% %
2176%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2177%
2178% CloneKernelInfo() creates a new clone of the given Kernel List so that its
2179% can be modified without effecting the original. The cloned kernel should
2180% be destroyed using DestroyKernelInfo() when no longer needed.
2181%
2182% The format of the CloneKernelInfo method is:
2183%
2184% KernelInfo *CloneKernelInfo(const KernelInfo *kernel)
2185%
2186% A description of each parameter follows:
2187%
2188% o kernel: the Morphology/Convolution kernel to be cloned
2189%
2190*/
2191MagickExport KernelInfo *CloneKernelInfo(const KernelInfo *kernel)
2192{
2193 ssize_t
2194 i;
2195
2197 *new_kernel;
2198
2199 assert(kernel != (KernelInfo *) NULL);
2200 new_kernel=(KernelInfo *) AcquireMagickMemory(sizeof(*kernel));
2201 if (new_kernel == (KernelInfo *) NULL)
2202 return(new_kernel);
2203 *new_kernel=(*kernel); /* copy values in structure */
2204
2205 /* replace the values with a copy of the values */
2206 if (AcquireKernelValues(new_kernel) == MagickFalse)
2207 return(DestroyKernelInfo(new_kernel));
2208 for (i=0; i < (ssize_t) (kernel->width*kernel->height); i++)
2209 new_kernel->values[i]=kernel->values[i];
2210
2211 /* Also clone the next kernel in the kernel list */
2212 if ( kernel->next != (KernelInfo *) NULL ) {
2213 new_kernel->next = CloneKernelInfo(kernel->next);
2214 if ( new_kernel->next == (KernelInfo *) NULL )
2215 return(DestroyKernelInfo(new_kernel));
2216 }
2217
2218 return(new_kernel);
2219}
2220␌
2221/*
2222%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2223% %
2224% %
2225% %
2226% D e s t r o y K e r n e l I n f o %
2227% %
2228% %
2229% %
2230%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2231%
2232% DestroyKernelInfo() frees the memory used by a Convolution/Morphology
2233% kernel.
2234%
2235% The format of the DestroyKernelInfo method is:
2236%
2237% KernelInfo *DestroyKernelInfo(KernelInfo *kernel)
2238%
2239% A description of each parameter follows:
2240%
2241% o kernel: the Morphology/Convolution kernel to be destroyed
2242%
2243*/
2244MagickExport KernelInfo *DestroyKernelInfo(KernelInfo *kernel)
2245{
2246 assert(kernel != (KernelInfo *) NULL);
2247 if (kernel->next != (KernelInfo *) NULL)
2248 kernel->next=DestroyKernelInfo(kernel->next);
2249 kernel->values=(MagickRealType *) RelinquishAlignedMemory(kernel->values);
2250 kernel=(KernelInfo *) RelinquishMagickMemory(kernel);
2251 return(kernel);
2252}
2253␌
2254/*
2255%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2256% %
2257% %
2258% %
2259+ E x p a n d M i r r o r K e r n e l I n f o %
2260% %
2261% %
2262% %
2263%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2264%
2265% ExpandMirrorKernelInfo() takes a single kernel, and expands it into a
2266% sequence of 90-degree rotated kernels but providing a reflected 180
2267% rotation, before the -/+ 90-degree rotations.
2268%
2269% This special rotation order produces a better, more symmetrical thinning of
2270% objects.
2271%
2272% The format of the ExpandMirrorKernelInfo method is:
2273%
2274% void ExpandMirrorKernelInfo(KernelInfo *kernel)
2275%
2276% A description of each parameter follows:
2277%
2278% o kernel: the Morphology/Convolution kernel
2279%
2280% This function is only internal to this module, as it is not finalized,
2281% especially with regard to non-orthogonal angles, and rotation of larger
2282% 2D kernels.
2283*/
2284
2285#if 0
2286static void FlopKernelInfo(KernelInfo *kernel)
2287 { /* Do a Flop by reversing each row. */
2288 size_t
2289 y;
2290 ssize_t
2291 x,r;
2292 double
2293 *k,t;
2294
2295 for ( y=0, k=kernel->values; y < kernel->height; y++, k+=kernel->width)
2296 for ( x=0, r=kernel->width-1; x<kernel->width/2; x++, r--)
2297 t=k[x], k[x]=k[r], k[r]=t;
2298
2299 kernel->x = kernel->width - kernel->x - 1;
2300 angle = fmod(angle+180.0, 360.0);
2301 }
2302#endif
2303
2304static void ExpandMirrorKernelInfo(KernelInfo *kernel)
2305{
2307 *clone,
2308 *last;
2309
2310 last = kernel;
2311
2312 clone = CloneKernelInfo(last);
2313 if (clone == (KernelInfo *) NULL)
2314 return;
2315 RotateKernelInfo(clone, 180); /* flip */
2316 LastKernelInfo(last)->next = clone;
2317 last = clone;
2318
2319 clone = CloneKernelInfo(last);
2320 if (clone == (KernelInfo *) NULL)
2321 return;
2322 RotateKernelInfo(clone, 90); /* transpose */
2323 LastKernelInfo(last)->next = clone;
2324 last = clone;
2325
2326 clone = CloneKernelInfo(last);
2327 if (clone == (KernelInfo *) NULL)
2328 return;
2329 RotateKernelInfo(clone, 180); /* flop */
2330 LastKernelInfo(last)->next = clone;
2331
2332 return;
2333}
2334␌
2335/*
2336%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2337% %
2338% %
2339% %
2340+ E x p a n d R o t a t e K e r n e l I n f o %
2341% %
2342% %
2343% %
2344%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2345%
2346% ExpandRotateKernelInfo() takes a kernel list, and expands it by rotating
2347% incrementally by the angle given, until the kernel repeats.
2348%
2349% WARNING: 45 degree rotations only works for 3x3 kernels.
2350% While 90 degree rotations only works for linear and square kernels
2351%
2352% The format of the ExpandRotateKernelInfo method is:
2353%
2354% void ExpandRotateKernelInfo(KernelInfo *kernel, double angle)
2355%
2356% A description of each parameter follows:
2357%
2358% o kernel: the Morphology/Convolution kernel
2359%
2360% o angle: angle to rotate in degrees
2361%
2362% This function is only internal to this module, as it is not finalized,
2363% especially with regard to non-orthogonal angles, and rotation of larger
2364% 2D kernels.
2365*/
2366
2367/* Internal Routine - Return true if two kernels are the same */
2368static MagickBooleanType SameKernelInfo(const KernelInfo *kernel1,
2369 const KernelInfo *kernel2)
2370{
2371 size_t
2372 i;
2373
2374 /* check size and origin location */
2375 if ( kernel1->width != kernel2->width
2376 || kernel1->height != kernel2->height
2377 || kernel1->x != kernel2->x
2378 || kernel1->y != kernel2->y )
2379 return MagickFalse;
2380
2381 /* check actual kernel values */
2382 for (i=0; i < (kernel1->width*kernel1->height); i++) {
2383 /* Test for Nan equivalence */
2384 if ( IsNaN(kernel1->values[i]) && !IsNaN(kernel2->values[i]) )
2385 return MagickFalse;
2386 if ( IsNaN(kernel2->values[i]) && !IsNaN(kernel1->values[i]) )
2387 return MagickFalse;
2388 /* Test actual values are equivalent */
2389 if ( fabs(kernel1->values[i] - kernel2->values[i]) >= MagickEpsilon )
2390 return MagickFalse;
2391 }
2392
2393 return MagickTrue;
2394}
2395
2396static void ExpandRotateKernelInfo(KernelInfo *kernel,const double angle)
2397{
2399 *clone_info,
2400 *last;
2401
2402 clone_info=(KernelInfo *) NULL;
2403 last=kernel;
2404DisableMSCWarning(4127)
2405 while (1) {
2406RestoreMSCWarning
2407 clone_info=CloneKernelInfo(last);
2408 if (clone_info == (KernelInfo *) NULL)
2409 break;
2410 RotateKernelInfo(clone_info,angle);
2411 if (SameKernelInfo(kernel,clone_info) != MagickFalse)
2412 break;
2413 LastKernelInfo(last)->next=clone_info;
2414 last=clone_info;
2415 }
2416 if (clone_info != (KernelInfo *) NULL)
2417 clone_info=DestroyKernelInfo(clone_info); /* kernel repeated - junk */
2418 return;
2419}
2420␌
2421/*
2422%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2423% %
2424% %
2425% %
2426+ C a l c M e t a K e r n a l I n f o %
2427% %
2428% %
2429% %
2430%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2431%
2432% CalcKernelMetaData() recalculate the KernelInfo meta-data of this kernel only,
2433% using the kernel values. This should only ne used if it is not possible to
2434% calculate that meta-data in some easier way.
2435%
2436% It is important that the meta-data is correct before ScaleKernelInfo() is
2437% used to perform kernel normalization.
2438%
2439% The format of the CalcKernelMetaData method is:
2440%
2441% void CalcKernelMetaData(KernelInfo *kernel, const double scale )
2442%
2443% A description of each parameter follows:
2444%
2445% o kernel: the Morphology/Convolution kernel to modify
2446%
2447% WARNING: Minimum and Maximum values are assumed to include zero, even if
2448% zero is not part of the kernel (as in Gaussian Derived kernels). This
2449% however is not true for flat-shaped morphological kernels.
2450%
2451% WARNING: Only the specific kernel pointed to is modified, not a list of
2452% multiple kernels.
2453%
2454% This is an internal function and not expected to be useful outside this
2455% module. This could change however.
2456*/
2457static void CalcKernelMetaData(KernelInfo *kernel)
2458{
2459 size_t
2460 i;
2461
2462 kernel->minimum = kernel->maximum = 0.0;
2463 kernel->negative_range = kernel->positive_range = 0.0;
2464 for (i=0; i < (kernel->width*kernel->height); i++)
2465 {
2466 if ( fabs(kernel->values[i]) < MagickEpsilon )
2467 kernel->values[i] = 0.0;
2468 ( kernel->values[i] < 0)
2469 ? ( kernel->negative_range += kernel->values[i] )
2470 : ( kernel->positive_range += kernel->values[i] );
2471 Minimize(kernel->minimum, kernel->values[i]);
2472 Maximize(kernel->maximum, kernel->values[i]);
2473 }
2474
2475 return;
2476}
2477␌
2478/*
2479%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2480% %
2481% %
2482% %
2483% M o r p h o l o g y A p p l y %
2484% %
2485% %
2486% %
2487%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2488%
2489% MorphologyApply() applies a morphological method, multiple times using
2490% a list of multiple kernels. This is the method that should be called by
2491% other 'operators' that internally use morphology operations as part of
2492% their processing.
2493%
2494% It is basically equivalent to as MorphologyImage() (see below) but without
2495% any user controls. This allows internal programs to use this method to
2496% perform a specific task without possible interference by any API user
2497% supplied settings.
2498%
2499% It is MorphologyImage() task to extract any such user controls, and
2500% pass them to this function for processing.
2501%
2502% More specifically all given kernels should already be scaled, normalised,
2503% and blended appropriately before being parred to this routine. The
2504% appropriate bias, and compose (typically 'UndefinedComposeOp') given.
2505%
2506% The format of the MorphologyApply method is:
2507%
2508% Image *MorphologyApply(const Image *image,MorphologyMethod method,
2509% const ssize_t iterations,const KernelInfo *kernel,
2510% const CompositeMethod compose,const double bias,
2511% ExceptionInfo *exception)
2512%
2513% A description of each parameter follows:
2514%
2515% o image: the source image
2516%
2517% o method: the morphology method to be applied.
2518%
2519% o iterations: apply the operation this many times (or no change).
2520% A value of -1 means loop until no change found.
2521% How this is applied may depend on the morphology method.
2522% Typically this is a value of 1.
2523%
2524% o channel: the channel type.
2525%
2526% o kernel: An array of double representing the morphology kernel.
2527%
2528% o compose: How to handle or merge multi-kernel results.
2529% If 'UndefinedCompositeOp' use default for the Morphology method.
2530% If 'NoCompositeOp' force image to be re-iterated by each kernel.
2531% Otherwise merge the results using the compose method given.
2532%
2533% o bias: Convolution Output Bias.
2534%
2535% o exception: return any errors or warnings in this structure.
2536%
2537*/
2538static ssize_t MorphologyPrimitive(const Image *image,Image *morphology_image,
2539 const MorphologyMethod method,const KernelInfo *kernel,const double bias,
2540 ExceptionInfo *exception)
2541{
2542#define MorphologyTag "Morphology/Image"
2543
2544 CacheView
2545 *image_view,
2546 *morphology_view;
2547
2548 MagickBooleanType
2549 status;
2550
2551 MagickOffsetType
2552 progress;
2553
2555 offset;
2556
2557 ssize_t
2558 j,
2559 y;
2560
2561 size_t
2562 changed,
2563 *changes,
2564 width;
2565
2566 /*
2567 Some methods (including convolve) needs to use a reflected kernel.
2568 Adjust 'origin' offsets to loop though kernel as a reflection.
2569 */
2570 assert(image != (Image *) NULL);
2571 assert(image->signature == MagickCoreSignature);
2572 assert(morphology_image != (Image *) NULL);
2573 assert(morphology_image->signature == MagickCoreSignature);
2574 assert(kernel != (KernelInfo *) NULL);
2575 assert(kernel->signature == MagickCoreSignature);
2576 assert(exception != (ExceptionInfo *) NULL);
2577 assert(exception->signature == MagickCoreSignature);
2578 status=MagickTrue;
2579 progress=0;
2580 image_view=AcquireVirtualCacheView(image,exception);
2581 morphology_view=AcquireAuthenticCacheView(morphology_image,exception);
2582 width=image->columns+kernel->width-1;
2583 offset.x=0;
2584 offset.y=0;
2585 switch (method)
2586 {
2587 case ConvolveMorphology:
2588 case DilateMorphology:
2589 case DilateIntensityMorphology:
2590 case IterativeDistanceMorphology:
2591 {
2592 /*
2593 Kernel needs to use a reflection about origin.
2594 */
2595 offset.x=(ssize_t) kernel->width-kernel->x-1;
2596 offset.y=(ssize_t) kernel->height-kernel->y-1;
2597 break;
2598 }
2599 case ErodeMorphology:
2600 case ErodeIntensityMorphology:
2601 case HitAndMissMorphology:
2602 case ThinningMorphology:
2603 case ThickenMorphology:
2604 {
2605 /*
2606 Use kernel as is, not reflection required.
2607 */
2608 offset.x=kernel->x;
2609 offset.y=kernel->y;
2610 break;
2611 }
2612 default:
2613 {
2614 ThrowMagickException(exception,GetMagickModule(),OptionWarning,
2615 "InvalidOption","`%s'","not a primitive morphology method");
2616 break;
2617 }
2618 }
2619 changed=0;
2620 changes=(size_t *) AcquireQuantumMemory(GetOpenMPMaximumThreads(),
2621 sizeof(*changes));
2622 if (changes == (size_t *) NULL)
2623 ThrowFatalException(ResourceLimitFatalError,"MemoryAllocationFailed");
2624 for (j=0; j < (ssize_t) GetOpenMPMaximumThreads(); j++)
2625 changes[j]=0;
2626 if ((method == ConvolveMorphology) && (kernel->width == 1))
2627 {
2628 ssize_t
2629 x;
2630
2631 /*
2632 Special handling (for speed) of vertical (blur) kernels. This performs
2633 its handling in columns rather than in rows. This is only done
2634 for convolve as it is the only method that generates very large 1-D
2635 vertical kernels (such as a 'BlurKernel')
2636 */
2637#if defined(MAGICKCORE_OPENMP_SUPPORT)
2638 #pragma omp parallel for schedule(static) shared(progress,status) \
2639 magick_number_threads(image,morphology_image,image->columns,1)
2640#endif
2641 for (x=0; x < (ssize_t) image->columns; x++)
2642 {
2643 const int
2644 id = GetOpenMPThreadId();
2645
2646 const Quantum
2647 *magick_restrict p;
2648
2649 Quantum
2650 *magick_restrict q;
2651
2652 ssize_t
2653 center,
2654 r;
2655
2656 if (status == MagickFalse)
2657 continue;
2658 p=GetCacheViewVirtualPixels(image_view,x,-offset.y,1,image->rows+
2659 kernel->height-1,exception);
2660 q=GetCacheViewAuthenticPixels(morphology_view,x,0,1,
2661 morphology_image->rows,exception);
2662 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
2663 {
2664 status=MagickFalse;
2665 continue;
2666 }
2667 center=(ssize_t) GetPixelChannels(image)*offset.y;
2668 for (r=0; r < (ssize_t) image->rows; r++)
2669 {
2670 ssize_t
2671 i;
2672
2673 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
2674 {
2675 double
2676 alpha,
2677 gamma,
2678 pixel;
2679
2680 PixelChannel
2681 channel;
2682
2683 PixelTrait
2684 morphology_traits,
2685 traits;
2686
2687 const MagickRealType
2688 *magick_restrict k;
2689
2690 const Quantum
2691 *magick_restrict pixels;
2692
2693 ssize_t
2694 v;
2695
2696 size_t
2697 count;
2698
2699 channel=GetPixelChannelChannel(image,i);
2700 traits=GetPixelChannelTraits(image,channel);
2701 morphology_traits=GetPixelChannelTraits(morphology_image,channel);
2702 if ((traits == UndefinedPixelTrait) ||
2703 (morphology_traits == UndefinedPixelTrait))
2704 continue;
2705 if ((traits & CopyPixelTrait) != 0)
2706 {
2707 SetPixelChannel(morphology_image,channel,p[center+i],q);
2708 continue;
2709 }
2710 k=(&kernel->values[kernel->height-1]);
2711 pixels=p;
2712 pixel=bias;
2713 gamma=1.0;
2714 count=0;
2715 if (((image->alpha_trait & BlendPixelTrait) == 0) ||
2716 ((morphology_traits & BlendPixelTrait) == 0))
2717 for (v=0; v < (ssize_t) kernel->height; v++)
2718 {
2719 if (!IsNaN(*k))
2720 {
2721 pixel+=(*k)*(double) pixels[i];
2722 count++;
2723 }
2724 k--;
2725 pixels+=(ptrdiff_t) GetPixelChannels(image);
2726 }
2727 else
2728 {
2729 gamma=0.0;
2730 for (v=0; v < (ssize_t) kernel->height; v++)
2731 {
2732 if (!IsNaN(*k))
2733 {
2734 alpha=(double) (QuantumScale*(double)
2735 GetPixelAlpha(image,pixels));
2736 pixel+=alpha*(*k)*(double) pixels[i];
2737 gamma+=alpha*(*k);
2738 count++;
2739 }
2740 k--;
2741 pixels+=(ptrdiff_t) GetPixelChannels(image);
2742 }
2743 }
2744 if (fabs(pixel-(double) p[center+i]) >= MagickEpsilon)
2745 changes[id]++;
2746 gamma=PerceptibleReciprocal(gamma);
2747 if (count != 0)
2748 gamma*=(double) kernel->height/count;
2749 SetPixelChannel(morphology_image,channel,ClampToQuantum(gamma*
2750 pixel),q);
2751 }
2752 p+=(ptrdiff_t) GetPixelChannels(image);
2753 q+=(ptrdiff_t) GetPixelChannels(morphology_image);
2754 }
2755 if (SyncCacheViewAuthenticPixels(morphology_view,exception) == MagickFalse)
2756 status=MagickFalse;
2757 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2758 {
2759 MagickBooleanType
2760 proceed;
2761
2762#if defined(MAGICKCORE_OPENMP_SUPPORT)
2763 #pragma omp atomic
2764#endif
2765 progress++;
2766 proceed=SetImageProgress(image,MorphologyTag,progress,
2767 image->columns);
2768 if (proceed == MagickFalse)
2769 status=MagickFalse;
2770 }
2771 }
2772 morphology_image->type=image->type;
2773 morphology_view=DestroyCacheView(morphology_view);
2774 image_view=DestroyCacheView(image_view);
2775 for (j=0; j < (ssize_t) GetOpenMPMaximumThreads(); j++)
2776 changed+=changes[j];
2777 changes=(size_t *) RelinquishMagickMemory(changes);
2778 return(status ? (ssize_t) (changed/GetImageChannels(image)) : 0);
2779 }
2780 /*
2781 Normal handling of horizontal or rectangular kernels (row by row).
2782 */
2783#if defined(MAGICKCORE_OPENMP_SUPPORT)
2784 #pragma omp parallel for schedule(static) shared(progress,status) \
2785 magick_number_threads(image,morphology_image,image->rows,1)
2786#endif
2787 for (y=0; y < (ssize_t) image->rows; y++)
2788 {
2789 const int
2790 id = GetOpenMPThreadId();
2791
2792 const Quantum
2793 *magick_restrict p;
2794
2795 Quantum
2796 *magick_restrict q;
2797
2798 ssize_t
2799 x;
2800
2801 ssize_t
2802 center;
2803
2804 if (status == MagickFalse)
2805 continue;
2806 p=GetCacheViewVirtualPixels(image_view,-offset.x,y-offset.y,width,
2807 kernel->height,exception);
2808 q=GetCacheViewAuthenticPixels(morphology_view,0,y,morphology_image->columns,
2809 1,exception);
2810 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
2811 {
2812 status=MagickFalse;
2813 continue;
2814 }
2815 center=(ssize_t) ((ssize_t) GetPixelChannels(image)*(ssize_t) width*
2816 offset.y+(ssize_t) GetPixelChannels(image)*offset.x);
2817 for (x=0; x < (ssize_t) image->columns; x++)
2818 {
2819 ssize_t
2820 i;
2821
2822 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
2823 {
2824 double
2825 alpha,
2826 gamma,
2827 intensity,
2828 maximum,
2829 minimum,
2830 pixel;
2831
2832 PixelChannel
2833 channel;
2834
2835 PixelTrait
2836 morphology_traits,
2837 traits;
2838
2839 const MagickRealType
2840 *magick_restrict k;
2841
2842 const Quantum
2843 *magick_restrict pixels,
2844 *magick_restrict quantum_pixels;
2845
2846 ssize_t
2847 u;
2848
2849 ssize_t
2850 v;
2851
2852 channel=GetPixelChannelChannel(image,i);
2853 traits=GetPixelChannelTraits(image,channel);
2854 morphology_traits=GetPixelChannelTraits(morphology_image,channel);
2855 if ((traits == UndefinedPixelTrait) ||
2856 (morphology_traits == UndefinedPixelTrait))
2857 continue;
2858 if ((traits & CopyPixelTrait) != 0)
2859 {
2860 SetPixelChannel(morphology_image,channel,p[center+i],q);
2861 continue;
2862 }
2863 pixels=p;
2864 quantum_pixels=(const Quantum *) NULL;
2865 maximum=0.0;
2866 minimum=(double) QuantumRange;
2867 switch (method)
2868 {
2869 case ConvolveMorphology:
2870 {
2871 pixel=bias;
2872 break;
2873 }
2874 case DilateMorphology:
2875 case ErodeIntensityMorphology:
2876 {
2877 pixel=0.0;
2878 break;
2879 }
2880 default:
2881 {
2882 pixel=(double) p[center+i];
2883 break;
2884 }
2885 }
2886 gamma=1.0;
2887 switch (method)
2888 {
2889 case ConvolveMorphology:
2890 {
2891 /*
2892 Weighted Average of pixels using reflected kernel
2893
2894 For correct working of this operation for asymmetrical kernels,
2895 the kernel needs to be applied in its reflected form. That is
2896 its values needs to be reversed.
2897
2898 Correlation is actually the same as this but without reflecting
2899 the kernel, and thus 'lower-level' that Convolution. However as
2900 Convolution is the more common method used, and it does not
2901 really cost us much in terms of processing to use a reflected
2902 kernel, so it is Convolution that is implemented.
2903
2904 Correlation will have its kernel reflected before calling this
2905 function to do a Convolve.
2906
2907 For more details of Correlation vs Convolution see
2908 http://www.cs.umd.edu/~djacobs/CMSC426/Convolution.pdf
2909 */
2910 k=(&kernel->values[kernel->width*kernel->height-1]);
2911 if (((image->alpha_trait & BlendPixelTrait) == 0) ||
2912 ((morphology_traits & BlendPixelTrait) == 0))
2913 {
2914 /*
2915 No alpha blending.
2916 */
2917 for (v=0; v < (ssize_t) kernel->height; v++)
2918 {
2919 for (u=0; u < (ssize_t) kernel->width; u++)
2920 {
2921 if (!IsNaN(*k))
2922 pixel+=(*k)*(double) pixels[i];
2923 k--;
2924 pixels+=(ptrdiff_t) GetPixelChannels(image);
2925 }
2926 pixels+=(image->columns-1)*GetPixelChannels(image);
2927 }
2928 break;
2929 }
2930 /*
2931 Alpha blending.
2932 */
2933 gamma=0.0;
2934 for (v=0; v < (ssize_t) kernel->height; v++)
2935 {
2936 for (u=0; u < (ssize_t) kernel->width; u++)
2937 {
2938 if (!IsNaN(*k))
2939 {
2940 alpha=(double) (QuantumScale*(double)
2941 GetPixelAlpha(image,pixels));
2942 pixel+=alpha*(*k)*(double) pixels[i];
2943 gamma+=alpha*(*k);
2944 }
2945 k--;
2946 pixels+=(ptrdiff_t) GetPixelChannels(image);
2947 }
2948 pixels+=(image->columns-1)*GetPixelChannels(image);
2949 }
2950 break;
2951 }
2952 case ErodeMorphology:
2953 {
2954 /*
2955 Minimum value within kernel neighbourhood.
2956
2957 The kernel is not reflected for this operation. In normal
2958 Greyscale Morphology, the kernel value should be added
2959 to the real value, this is currently not done, due to the
2960 nature of the boolean kernels being used.
2961 */
2962 k=kernel->values;
2963 for (v=0; v < (ssize_t) kernel->height; v++)
2964 {
2965 for (u=0; u < (ssize_t) kernel->width; u++)
2966 {
2967 if (!IsNaN(*k) && (*k >= 0.5))
2968 {
2969 if ((double) pixels[i] < pixel)
2970 pixel=(double) pixels[i];
2971 }
2972 k++;
2973 pixels+=(ptrdiff_t) GetPixelChannels(image);
2974 }
2975 pixels+=(image->columns-1)*GetPixelChannels(image);
2976 }
2977 break;
2978 }
2979 case DilateMorphology:
2980 {
2981 /*
2982 Maximum value within kernel neighbourhood.
2983
2984 For correct working of this operation for asymmetrical kernels,
2985 the kernel needs to be applied in its reflected form. That is
2986 its values needs to be reversed.
2987
2988 In normal Greyscale Morphology, the kernel value should be
2989 added to the real value, this is currently not done, due to the
2990 nature of the boolean kernels being used.
2991 */
2992 k=(&kernel->values[kernel->width*kernel->height-1]);
2993 for (v=0; v < (ssize_t) kernel->height; v++)
2994 {
2995 for (u=0; u < (ssize_t) kernel->width; u++)
2996 {
2997 if (!IsNaN(*k) && (*k > 0.5))
2998 {
2999 if ((double) pixels[i] > pixel)
3000 pixel=(double) pixels[i];
3001 }
3002 k--;
3003 pixels+=(ptrdiff_t) GetPixelChannels(image);
3004 }
3005 pixels+=(image->columns-1)*GetPixelChannels(image);
3006 }
3007 break;
3008 }
3009 case HitAndMissMorphology:
3010 case ThinningMorphology:
3011 case ThickenMorphology:
3012 {
3013 /*
3014 Minimum of foreground pixel minus maximum of background pixels.
3015
3016 The kernel is not reflected for this operation, and consists
3017 of both foreground and background pixel neighbourhoods, 0.0 for
3018 background, and 1.0 for foreground with either Nan or 0.5 values
3019 for don't care.
3020
3021 This never produces a meaningless negative result. Such results
3022 cause Thinning/Thicken to not work correctly when used against a
3023 greyscale image.
3024 */
3025 k=kernel->values;
3026 for (v=0; v < (ssize_t) kernel->height; v++)
3027 {
3028 for (u=0; u < (ssize_t) kernel->width; u++)
3029 {
3030 if (!IsNaN(*k))
3031 {
3032 if (*k > 0.7)
3033 {
3034 if ((double) pixels[i] < minimum)
3035 minimum=(double) pixels[i];
3036 }
3037 else
3038 if (*k < 0.3)
3039 {
3040 if ((double) pixels[i] > maximum)
3041 maximum=(double) pixels[i];
3042 }
3043 }
3044 k++;
3045 pixels+=(ptrdiff_t) GetPixelChannels(image);
3046 }
3047 pixels+=(image->columns-1)*GetPixelChannels(image);
3048 }
3049 minimum-=maximum;
3050 if (minimum < 0.0)
3051 minimum=0.0;
3052 pixel=minimum;
3053 if (method == ThinningMorphology)
3054 pixel=(double) p[center+i]-minimum;
3055 else
3056 if (method == ThickenMorphology)
3057 pixel=(double) p[center+i]+minimum;
3058 break;
3059 }
3060 case ErodeIntensityMorphology:
3061 {
3062 /*
3063 Select pixel with minimum intensity within kernel neighbourhood.
3064
3065 The kernel is not reflected for this operation.
3066 */
3067 k=kernel->values;
3068 for (v=0; v < (ssize_t) kernel->height; v++)
3069 {
3070 for (u=0; u < (ssize_t) kernel->width; u++)
3071 {
3072 if (!IsNaN(*k) && (*k >= 0.5))
3073 {
3074 intensity=(double) GetPixelIntensity(image,pixels);
3075 if (intensity < minimum)
3076 {
3077 quantum_pixels=pixels;
3078 pixel=(double) pixels[i];
3079 minimum=intensity;
3080 }
3081 }
3082 k++;
3083 pixels+=(ptrdiff_t) GetPixelChannels(image);
3084 }
3085 pixels+=(image->columns-1)*GetPixelChannels(image);
3086 }
3087 break;
3088 }
3089 case DilateIntensityMorphology:
3090 {
3091 /*
3092 Select pixel with maximum intensity within kernel neighbourhood.
3093
3094 The kernel is not reflected for this operation.
3095 */
3096 k=(&kernel->values[kernel->width*kernel->height-1]);
3097 for (v=0; v < (ssize_t) kernel->height; v++)
3098 {
3099 for (u=0; u < (ssize_t) kernel->width; u++)
3100 {
3101 if (!IsNaN(*k) && (*k >= 0.5))
3102 {
3103 intensity=(double) GetPixelIntensity(image,pixels);
3104 if (intensity > maximum)
3105 {
3106 pixel=(double) pixels[i];
3107 quantum_pixels=pixels;
3108 maximum=intensity;
3109 }
3110 }
3111 k--;
3112 pixels+=(ptrdiff_t) GetPixelChannels(image);
3113 }
3114 pixels+=(image->columns-1)*GetPixelChannels(image);
3115 }
3116 break;
3117 }
3118 case IterativeDistanceMorphology:
3119 {
3120 /*
3121 Compute th iterative distance from black edge of a white image
3122 shape. Essentially white values are decreased to the smallest
3123 'distance from edge' it can find.
3124
3125 It works by adding kernel values to the neighbourhood, and
3126 select the minimum value found. The kernel is rotated before
3127 use, so kernel distances match resulting distances, when a user
3128 provided asymmetric kernel is applied.
3129
3130 This code is nearly identical to True GrayScale Morphology but
3131 not quite.
3132
3133 GreyDilate Kernel values added, maximum value found Kernel is
3134 rotated before use.
3135
3136 GrayErode: Kernel values subtracted and minimum value found No
3137 kernel rotation used.
3138
3139 Note the Iterative Distance method is essentially a
3140 GrayErode, but with negative kernel values, and kernel rotation
3141 applied.
3142 */
3143 k=(&kernel->values[kernel->width*kernel->height-1]);
3144 for (v=0; v < (ssize_t) kernel->height; v++)
3145 {
3146 for (u=0; u < (ssize_t) kernel->width; u++)
3147 {
3148 if (!IsNaN(*k))
3149 {
3150 if (((double) pixels[i]+(*k)) < pixel)
3151 pixel=(double) pixels[i]+(*k);
3152 }
3153 k--;
3154 pixels+=(ptrdiff_t) GetPixelChannels(image);
3155 }
3156 pixels+=(image->columns-1)*GetPixelChannels(image);
3157 }
3158 break;
3159 }
3160 case UndefinedMorphology:
3161 default:
3162 break;
3163 }
3164 if (quantum_pixels != (const Quantum *) NULL)
3165 {
3166 SetPixelChannel(morphology_image,channel,quantum_pixels[i],q);
3167 continue;
3168 }
3169 gamma=PerceptibleReciprocal(gamma);
3170 SetPixelChannel(morphology_image,channel,ClampToQuantum(gamma*pixel),q);
3171 if (fabs(pixel-(double) p[center+i]) >= MagickEpsilon)
3172 changes[id]++;
3173 }
3174 p+=(ptrdiff_t) GetPixelChannels(image);
3175 q+=(ptrdiff_t) GetPixelChannels(morphology_image);
3176 }
3177 if (SyncCacheViewAuthenticPixels(morphology_view,exception) == MagickFalse)
3178 status=MagickFalse;
3179 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3180 {
3181 MagickBooleanType
3182 proceed;
3183
3184#if defined(MAGICKCORE_OPENMP_SUPPORT)
3185 #pragma omp atomic
3186#endif
3187 progress++;
3188 proceed=SetImageProgress(image,MorphologyTag,progress,image->rows);
3189 if (proceed == MagickFalse)
3190 status=MagickFalse;
3191 }
3192 }
3193 morphology_view=DestroyCacheView(morphology_view);
3194 image_view=DestroyCacheView(image_view);
3195 for (j=0; j < (ssize_t) GetOpenMPMaximumThreads(); j++)
3196 changed+=changes[j];
3197 changes=(size_t *) RelinquishMagickMemory(changes);
3198 return(status ? (ssize_t) (changed/GetImageChannels(image)) : -1);
3199}
3200
3201/*
3202 This is almost identical to the MorphologyPrimitive() function above, but
3203 applies the primitive directly to the actual image using two passes, once in
3204 each direction, with the results of the previous (and current) row being
3205 re-used.
3206
3207 That is after each row is 'Sync'ed' into the image, the next row makes use of
3208 those values as part of the calculation of the next row. It repeats, but
3209 going in the opposite (bottom-up) direction.
3210
3211 Because of this 're-use of results' this function can not make use of multi-
3212 threaded, parallel processing.
3213*/
3214static ssize_t MorphologyPrimitiveDirect(Image *image,
3215 const MorphologyMethod method,const KernelInfo *kernel,
3216 ExceptionInfo *exception)
3217{
3218 CacheView
3219 *morphology_view,
3220 *image_view;
3221
3222 MagickBooleanType
3223 status;
3224
3225 MagickOffsetType
3226 progress;
3227
3229 offset;
3230
3231 size_t
3232 width,
3233 changed;
3234
3235 ssize_t
3236 y;
3237
3238 assert(image != (Image *) NULL);
3239 assert(image->signature == MagickCoreSignature);
3240 assert(kernel != (KernelInfo *) NULL);
3241 assert(kernel->signature == MagickCoreSignature);
3242 assert(exception != (ExceptionInfo *) NULL);
3243 assert(exception->signature == MagickCoreSignature);
3244 status=MagickTrue;
3245 changed=0;
3246 progress=0;
3247 switch(method)
3248 {
3249 case DistanceMorphology:
3250 case VoronoiMorphology:
3251 {
3252 /*
3253 Kernel reflected about origin.
3254 */
3255 offset.x=(ssize_t) kernel->width-kernel->x-1;
3256 offset.y=(ssize_t) kernel->height-kernel->y-1;
3257 break;
3258 }
3259 default:
3260 {
3261 offset.x=kernel->x;
3262 offset.y=kernel->y;
3263 break;
3264 }
3265 }
3266 /*
3267 Two views into same image, do not thread.
3268 */
3269 image_view=AcquireVirtualCacheView(image,exception);
3270 morphology_view=AcquireAuthenticCacheView(image,exception);
3271 width=image->columns+kernel->width-1;
3272 for (y=0; y < (ssize_t) image->rows; y++)
3273 {
3274 const Quantum
3275 *magick_restrict p;
3276
3277 Quantum
3278 *magick_restrict q;
3279
3280 ssize_t
3281 x;
3282
3283 /*
3284 Read virtual pixels, and authentic pixels, from the same image! We read
3285 using virtual to get virtual pixel handling, but write back into the same
3286 image.
3287
3288 Only top half of kernel is processed as we do a single pass downward
3289 through the image iterating the distance function as we go.
3290 */
3291 if (status == MagickFalse)
3292 continue;
3293 p=GetCacheViewVirtualPixels(image_view,-offset.x,y-offset.y,width,(size_t)
3294 offset.y+1,exception);
3295 q=GetCacheViewAuthenticPixels(morphology_view,0,y,image->columns,1,
3296 exception);
3297 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
3298 {
3299 status=MagickFalse;
3300 continue;
3301 }
3302 for (x=0; x < (ssize_t) image->columns; x++)
3303 {
3304 ssize_t
3305 i;
3306
3307 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
3308 {
3309 double
3310 pixel;
3311
3312 PixelChannel
3313 channel;
3314
3315 PixelTrait
3316 traits;
3317
3318 const MagickRealType
3319 *magick_restrict k;
3320
3321 const Quantum
3322 *magick_restrict pixels;
3323
3324 ssize_t
3325 u;
3326
3327 ssize_t
3328 v;
3329
3330 channel=GetPixelChannelChannel(image,i);
3331 traits=GetPixelChannelTraits(image,channel);
3332 if (traits == UndefinedPixelTrait)
3333 continue;
3334 if ((traits & CopyPixelTrait) != 0)
3335 continue;
3336 pixels=p;
3337 pixel=(double) QuantumRange;
3338 switch (method)
3339 {
3340 case DistanceMorphology:
3341 {
3342 k=(&kernel->values[kernel->width*kernel->height-1]);
3343 for (v=0; v <= offset.y; v++)
3344 {
3345 for (u=0; u < (ssize_t) kernel->width; u++)
3346 {
3347 if (!IsNaN(*k))
3348 {
3349 if (((double) pixels[i]+(*k)) < pixel)
3350 pixel=(double) pixels[i]+(*k);
3351 }
3352 k--;
3353 pixels+=(ptrdiff_t) GetPixelChannels(image);
3354 }
3355 pixels+=(image->columns-1)*GetPixelChannels(image);
3356 }
3357 k=(&kernel->values[(ssize_t) kernel->width*(kernel->y+1)-1]);
3358 pixels=q-offset.x*(ssize_t) GetPixelChannels(image);
3359 for (u=0; u < offset.x; u++)
3360 {
3361 if (!IsNaN(*k) && ((x+u-offset.x) >= 0))
3362 {
3363 if (((double) pixels[i]+(*k)) < pixel)
3364 pixel=(double) pixels[i]+(*k);
3365 }
3366 k--;
3367 pixels+=(ptrdiff_t) GetPixelChannels(image);
3368 }
3369 break;
3370 }
3371 case VoronoiMorphology:
3372 {
3373 k=(&kernel->values[kernel->width*kernel->height-1]);
3374 for (v=0; v < offset.y; v++)
3375 {
3376 for (u=0; u < (ssize_t) kernel->width; u++)
3377 {
3378 if (!IsNaN(*k))
3379 {
3380 if (((double) pixels[i]+(*k)) < pixel)
3381 pixel=(double) pixels[i]+(*k);
3382 }
3383 k--;
3384 pixels+=(ptrdiff_t) GetPixelChannels(image);
3385 }
3386 pixels+=(image->columns-1)*GetPixelChannels(image);
3387 }
3388 k=(&kernel->values[(ssize_t) kernel->width*(kernel->y+1)-1]);
3389 pixels=q-offset.x*(ssize_t) GetPixelChannels(image);
3390 for (u=0; u < offset.x; u++)
3391 {
3392 if (!IsNaN(*k) && ((x+u-offset.x) >= 0))
3393 {
3394 if (((double) pixels[i]+(*k)) < pixel)
3395 pixel=(double) pixels[i]+(*k);
3396 }
3397 k--;
3398 pixels+=(ptrdiff_t) GetPixelChannels(image);
3399 }
3400 break;
3401 }
3402 default:
3403 break;
3404 }
3405 if (fabs(pixel-(double) q[i]) > MagickEpsilon)
3406 changed++;
3407 q[i]=ClampToQuantum(pixel);
3408 }
3409 p+=(ptrdiff_t) GetPixelChannels(image);
3410 q+=(ptrdiff_t) GetPixelChannels(image);
3411 }
3412 if (SyncCacheViewAuthenticPixels(morphology_view,exception) == MagickFalse)
3413 status=MagickFalse;
3414 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3415 {
3416 MagickBooleanType
3417 proceed;
3418
3419#if defined(MAGICKCORE_OPENMP_SUPPORT)
3420 #pragma omp atomic
3421#endif
3422 progress++;
3423 proceed=SetImageProgress(image,MorphologyTag,progress,2*image->rows);
3424 if (proceed == MagickFalse)
3425 status=MagickFalse;
3426 }
3427 }
3428 morphology_view=DestroyCacheView(morphology_view);
3429 image_view=DestroyCacheView(image_view);
3430 /*
3431 Do the reverse pass through the image.
3432 */
3433 image_view=AcquireVirtualCacheView(image,exception);
3434 morphology_view=AcquireAuthenticCacheView(image,exception);
3435 for (y=(ssize_t) image->rows-1; y >= 0; y--)
3436 {
3437 const Quantum
3438 *magick_restrict p;
3439
3440 Quantum
3441 *magick_restrict q;
3442
3443 ssize_t
3444 x;
3445
3446 /*
3447 Read virtual pixels, and authentic pixels, from the same image. We
3448 read using virtual to get virtual pixel handling, but write back
3449 into the same image.
3450
3451 Only the bottom half of the kernel is processed as we up the image.
3452 */
3453 if (status == MagickFalse)
3454 continue;
3455 p=GetCacheViewVirtualPixels(image_view,-offset.x,y,width,(size_t)
3456 kernel->y+1,exception);
3457 q=GetCacheViewAuthenticPixels(morphology_view,0,y,image->columns,1,
3458 exception);
3459 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
3460 {
3461 status=MagickFalse;
3462 continue;
3463 }
3464 p+=(ptrdiff_t) (image->columns-1)*GetPixelChannels(image);
3465 q+=(ptrdiff_t) (image->columns-1)*GetPixelChannels(image);
3466 for (x=(ssize_t) image->columns-1; x >= 0; x--)
3467 {
3468 ssize_t
3469 i;
3470
3471 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
3472 {
3473 double
3474 pixel;
3475
3476 PixelChannel
3477 channel;
3478
3479 PixelTrait
3480 traits;
3481
3482 const MagickRealType
3483 *magick_restrict k;
3484
3485 const Quantum
3486 *magick_restrict pixels;
3487
3488 ssize_t
3489 u;
3490
3491 ssize_t
3492 v;
3493
3494 channel=GetPixelChannelChannel(image,i);
3495 traits=GetPixelChannelTraits(image,channel);
3496 if (traits == UndefinedPixelTrait)
3497 continue;
3498 if ((traits & CopyPixelTrait) != 0)
3499 continue;
3500 pixels=p;
3501 pixel=(double) QuantumRange;
3502 switch (method)
3503 {
3504 case DistanceMorphology:
3505 {
3506 k=(&kernel->values[(ssize_t) kernel->width*(kernel->y+1)-1]);
3507 for (v=offset.y; v < (ssize_t) kernel->height; v++)
3508 {
3509 for (u=0; u < (ssize_t) kernel->width; u++)
3510 {
3511 if (!IsNaN(*k))
3512 {
3513 if (((double) pixels[i]+(*k)) < pixel)
3514 pixel=(double) pixels[i]+(*k);
3515 }
3516 k--;
3517 pixels+=(ptrdiff_t) GetPixelChannels(image);
3518 }
3519 pixels+=(image->columns-1)*GetPixelChannels(image);
3520 }
3521 k=(&kernel->values[(ssize_t) kernel->width*kernel->y+kernel->x-1]);
3522 pixels=q;
3523 for (u=offset.x+1; u < (ssize_t) kernel->width; u++)
3524 {
3525 pixels+=(ptrdiff_t) GetPixelChannels(image);
3526 if (!IsNaN(*k) && ((x+u-offset.x) < (ssize_t) image->columns))
3527 {
3528 if (((double) pixels[i]+(*k)) < pixel)
3529 pixel=(double) pixels[i]+(*k);
3530 }
3531 k--;
3532 }
3533 break;
3534 }
3535 case VoronoiMorphology:
3536 {
3537 k=(&kernel->values[(ssize_t) kernel->width*(kernel->y+1)-1]);
3538 for (v=offset.y; v < (ssize_t) kernel->height; v++)
3539 {
3540 for (u=0; u < (ssize_t) kernel->width; u++)
3541 {
3542 if (!IsNaN(*k))
3543 {
3544 if (((double) pixels[i]+(*k)) < pixel)
3545 pixel=(double) pixels[i]+(*k);
3546 }
3547 k--;
3548 pixels+=(ptrdiff_t) GetPixelChannels(image);
3549 }
3550 pixels+=(image->columns-1)*GetPixelChannels(image);
3551 }
3552 k=(&kernel->values[(ssize_t) kernel->width*(kernel->y+1)-1]);
3553 pixels=q;
3554 for (u=offset.x+1; u < (ssize_t) kernel->width; u++)
3555 {
3556 pixels+=(ptrdiff_t) GetPixelChannels(image);
3557 if (!IsNaN(*k) && ((x+u-offset.x) < (ssize_t) image->columns))
3558 {
3559 if (((double) pixels[i]+(*k)) < pixel)
3560 pixel=(double) pixels[i]+(*k);
3561 }
3562 k--;
3563 }
3564 break;
3565 }
3566 default:
3567 break;
3568 }
3569 if (fabs(pixel-(double) q[i]) > MagickEpsilon)
3570 changed++;
3571 q[i]=ClampToQuantum(pixel);
3572 }
3573 p-=(ptrdiff_t)GetPixelChannels(image);
3574 q-=GetPixelChannels(image);
3575 }
3576 if (SyncCacheViewAuthenticPixels(morphology_view,exception) == MagickFalse)
3577 status=MagickFalse;
3578 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3579 {
3580 MagickBooleanType
3581 proceed;
3582
3583#if defined(MAGICKCORE_OPENMP_SUPPORT)
3584 #pragma omp atomic
3585#endif
3586 progress++;
3587 proceed=SetImageProgress(image,MorphologyTag,progress,2*image->rows);
3588 if (proceed == MagickFalse)
3589 status=MagickFalse;
3590 }
3591 }
3592 morphology_view=DestroyCacheView(morphology_view);
3593 image_view=DestroyCacheView(image_view);
3594 return(status ? (ssize_t) (changed/GetImageChannels(image)) : -1);
3595}
3596
3597/*
3598 Apply a Morphology by calling one of the above low level primitive
3599 application functions. This function handles any iteration loops,
3600 composition or re-iteration of results, and compound morphology methods that
3601 is based on multiple low-level (staged) morphology methods.
3602
3603 Basically this provides the complex glue between the requested morphology
3604 method and raw low-level implementation (above).
3605*/
3606MagickPrivate Image *MorphologyApply(const Image *image,
3607 const MorphologyMethod method, const ssize_t iterations,
3608 const KernelInfo *kernel, const CompositeOperator compose,const double bias,
3609 ExceptionInfo *exception)
3610{
3611 CompositeOperator
3612 curr_compose;
3613
3614 Image
3615 *curr_image, /* Image we are working with or iterating */
3616 *work_image, /* secondary image for primitive iteration */
3617 *save_image, /* saved image - for 'edge' method only */
3618 *rslt_image; /* resultant image - after multi-kernel handling */
3619
3621 *reflected_kernel, /* A reflected copy of the kernel (if needed) */
3622 *norm_kernel, /* the current normal un-reflected kernel */
3623 *rflt_kernel, /* the current reflected kernel (if needed) */
3624 *this_kernel; /* the kernel being applied */
3625
3626 MorphologyMethod
3627 primitive; /* the current morphology primitive being applied */
3628
3629 CompositeOperator
3630 rslt_compose; /* multi-kernel compose method for results to use */
3631
3632 MagickBooleanType
3633 special, /* do we use a direct modify function? */
3634 verbose; /* verbose output of results */
3635
3636 size_t
3637 method_loop, /* Loop 1: number of compound method iterations (norm 1) */
3638 method_limit, /* maximum number of compound method iterations */
3639 kernel_number, /* Loop 2: the kernel number being applied */
3640 stage_loop, /* Loop 3: primitive loop for compound morphology */
3641 stage_limit, /* how many primitives are in this compound */
3642 kernel_loop, /* Loop 4: iterate the kernel over image */
3643 kernel_limit, /* number of times to iterate kernel */
3644 count, /* total count of primitive steps applied */
3645 kernel_changed, /* total count of changed using iterated kernel */
3646 method_changed; /* total count of changed over method iteration */
3647
3648 ssize_t
3649 changed; /* number pixels changed by last primitive operation */
3650
3651 char
3652 v_info[MagickPathExtent];
3653
3654 assert(image != (Image *) NULL);
3655 assert(image->signature == MagickCoreSignature);
3656 assert(kernel != (KernelInfo *) NULL);
3657 assert(kernel->signature == MagickCoreSignature);
3658 assert(exception != (ExceptionInfo *) NULL);
3659 assert(exception->signature == MagickCoreSignature);
3660
3661 count = 0; /* number of low-level morphology primitives performed */
3662 if ( iterations == 0 )
3663 return((Image *) NULL); /* null operation - nothing to do! */
3664
3665 kernel_limit = (size_t) iterations;
3666 if ( iterations < 0 ) /* negative interactions = infinite (well almost) */
3667 kernel_limit = image->columns>image->rows ? image->columns : image->rows;
3668
3669 verbose = IsStringTrue(GetImageArtifact(image,"debug"));
3670
3671 /* initialise for cleanup */
3672 curr_image = (Image *) image;
3673 curr_compose = image->compose;
3674 (void) curr_compose;
3675 work_image = save_image = rslt_image = (Image *) NULL;
3676 reflected_kernel = (KernelInfo *) NULL;
3677
3678 /* Initialize specific methods
3679 * + which loop should use the given iterations
3680 * + how many primitives make up the compound morphology
3681 * + multi-kernel compose method to use (by default)
3682 */
3683 method_limit = 1; /* just do method once, unless otherwise set */
3684 stage_limit = 1; /* assume method is not a compound */
3685 special = MagickFalse; /* assume it is NOT a direct modify primitive */
3686 rslt_compose = compose; /* and we are composing multi-kernels as given */
3687 switch( method ) {
3688 case SmoothMorphology: /* 4 primitive compound morphology */
3689 stage_limit = 4;
3690 break;
3691 case OpenMorphology: /* 2 primitive compound morphology */
3692 case OpenIntensityMorphology:
3693 case TopHatMorphology:
3694 case CloseMorphology:
3695 case CloseIntensityMorphology:
3696 case BottomHatMorphology:
3697 case EdgeMorphology:
3698 stage_limit = 2;
3699 break;
3700 case HitAndMissMorphology:
3701 rslt_compose = LightenCompositeOp; /* Union of multi-kernel results */
3702 magick_fallthrough;
3703 case ThinningMorphology:
3704 case ThickenMorphology:
3705 method_limit = kernel_limit; /* iterate the whole method */
3706 kernel_limit = 1; /* do not do kernel iteration */
3707 break;
3708 case DistanceMorphology:
3709 case VoronoiMorphology:
3710 special = MagickTrue; /* use special direct primitive */
3711 break;
3712 default:
3713 break;
3714 }
3715
3716 /* Apply special methods with special requirements
3717 ** For example, single run only, or post-processing requirements
3718 */
3719 if ( special != MagickFalse )
3720 {
3721 rslt_image=CloneImage(image,0,0,MagickTrue,exception);
3722 if (rslt_image == (Image *) NULL)
3723 goto error_cleanup;
3724 if (SetImageStorageClass(rslt_image,DirectClass,exception) == MagickFalse)
3725 goto error_cleanup;
3726
3727 changed=MorphologyPrimitiveDirect(rslt_image,method,kernel,exception);
3728
3729 if (verbose != MagickFalse)
3730 (void) (void) FormatLocaleFile(stderr,
3731 "%s:%.20g.%.20g #%.20g => Changed %.20g\n",
3732 CommandOptionToMnemonic(MagickMorphologyOptions, method),
3733 1.0,0.0,1.0, (double) changed);
3734
3735 if ( changed < 0 )
3736 goto error_cleanup;
3737
3738 if ( method == VoronoiMorphology ) {
3739 /* Preserve the alpha channel of input image - but turned it off */
3740 (void) SetImageAlphaChannel(rslt_image, DeactivateAlphaChannel,
3741 exception);
3742 (void) CompositeImage(rslt_image,image,CopyAlphaCompositeOp,
3743 MagickTrue,0,0,exception);
3744 (void) SetImageAlphaChannel(rslt_image, DeactivateAlphaChannel,
3745 exception);
3746 }
3747 goto exit_cleanup;
3748 }
3749
3750 /* Handle user (caller) specified multi-kernel composition method */
3751 if ( compose != UndefinedCompositeOp )
3752 rslt_compose = compose; /* override default composition for method */
3753 if ( rslt_compose == UndefinedCompositeOp )
3754 rslt_compose = NoCompositeOp; /* still not defined! Then re-iterate */
3755
3756 /* Some methods require a reflected kernel to use with primitives.
3757 * Create the reflected kernel for those methods. */
3758 switch ( method ) {
3759 case CorrelateMorphology:
3760 case CloseMorphology:
3761 case CloseIntensityMorphology:
3762 case BottomHatMorphology:
3763 case SmoothMorphology:
3764 reflected_kernel = CloneKernelInfo(kernel);
3765 if (reflected_kernel == (KernelInfo *) NULL)
3766 goto error_cleanup;
3767 RotateKernelInfo(reflected_kernel,180);
3768 break;
3769 default:
3770 break;
3771 }
3772
3773 /* Loops around more primitive morphology methods
3774 ** erose, dilate, open, close, smooth, edge, etc...
3775 */
3776 /* Loop 1: iterate the compound method */
3777 method_loop = 0;
3778 method_changed = 1;
3779 while ( method_loop < method_limit && method_changed > 0 ) {
3780 method_loop++;
3781 method_changed = 0;
3782
3783 /* Loop 2: iterate over each kernel in a multi-kernel list */
3784 norm_kernel = (KernelInfo *) kernel;
3785 this_kernel = (KernelInfo *) kernel;
3786 rflt_kernel = reflected_kernel;
3787
3788 kernel_number = 0;
3789 while ( norm_kernel != NULL ) {
3790
3791 /* Loop 3: Compound Morphology Staging - Select Primitive to apply */
3792 stage_loop = 0; /* the compound morphology stage number */
3793 while ( stage_loop < stage_limit ) {
3794 stage_loop++; /* The stage of the compound morphology */
3795
3796 /* Select primitive morphology for this stage of compound method */
3797 this_kernel = norm_kernel; /* default use unreflected kernel */
3798 primitive = method; /* Assume method is a primitive */
3799 switch( method ) {
3800 case ErodeMorphology: /* just erode */
3801 case EdgeInMorphology: /* erode and image difference */
3802 primitive = ErodeMorphology;
3803 break;
3804 case DilateMorphology: /* just dilate */
3805 case EdgeOutMorphology: /* dilate and image difference */
3806 primitive = DilateMorphology;
3807 break;
3808 case OpenMorphology: /* erode then dilate */
3809 case TopHatMorphology: /* open and image difference */
3810 primitive = ErodeMorphology;
3811 if ( stage_loop == 2 )
3812 primitive = DilateMorphology;
3813 break;
3814 case OpenIntensityMorphology:
3815 primitive = ErodeIntensityMorphology;
3816 if ( stage_loop == 2 )
3817 primitive = DilateIntensityMorphology;
3818 break;
3819 case CloseMorphology: /* dilate, then erode */
3820 case BottomHatMorphology: /* close and image difference */
3821 this_kernel = rflt_kernel; /* use the reflected kernel */
3822 primitive = DilateMorphology;
3823 if ( stage_loop == 2 )
3824 primitive = ErodeMorphology;
3825 break;
3826 case CloseIntensityMorphology:
3827 this_kernel = rflt_kernel; /* use the reflected kernel */
3828 primitive = DilateIntensityMorphology;
3829 if ( stage_loop == 2 )
3830 primitive = ErodeIntensityMorphology;
3831 break;
3832 case SmoothMorphology: /* open, close */
3833 switch ( stage_loop ) {
3834 case 1: /* start an open method, which starts with Erode */
3835 primitive = ErodeMorphology;
3836 break;
3837 case 2: /* now Dilate the Erode */
3838 primitive = DilateMorphology;
3839 break;
3840 case 3: /* Reflect kernel a close */
3841 this_kernel = rflt_kernel; /* use the reflected kernel */
3842 primitive = DilateMorphology;
3843 break;
3844 case 4: /* Finish the Close */
3845 this_kernel = rflt_kernel; /* use the reflected kernel */
3846 primitive = ErodeMorphology;
3847 break;
3848 }
3849 break;
3850 case EdgeMorphology: /* dilate and erode difference */
3851 primitive = DilateMorphology;
3852 if ( stage_loop == 2 ) {
3853 save_image = curr_image; /* save the image difference */
3854 curr_image = (Image *) image;
3855 primitive = ErodeMorphology;
3856 }
3857 break;
3858 case CorrelateMorphology:
3859 /* A Correlation is a Convolution with a reflected kernel.
3860 ** However a Convolution is a weighted sum using a reflected
3861 ** kernel. It may seem strange to convert a Correlation into a
3862 ** Convolution as the Correlation is the simpler method, but
3863 ** Convolution is much more commonly used, and it makes sense to
3864 ** implement it directly so as to avoid the need to duplicate the
3865 ** kernel when it is not required (which is typically the
3866 ** default).
3867 */
3868 this_kernel = rflt_kernel; /* use the reflected kernel */
3869 primitive = ConvolveMorphology;
3870 break;
3871 default:
3872 break;
3873 }
3874 assert( this_kernel != (KernelInfo *) NULL );
3875
3876 /* Extra information for debugging compound operations */
3877 if (verbose != MagickFalse) {
3878 if ( stage_limit > 1 )
3879 (void) FormatLocaleString(v_info,MagickPathExtent,"%s:%.20g.%.20g -> ",
3880 CommandOptionToMnemonic(MagickMorphologyOptions,method),(double)
3881 method_loop,(double) stage_loop);
3882 else if ( primitive != method )
3883 (void) FormatLocaleString(v_info, MagickPathExtent, "%s:%.20g -> ",
3884 CommandOptionToMnemonic(MagickMorphologyOptions, method),(double)
3885 method_loop);
3886 else
3887 v_info[0] = '\0';
3888 }
3889
3890 /* Loop 4: Iterate the kernel with primitive */
3891 kernel_loop = 0;
3892 kernel_changed = 0;
3893 changed = 1;
3894 while ( kernel_loop < kernel_limit && changed > 0 ) {
3895 kernel_loop++; /* the iteration of this kernel */
3896
3897 /* Create a clone as the destination image, if not yet defined */
3898 if ( work_image == (Image *) NULL )
3899 {
3900 work_image=CloneImage(image,0,0,MagickTrue,exception);
3901 if (work_image == (Image *) NULL)
3902 goto error_cleanup;
3903 if (SetImageStorageClass(work_image,DirectClass,exception) == MagickFalse)
3904 goto error_cleanup;
3905 }
3906
3907 /* APPLY THE MORPHOLOGICAL PRIMITIVE (curr -> work) */
3908 count++;
3909 changed = MorphologyPrimitive(curr_image, work_image, primitive,
3910 this_kernel, bias, exception);
3911 if (verbose != MagickFalse) {
3912 if ( kernel_loop > 1 )
3913 (void) FormatLocaleFile(stderr, "\n"); /* add end-of-line from previous */
3914 (void) (void) FormatLocaleFile(stderr,
3915 "%s%s%s:%.20g.%.20g #%.20g => Changed %.20g",
3916 v_info,CommandOptionToMnemonic(MagickMorphologyOptions,
3917 primitive),(this_kernel == rflt_kernel ) ? "*" : "",
3918 (double) (method_loop+kernel_loop-1),(double) kernel_number,
3919 (double) count,(double) changed);
3920 }
3921 if ( changed < 0 )
3922 goto error_cleanup;
3923 kernel_changed = (size_t) ((ssize_t) kernel_changed+changed);
3924 method_changed = (size_t) ((ssize_t) method_changed+changed);
3925
3926 /* prepare next loop */
3927 { Image *tmp = work_image; /* swap images for iteration */
3928 work_image = curr_image;
3929 curr_image = tmp;
3930 }
3931 if ( work_image == image )
3932 work_image = (Image *) NULL; /* replace input 'image' */
3933
3934 } /* End Loop 4: Iterate the kernel with primitive */
3935
3936 if (verbose != MagickFalse && kernel_changed != (size_t)changed)
3937 (void) FormatLocaleFile(stderr, " Total %.20g",(double) kernel_changed);
3938 if (verbose != MagickFalse && stage_loop < stage_limit)
3939 (void) FormatLocaleFile(stderr, "\n"); /* add end-of-line before looping */
3940
3941#if 0
3942 (void) FormatLocaleFile(stderr, "--E-- image=0x%lx\n", (unsigned long)image);
3943 (void) FormatLocaleFile(stderr, " curr =0x%lx\n", (unsigned long)curr_image);
3944 (void) FormatLocaleFile(stderr, " work =0x%lx\n", (unsigned long)work_image);
3945 (void) FormatLocaleFile(stderr, " save =0x%lx\n", (unsigned long)save_image);
3946 (void) FormatLocaleFile(stderr, " union=0x%lx\n", (unsigned long)rslt_image);
3947#endif
3948
3949 } /* End Loop 3: Primitive (staging) Loop for Compound Methods */
3950
3951 /* Final Post-processing for some Compound Methods
3952 **
3953 ** The removal of any 'Sync' channel flag in the Image Composition
3954 ** below ensures the mathematical compose method is applied in a
3955 ** purely mathematical way, and only to the selected channels.
3956 ** Turn off SVG composition 'alpha blending'.
3957 */
3958 switch( method ) {
3959 case EdgeOutMorphology:
3960 case EdgeInMorphology:
3961 case TopHatMorphology:
3962 case BottomHatMorphology:
3963 if (verbose != MagickFalse)
3964 (void) FormatLocaleFile(stderr,
3965 "\n%s: Difference with original image",CommandOptionToMnemonic(
3966 MagickMorphologyOptions, method) );
3967 (void) CompositeImage(curr_image,image,DifferenceCompositeOp,
3968 MagickTrue,0,0,exception);
3969 break;
3970 case EdgeMorphology:
3971 if (verbose != MagickFalse)
3972 (void) FormatLocaleFile(stderr,
3973 "\n%s: Difference of Dilate and Erode",CommandOptionToMnemonic(
3974 MagickMorphologyOptions, method) );
3975 (void) CompositeImage(curr_image,save_image,DifferenceCompositeOp,
3976 MagickTrue,0,0,exception);
3977 save_image = DestroyImage(save_image); /* finished with save image */
3978 break;
3979 default:
3980 break;
3981 }
3982
3983 /* multi-kernel handling: re-iterate, or compose results */
3984 if ( kernel->next == (KernelInfo *) NULL )
3985 rslt_image = curr_image; /* just return the resulting image */
3986 else if ( rslt_compose == NoCompositeOp )
3987 { if (verbose != MagickFalse) {
3988 if ( this_kernel->next != (KernelInfo *) NULL )
3989 (void) FormatLocaleFile(stderr, " (re-iterate)");
3990 else
3991 (void) FormatLocaleFile(stderr, " (done)");
3992 }
3993 rslt_image = curr_image; /* return result, and re-iterate */
3994 }
3995 else if ( rslt_image == (Image *) NULL)
3996 { if (verbose != MagickFalse)
3997 (void) FormatLocaleFile(stderr, " (save for compose)");
3998 rslt_image = curr_image;
3999 curr_image = (Image *) image; /* continue with original image */
4000 }
4001 else
4002 { /* Add the new 'current' result to the composition
4003 **
4004 ** The removal of any 'Sync' channel flag in the Image Composition
4005 ** below ensures the mathematical compose method is applied in a
4006 ** purely mathematical way, and only to the selected channels.
4007 ** IE: Turn off SVG composition 'alpha blending'.
4008 */
4009 if (verbose != MagickFalse)
4010 (void) FormatLocaleFile(stderr, " (compose \"%s\")",
4011 CommandOptionToMnemonic(MagickComposeOptions, rslt_compose) );
4012 (void) CompositeImage(rslt_image,curr_image,rslt_compose,MagickTrue,
4013 0,0,exception);
4014 curr_image = DestroyImage(curr_image);
4015 curr_image = (Image *) image; /* continue with original image */
4016 }
4017 if (verbose != MagickFalse)
4018 (void) FormatLocaleFile(stderr, "\n");
4019
4020 /* loop to the next kernel in a multi-kernel list */
4021 norm_kernel = norm_kernel->next;
4022 if ( rflt_kernel != (KernelInfo *) NULL )
4023 rflt_kernel = rflt_kernel->next;
4024 kernel_number++;
4025 } /* End Loop 2: Loop over each kernel */
4026
4027 } /* End Loop 1: compound method interaction */
4028
4029 goto exit_cleanup;
4030
4031 /* Yes goto's are bad, but it makes cleanup lot more efficient */
4032error_cleanup:
4033 if ( curr_image == rslt_image )
4034 curr_image = (Image *) NULL;
4035 if ( rslt_image != (Image *) NULL )
4036 rslt_image = DestroyImage(rslt_image);
4037exit_cleanup:
4038 if ( curr_image == rslt_image || curr_image == image )
4039 curr_image = (Image *) NULL;
4040 if ( curr_image != (Image *) NULL )
4041 curr_image = DestroyImage(curr_image);
4042 if ( work_image != (Image *) NULL )
4043 work_image = DestroyImage(work_image);
4044 if ( save_image != (Image *) NULL )
4045 save_image = DestroyImage(save_image);
4046 if ( reflected_kernel != (KernelInfo *) NULL )
4047 reflected_kernel = DestroyKernelInfo(reflected_kernel);
4048 return(rslt_image);
4049}
4050
4051␌
4052/*
4053%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4054% %
4055% %
4056% %
4057% M o r p h o l o g y I m a g e %
4058% %
4059% %
4060% %
4061%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4062%
4063% MorphologyImage() applies a user supplied kernel to the image according to
4064% the given morphology method.
4065%
4066% This function applies any and all user defined settings before calling
4067% the above internal function MorphologyApply().
4068%
4069% User defined settings include...
4070% * Output Bias for Convolution and correlation ("-define convolve:bias=??")
4071% * Kernel Scale/normalize settings ("-define convolve:scale=??")
4072% This can also includes the addition of a scaled unity kernel.
4073% * Show Kernel being applied ("-define morphology:showKernel=1")
4074%
4075% Other operators that do not want user supplied options interfering,
4076% especially "convolve:bias" and "morphology:showKernel" should use
4077% MorphologyApply() directly.
4078%
4079% The format of the MorphologyImage method is:
4080%
4081% Image *MorphologyImage(const Image *image,MorphologyMethod method,
4082% const ssize_t iterations,KernelInfo *kernel,ExceptionInfo *exception)
4083%
4084% A description of each parameter follows:
4085%
4086% o image: the image.
4087%
4088% o method: the morphology method to be applied.
4089%
4090% o iterations: apply the operation this many times (or no change).
4091% A value of -1 means loop until no change found.
4092% How this is applied may depend on the morphology method.
4093% Typically this is a value of 1.
4094%
4095% o kernel: An array of double representing the morphology kernel.
4096% Warning: kernel may be normalized for the Convolve method.
4097%
4098% o exception: return any errors or warnings in this structure.
4099%
4100*/
4101MagickExport Image *MorphologyImage(const Image *image,
4102 const MorphologyMethod method,const ssize_t iterations,
4103 const KernelInfo *kernel,ExceptionInfo *exception)
4104{
4105 const char
4106 *artifact;
4107
4108 CompositeOperator
4109 compose;
4110
4111 double
4112 bias;
4113
4114 Image
4115 *morphology_image;
4116
4118 *curr_kernel;
4119
4120 assert(image != (const Image *) NULL);
4121 assert(image->signature == MagickCoreSignature);
4122 assert(exception != (ExceptionInfo *) NULL);
4123 assert(exception->signature == MagickCoreSignature);
4124 if (IsEventLogging() != MagickFalse)
4125 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
4126 curr_kernel = (KernelInfo *) kernel;
4127 bias=0.0;
4128 compose = UndefinedCompositeOp; /* use default for method */
4129
4130 /* Apply Convolve/Correlate Normalization and Scaling Factors.
4131 * This is done BEFORE the ShowKernelInfo() function is called so that
4132 * users can see the results of the 'option:convolve:scale' option.
4133 */
4134 if ( method == ConvolveMorphology || method == CorrelateMorphology ) {
4135 /* Get the bias value as it will be needed */
4136 artifact = GetImageArtifact(image,"convolve:bias");
4137 if ( artifact != (const char *) NULL) {
4138 if (IsGeometry(artifact) == MagickFalse)
4139 (void) ThrowMagickException(exception,GetMagickModule(),
4140 OptionWarning,"InvalidSetting","'%s' '%s'",
4141 "convolve:bias",artifact);
4142 else
4143 bias=StringToDoubleInterval(artifact,(double) QuantumRange+1.0);
4144 }
4145
4146 /* Scale kernel according to user wishes */
4147 artifact = GetImageArtifact(image,"convolve:scale");
4148 if ( artifact != (const char *) NULL ) {
4149 if (IsGeometry(artifact) == MagickFalse)
4150 (void) ThrowMagickException(exception,GetMagickModule(),
4151 OptionWarning,"InvalidSetting","'%s' '%s'",
4152 "convolve:scale",artifact);
4153 else {
4154 if ( curr_kernel == kernel )
4155 curr_kernel = CloneKernelInfo(kernel);
4156 if (curr_kernel == (KernelInfo *) NULL)
4157 return((Image *) NULL);
4158 ScaleGeometryKernelInfo(curr_kernel, artifact);
4159 }
4160 }
4161 }
4162
4163 /* display the (normalized) kernel via stderr */
4164 artifact=GetImageArtifact(image,"morphology:showKernel");
4165 if (IsStringTrue(artifact) != MagickFalse)
4166 ShowKernelInfo(curr_kernel);
4167
4168 /* Override the default handling of multi-kernel morphology results
4169 * If 'Undefined' use the default method
4170 * If 'None' (default for 'Convolve') re-iterate previous result
4171 * Otherwise merge resulting images using compose method given.
4172 * Default for 'HitAndMiss' is 'Lighten'.
4173 */
4174 {
4175 ssize_t
4176 parse;
4177
4178 artifact = GetImageArtifact(image,"morphology:compose");
4179 if ( artifact != (const char *) NULL) {
4180 parse=ParseCommandOption(MagickComposeOptions,
4181 MagickFalse,artifact);
4182 if ( parse < 0 )
4183 (void) ThrowMagickException(exception,GetMagickModule(),
4184 OptionWarning,"UnrecognizedComposeOperator","'%s' '%s'",
4185 "morphology:compose",artifact);
4186 else
4187 compose=(CompositeOperator)parse;
4188 }
4189 }
4190 /* Apply the Morphology */
4191 morphology_image = MorphologyApply(image,method,iterations,
4192 curr_kernel,compose,bias,exception);
4193
4194 /* Cleanup and Exit */
4195 if ( curr_kernel != kernel )
4196 curr_kernel=DestroyKernelInfo(curr_kernel);
4197 return(morphology_image);
4198}
4199␌
4200/*
4201%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4202% %
4203% %
4204% %
4205+ R o t a t e K e r n e l I n f o %
4206% %
4207% %
4208% %
4209%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4210%
4211% RotateKernelInfo() rotates the kernel by the angle given.
4212%
4213% Currently it is restricted to 90 degree angles, of either 1D kernels
4214% or square kernels. And 'circular' rotations of 45 degrees for 3x3 kernels.
4215% It will ignore useless rotations for specific 'named' built-in kernels.
4216%
4217% The format of the RotateKernelInfo method is:
4218%
4219% void RotateKernelInfo(KernelInfo *kernel, double angle)
4220%
4221% A description of each parameter follows:
4222%
4223% o kernel: the Morphology/Convolution kernel
4224%
4225% o angle: angle to rotate in degrees
4226%
4227% This function is currently internal to this module only, but can be exported
4228% to other modules if needed.
4229*/
4230static void RotateKernelInfo(KernelInfo *kernel, double angle)
4231{
4232 /* angle the lower kernels first */
4233 if ( kernel->next != (KernelInfo *) NULL)
4234 RotateKernelInfo(kernel->next, angle);
4235
4236 /* WARNING: Currently assumes the kernel (rightly) is horizontally symmetrical
4237 **
4238 ** TODO: expand beyond simple 90 degree rotates, flips and flops
4239 */
4240
4241 /* Modulus the angle */
4242 angle = fmod(angle, 360.0);
4243 if ( angle < 0 )
4244 angle += 360.0;
4245
4246 if ( 337.5 < angle || angle <= 22.5 )
4247 return; /* Near zero angle - no change! - At least not at this time */
4248
4249 /* Handle special cases */
4250 switch (kernel->type) {
4251 /* These built-in kernels are cylindrical kernels, rotating is useless */
4252 case GaussianKernel:
4253 case DoGKernel:
4254 case LoGKernel:
4255 case DiskKernel:
4256 case PeaksKernel:
4257 case LaplacianKernel:
4258 case ChebyshevKernel:
4259 case ManhattanKernel:
4260 case EuclideanKernel:
4261 return;
4262
4263 /* These may be rotatable at non-90 angles in the future */
4264 /* but simply rotating them in multiples of 90 degrees is useless */
4265 case SquareKernel:
4266 case DiamondKernel:
4267 case PlusKernel:
4268 case CrossKernel:
4269 return;
4270
4271 /* These only allows a +/-90 degree rotation (by transpose) */
4272 /* A 180 degree rotation is useless */
4273 case BlurKernel:
4274 if ( 135.0 < angle && angle <= 225.0 )
4275 return;
4276 if ( 225.0 < angle && angle <= 315.0 )
4277 angle -= 180;
4278 break;
4279
4280 default:
4281 break;
4282 }
4283 /* Attempt rotations by 45 degrees -- 3x3 kernels only */
4284 if ( 22.5 < fmod(angle,90.0) && fmod(angle,90.0) <= 67.5 )
4285 {
4286 if ( kernel->width == 3 && kernel->height == 3 )
4287 { /* Rotate a 3x3 square by 45 degree angle */
4288 double t = kernel->values[0];
4289 kernel->values[0] = kernel->values[3];
4290 kernel->values[3] = kernel->values[6];
4291 kernel->values[6] = kernel->values[7];
4292 kernel->values[7] = kernel->values[8];
4293 kernel->values[8] = kernel->values[5];
4294 kernel->values[5] = kernel->values[2];
4295 kernel->values[2] = kernel->values[1];
4296 kernel->values[1] = t;
4297 /* rotate non-centered origin */
4298 if ( kernel->x != 1 || kernel->y != 1 ) {
4299 ssize_t x,y;
4300 x = (ssize_t) kernel->x-1;
4301 y = (ssize_t) kernel->y-1;
4302 if ( x == y ) x = 0;
4303 else if ( x == 0 ) x = -y;
4304 else if ( x == -y ) y = 0;
4305 else if ( y == 0 ) y = x;
4306 kernel->x = (ssize_t) x+1;
4307 kernel->y = (ssize_t) y+1;
4308 }
4309 angle = fmod(angle+315.0, 360.0); /* angle reduced 45 degrees */
4310 kernel->angle = fmod(kernel->angle+45.0, 360.0);
4311 }
4312 else
4313 perror("Unable to rotate non-3x3 kernel by 45 degrees");
4314 }
4315 if ( 45.0 < fmod(angle, 180.0) && fmod(angle,180.0) <= 135.0 )
4316 {
4317 if ( kernel->width == 1 || kernel->height == 1 )
4318 { /* Do a transpose of a 1 dimensional kernel,
4319 ** which results in a fast 90 degree rotation of some type.
4320 */
4321 ssize_t
4322 t;
4323 t = (ssize_t) kernel->width;
4324 kernel->width = kernel->height;
4325 kernel->height = (size_t) t;
4326 t = kernel->x;
4327 kernel->x = kernel->y;
4328 kernel->y = t;
4329 if ( kernel->width == 1 ) {
4330 angle = fmod(angle+270.0, 360.0); /* angle reduced 90 degrees */
4331 kernel->angle = fmod(kernel->angle+90.0, 360.0);
4332 } else {
4333 angle = fmod(angle+90.0, 360.0); /* angle increased 90 degrees */
4334 kernel->angle = fmod(kernel->angle+270.0, 360.0);
4335 }
4336 }
4337 else if ( kernel->width == kernel->height )
4338 { /* Rotate a square array of values by 90 degrees */
4339 { ssize_t
4340 i,j,x,y;
4341
4342 MagickRealType
4343 *k,t;
4344
4345 k=kernel->values;
4346 for( i=0, x=(ssize_t) kernel->width-1; i<=x; i++, x--)
4347 for( j=0, y=(ssize_t) kernel->height-1; j<y; j++, y--)
4348 { t = k[i+j*(ssize_t) kernel->width];
4349 k[i+j*(ssize_t) kernel->width] = k[j+x*(ssize_t) kernel->width];
4350 k[j+x*(ssize_t) kernel->width] = k[x+y*(ssize_t) kernel->width];
4351 k[x+y*(ssize_t) kernel->width] = k[y+i*(ssize_t) kernel->width];
4352 k[y+i*(ssize_t) kernel->width] = t;
4353 }
4354 }
4355 /* rotate the origin - relative to center of array */
4356 { ssize_t x,y;
4357 x = (ssize_t) (kernel->x*2-(ssize_t) kernel->width+1);
4358 y = (ssize_t) (kernel->y*2-(ssize_t) kernel->height+1);
4359 kernel->x = (ssize_t) ( -y +(ssize_t) kernel->width-1)/2;
4360 kernel->y = (ssize_t) ( +x +(ssize_t) kernel->height-1)/2;
4361 }
4362 angle = fmod(angle+270.0, 360.0); /* angle reduced 90 degrees */
4363 kernel->angle = fmod(kernel->angle+90.0, 360.0);
4364 }
4365 else
4366 perror("Unable to rotate a non-square, non-linear kernel 90 degrees");
4367 }
4368 if ( 135.0 < angle && angle <= 225.0 )
4369 {
4370 /* For a 180 degree rotation - also know as a reflection
4371 * This is actually a very very common operation!
4372 * Basically all that is needed is a reversal of the kernel data!
4373 * And a reflection of the origin
4374 */
4375 MagickRealType
4376 t;
4377
4378 MagickRealType
4379 *k;
4380
4381 ssize_t
4382 i,
4383 j;
4384
4385 k=kernel->values;
4386 j=(ssize_t) (kernel->width*kernel->height-1);
4387 for (i=0; i < j; i++, j--)
4388 t=k[i], k[i]=k[j], k[j]=t;
4389
4390 kernel->x = (ssize_t) kernel->width - kernel->x - 1;
4391 kernel->y = (ssize_t) kernel->height - kernel->y - 1;
4392 angle = fmod(angle-180.0, 360.0); /* angle+180 degrees */
4393 kernel->angle = fmod(kernel->angle+180.0, 360.0);
4394 }
4395 /* At this point angle should at least between -45 (315) and +45 degrees
4396 * In the future some form of non-orthogonal angled rotates could be
4397 * performed here, possibly with a linear kernel restriction.
4398 */
4399
4400 return;
4401}
4402␌
4403/*
4404%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4405% %
4406% %
4407% %
4408% S c a l e G e o m e t r y K e r n e l I n f o %
4409% %
4410% %
4411% %
4412%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4413%
4414% ScaleGeometryKernelInfo() takes a geometry argument string, typically
4415% provided as a "-set option:convolve:scale {geometry}" user setting,
4416% and modifies the kernel according to the parsed arguments of that setting.
4417%
4418% The first argument (and any normalization flags) are passed to
4419% ScaleKernelInfo() to scale/normalize the kernel. The second argument
4420% is then passed to UnityAddKernelInfo() to add a scaled unity kernel
4421% into the scaled/normalized kernel.
4422%
4423% The format of the ScaleGeometryKernelInfo method is:
4424%
4425% void ScaleGeometryKernelInfo(KernelInfo *kernel,
4426% const double scaling_factor,const MagickStatusType normalize_flags)
4427%
4428% A description of each parameter follows:
4429%
4430% o kernel: the Morphology/Convolution kernel to modify
4431%
4432% o geometry:
4433% The geometry string to parse, typically from the user provided
4434% "-set option:convolve:scale {geometry}" setting.
4435%
4436*/
4437MagickExport void ScaleGeometryKernelInfo (KernelInfo *kernel,
4438 const char *geometry)
4439{
4440 MagickStatusType
4441 flags;
4442
4444 args;
4445
4446 SetGeometryInfo(&args);
4447 flags = ParseGeometry(geometry, &args);
4448
4449#if 0
4450 /* For Debugging Geometry Input */
4451 (void) FormatLocaleFile(stderr, "Geometry = 0x%04X : %lg x %lg %+lg %+lg\n",
4452 flags, args.rho, args.sigma, args.xi, args.psi );
4453#endif
4454
4455 if ( (flags & PercentValue) != 0 ) /* Handle Percentage flag*/
4456 args.rho *= 0.01, args.sigma *= 0.01;
4457
4458 if ( (flags & RhoValue) == 0 ) /* Set Defaults for missing args */
4459 args.rho = 1.0;
4460 if ( (flags & SigmaValue) == 0 )
4461 args.sigma = 0.0;
4462
4463 /* Scale/Normalize the input kernel */
4464 ScaleKernelInfo(kernel, args.rho, (GeometryFlags) flags);
4465
4466 /* Add Unity Kernel, for blending with original */
4467 if ( (flags & SigmaValue) != 0 )
4468 UnityAddKernelInfo(kernel, args.sigma);
4469
4470 return;
4471}
4472/*
4473%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4474% %
4475% %
4476% %
4477% S c a l e K e r n e l I n f o %
4478% %
4479% %
4480% %
4481%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4482%
4483% ScaleKernelInfo() scales the given kernel list by the given amount, with or
4484% without normalization of the sum of the kernel values (as per given flags).
4485%
4486% By default (no flags given) the values within the kernel is scaled
4487% directly using given scaling factor without change.
4488%
4489% If either of the two 'normalize_flags' are given the kernel will first be
4490% normalized and then further scaled by the scaling factor value given.
4491%
4492% Kernel normalization ('normalize_flags' given) is designed to ensure that
4493% any use of the kernel scaling factor with 'Convolve' or 'Correlate'
4494% morphology methods will fall into -1.0 to +1.0 range. Note that for
4495% non-HDRI versions of IM this may cause images to have any negative results
4496% clipped, unless some 'bias' is used.
4497%
4498% More specifically. Kernels which only contain positive values (such as a
4499% 'Gaussian' kernel) will be scaled so that those values sum to +1.0,
4500% ensuring a 0.0 to +1.0 output range for non-HDRI images.
4501%
4502% For Kernels that contain some negative values, (such as 'Sharpen' kernels)
4503% the kernel will be scaled by the absolute of the sum of kernel values, so
4504% that it will generally fall within the +/- 1.0 range.
4505%
4506% For kernels whose values sum to zero, (such as 'Laplacian' kernels) kernel
4507% will be scaled by just the sum of the positive values, so that its output
4508% range will again fall into the +/- 1.0 range.
4509%
4510% For special kernels designed for locating shapes using 'Correlate', (often
4511% only containing +1 and -1 values, representing foreground/background
4512% matching) a special normalization method is provided to scale the positive
4513% values separately to those of the negative values, so the kernel will be
4514% forced to become a zero-sum kernel better suited to such searches.
4515%
4516% WARNING: Correct normalization of the kernel assumes that the '*_range'
4517% attributes within the kernel structure have been correctly set during the
4518% kernels creation.
4519%
4520% NOTE: The values used for 'normalize_flags' have been selected specifically
4521% to match the use of geometry options, so that '!' means NormalizeValue, '^'
4522% means CorrelateNormalizeValue. All other GeometryFlags values are ignored.
4523%
4524% The format of the ScaleKernelInfo method is:
4525%
4526% void ScaleKernelInfo(KernelInfo *kernel, const double scaling_factor,
4527% const MagickStatusType normalize_flags )
4528%
4529% A description of each parameter follows:
4530%
4531% o kernel: the Morphology/Convolution kernel
4532%
4533% o scaling_factor:
4534% multiply all values (after normalization) by this factor if not
4535% zero. If the kernel is normalized regardless of any flags.
4536%
4537% o normalize_flags:
4538% GeometryFlags defining normalization method to use.
4539% specifically: NormalizeValue, CorrelateNormalizeValue,
4540% and/or PercentValue
4541%
4542*/
4543MagickExport void ScaleKernelInfo(KernelInfo *kernel,
4544 const double scaling_factor,const GeometryFlags normalize_flags)
4545{
4546 double
4547 pos_scale,
4548 neg_scale;
4549
4550 ssize_t
4551 i;
4552
4553 /* do the other kernels in a multi-kernel list first */
4554 if ( kernel->next != (KernelInfo *) NULL)
4555 ScaleKernelInfo(kernel->next, scaling_factor, normalize_flags);
4556
4557 /* Normalization of Kernel */
4558 pos_scale = 1.0;
4559 if ( (normalize_flags&NormalizeValue) != 0 ) {
4560 if ( fabs(kernel->positive_range + kernel->negative_range) >= MagickEpsilon )
4561 /* non-zero-summing kernel (generally positive) */
4562 pos_scale = fabs(kernel->positive_range + kernel->negative_range);
4563 else
4564 /* zero-summing kernel */
4565 pos_scale = kernel->positive_range;
4566 }
4567 /* Force kernel into a normalized zero-summing kernel */
4568 if ( (normalize_flags&CorrelateNormalizeValue) != 0 ) {
4569 pos_scale = ( fabs(kernel->positive_range) >= MagickEpsilon )
4570 ? kernel->positive_range : 1.0;
4571 neg_scale = ( fabs(kernel->negative_range) >= MagickEpsilon )
4572 ? -kernel->negative_range : 1.0;
4573 }
4574 else
4575 neg_scale = pos_scale;
4576
4577 /* finalize scaling_factor for positive and negative components */
4578 pos_scale = scaling_factor/pos_scale;
4579 neg_scale = scaling_factor/neg_scale;
4580
4581 for (i=0; i < (ssize_t) (kernel->width*kernel->height); i++)
4582 if (!IsNaN(kernel->values[i]))
4583 kernel->values[i] *= (kernel->values[i] >= 0) ? pos_scale : neg_scale;
4584
4585 /* convolution output range */
4586 kernel->positive_range *= pos_scale;
4587 kernel->negative_range *= neg_scale;
4588 /* maximum and minimum values in kernel */
4589 kernel->maximum *= (kernel->maximum >= 0.0) ? pos_scale : neg_scale;
4590 kernel->minimum *= (kernel->minimum >= 0.0) ? pos_scale : neg_scale;
4591
4592 /* swap kernel settings if user's scaling factor is negative */
4593 if ( scaling_factor < MagickEpsilon ) {
4594 double t;
4595 t = kernel->positive_range;
4596 kernel->positive_range = kernel->negative_range;
4597 kernel->negative_range = t;
4598 t = kernel->maximum;
4599 kernel->maximum = kernel->minimum;
4600 kernel->minimum = 1;
4601 }
4602
4603 return;
4604}
4605␌
4606/*
4607%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4608% %
4609% %
4610% %
4611% S h o w K e r n e l I n f o %
4612% %
4613% %
4614% %
4615%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4616%
4617% ShowKernelInfo() outputs the details of the given kernel definition to
4618% standard error, generally due to a users 'morphology:showKernel' option
4619% request.
4620%
4621% The format of the ShowKernel method is:
4622%
4623% void ShowKernelInfo(const KernelInfo *kernel)
4624%
4625% A description of each parameter follows:
4626%
4627% o kernel: the Morphology/Convolution kernel
4628%
4629*/
4630MagickPrivate void ShowKernelInfo(const KernelInfo *kernel)
4631{
4632 const KernelInfo
4633 *k;
4634
4635 size_t
4636 c, i, u, v;
4637
4638 for (c=0, k=kernel; k != (KernelInfo *) NULL; c++, k=k->next ) {
4639
4640 (void) FormatLocaleFile(stderr, "Kernel");
4641 if ( kernel->next != (KernelInfo *) NULL )
4642 (void) FormatLocaleFile(stderr, " #%lu", (unsigned long) c );
4643 (void) FormatLocaleFile(stderr, " \"%s",
4644 CommandOptionToMnemonic(MagickKernelOptions, k->type) );
4645 if ( fabs(k->angle) >= MagickEpsilon )
4646 (void) FormatLocaleFile(stderr, "@%lg", k->angle);
4647 (void) FormatLocaleFile(stderr, "\" of size %lux%lu%+ld%+ld",(unsigned long)
4648 k->width,(unsigned long) k->height,(long) k->x,(long) k->y);
4649 (void) FormatLocaleFile(stderr,
4650 " with values from %.*lg to %.*lg\n",
4651 GetMagickPrecision(), k->minimum,
4652 GetMagickPrecision(), k->maximum);
4653 (void) FormatLocaleFile(stderr, "Forming a output range from %.*lg to %.*lg",
4654 GetMagickPrecision(), k->negative_range,
4655 GetMagickPrecision(), k->positive_range);
4656 if ( fabs(k->positive_range+k->negative_range) < MagickEpsilon )
4657 (void) FormatLocaleFile(stderr, " (Zero-Summing)\n");
4658 else if ( fabs(k->positive_range+k->negative_range-1.0) < MagickEpsilon )
4659 (void) FormatLocaleFile(stderr, " (Normalized)\n");
4660 else
4661 (void) FormatLocaleFile(stderr, " (Sum %.*lg)\n",
4662 GetMagickPrecision(), k->positive_range+k->negative_range);
4663 for (i=v=0; v < k->height; v++) {
4664 (void) FormatLocaleFile(stderr, "%2lu:", (unsigned long) v );
4665 for (u=0; u < k->width; u++, i++)
4666 if (IsNaN(k->values[i]))
4667 (void) FormatLocaleFile(stderr," %*s", GetMagickPrecision()+3, "nan");
4668 else
4669 (void) FormatLocaleFile(stderr," %*.*lg", GetMagickPrecision()+3,
4670 GetMagickPrecision(), (double) k->values[i]);
4671 (void) FormatLocaleFile(stderr,"\n");
4672 }
4673 }
4674}
4675␌
4676/*
4677%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4678% %
4679% %
4680% %
4681% U n i t y A d d K e r n a l I n f o %
4682% %
4683% %
4684% %
4685%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4686%
4687% UnityAddKernelInfo() Adds a given amount of the 'Unity' Convolution Kernel
4688% to the given pre-scaled and normalized Kernel. This in effect adds that
4689% amount of the original image into the resulting convolution kernel. This
4690% value is usually provided by the user as a percentage value in the
4691% 'convolve:scale' setting.
4692%
4693% The resulting effect is to convert the defined kernels into blended
4694% soft-blurs, unsharp kernels or into sharpening kernels.
4695%
4696% The format of the UnityAdditionKernelInfo method is:
4697%
4698% void UnityAdditionKernelInfo(KernelInfo *kernel, const double scale )
4699%
4700% A description of each parameter follows:
4701%
4702% o kernel: the Morphology/Convolution kernel
4703%
4704% o scale:
4705% scaling factor for the unity kernel to be added to
4706% the given kernel.
4707%
4708*/
4709MagickExport void UnityAddKernelInfo(KernelInfo *kernel,
4710 const double scale)
4711{
4712 /* do the other kernels in a multi-kernel list first */
4713 if ( kernel->next != (KernelInfo *) NULL)
4714 UnityAddKernelInfo(kernel->next, scale);
4715
4716 /* Add the scaled unity kernel to the existing kernel */
4717 kernel->values[kernel->x+kernel->y*(ssize_t) kernel->width] += scale;
4718 CalcKernelMetaData(kernel); /* recalculate the meta-data */
4719
4720 return;
4721}
4722␌
4723/*
4724%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4725% %
4726% %
4727% %
4728% Z e r o K e r n e l N a n s %
4729% %
4730% %
4731% %
4732%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4733%
4734% ZeroKernelNans() replaces any special 'nan' value that may be present in
4735% the kernel with a zero value. This is typically done when the kernel will
4736% be used in special hardware (GPU) convolution processors, to simply
4737% matters.
4738%
4739% The format of the ZeroKernelNans method is:
4740%
4741% void ZeroKernelNans (KernelInfo *kernel)
4742%
4743% A description of each parameter follows:
4744%
4745% o kernel: the Morphology/Convolution kernel
4746%
4747*/
4748MagickPrivate void ZeroKernelNans(KernelInfo *kernel)
4749{
4750 size_t
4751 i;
4752
4753 /* do the other kernels in a multi-kernel list first */
4754 if (kernel->next != (KernelInfo *) NULL)
4755 ZeroKernelNans(kernel->next);
4756
4757 for (i=0; i < (kernel->width*kernel->height); i++)
4758 if (IsNaN(kernel->values[i]))
4759 kernel->values[i]=0.0;
4760
4761 return;
4762}