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