|
MagickCore
6.7.5
|
00001 /* 00002 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 00003 % % 00004 % % 00005 % % 00006 % M M OOO RRRR PPPP H H OOO L OOO GGGG Y Y % 00007 % MM MM O O R R P P H H O O L O O G Y Y % 00008 % M M M O O RRRR PPPP HHHHH O O L O O G GGG Y % 00009 % M M O O R R P H H O O L O O G G Y % 00010 % M M OOO R R P H H OOO LLLLL OOO GGG Y % 00011 % % 00012 % % 00013 % MagickCore Morphology Methods % 00014 % % 00015 % Software Design % 00016 % Anthony Thyssen % 00017 % January 2010 % 00018 % % 00019 % % 00020 % Copyright 1999-2012 ImageMagick Studio LLC, a non-profit organization % 00021 % dedicated to making software imaging solutions freely available. % 00022 % % 00023 % You may not use this file except in compliance with the License. You may % 00024 % obtain a copy of the License at % 00025 % % 00026 % http://www.imagemagick.org/script/license.php % 00027 % % 00028 % Unless required by applicable law or agreed to in writing, software % 00029 % distributed under the License is distributed on an "AS IS" BASIS, % 00030 % WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. % 00031 % See the License for the specific language governing permissions and % 00032 % limitations under the License. % 00033 % % 00034 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 00035 % 00036 % Morpology is the the application of various kernels, of any size and even 00037 % shape, to a image in various ways (typically binary, but not always). 00038 % 00039 % Convolution (weighted sum or average) is just one specific type of 00040 % morphology. Just one that is very common for image bluring and sharpening 00041 % effects. Not only 2D Gaussian blurring, but also 2-pass 1D Blurring. 00042 % 00043 % This module provides not only a general morphology function, and the ability 00044 % to apply more advanced or iterative morphologies, but also functions for the 00045 % generation of many different types of kernel arrays from user supplied 00046 % arguments. Prehaps even the generation of a kernel from a small image. 00047 */ 00048 00049 /* 00050 Include declarations. 00051 */ 00052 #include "MagickCore/studio.h" 00053 #include "MagickCore/artifact.h" 00054 #include "MagickCore/cache-view.h" 00055 #include "MagickCore/color-private.h" 00056 #include "MagickCore/enhance.h" 00057 #include "MagickCore/exception.h" 00058 #include "MagickCore/exception-private.h" 00059 #include "MagickCore/gem.h" 00060 #include "MagickCore/gem-private.h" 00061 #include "MagickCore/hashmap.h" 00062 #include "MagickCore/image.h" 00063 #include "MagickCore/image-private.h" 00064 #include "MagickCore/list.h" 00065 #include "MagickCore/magick.h" 00066 #include "MagickCore/memory_.h" 00067 #include "MagickCore/monitor-private.h" 00068 #include "MagickCore/morphology.h" 00069 #include "MagickCore/morphology-private.h" 00070 #include "MagickCore/option.h" 00071 #include "MagickCore/pixel-accessor.h" 00072 #include "MagickCore/prepress.h" 00073 #include "MagickCore/quantize.h" 00074 #include "MagickCore/registry.h" 00075 #include "MagickCore/semaphore.h" 00076 #include "MagickCore/splay-tree.h" 00077 #include "MagickCore/statistic.h" 00078 #include "MagickCore/string_.h" 00079 #include "MagickCore/string-private.h" 00080 #include "MagickCore/token.h" 00081 #include "MagickCore/utility.h" 00082 #include "MagickCore/utility-private.h" 00083 00084 00085 /* 00086 ** The following test is for special floating point numbers of value NaN (not 00087 ** a number), that may be used within a Kernel Definition. NaN's are defined 00088 ** as part of the IEEE standard for floating point number representation. 00089 ** 00090 ** These are used as a Kernel value to mean that this kernel position is not 00091 ** part of the kernel neighbourhood for convolution or morphology processing, 00092 ** and thus should be ignored. This allows the use of 'shaped' kernels. 00093 ** 00094 ** The special properity that two NaN's are never equal, even if they are from 00095 ** the same variable allow you to test if a value is special NaN value. 00096 ** 00097 ** This macro IsNaN() is thus is only true if the value given is NaN. 00098 */ 00099 #define IsNan(a) ((a)!=(a)) 00100 00101 /* 00102 Other global definitions used by module. 00103 */ 00104 static inline double MagickMin(const double x,const double y) 00105 { 00106 return( x < y ? x : y); 00107 } 00108 static inline double MagickMax(const double x,const double y) 00109 { 00110 return( x > y ? x : y); 00111 } 00112 #define Minimize(assign,value) assign=MagickMin(assign,value) 00113 #define Maximize(assign,value) assign=MagickMax(assign,value) 00114 00115 /* Currently these are only internal to this module */ 00116 static void 00117 CalcKernelMetaData(KernelInfo *), 00118 ExpandMirrorKernelInfo(KernelInfo *), 00119 ExpandRotateKernelInfo(KernelInfo *, const double), 00120 RotateKernelInfo(KernelInfo *, double); 00121 00122 00123 /* Quick function to find last kernel in a kernel list */ 00124 static inline KernelInfo *LastKernelInfo(KernelInfo *kernel) 00125 { 00126 while (kernel->next != (KernelInfo *) NULL) 00127 kernel = kernel->next; 00128 return(kernel); 00129 } 00130 00131 /* 00132 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 00133 % % 00134 % % 00135 % % 00136 % A c q u i r e K e r n e l I n f o % 00137 % % 00138 % % 00139 % % 00140 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 00141 % 00142 % AcquireKernelInfo() takes the given string (generally supplied by the 00143 % user) and converts it into a Morphology/Convolution Kernel. This allows 00144 % users to specify a kernel from a number of pre-defined kernels, or to fully 00145 % specify their own kernel for a specific Convolution or Morphology 00146 % Operation. 00147 % 00148 % The kernel so generated can be any rectangular array of floating point 00149 % values (doubles) with the 'control point' or 'pixel being affected' 00150 % anywhere within that array of values. 00151 % 00152 % Previously IM was restricted to a square of odd size using the exact 00153 % center as origin, this is no longer the case, and any rectangular kernel 00154 % with any value being declared the origin. This in turn allows the use of 00155 % highly asymmetrical kernels. 00156 % 00157 % The floating point values in the kernel can also include a special value 00158 % known as 'nan' or 'not a number' to indicate that this value is not part 00159 % of the kernel array. This allows you to shaped the kernel within its 00160 % rectangular area. That is 'nan' values provide a 'mask' for the kernel 00161 % shape. However at least one non-nan value must be provided for correct 00162 % working of a kernel. 00163 % 00164 % The returned kernel should be freed using the DestroyKernelInfo() when you 00165 % are finished with it. Do not free this memory yourself. 00166 % 00167 % Input kernel defintion strings can consist of any of three types. 00168 % 00169 % "name:args[[@><]" 00170 % Select from one of the built in kernels, using the name and 00171 % geometry arguments supplied. See AcquireKernelBuiltIn() 00172 % 00173 % "WxH[+X+Y][@><]:num, num, num ..." 00174 % a kernel of size W by H, with W*H floating point numbers following. 00175 % the 'center' can be optionally be defined at +X+Y (such that +0+0 00176 % is top left corner). If not defined the pixel in the center, for 00177 % odd sizes, or to the immediate top or left of center for even sizes 00178 % is automatically selected. 00179 % 00180 % "num, num, num, num, ..." 00181 % list of floating point numbers defining an 'old style' odd sized 00182 % square kernel. At least 9 values should be provided for a 3x3 00183 % square kernel, 25 for a 5x5 square kernel, 49 for 7x7, etc. 00184 % Values can be space or comma separated. This is not recommended. 00185 % 00186 % You can define a 'list of kernels' which can be used by some morphology 00187 % operators A list is defined as a semi-colon separated list kernels. 00188 % 00189 % " kernel ; kernel ; kernel ; " 00190 % 00191 % Any extra ';' characters, at start, end or between kernel defintions are 00192 % simply ignored. 00193 % 00194 % The special flags will expand a single kernel, into a list of rotated 00195 % kernels. A '@' flag will expand a 3x3 kernel into a list of 45-degree 00196 % cyclic rotations, while a '>' will generate a list of 90-degree rotations. 00197 % The '<' also exands using 90-degree rotates, but giving a 180-degree 00198 % reflected kernel before the +/- 90-degree rotations, which can be important 00199 % for Thinning operations. 00200 % 00201 % Note that 'name' kernels will start with an alphabetic character while the 00202 % new kernel specification has a ':' character in its specification string. 00203 % If neither is the case, it is assumed an old style of a simple list of 00204 % numbers generating a odd-sized square kernel has been given. 00205 % 00206 % The format of the AcquireKernal method is: 00207 % 00208 % KernelInfo *AcquireKernelInfo(const char *kernel_string) 00209 % 00210 % A description of each parameter follows: 00211 % 00212 % o kernel_string: the Morphology/Convolution kernel wanted. 00213 % 00214 */ 00215 00216 /* This was separated so that it could be used as a separate 00217 ** array input handling function, such as for -color-matrix 00218 */ 00219 static KernelInfo *ParseKernelArray(const char *kernel_string) 00220 { 00221 KernelInfo 00222 *kernel; 00223 00224 char 00225 token[MaxTextExtent]; 00226 00227 const char 00228 *p, 00229 *end; 00230 00231 register ssize_t 00232 i; 00233 00234 double 00235 nan = sqrt((double)-1.0); /* Special Value : Not A Number */ 00236 00237 MagickStatusType 00238 flags; 00239 00240 GeometryInfo 00241 args; 00242 00243 kernel=(KernelInfo *) AcquireQuantumMemory(1,sizeof(*kernel)); 00244 if (kernel == (KernelInfo *)NULL) 00245 return(kernel); 00246 (void) ResetMagickMemory(kernel,0,sizeof(*kernel)); 00247 kernel->minimum = kernel->maximum = kernel->angle = 0.0; 00248 kernel->negative_range = kernel->positive_range = 0.0; 00249 kernel->type = UserDefinedKernel; 00250 kernel->next = (KernelInfo *) NULL; 00251 kernel->signature = MagickSignature; 00252 if (kernel_string == (const char *) NULL) 00253 return(kernel); 00254 00255 /* find end of this specific kernel definition string */ 00256 end = strchr(kernel_string, ';'); 00257 if ( end == (char *) NULL ) 00258 end = strchr(kernel_string, '\0'); 00259 00260 /* clear flags - for Expanding kernel lists thorugh rotations */ 00261 flags = NoValue; 00262 00263 /* Has a ':' in argument - New user kernel specification */ 00264 p = strchr(kernel_string, ':'); 00265 if ( p != (char *) NULL && p < end) 00266 { 00267 /* ParseGeometry() needs the geometry separated! -- Arrgghh */ 00268 memcpy(token, kernel_string, (size_t) (p-kernel_string)); 00269 token[p-kernel_string] = '\0'; 00270 SetGeometryInfo(&args); 00271 flags = ParseGeometry(token, &args); 00272 00273 /* Size handling and checks of geometry settings */ 00274 if ( (flags & WidthValue) == 0 ) /* if no width then */ 00275 args.rho = args.sigma; /* then width = height */ 00276 if ( args.rho < 1.0 ) /* if width too small */ 00277 args.rho = 1.0; /* then width = 1 */ 00278 if ( args.sigma < 1.0 ) /* if height too small */ 00279 args.sigma = args.rho; /* then height = width */ 00280 kernel->width = (size_t)args.rho; 00281 kernel->height = (size_t)args.sigma; 00282 00283 /* Offset Handling and Checks */ 00284 if ( args.xi < 0.0 || args.psi < 0.0 ) 00285 return(DestroyKernelInfo(kernel)); 00286 kernel->x = ((flags & XValue)!=0) ? (ssize_t)args.xi 00287 : (ssize_t) (kernel->width-1)/2; 00288 kernel->y = ((flags & YValue)!=0) ? (ssize_t)args.psi 00289 : (ssize_t) (kernel->height-1)/2; 00290 if ( kernel->x >= (ssize_t) kernel->width || 00291 kernel->y >= (ssize_t) kernel->height ) 00292 return(DestroyKernelInfo(kernel)); 00293 00294 p++; /* advance beyond the ':' */ 00295 } 00296 else 00297 { /* ELSE - Old old specification, forming odd-square kernel */ 00298 /* count up number of values given */ 00299 p=(const char *) kernel_string; 00300 while ((isspace((int) ((unsigned char) *p)) != 0) || (*p == '\'')) 00301 p++; /* ignore "'" chars for convolve filter usage - Cristy */ 00302 for (i=0; p < end; i++) 00303 { 00304 GetMagickToken(p,&p,token); 00305 if (*token == ',') 00306 GetMagickToken(p,&p,token); 00307 } 00308 /* set the size of the kernel - old sized square */ 00309 kernel->width = kernel->height= (size_t) sqrt((double) i+1.0); 00310 kernel->x = kernel->y = (ssize_t) (kernel->width-1)/2; 00311 p=(const char *) kernel_string; 00312 while ((isspace((int) ((unsigned char) *p)) != 0) || (*p == '\'')) 00313 p++; /* ignore "'" chars for convolve filter usage - Cristy */ 00314 } 00315 00316 /* Read in the kernel values from rest of input string argument */ 00317 kernel->values=(MagickRealType *) AcquireAlignedMemory(kernel->width, 00318 kernel->height*sizeof(*kernel->values)); 00319 if (kernel->values == (MagickRealType *) NULL) 00320 return(DestroyKernelInfo(kernel)); 00321 kernel->minimum = +MagickHuge; 00322 kernel->maximum = -MagickHuge; 00323 kernel->negative_range = kernel->positive_range = 0.0; 00324 for (i=0; (i < (ssize_t) (kernel->width*kernel->height)) && (p < end); i++) 00325 { 00326 GetMagickToken(p,&p,token); 00327 if (*token == ',') 00328 GetMagickToken(p,&p,token); 00329 if ( LocaleCompare("nan",token) == 0 00330 || LocaleCompare("-",token) == 0 ) { 00331 kernel->values[i] = nan; /* do not include this value in kernel */ 00332 } 00333 else { 00334 kernel->values[i]=StringToDouble(token,(char **) NULL); 00335 ( kernel->values[i] < 0) 00336 ? ( kernel->negative_range += kernel->values[i] ) 00337 : ( kernel->positive_range += kernel->values[i] ); 00338 Minimize(kernel->minimum, kernel->values[i]); 00339 Maximize(kernel->maximum, kernel->values[i]); 00340 } 00341 } 00342 00343 /* sanity check -- no more values in kernel definition */ 00344 GetMagickToken(p,&p,token); 00345 if ( *token != '\0' && *token != ';' && *token != '\'' ) 00346 return(DestroyKernelInfo(kernel)); 00347 00348 #if 0 00349 /* this was the old method of handling a incomplete kernel */ 00350 if ( i < (ssize_t) (kernel->width*kernel->height) ) { 00351 Minimize(kernel->minimum, kernel->values[i]); 00352 Maximize(kernel->maximum, kernel->values[i]); 00353 for ( ; i < (ssize_t) (kernel->width*kernel->height); i++) 00354 kernel->values[i]=0.0; 00355 } 00356 #else 00357 /* Number of values for kernel was not enough - Report Error */ 00358 if ( i < (ssize_t) (kernel->width*kernel->height) ) 00359 return(DestroyKernelInfo(kernel)); 00360 #endif 00361 00362 /* check that we recieved at least one real (non-nan) value! */ 00363 if ( kernel->minimum == MagickHuge ) 00364 return(DestroyKernelInfo(kernel)); 00365 00366 if ( (flags & AreaValue) != 0 ) /* '@' symbol in kernel size */ 00367 ExpandRotateKernelInfo(kernel, 45.0); /* cyclic rotate 3x3 kernels */ 00368 else if ( (flags & GreaterValue) != 0 ) /* '>' symbol in kernel args */ 00369 ExpandRotateKernelInfo(kernel, 90.0); /* 90 degree rotate of kernel */ 00370 else if ( (flags & LessValue) != 0 ) /* '<' symbol in kernel args */ 00371 ExpandMirrorKernelInfo(kernel); /* 90 degree mirror rotate */ 00372 00373 return(kernel); 00374 } 00375 00376 static KernelInfo *ParseKernelName(const char *kernel_string) 00377 { 00378 char 00379 token[MaxTextExtent]; 00380 00381 const char 00382 *p, 00383 *end; 00384 00385 GeometryInfo 00386 args; 00387 00388 KernelInfo 00389 *kernel; 00390 00391 MagickStatusType 00392 flags; 00393 00394 ssize_t 00395 type; 00396 00397 /* Parse special 'named' kernel */ 00398 GetMagickToken(kernel_string,&p,token); 00399 type=ParseCommandOption(MagickKernelOptions,MagickFalse,token); 00400 if ( type < 0 || type == UserDefinedKernel ) 00401 return((KernelInfo *)NULL); /* not a valid named kernel */ 00402 00403 while (((isspace((int) ((unsigned char) *p)) != 0) || 00404 (*p == ',') || (*p == ':' )) && (*p != '\0') && (*p != ';')) 00405 p++; 00406 00407 end = strchr(p, ';'); /* end of this kernel defintion */ 00408 if ( end == (char *) NULL ) 00409 end = strchr(p, '\0'); 00410 00411 /* ParseGeometry() needs the geometry separated! -- Arrgghh */ 00412 memcpy(token, p, (size_t) (end-p)); 00413 token[end-p] = '\0'; 00414 SetGeometryInfo(&args); 00415 flags = ParseGeometry(token, &args); 00416 00417 #if 0 00418 /* For Debugging Geometry Input */ 00419 (void) FormatLocaleFile(stderr, "Geometry = 0x%04X : %lg x %lg %+lg %+lg\n", 00420 flags, args.rho, args.sigma, args.xi, args.psi ); 00421 #endif 00422 00423 /* special handling of missing values in input string */ 00424 switch( type ) { 00425 /* Shape Kernel Defaults */ 00426 case UnityKernel: 00427 if ( (flags & WidthValue) == 0 ) 00428 args.rho = 1.0; /* Default scale = 1.0, zero is valid */ 00429 break; 00430 case SquareKernel: 00431 case DiamondKernel: 00432 case OctagonKernel: 00433 case DiskKernel: 00434 case PlusKernel: 00435 case CrossKernel: 00436 if ( (flags & HeightValue) == 0 ) 00437 args.sigma = 1.0; /* Default scale = 1.0, zero is valid */ 00438 break; 00439 case RingKernel: 00440 if ( (flags & XValue) == 0 ) 00441 args.xi = 1.0; /* Default scale = 1.0, zero is valid */ 00442 break; 00443 case RectangleKernel: /* Rectangle - set size defaults */ 00444 if ( (flags & WidthValue) == 0 ) /* if no width then */ 00445 args.rho = args.sigma; /* then width = height */ 00446 if ( args.rho < 1.0 ) /* if width too small */ 00447 args.rho = 3; /* then width = 3 */ 00448 if ( args.sigma < 1.0 ) /* if height too small */ 00449 args.sigma = args.rho; /* then height = width */ 00450 if ( (flags & XValue) == 0 ) /* center offset if not defined */ 00451 args.xi = (double)(((ssize_t)args.rho-1)/2); 00452 if ( (flags & YValue) == 0 ) 00453 args.psi = (double)(((ssize_t)args.sigma-1)/2); 00454 break; 00455 /* Distance Kernel Defaults */ 00456 case ChebyshevKernel: 00457 case ManhattanKernel: 00458 case OctagonalKernel: 00459 case EuclideanKernel: 00460 if ( (flags & HeightValue) == 0 ) /* no distance scale */ 00461 args.sigma = 100.0; /* default distance scaling */ 00462 else if ( (flags & AspectValue ) != 0 ) /* '!' flag */ 00463 args.sigma = QuantumRange/(args.sigma+1); /* maximum pixel distance */ 00464 else if ( (flags & PercentValue ) != 0 ) /* '%' flag */ 00465 args.sigma *= QuantumRange/100.0; /* percentage of color range */ 00466 break; 00467 default: 00468 break; 00469 } 00470 00471 kernel = AcquireKernelBuiltIn((KernelInfoType)type, &args); 00472 if ( kernel == (KernelInfo *) NULL ) 00473 return(kernel); 00474 00475 /* global expand to rotated kernel list - only for single kernels */ 00476 if ( kernel->next == (KernelInfo *) NULL ) { 00477 if ( (flags & AreaValue) != 0 ) /* '@' symbol in kernel args */ 00478 ExpandRotateKernelInfo(kernel, 45.0); 00479 else if ( (flags & GreaterValue) != 0 ) /* '>' symbol in kernel args */ 00480 ExpandRotateKernelInfo(kernel, 90.0); 00481 else if ( (flags & LessValue) != 0 ) /* '<' symbol in kernel args */ 00482 ExpandMirrorKernelInfo(kernel); 00483 } 00484 00485 return(kernel); 00486 } 00487 00488 MagickExport KernelInfo *AcquireKernelInfo(const char *kernel_string) 00489 { 00490 00491 KernelInfo 00492 *kernel, 00493 *new_kernel; 00494 00495 char 00496 token[MaxTextExtent]; 00497 00498 const char 00499 *p; 00500 00501 size_t 00502 kernel_number; 00503 00504 if (kernel_string == (const char *) NULL) 00505 return(ParseKernelArray(kernel_string)); 00506 p = kernel_string; 00507 kernel = NULL; 00508 kernel_number = 0; 00509 00510 while ( GetMagickToken(p,NULL,token), *token != '\0' ) { 00511 00512 /* ignore extra or multiple ';' kernel separators */ 00513 if ( *token != ';' ) { 00514 00515 /* tokens starting with alpha is a Named kernel */ 00516 if (isalpha((int) *token) != 0) 00517 new_kernel = ParseKernelName(p); 00518 else /* otherwise a user defined kernel array */ 00519 new_kernel = ParseKernelArray(p); 00520 00521 /* Error handling -- this is not proper error handling! */ 00522 if ( new_kernel == (KernelInfo *) NULL ) { 00523 (void) FormatLocaleFile(stderr, "Failed to parse kernel number #%.20g\n", 00524 (double) kernel_number); 00525 if ( kernel != (KernelInfo *) NULL ) 00526 kernel=DestroyKernelInfo(kernel); 00527 return((KernelInfo *) NULL); 00528 } 00529 00530 /* initialise or append the kernel list */ 00531 if ( kernel == (KernelInfo *) NULL ) 00532 kernel = new_kernel; 00533 else 00534 LastKernelInfo(kernel)->next = new_kernel; 00535 } 00536 00537 /* look for the next kernel in list */ 00538 p = strchr(p, ';'); 00539 if ( p == (char *) NULL ) 00540 break; 00541 p++; 00542 00543 } 00544 return(kernel); 00545 } 00546 00547 00548 /* 00549 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 00550 % % 00551 % % 00552 % % 00553 % A c q u i r e K e r n e l B u i l t I n % 00554 % % 00555 % % 00556 % % 00557 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 00558 % 00559 % AcquireKernelBuiltIn() returned one of the 'named' built-in types of 00560 % kernels used for special purposes such as gaussian blurring, skeleton 00561 % pruning, and edge distance determination. 00562 % 00563 % They take a KernelType, and a set of geometry style arguments, which were 00564 % typically decoded from a user supplied string, or from a more complex 00565 % Morphology Method that was requested. 00566 % 00567 % The format of the AcquireKernalBuiltIn method is: 00568 % 00569 % KernelInfo *AcquireKernelBuiltIn(const KernelInfoType type, 00570 % const GeometryInfo args) 00571 % 00572 % A description of each parameter follows: 00573 % 00574 % o type: the pre-defined type of kernel wanted 00575 % 00576 % o args: arguments defining or modifying the kernel 00577 % 00578 % Convolution Kernels 00579 % 00580 % Unity 00581 % The a No-Op or Scaling single element kernel. 00582 % 00583 % Gaussian:{radius},{sigma} 00584 % Generate a two-dimensional gaussian kernel, as used by -gaussian. 00585 % The sigma for the curve is required. The resulting kernel is 00586 % normalized, 00587 % 00588 % If 'sigma' is zero, you get a single pixel on a field of zeros. 00589 % 00590 % NOTE: that the 'radius' is optional, but if provided can limit (clip) 00591 % the final size of the resulting kernel to a square 2*radius+1 in size. 00592 % The radius should be at least 2 times that of the sigma value, or 00593 % sever clipping and aliasing may result. If not given or set to 0 the 00594 % radius will be determined so as to produce the best minimal error 00595 % result, which is usally much larger than is normally needed. 00596 % 00597 % LoG:{radius},{sigma} 00598 % "Laplacian of a Gaussian" or "Mexician Hat" Kernel. 00599 % The supposed ideal edge detection, zero-summing kernel. 00600 % 00601 % An alturnative to this kernel is to use a "DoG" with a sigma ratio of 00602 % approx 1.6 (according to wikipedia). 00603 % 00604 % DoG:{radius},{sigma1},{sigma2} 00605 % "Difference of Gaussians" Kernel. 00606 % As "Gaussian" but with a gaussian produced by 'sigma2' subtracted 00607 % from the gaussian produced by 'sigma1'. Typically sigma2 > sigma1. 00608 % The result is a zero-summing kernel. 00609 % 00610 % Blur:{radius},{sigma}[,{angle}] 00611 % Generates a 1 dimensional or linear gaussian blur, at the angle given 00612 % (current restricted to orthogonal angles). If a 'radius' is given the 00613 % kernel is clipped to a width of 2*radius+1. Kernel can be rotated 00614 % by a 90 degree angle. 00615 % 00616 % If 'sigma' is zero, you get a single pixel on a field of zeros. 00617 % 00618 % Note that two convolutions with two "Blur" kernels perpendicular to 00619 % each other, is equivalent to a far larger "Gaussian" kernel with the 00620 % same sigma value, However it is much faster to apply. This is how the 00621 % "-blur" operator actually works. 00622 % 00623 % Comet:{width},{sigma},{angle} 00624 % Blur in one direction only, much like how a bright object leaves 00625 % a comet like trail. The Kernel is actually half a gaussian curve, 00626 % Adding two such blurs in opposite directions produces a Blur Kernel. 00627 % Angle can be rotated in multiples of 90 degrees. 00628 % 00629 % Note that the first argument is the width of the kernel and not the 00630 % radius of the kernel. 00631 % 00632 % # Still to be implemented... 00633 % # 00634 % # Filter2D 00635 % # Filter1D 00636 % # Set kernel values using a resize filter, and given scale (sigma) 00637 % # Cylindrical or Linear. Is this possible with an image? 00638 % # 00639 % 00640 % Named Constant Convolution Kernels 00641 % 00642 % All these are unscaled, zero-summing kernels by default. As such for 00643 % non-HDRI version of ImageMagick some form of normalization, user scaling, 00644 % and biasing the results is recommended, to prevent the resulting image 00645 % being 'clipped'. 00646 % 00647 % The 3x3 kernels (most of these) can be circularly rotated in multiples of 00648 % 45 degrees to generate the 8 angled varients of each of the kernels. 00649 % 00650 % Laplacian:{type} 00651 % Discrete Lapacian Kernels, (without normalization) 00652 % Type 0 : 3x3 with center:8 surounded by -1 (8 neighbourhood) 00653 % Type 1 : 3x3 with center:4 edge:-1 corner:0 (4 neighbourhood) 00654 % Type 2 : 3x3 with center:4 edge:1 corner:-2 00655 % Type 3 : 3x3 with center:4 edge:-2 corner:1 00656 % Type 5 : 5x5 laplacian 00657 % Type 7 : 7x7 laplacian 00658 % Type 15 : 5x5 LoG (sigma approx 1.4) 00659 % Type 19 : 9x9 LoG (sigma approx 1.4) 00660 % 00661 % Sobel:{angle} 00662 % Sobel 'Edge' convolution kernel (3x3) 00663 % | -1, 0, 1 | 00664 % | -2, 0,-2 | 00665 % | -1, 0, 1 | 00666 % 00667 % Roberts:{angle} 00668 % Roberts convolution kernel (3x3) 00669 % | 0, 0, 0 | 00670 % | -1, 1, 0 | 00671 % | 0, 0, 0 | 00672 % 00673 % Prewitt:{angle} 00674 % Prewitt Edge convolution kernel (3x3) 00675 % | -1, 0, 1 | 00676 % | -1, 0, 1 | 00677 % | -1, 0, 1 | 00678 % 00679 % Compass:{angle} 00680 % Prewitt's "Compass" convolution kernel (3x3) 00681 % | -1, 1, 1 | 00682 % | -1,-2, 1 | 00683 % | -1, 1, 1 | 00684 % 00685 % Kirsch:{angle} 00686 % Kirsch's "Compass" convolution kernel (3x3) 00687 % | -3,-3, 5 | 00688 % | -3, 0, 5 | 00689 % | -3,-3, 5 | 00690 % 00691 % FreiChen:{angle} 00692 % Frei-Chen Edge Detector is based on a kernel that is similar to 00693 % the Sobel Kernel, but is designed to be isotropic. That is it takes 00694 % into account the distance of the diagonal in the kernel. 00695 % 00696 % | 1, 0, -1 | 00697 % | sqrt(2), 0, -sqrt(2) | 00698 % | 1, 0, -1 | 00699 % 00700 % FreiChen:{type},{angle} 00701 % 00702 % Frei-Chen Pre-weighted kernels... 00703 % 00704 % Type 0: default un-nomalized version shown above. 00705 % 00706 % Type 1: Orthogonal Kernel (same as type 11 below) 00707 % | 1, 0, -1 | 00708 % | sqrt(2), 0, -sqrt(2) | / 2*sqrt(2) 00709 % | 1, 0, -1 | 00710 % 00711 % Type 2: Diagonal form of Kernel... 00712 % | 1, sqrt(2), 0 | 00713 % | sqrt(2), 0, -sqrt(2) | / 2*sqrt(2) 00714 % | 0, -sqrt(2) -1 | 00715 % 00716 % However this kernel is als at the heart of the FreiChen Edge Detection 00717 % Process which uses a set of 9 specially weighted kernel. These 9 00718 % kernels not be normalized, but directly applied to the image. The 00719 % results is then added together, to produce the intensity of an edge in 00720 % a specific direction. The square root of the pixel value can then be 00721 % taken as the cosine of the edge, and at least 2 such runs at 90 degrees 00722 % from each other, both the direction and the strength of the edge can be 00723 % determined. 00724 % 00725 % Type 10: All 9 of the following pre-weighted kernels... 00726 % 00727 % Type 11: | 1, 0, -1 | 00728 % | sqrt(2), 0, -sqrt(2) | / 2*sqrt(2) 00729 % | 1, 0, -1 | 00730 % 00731 % Type 12: | 1, sqrt(2), 1 | 00732 % | 0, 0, 0 | / 2*sqrt(2) 00733 % | 1, sqrt(2), 1 | 00734 % 00735 % Type 13: | sqrt(2), -1, 0 | 00736 % | -1, 0, 1 | / 2*sqrt(2) 00737 % | 0, 1, -sqrt(2) | 00738 % 00739 % Type 14: | 0, 1, -sqrt(2) | 00740 % | -1, 0, 1 | / 2*sqrt(2) 00741 % | sqrt(2), -1, 0 | 00742 % 00743 % Type 15: | 0, -1, 0 | 00744 % | 1, 0, 1 | / 2 00745 % | 0, -1, 0 | 00746 % 00747 % Type 16: | 1, 0, -1 | 00748 % | 0, 0, 0 | / 2 00749 % | -1, 0, 1 | 00750 % 00751 % Type 17: | 1, -2, 1 | 00752 % | -2, 4, -2 | / 6 00753 % | -1, -2, 1 | 00754 % 00755 % Type 18: | -2, 1, -2 | 00756 % | 1, 4, 1 | / 6 00757 % | -2, 1, -2 | 00758 % 00759 % Type 19: | 1, 1, 1 | 00760 % | 1, 1, 1 | / 3 00761 % | 1, 1, 1 | 00762 % 00763 % The first 4 are for edge detection, the next 4 are for line detection 00764 % and the last is to add a average component to the results. 00765 % 00766 % Using a special type of '-1' will return all 9 pre-weighted kernels 00767 % as a multi-kernel list, so that you can use them directly (without 00768 % normalization) with the special "-set option:morphology:compose Plus" 00769 % setting to apply the full FreiChen Edge Detection Technique. 00770 % 00771 % If 'type' is large it will be taken to be an actual rotation angle for 00772 % the default FreiChen (type 0) kernel. As such FreiChen:45 will look 00773 % like a Sobel:45 but with 'sqrt(2)' instead of '2' values. 00774 % 00775 % WARNING: The above was layed out as per 00776 % http://www.math.tau.ac.il/~turkel/notes/edge_detectors.pdf 00777 % But rotated 90 degrees so direction is from left rather than the top. 00778 % I have yet to find any secondary confirmation of the above. The only 00779 % other source found was actual source code at 00780 % http://ltswww.epfl.ch/~courstiv/exos_labos/sol3.pdf 00781 % Neigher paper defineds the kernels in a way that looks locical or 00782 % correct when taken as a whole. 00783 % 00784 % Boolean Kernels 00785 % 00786 % Diamond:[{radius}[,{scale}]] 00787 % Generate a diamond shaped kernel with given radius to the points. 00788 % Kernel size will again be radius*2+1 square and defaults to radius 1, 00789 % generating a 3x3 kernel that is slightly larger than a square. 00790 % 00791 % Square:[{radius}[,{scale}]] 00792 % Generate a square shaped kernel of size radius*2+1, and defaulting 00793 % to a 3x3 (radius 1). 00794 % 00795 % Octagon:[{radius}[,{scale}]] 00796 % Generate octagonal shaped kernel of given radius and constant scale. 00797 % Default radius is 3 producing a 7x7 kernel. A radius of 1 will result 00798 % in "Diamond" kernel. 00799 % 00800 % Disk:[{radius}[,{scale}]] 00801 % Generate a binary disk, thresholded at the radius given, the radius 00802 % may be a float-point value. Final Kernel size is floor(radius)*2+1 00803 % square. A radius of 5.3 is the default. 00804 % 00805 % NOTE: That a low radii Disk kernels produce the same results as 00806 % many of the previously defined kernels, but differ greatly at larger 00807 % radii. Here is a table of equivalences... 00808 % "Disk:1" => "Diamond", "Octagon:1", or "Cross:1" 00809 % "Disk:1.5" => "Square" 00810 % "Disk:2" => "Diamond:2" 00811 % "Disk:2.5" => "Octagon" 00812 % "Disk:2.9" => "Square:2" 00813 % "Disk:3.5" => "Octagon:3" 00814 % "Disk:4.5" => "Octagon:4" 00815 % "Disk:5.4" => "Octagon:5" 00816 % "Disk:6.4" => "Octagon:6" 00817 % All other Disk shapes are unique to this kernel, but because a "Disk" 00818 % is more circular when using a larger radius, using a larger radius is 00819 % preferred over iterating the morphological operation. 00820 % 00821 % Rectangle:{geometry} 00822 % Simply generate a rectangle of 1's with the size given. You can also 00823 % specify the location of the 'control point', otherwise the closest 00824 % pixel to the center of the rectangle is selected. 00825 % 00826 % Properly centered and odd sized rectangles work the best. 00827 % 00828 % Symbol Dilation Kernels 00829 % 00830 % These kernel is not a good general morphological kernel, but is used 00831 % more for highlighting and marking any single pixels in an image using, 00832 % a "Dilate" method as appropriate. 00833 % 00834 % For the same reasons iterating these kernels does not produce the 00835 % same result as using a larger radius for the symbol. 00836 % 00837 % Plus:[{radius}[,{scale}]] 00838 % Cross:[{radius}[,{scale}]] 00839 % Generate a kernel in the shape of a 'plus' or a 'cross' with 00840 % a each arm the length of the given radius (default 2). 00841 % 00842 % NOTE: "plus:1" is equivalent to a "Diamond" kernel. 00843 % 00844 % Ring:{radius1},{radius2}[,{scale}] 00845 % A ring of the values given that falls between the two radii. 00846 % Defaults to a ring of approximataly 3 radius in a 7x7 kernel. 00847 % This is the 'edge' pixels of the default "Disk" kernel, 00848 % More specifically, "Ring" -> "Ring:2.5,3.5,1.0" 00849 % 00850 % Hit and Miss Kernels 00851 % 00852 % Peak:radius1,radius2 00853 % Find any peak larger than the pixels the fall between the two radii. 00854 % The default ring of pixels is as per "Ring". 00855 % Edges 00856 % Find flat orthogonal edges of a binary shape 00857 % Corners 00858 % Find 90 degree corners of a binary shape 00859 % Diagonals:type 00860 % A special kernel to thin the 'outside' of diagonals 00861 % LineEnds:type 00862 % Find end points of lines (for pruning a skeletion) 00863 % Two types of lines ends (default to both) can be searched for 00864 % Type 0: All line ends 00865 % Type 1: single kernel for 4-conneected line ends 00866 % Type 2: single kernel for simple line ends 00867 % LineJunctions 00868 % Find three line junctions (within a skeletion) 00869 % Type 0: all line junctions 00870 % Type 1: Y Junction kernel 00871 % Type 2: Diagonal T Junction kernel 00872 % Type 3: Orthogonal T Junction kernel 00873 % Type 4: Diagonal X Junction kernel 00874 % Type 5: Orthogonal + Junction kernel 00875 % Ridges:type 00876 % Find single pixel ridges or thin lines 00877 % Type 1: Fine single pixel thick lines and ridges 00878 % Type 2: Find two pixel thick lines and ridges 00879 % ConvexHull 00880 % Octagonal Thickening Kernel, to generate convex hulls of 45 degrees 00881 % Skeleton:type 00882 % Traditional skeleton generating kernels. 00883 % Type 1: Tradional Skeleton kernel (4 connected skeleton) 00884 % Type 2: HIPR2 Skeleton kernel (8 connected skeleton) 00885 % Type 3: Thinning skeleton based on a ressearch paper by 00886 % Dan S. Bloomberg (Default Type) 00887 % ThinSE:type 00888 % A huge variety of Thinning Kernels designed to preserve conectivity. 00889 % many other kernel sets use these kernels as source definitions. 00890 % Type numbers are 41-49, 81-89, 481, and 482 which are based on 00891 % the super and sub notations used in the source research paper. 00892 % 00893 % Distance Measuring Kernels 00894 % 00895 % Different types of distance measuring methods, which are used with the 00896 % a 'Distance' morphology method for generating a gradient based on 00897 % distance from an edge of a binary shape, though there is a technique 00898 % for handling a anti-aliased shape. 00899 % 00900 % See the 'Distance' Morphological Method, for information of how it is 00901 % applied. 00902 % 00903 % Chebyshev:[{radius}][x{scale}[%!]] 00904 % Chebyshev Distance (also known as Tchebychev or Chessboard distance) 00905 % is a value of one to any neighbour, orthogonal or diagonal. One why 00906 % of thinking of it is the number of squares a 'King' or 'Queen' in 00907 % chess needs to traverse reach any other position on a chess board. 00908 % It results in a 'square' like distance function, but one where 00909 % diagonals are given a value that is closer than expected. 00910 % 00911 % Manhattan:[{radius}][x{scale}[%!]] 00912 % Manhattan Distance (also known as Rectilinear, City Block, or the Taxi 00913 % Cab distance metric), it is the distance needed when you can only 00914 % travel in horizontal or vertical directions only. It is the 00915 % distance a 'Rook' in chess would have to travel, and results in a 00916 % diamond like distances, where diagonals are further than expected. 00917 % 00918 % Octagonal:[{radius}][x{scale}[%!]] 00919 % An interleving of Manhatten and Chebyshev metrics producing an 00920 % increasing octagonally shaped distance. Distances matches those of 00921 % the "Octagon" shaped kernel of the same radius. The minimum radius 00922 % and default is 2, producing a 5x5 kernel. 00923 % 00924 % Euclidean:[{radius}][x{scale}[%!]] 00925 % Euclidean distance is the 'direct' or 'as the crow flys' distance. 00926 % However by default the kernel size only has a radius of 1, which 00927 % limits the distance to 'Knight' like moves, with only orthogonal and 00928 % diagonal measurements being correct. As such for the default kernel 00929 % you will get octagonal like distance function. 00930 % 00931 % However using a larger radius such as "Euclidean:4" you will get a 00932 % much smoother distance gradient from the edge of the shape. Especially 00933 % if the image is pre-processed to include any anti-aliasing pixels. 00934 % Of course a larger kernel is slower to use, and not always needed. 00935 % 00936 % The first three Distance Measuring Kernels will only generate distances 00937 % of exact multiples of {scale} in binary images. As such you can use a 00938 % scale of 1 without loosing any information. However you also need some 00939 % scaling when handling non-binary anti-aliased shapes. 00940 % 00941 % The "Euclidean" Distance Kernel however does generate a non-integer 00942 % fractional results, and as such scaling is vital even for binary shapes. 00943 % 00944 */ 00945 00946 MagickExport KernelInfo *AcquireKernelBuiltIn(const KernelInfoType type, 00947 const GeometryInfo *args) 00948 { 00949 KernelInfo 00950 *kernel; 00951 00952 register ssize_t 00953 i; 00954 00955 register ssize_t 00956 u, 00957 v; 00958 00959 double 00960 nan = sqrt((double)-1.0); /* Special Value : Not A Number */ 00961 00962 /* Generate a new empty kernel if needed */ 00963 kernel=(KernelInfo *) NULL; 00964 switch(type) { 00965 case UndefinedKernel: /* These should not call this function */ 00966 case UserDefinedKernel: 00967 assert("Should not call this function" != (char *)NULL); 00968 break; 00969 case LaplacianKernel: /* Named Descrete Convolution Kernels */ 00970 case SobelKernel: /* these are defined using other kernels */ 00971 case RobertsKernel: 00972 case PrewittKernel: 00973 case CompassKernel: 00974 case KirschKernel: 00975 case FreiChenKernel: 00976 case EdgesKernel: /* Hit and Miss kernels */ 00977 case CornersKernel: 00978 case DiagonalsKernel: 00979 case LineEndsKernel: 00980 case LineJunctionsKernel: 00981 case RidgesKernel: 00982 case ConvexHullKernel: 00983 case SkeletonKernel: 00984 case ThinSEKernel: 00985 break; /* A pre-generated kernel is not needed */ 00986 #if 0 00987 /* set to 1 to do a compile-time check that we haven't missed anything */ 00988 case UnityKernel: 00989 case GaussianKernel: 00990 case DoGKernel: 00991 case LoGKernel: 00992 case BlurKernel: 00993 case CometKernel: 00994 case DiamondKernel: 00995 case SquareKernel: 00996 case RectangleKernel: 00997 case OctagonKernel: 00998 case DiskKernel: 00999 case PlusKernel: 01000 case CrossKernel: 01001 case RingKernel: 01002 case PeaksKernel: 01003 case ChebyshevKernel: 01004 case ManhattanKernel: 01005 case OctangonalKernel: 01006 case EuclideanKernel: 01007 #else 01008 default: 01009 #endif 01010 /* Generate the base Kernel Structure */ 01011 kernel=(KernelInfo *) AcquireMagickMemory(sizeof(*kernel)); 01012 if (kernel == (KernelInfo *) NULL) 01013 return(kernel); 01014 (void) ResetMagickMemory(kernel,0,sizeof(*kernel)); 01015 kernel->minimum = kernel->maximum = kernel->angle = 0.0; 01016 kernel->negative_range = kernel->positive_range = 0.0; 01017 kernel->type = type; 01018 kernel->next = (KernelInfo *) NULL; 01019 kernel->signature = MagickSignature; 01020 break; 01021 } 01022 01023 switch(type) { 01024 /* 01025 Convolution Kernels 01026 */ 01027 case UnityKernel: 01028 { 01029 kernel->height = kernel->width = (size_t) 1; 01030 kernel->x = kernel->y = (ssize_t) 0; 01031 kernel->values=(MagickRealType *) AcquireAlignedMemory(1, 01032 sizeof(*kernel->values)); 01033 if (kernel->values == (MagickRealType *) NULL) 01034 return(DestroyKernelInfo(kernel)); 01035 kernel->maximum = kernel->values[0] = args->rho; 01036 break; 01037 } 01038 break; 01039 case GaussianKernel: 01040 case DoGKernel: 01041 case LoGKernel: 01042 { double 01043 sigma = fabs(args->sigma), 01044 sigma2 = fabs(args->xi), 01045 A, B, R; 01046 01047 if ( args->rho >= 1.0 ) 01048 kernel->width = (size_t)args->rho*2+1; 01049 else if ( (type != DoGKernel) || (sigma >= sigma2) ) 01050 kernel->width = GetOptimalKernelWidth2D(args->rho,sigma); 01051 else 01052 kernel->width = GetOptimalKernelWidth2D(args->rho,sigma2); 01053 kernel->height = kernel->width; 01054 kernel->x = kernel->y = (ssize_t) (kernel->width-1)/2; 01055 kernel->values=(MagickRealType *) AcquireAlignedMemory(kernel->width, 01056 kernel->height*sizeof(*kernel->values)); 01057 if (kernel->values == (MagickRealType *) NULL) 01058 return(DestroyKernelInfo(kernel)); 01059 01060 /* WARNING: The following generates a 'sampled gaussian' kernel. 01061 * What we really want is a 'discrete gaussian' kernel. 01062 * 01063 * How to do this is I don't know, but appears to be basied on the 01064 * Error Function 'erf()' (intergral of a gaussian) 01065 */ 01066 01067 if ( type == GaussianKernel || type == DoGKernel ) 01068 { /* Calculate a Gaussian, OR positive half of a DoG */ 01069 if ( sigma > MagickEpsilon ) 01070 { A = 1.0/(2.0*sigma*sigma); /* simplify loop expressions */ 01071 B = (double) (1.0/(Magick2PI*sigma*sigma)); 01072 for ( i=0, v=-kernel->y; v <= (ssize_t)kernel->y; v++) 01073 for ( u=-kernel->x; u <= (ssize_t)kernel->x; u++, i++) 01074 kernel->values[i] = exp(-((double)(u*u+v*v))*A)*B; 01075 } 01076 else /* limiting case - a unity (normalized Dirac) kernel */ 01077 { (void) ResetMagickMemory(kernel->values,0, (size_t) 01078 kernel->width*kernel->height*sizeof(double)); 01079 kernel->values[kernel->x+kernel->y*kernel->width] = 1.0; 01080 } 01081 } 01082 01083 if ( type == DoGKernel ) 01084 { /* Subtract a Negative Gaussian for "Difference of Gaussian" */ 01085 if ( sigma2 > MagickEpsilon ) 01086 { sigma = sigma2; /* simplify loop expressions */ 01087 A = 1.0/(2.0*sigma*sigma); 01088 B = (double) (1.0/(Magick2PI*sigma*sigma)); 01089 for ( i=0, v=-kernel->y; v <= (ssize_t)kernel->y; v++) 01090 for ( u=-kernel->x; u <= (ssize_t)kernel->x; u++, i++) 01091 kernel->values[i] -= exp(-((double)(u*u+v*v))*A)*B; 01092 } 01093 else /* limiting case - a unity (normalized Dirac) kernel */ 01094 kernel->values[kernel->x+kernel->y*kernel->width] -= 1.0; 01095 } 01096 01097 if ( type == LoGKernel ) 01098 { /* Calculate a Laplacian of a Gaussian - Or Mexician Hat */ 01099 if ( sigma > MagickEpsilon ) 01100 { A = 1.0/(2.0*sigma*sigma); /* simplify loop expressions */ 01101 B = (double) (1.0/(MagickPI*sigma*sigma*sigma*sigma)); 01102 for ( i=0, v=-kernel->y; v <= (ssize_t)kernel->y; v++) 01103 for ( u=-kernel->x; u <= (ssize_t)kernel->x; u++, i++) 01104 { R = ((double)(u*u+v*v))*A; 01105 kernel->values[i] = (1-R)*exp(-R)*B; 01106 } 01107 } 01108 else /* special case - generate a unity kernel */ 01109 { (void) ResetMagickMemory(kernel->values,0, (size_t) 01110 kernel->width*kernel->height*sizeof(double)); 01111 kernel->values[kernel->x+kernel->y*kernel->width] = 1.0; 01112 } 01113 } 01114 01115 /* Note the above kernels may have been 'clipped' by a user defined 01116 ** radius, producing a smaller (darker) kernel. Also for very small 01117 ** sigma's (> 0.1) the central value becomes larger than one, and thus 01118 ** producing a very bright kernel. 01119 ** 01120 ** Normalization will still be needed. 01121 */ 01122 01123 /* Normalize the 2D Gaussian Kernel 01124 ** 01125 ** NB: a CorrelateNormalize performs a normal Normalize if 01126 ** there are no negative values. 01127 */ 01128 CalcKernelMetaData(kernel); /* the other kernel meta-data */ 01129 ScaleKernelInfo(kernel, 1.0, CorrelateNormalizeValue); 01130 01131 break; 01132 } 01133 case BlurKernel: 01134 { double 01135 sigma = fabs(args->sigma), 01136 alpha, beta; 01137 01138 if ( args->rho >= 1.0 ) 01139 kernel->width = (size_t)args->rho*2+1; 01140 else 01141 kernel->width = GetOptimalKernelWidth1D(args->rho,sigma); 01142 kernel->height = 1; 01143 kernel->x = (ssize_t) (kernel->width-1)/2; 01144 kernel->y = 0; 01145 kernel->negative_range = kernel->positive_range = 0.0; 01146 kernel->values=(MagickRealType *) AcquireAlignedMemory(kernel->width, 01147 kernel->height*sizeof(*kernel->values)); 01148 if (kernel->values == (MagickRealType *) NULL) 01149 return(DestroyKernelInfo(kernel)); 01150 01151 #if 1 01152 #define KernelRank 3 01153 /* Formula derived from GetBlurKernel() in "effect.c" (plus bug fix). 01154 ** It generates a gaussian 3 times the width, and compresses it into 01155 ** the expected range. This produces a closer normalization of the 01156 ** resulting kernel, especially for very low sigma values. 01157 ** As such while wierd it is prefered. 01158 ** 01159 ** I am told this method originally came from Photoshop. 01160 ** 01161 ** A properly normalized curve is generated (apart from edge clipping) 01162 ** even though we later normalize the result (for edge clipping) 01163 ** to allow the correct generation of a "Difference of Blurs". 01164 */ 01165 01166 /* initialize */ 01167 v = (ssize_t) (kernel->width*KernelRank-1)/2; /* start/end points to fit range */ 01168 (void) ResetMagickMemory(kernel->values,0, (size_t) 01169 kernel->width*kernel->height*sizeof(double)); 01170 /* Calculate a Positive 1D Gaussian */ 01171 if ( sigma > MagickEpsilon ) 01172 { sigma *= KernelRank; /* simplify loop expressions */ 01173 alpha = 1.0/(2.0*sigma*sigma); 01174 beta= (double) (1.0/(MagickSQ2PI*sigma )); 01175 for ( u=-v; u <= v; u++) { 01176 kernel->values[(u+v)/KernelRank] += 01177 exp(-((double)(u*u))*alpha)*beta; 01178 } 01179 } 01180 else /* special case - generate a unity kernel */ 01181 kernel->values[kernel->x+kernel->y*kernel->width] = 1.0; 01182 #else 01183 /* Direct calculation without curve averaging */ 01184 01185 /* Calculate a Positive Gaussian */ 01186 if ( sigma > MagickEpsilon ) 01187 { alpha = 1.0/(2.0*sigma*sigma); /* simplify loop expressions */ 01188 beta = 1.0/(MagickSQ2PI*sigma); 01189 for ( i=0, u=-kernel->x; u <= (ssize_t)kernel->x; u++, i++) 01190 kernel->values[i] = exp(-((double)(u*u))*alpha)*beta; 01191 } 01192 else /* special case - generate a unity kernel */ 01193 { (void) ResetMagickMemory(kernel->values,0, (size_t) 01194 kernel->width*kernel->height*sizeof(double)); 01195 kernel->values[kernel->x+kernel->y*kernel->width] = 1.0; 01196 } 01197 #endif 01198 /* Note the above kernel may have been 'clipped' by a user defined 01199 ** radius, producing a smaller (darker) kernel. Also for very small 01200 ** sigma's (> 0.1) the central value becomes larger than one, and thus 01201 ** producing a very bright kernel. 01202 ** 01203 ** Normalization will still be needed. 01204 */ 01205 01206 /* Normalize the 1D Gaussian Kernel 01207 ** 01208 ** NB: a CorrelateNormalize performs a normal Normalize if 01209 ** there are no negative values. 01210 */ 01211 CalcKernelMetaData(kernel); /* the other kernel meta-data */ 01212 ScaleKernelInfo(kernel, 1.0, CorrelateNormalizeValue); 01213 01214 /* rotate the 1D kernel by given angle */ 01215 RotateKernelInfo(kernel, args->xi ); 01216 break; 01217 } 01218 case CometKernel: 01219 { double 01220 sigma = fabs(args->sigma), 01221 A; 01222 01223 if ( args->rho < 1.0 ) 01224 kernel->width = (GetOptimalKernelWidth1D(args->rho,sigma)-1)/2+1; 01225 else 01226 kernel->width = (size_t)args->rho; 01227 kernel->x = kernel->y = 0; 01228 kernel->height = 1; 01229 kernel->negative_range = kernel->positive_range = 0.0; 01230 kernel->values=(MagickRealType *) AcquireAlignedMemory(kernel->width, 01231 kernel->height*sizeof(*kernel->values)); 01232 if (kernel->values == (MagickRealType *) NULL) 01233 return(DestroyKernelInfo(kernel)); 01234 01235 /* A comet blur is half a 1D gaussian curve, so that the object is 01236 ** blurred in one direction only. This may not be quite the right 01237 ** curve to use so may change in the future. The function must be 01238 ** normalised after generation, which also resolves any clipping. 01239 ** 01240 ** As we are normalizing and not subtracting gaussians, 01241 ** there is no need for a divisor in the gaussian formula 01242 ** 01243 ** It is less comples 01244 */ 01245 if ( sigma > MagickEpsilon ) 01246 { 01247 #if 1 01248 #define KernelRank 3 01249 v = (ssize_t) kernel->width*KernelRank; /* start/end points */ 01250 (void) ResetMagickMemory(kernel->values,0, (size_t) 01251 kernel->width*sizeof(double)); 01252 sigma *= KernelRank; /* simplify the loop expression */ 01253 A = 1.0/(2.0*sigma*sigma); 01254 /* B = 1.0/(MagickSQ2PI*sigma); */ 01255 for ( u=0; u < v; u++) { 01256 kernel->values[u/KernelRank] += 01257 exp(-((double)(u*u))*A); 01258 /* exp(-((double)(i*i))/2.0*sigma*sigma)/(MagickSQ2PI*sigma); */ 01259 } 01260 for (i=0; i < (ssize_t) kernel->width; i++) 01261 kernel->positive_range += kernel->values[i]; 01262 #else 01263 A = 1.0/(2.0*sigma*sigma); /* simplify the loop expression */ 01264 /* B = 1.0/(MagickSQ2PI*sigma); */ 01265 for ( i=0; i < (ssize_t) kernel->width; i++) 01266 kernel->positive_range += 01267 kernel->values[i] = 01268 exp(-((double)(i*i))*A); 01269 /* exp(-((double)(i*i))/2.0*sigma*sigma)/(MagickSQ2PI*sigma); */ 01270 #endif 01271 } 01272 else /* special case - generate a unity kernel */ 01273 { (void) ResetMagickMemory(kernel->values,0, (size_t) 01274 kernel->width*kernel->height*sizeof(double)); 01275 kernel->values[kernel->x+kernel->y*kernel->width] = 1.0; 01276 kernel->positive_range = 1.0; 01277 } 01278 01279 kernel->minimum = 0.0; 01280 kernel->maximum = kernel->values[0]; 01281 kernel->negative_range = 0.0; 01282 01283 ScaleKernelInfo(kernel, 1.0, NormalizeValue); /* Normalize */ 01284 RotateKernelInfo(kernel, args->xi); /* Rotate by angle */ 01285 break; 01286 } 01287 01288 /* 01289 Convolution Kernels - Well Known Named Constant Kernels 01290 */ 01291 case LaplacianKernel: 01292 { switch ( (int) args->rho ) { 01293 case 0: 01294 default: /* laplacian square filter -- default */ 01295 kernel=ParseKernelArray("3: -1,-1,-1 -1,8,-1 -1,-1,-1"); 01296 break; 01297 case 1: /* laplacian diamond filter */ 01298 kernel=ParseKernelArray("3: 0,-1,0 -1,4,-1 0,-1,0"); 01299 break; 01300 case 2: 01301 kernel=ParseKernelArray("3: -2,1,-2 1,4,1 -2,1,-2"); 01302 break; 01303 case 3: 01304 kernel=ParseKernelArray("3: 1,-2,1 -2,4,-2 1,-2,1"); 01305 break; 01306 case 5: /* a 5x5 laplacian */ 01307 kernel=ParseKernelArray( 01308 "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"); 01309 break; 01310 case 7: /* a 7x7 laplacian */ 01311 kernel=ParseKernelArray( 01312 "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" ); 01313 break; 01314 case 15: /* a 5x5 LoG (sigma approx 1.4) */ 01315 kernel=ParseKernelArray( 01316 "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"); 01317 break; 01318 case 19: /* a 9x9 LoG (sigma approx 1.4) */ 01319 /* http://www.cscjournals.org/csc/manuscript/Journals/IJIP/volume3/Issue1/IJIP-15.pdf */ 01320 kernel=ParseKernelArray( 01321 "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"); 01322 break; 01323 } 01324 if (kernel == (KernelInfo *) NULL) 01325 return(kernel); 01326 kernel->type = type; 01327 break; 01328 } 01329 case SobelKernel: 01330 { /* Simple Sobel Kernel */ 01331 kernel=ParseKernelArray("3: 1,0,-1 2,0,-2 1,0,-1"); 01332 if (kernel == (KernelInfo *) NULL) 01333 return(kernel); 01334 kernel->type = type; 01335 RotateKernelInfo(kernel, args->rho); 01336 break; 01337 } 01338 case RobertsKernel: 01339 { 01340 kernel=ParseKernelArray("3: 0,0,0 1,-1,0 0,0,0"); 01341 if (kernel == (KernelInfo *) NULL) 01342 return(kernel); 01343 kernel->type = type; 01344 RotateKernelInfo(kernel, args->rho); 01345 break; 01346 } 01347 case PrewittKernel: 01348 { 01349 kernel=ParseKernelArray("3: 1,0,-1 1,0,-1 1,0,-1"); 01350 if (kernel == (KernelInfo *) NULL) 01351 return(kernel); 01352 kernel->type = type; 01353 RotateKernelInfo(kernel, args->rho); 01354 break; 01355 } 01356 case CompassKernel: 01357 { 01358 kernel=ParseKernelArray("3: 1,1,-1 1,-2,-1 1,1,-1"); 01359 if (kernel == (KernelInfo *) NULL) 01360 return(kernel); 01361 kernel->type = type; 01362 RotateKernelInfo(kernel, args->rho); 01363 break; 01364 } 01365 case KirschKernel: 01366 { 01367 kernel=ParseKernelArray("3: 5,-3,-3 5,0,-3 5,-3,-3"); 01368 if (kernel == (KernelInfo *) NULL) 01369 return(kernel); 01370 kernel->type = type; 01371 RotateKernelInfo(kernel, args->rho); 01372 break; 01373 } 01374 case FreiChenKernel: 01375 /* Direction is set to be left to right positive */ 01376 /* http://www.math.tau.ac.il/~turkel/notes/edge_detectors.pdf -- RIGHT? */ 01377 /* http://ltswww.epfl.ch/~courstiv/exos_labos/sol3.pdf -- WRONG? */ 01378 { switch ( (int) args->rho ) { 01379 default: 01380 case 0: 01381 kernel=ParseKernelArray("3: 1,0,-1 2,0,-2 1,0,-1"); 01382 if (kernel == (KernelInfo *) NULL) 01383 return(kernel); 01384 kernel->type = type; 01385 kernel->values[3] = +MagickSQ2; 01386 kernel->values[5] = -MagickSQ2; 01387 CalcKernelMetaData(kernel); /* recalculate meta-data */ 01388 break; 01389 case 2: 01390 kernel=ParseKernelArray("3: 1,2,0 2,0,-2 0,-2,-1"); 01391 if (kernel == (KernelInfo *) NULL) 01392 return(kernel); 01393 kernel->type = type; 01394 kernel->values[1] = kernel->values[3] = +MagickSQ2; 01395 kernel->values[5] = kernel->values[7] = -MagickSQ2; 01396 CalcKernelMetaData(kernel); /* recalculate meta-data */ 01397 ScaleKernelInfo(kernel, (double) (1.0/2.0*MagickSQ2), NoValue); 01398 break; 01399 case 10: 01400 kernel=AcquireKernelInfo("FreiChen:11;FreiChen:12;FreiChen:13;FreiChen:14;FreiChen:15;FreiChen:16;FreiChen:17;FreiChen:18;FreiChen:19"); 01401 if (kernel == (KernelInfo *) NULL) 01402 return(kernel); 01403 break; 01404 case 1: 01405 case 11: 01406 kernel=ParseKernelArray("3: 1,0,-1 2,0,-2 1,0,-1"); 01407 if (kernel == (KernelInfo *) NULL) 01408 return(kernel); 01409 kernel->type = type; 01410 kernel->values[3] = +MagickSQ2; 01411 kernel->values[5] = -MagickSQ2; 01412 CalcKernelMetaData(kernel); /* recalculate meta-data */ 01413 ScaleKernelInfo(kernel, (double) (1.0/2.0*MagickSQ2), NoValue); 01414 break; 01415 case 12: 01416 kernel=ParseKernelArray("3: 1,2,1 0,0,0 1,2,1"); 01417 if (kernel == (KernelInfo *) NULL) 01418 return(kernel); 01419 kernel->type = type; 01420 kernel->values[1] = +MagickSQ2; 01421 kernel->values[7] = +MagickSQ2; 01422 CalcKernelMetaData(kernel); 01423 ScaleKernelInfo(kernel, (double) (1.0/2.0*MagickSQ2), NoValue); 01424 break; 01425 case 13: 01426 kernel=ParseKernelArray("3: 2,-1,0 -1,0,1 0,1,-2"); 01427 if (kernel == (KernelInfo *) NULL) 01428 return(kernel); 01429 kernel->type = type; 01430 kernel->values[0] = +MagickSQ2; 01431 kernel->values[8] = -MagickSQ2; 01432 CalcKernelMetaData(kernel); 01433 ScaleKernelInfo(kernel, (double) (1.0/2.0*MagickSQ2), NoValue); 01434 break; 01435 case 14: 01436 kernel=ParseKernelArray("3: 0,1,-2 -1,0,1 2,-1,0"); 01437 if (kernel == (KernelInfo *) NULL) 01438 return(kernel); 01439 kernel->type = type; 01440 kernel->values[2] = -MagickSQ2; 01441 kernel->values[6] = +MagickSQ2; 01442 CalcKernelMetaData(kernel); 01443 ScaleKernelInfo(kernel, (double) (1.0/2.0*MagickSQ2), NoValue); 01444 break; 01445 case 15: 01446 kernel=ParseKernelArray("3: 0,-1,0 1,0,1 0,-1,0"); 01447 if (kernel == (KernelInfo *) NULL) 01448 return(kernel); 01449 kernel->type = type; 01450 ScaleKernelInfo(kernel, 1.0/2.0, NoValue); 01451 break; 01452 case 16: 01453 kernel=ParseKernelArray("3: 1,0,-1 0,0,0 -1,0,1"); 01454 if (kernel == (KernelInfo *) NULL) 01455 return(kernel); 01456 kernel->type = type; 01457 ScaleKernelInfo(kernel, 1.0/2.0, NoValue); 01458 break; 01459 case 17: 01460 kernel=ParseKernelArray("3: 1,-2,1 -2,4,-2 -1,-2,1"); 01461 if (kernel == (KernelInfo *) NULL) 01462 return(kernel); 01463 kernel->type = type; 01464 ScaleKernelInfo(kernel, 1.0/6.0, NoValue); 01465 break; 01466 case 18: 01467 kernel=ParseKernelArray("3: -2,1,-2 1,4,1 -2,1,-2"); 01468 if (kernel == (KernelInfo *) NULL) 01469 return(kernel); 01470 kernel->type = type; 01471 ScaleKernelInfo(kernel, 1.0/6.0, NoValue); 01472 break; 01473 case 19: 01474 kernel=ParseKernelArray("3: 1,1,1 1,1,1 1,1,1"); 01475 if (kernel == (KernelInfo *) NULL) 01476 return(kernel); 01477 kernel->type = type; 01478 ScaleKernelInfo(kernel, 1.0/3.0, NoValue); 01479 break; 01480 } 01481 if ( fabs(args->sigma) > MagickEpsilon ) 01482 /* Rotate by correctly supplied 'angle' */ 01483 RotateKernelInfo(kernel, args->sigma); 01484 else if ( args->rho > 30.0 || args->rho < -30.0 ) 01485 /* Rotate by out of bounds 'type' */ 01486 RotateKernelInfo(kernel, args->rho); 01487 break; 01488 } 01489 01490 /* 01491 Boolean or Shaped Kernels 01492 */ 01493 case DiamondKernel: 01494 { 01495 if (args->rho < 1.0) 01496 kernel->width = kernel->height = 3; /* default radius = 1 */ 01497 else 01498 kernel->width = kernel->height = ((size_t)args->rho)*2+1; 01499 kernel->x = kernel->y = (ssize_t) (kernel->width-1)/2; 01500 01501 kernel->values=(MagickRealType *) AcquireAlignedMemory(kernel->width, 01502 kernel->height*sizeof(*kernel->values)); 01503 if (kernel->values == (MagickRealType *) NULL) 01504 return(DestroyKernelInfo(kernel)); 01505 01506 /* set all kernel values within diamond area to scale given */ 01507 for ( i=0, v=-kernel->y; v <= (ssize_t)kernel->y; v++) 01508 for ( u=-kernel->x; u <= (ssize_t)kernel->x; u++, i++) 01509 if ( (labs((long) u)+labs((long) v)) <= (long) kernel->x) 01510 kernel->positive_range += kernel->values[i] = args->sigma; 01511 else 01512 kernel->values[i] = nan; 01513 kernel->minimum = kernel->maximum = args->sigma; /* a flat shape */ 01514 break; 01515 } 01516 case SquareKernel: 01517 case RectangleKernel: 01518 { double 01519 scale; 01520 if ( type == SquareKernel ) 01521 { 01522 if (args->rho < 1.0) 01523 kernel->width = kernel->height = 3; /* default radius = 1 */ 01524 else 01525 kernel->width = kernel->height = (size_t) (2*args->rho+1); 01526 kernel->x = kernel->y = (ssize_t) (kernel->width-1)/2; 01527 scale = args->sigma; 01528 } 01529 else { 01530 /* NOTE: user defaults set in "AcquireKernelInfo()" */ 01531 if ( args->rho < 1.0 || args->sigma < 1.0 ) 01532 return(DestroyKernelInfo(kernel)); /* invalid args given */ 01533 kernel->width = (size_t)args->rho; 01534 kernel->height = (size_t)args->sigma; 01535 if ( args->xi < 0.0 || args->xi > (double)kernel->width || 01536 args->psi < 0.0 || args->psi > (double)kernel->height ) 01537 return(DestroyKernelInfo(kernel)); /* invalid args given */ 01538 kernel->x = (ssize_t) args->xi; 01539 kernel->y = (ssize_t) args->psi; 01540 scale = 1.0; 01541 } 01542 kernel->values=(MagickRealType *) AcquireAlignedMemory(kernel->width, 01543 kernel->height*sizeof(*kernel->values)); 01544 if (kernel->values == (MagickRealType *) NULL) 01545 return(DestroyKernelInfo(kernel)); 01546 01547 /* set all kernel values to scale given */ 01548 u=(ssize_t) (kernel->width*kernel->height); 01549 for ( i=0; i < u; i++) 01550 kernel->values[i] = scale; 01551 kernel->minimum = kernel->maximum = scale; /* a flat shape */ 01552 kernel->positive_range = scale*u; 01553 break; 01554 } 01555 case OctagonKernel: 01556 { 01557 if (args->rho < 1.0) 01558 kernel->width = kernel->height = 5; /* default radius = 2 */ 01559 else 01560 kernel->width = kernel->height = ((size_t)args->rho)*2+1; 01561 kernel->x = kernel->y = (ssize_t) (kernel->width-1)/2; 01562 01563 kernel->values=(MagickRealType *) AcquireAlignedMemory(kernel->width, 01564 kernel->height*sizeof(*kernel->values)); 01565 if (kernel->values == (MagickRealType *) NULL) 01566 return(DestroyKernelInfo(kernel)); 01567 01568 for ( i=0, v=-kernel->y; v <= (ssize_t)kernel->y; v++) 01569 for ( u=-kernel->x; u <= (ssize_t)kernel->x; u++, i++) 01570 if ( (labs((long) u)+labs((long) v)) <= 01571 ((long)kernel->x + (long)(kernel->x/2)) ) 01572 kernel->positive_range += kernel->values[i] = args->sigma; 01573 else 01574 kernel->values[i] = nan; 01575 kernel->minimum = kernel->maximum = args->sigma; /* a flat shape */ 01576 break; 01577 } 01578 case DiskKernel: 01579 { 01580 ssize_t 01581 limit = (ssize_t)(args->rho*args->rho); 01582 01583 if (args->rho < 0.4) /* default radius approx 4.3 */ 01584 kernel->width = kernel->height = 9L, limit = 18L; 01585 else 01586 kernel->width = kernel->height = (size_t)fabs(args->rho)*2+1; 01587 kernel->x = kernel->y = (ssize_t) (kernel->width-1)/2; 01588 01589 kernel->values=(MagickRealType *) AcquireAlignedMemory(kernel->width, 01590 kernel->height*sizeof(*kernel->values)); 01591 if (kernel->values == (MagickRealType *) NULL) 01592 return(DestroyKernelInfo(kernel)); 01593 01594 for ( i=0, v=-kernel->y; v <= (ssize_t)kernel->y; v++) 01595 for ( u=-kernel->x; u <= (ssize_t)kernel->x; u++, i++) 01596 if ((u*u+v*v) <= limit) 01597 kernel->positive_range += kernel->values[i] = args->sigma; 01598 else 01599 kernel->values[i] = nan; 01600 kernel->minimum = kernel->maximum = args->sigma; /* a flat shape */ 01601 break; 01602 } 01603 case PlusKernel: 01604 { 01605 if (args->rho < 1.0) 01606 kernel->width = kernel->height = 5; /* default radius 2 */ 01607 else 01608 kernel->width = kernel->height = ((size_t)args->rho)*2+1; 01609 kernel->x = kernel->y = (ssize_t) (kernel->width-1)/2; 01610 01611 kernel->values=(MagickRealType *) AcquireAlignedMemory(kernel->width, 01612 kernel->height*sizeof(*kernel->values)); 01613 if (kernel->values == (MagickRealType *) NULL) 01614 return(DestroyKernelInfo(kernel)); 01615 01616 /* set all kernel values along axises to given scale */ 01617 for ( i=0, v=-kernel->y; v <= (ssize_t)kernel->y; v++) 01618 for ( u=-kernel->x; u <= (ssize_t)kernel->x; u++, i++) 01619 kernel->values[i] = (u == 0 || v == 0) ? args->sigma : nan; 01620 kernel->minimum = kernel->maximum = args->sigma; /* a flat shape */ 01621 kernel->positive_range = args->sigma*(kernel->width*2.0 - 1.0); 01622 break; 01623 } 01624 case CrossKernel: 01625 { 01626 if (args->rho < 1.0) 01627 kernel->width = kernel->height = 5; /* default radius 2 */ 01628 else 01629 kernel->width = kernel->height = ((size_t)args->rho)*2+1; 01630 kernel->x = kernel->y = (ssize_t) (kernel->width-1)/2; 01631 01632 kernel->values=(MagickRealType *) AcquireAlignedMemory(kernel->width, 01633 kernel->height*sizeof(*kernel->values)); 01634 if (kernel->values == (MagickRealType *) NULL) 01635 return(DestroyKernelInfo(kernel)); 01636 01637 /* set all kernel values along axises to given scale */ 01638 for ( i=0, v=-kernel->y; v <= (ssize_t)kernel->y; v++) 01639 for ( u=-kernel->x; u <= (ssize_t)kernel->x; u++, i++) 01640 kernel->values[i] = (u == v || u == -v) ? args->sigma : nan; 01641 kernel->minimum = kernel->maximum = args->sigma; /* a flat shape */ 01642 kernel->positive_range = args->sigma*(kernel->width*2.0 - 1.0); 01643 break; 01644 } 01645 /* 01646 HitAndMiss Kernels 01647 */ 01648 case RingKernel: 01649 case PeaksKernel: 01650 { 01651 ssize_t 01652 limit1, 01653 limit2, 01654 scale; 01655 01656 if (args->rho < args->sigma) 01657 { 01658 kernel->width = ((size_t)args->sigma)*2+1; 01659 limit1 = (ssize_t)(args->rho*args->rho); 01660 limit2 = (ssize_t)(args->sigma*args->sigma); 01661 } 01662 else 01663 { 01664 kernel->width = ((size_t)args->rho)*2+1; 01665 limit1 = (ssize_t)(args->sigma*args->sigma); 01666 limit2 = (ssize_t)(args->rho*args->rho); 01667 } 01668 if ( limit2 <= 0 ) 01669 kernel->width = 7L, limit1 = 7L, limit2 = 11L; 01670 01671 kernel->height = kernel->width; 01672 kernel->x = kernel->y = (ssize_t) (kernel->width-1)/2; 01673 kernel->values=(MagickRealType *) AcquireAlignedMemory(kernel->width, 01674 kernel->height*sizeof(*kernel->values)); 01675 if (kernel->values == (MagickRealType *) NULL) 01676 return(DestroyKernelInfo(kernel)); 01677 01678 /* set a ring of points of 'scale' ( 0.0 for PeaksKernel ) */ 01679 scale = (ssize_t) (( type == PeaksKernel) ? 0.0 : args->xi); 01680 for ( i=0, v= -kernel->y; v <= (ssize_t)kernel->y; v++) 01681 for ( u=-kernel->x; u <= (ssize_t)kernel->x; u++, i++) 01682 { ssize_t radius=u*u+v*v; 01683 if (limit1 < radius && radius <= limit2) 01684 kernel->positive_range += kernel->values[i] = (double) scale; 01685 else 01686 kernel->values[i] = nan; 01687 } 01688 kernel->minimum = kernel->maximum = (double) scale; 01689 if ( type == PeaksKernel ) { 01690 /* set the central point in the middle */ 01691 kernel->values[kernel->x+kernel->y*kernel->width] = 1.0; 01692 kernel->positive_range = 1.0; 01693 kernel->maximum = 1.0; 01694 } 01695 break; 01696 } 01697 case EdgesKernel: 01698 { 01699 kernel=AcquireKernelInfo("ThinSE:482"); 01700 if (kernel == (KernelInfo *) NULL) 01701 return(kernel); 01702 kernel->type = type; 01703 ExpandMirrorKernelInfo(kernel); /* mirror expansion of kernels */ 01704 break; 01705 } 01706 case CornersKernel: 01707 { 01708 kernel=AcquireKernelInfo("ThinSE:87"); 01709 if (kernel == (KernelInfo *) NULL) 01710 return(kernel); 01711 kernel->type = type; 01712 ExpandRotateKernelInfo(kernel, 90.0); /* Expand 90 degree rotations */ 01713 break; 01714 } 01715 case DiagonalsKernel: 01716 { 01717 switch ( (int) args->rho ) { 01718 case 0: 01719 default: 01720 { KernelInfo 01721 *new_kernel; 01722 kernel=ParseKernelArray("3: 0,0,0 0,-,1 1,1,-"); 01723 if (kernel == (KernelInfo *) NULL) 01724 return(kernel); 01725 kernel->type = type; 01726 new_kernel=ParseKernelArray("3: 0,0,1 0,-,1 0,1,-"); 01727 if (new_kernel == (KernelInfo *) NULL) 01728 return(DestroyKernelInfo(kernel)); 01729 new_kernel->type = type; 01730 LastKernelInfo(kernel)->next = new_kernel; 01731 ExpandMirrorKernelInfo(kernel); 01732 return(kernel); 01733 } 01734 case 1: 01735 kernel=ParseKernelArray("3: 0,0,0 0,-,1 1,1,-"); 01736 break; 01737 case 2: 01738 kernel=ParseKernelArray("3: 0,0,1 0,-,1 0,1,-"); 01739 break; 01740 } 01741 if (kernel == (KernelInfo *) NULL) 01742 return(kernel); 01743 kernel->type = type; 01744 RotateKernelInfo(kernel, args->sigma); 01745 break; 01746 } 01747 case LineEndsKernel: 01748 { /* Kernels for finding the end of thin lines */ 01749 switch ( (int) args->rho ) { 01750 case 0: 01751 default: 01752 /* set of kernels to find all end of lines */ 01753 return(AcquireKernelInfo("LineEnds:1>;LineEnds:2>")); 01754 case 1: 01755 /* kernel for 4-connected line ends - no rotation */ 01756 kernel=ParseKernelArray("3: 0,0,- 0,1,1 0,0,-"); 01757 break; 01758 case 2: 01759 /* kernel to add for 8-connected lines - no rotation */ 01760 kernel=ParseKernelArray("3: 0,0,0 0,1,0 0,0,1"); 01761 break; 01762 case 3: 01763 /* kernel to add for orthogonal line ends - does not find corners */ 01764 kernel=ParseKernelArray("3: 0,0,0 0,1,1 0,0,0"); 01765 break; 01766 case 4: 01767 /* traditional line end - fails on last T end */ 01768 kernel=ParseKernelArray("3: 0,0,0 0,1,- 0,0,-"); 01769 break; 01770 } 01771 if (kernel == (KernelInfo *) NULL) 01772 return(kernel); 01773 kernel->type = type; 01774 RotateKernelInfo(kernel, args->sigma); 01775 break; 01776 } 01777 case LineJunctionsKernel: 01778 { /* kernels for finding the junctions of multiple lines */ 01779 switch ( (int) args->rho ) { 01780 case 0: 01781 default: 01782 /* set of kernels to find all line junctions */ 01783 return(AcquireKernelInfo("LineJunctions:1@;LineJunctions:2>")); 01784 case 1: 01785 /* Y Junction */ 01786 kernel=ParseKernelArray("3: 1,-,1 -,1,- -,1,-"); 01787 break; 01788 case 2: 01789 /* Diagonal T Junctions */ 01790 kernel=ParseKernelArray("3: 1,-,- -,1,- 1,-,1"); 01791 break; 01792 case 3: 01793 /* Orthogonal T Junctions */ 01794 kernel=ParseKernelArray("3: -,-,- 1,1,1 -,1,-"); 01795 break; 01796 case 4: 01797 /* Diagonal X Junctions */ 01798 kernel=ParseKernelArray("3: 1,-,1 -,1,- 1,-,1"); 01799 break; 01800 case 5: 01801 /* Orthogonal X Junctions - minimal diamond kernel */ 01802 kernel=ParseKernelArray("3: -,1,- 1,1,1 -,1,-"); 01803 break; 01804 } 01805 if (kernel == (KernelInfo *) NULL) 01806 return(kernel); 01807 kernel->type = type; 01808 RotateKernelInfo(kernel, args->sigma); 01809 break; 01810 } 01811 case RidgesKernel: 01812 { /* Ridges - Ridge finding kernels */ 01813 KernelInfo 01814 *new_kernel; 01815 switch ( (int) args->rho ) { 01816 case 1: 01817 default: 01818 kernel=ParseKernelArray("3x1:0,1,0"); 01819 if (kernel == (KernelInfo *) NULL) 01820 return(kernel); 01821 kernel->type = type; 01822 ExpandRotateKernelInfo(kernel, 90.0); /* 2 rotated kernels (symmetrical) */ 01823 break; 01824 case 2: 01825 kernel=ParseKernelArray("4x1:0,1,1,0"); 01826 if (kernel == (KernelInfo *) NULL) 01827 return(kernel); 01828 kernel->type = type; 01829 ExpandRotateKernelInfo(kernel, 90.0); /* 4 rotated kernels */ 01830 01831 /* Kernels to find a stepped 'thick' line, 4 rotates + mirrors */ 01832 /* Unfortunatally we can not yet rotate a non-square kernel */ 01833 /* But then we can't flip a non-symetrical kernel either */ 01834 new_kernel=ParseKernelArray("4x3+1+1:0,1,1,- -,1,1,- -,1,1,0"); 01835 if (new_kernel == (KernelInfo *) NULL) 01836 return(DestroyKernelInfo(kernel)); 01837 new_kernel->type = type; 01838 LastKernelInfo(kernel)->next = new_kernel; 01839 new_kernel=ParseKernelArray("4x3+2+1:0,1,1,- -,1,1,- -,1,1,0"); 01840 if (new_kernel == (KernelInfo *) NULL) 01841 return(DestroyKernelInfo(kernel)); 01842 new_kernel->type = type; 01843 LastKernelInfo(kernel)->next = new_kernel; 01844 new_kernel=ParseKernelArray("4x3+1+1:-,1,1,0 -,1,1,- 0,1,1,-"); 01845 if (new_kernel == (KernelInfo *) NULL) 01846 return(DestroyKernelInfo(kernel)); 01847 new_kernel->type = type; 01848 LastKernelInfo(kernel)->next = new_kernel; 01849 new_kernel=ParseKernelArray("4x3+2+1:-,1,1,0 -,1,1,- 0,1,1,-"); 01850 if (new_kernel == (KernelInfo *) NULL) 01851 return(DestroyKernelInfo(kernel)); 01852 new_kernel->type = type; 01853 LastKernelInfo(kernel)->next = new_kernel; 01854 new_kernel=ParseKernelArray("3x4+1+1:0,-,- 1,1,1 1,1,1 -,-,0"); 01855 if (new_kernel == (KernelInfo *) NULL) 01856 return(DestroyKernelInfo(kernel)); 01857 new_kernel->type = type; 01858 LastKernelInfo(kernel)->next = new_kernel; 01859 new_kernel=ParseKernelArray("3x4+1+2:0,-,- 1,1,1 1,1,1 -,-,0"); 01860 if (new_kernel == (KernelInfo *) NULL) 01861 return(DestroyKernelInfo(kernel)); 01862 new_kernel->type = type; 01863 LastKernelInfo(kernel)->next = new_kernel; 01864 new_kernel=ParseKernelArray("3x4+1+1:-,-,0 1,1,1 1,1,1 0,-,-"); 01865 if (new_kernel == (KernelInfo *) NULL) 01866 return(DestroyKernelInfo(kernel)); 01867 new_kernel->type = type; 01868 LastKernelInfo(kernel)->next = new_kernel; 01869 new_kernel=ParseKernelArray("3x4+1+2:-,-,0 1,1,1 1,1,1 0,-,-"); 01870 if (new_kernel == (KernelInfo *) NULL) 01871 return(DestroyKernelInfo(kernel)); 01872 new_kernel->type = type; 01873 LastKernelInfo(kernel)->next = new_kernel; 01874 break; 01875 } 01876 break; 01877 } 01878 case ConvexHullKernel: 01879 { 01880 KernelInfo 01881 *new_kernel; 01882 /* first set of 8 kernels */ 01883 kernel=ParseKernelArray("3: 1,1,- 1,0,- 1,-,0"); 01884 if (kernel == (KernelInfo *) NULL) 01885 return(kernel); 01886 kernel->type = type; 01887 ExpandRotateKernelInfo(kernel, 90.0); 01888 /* append the mirror versions too - no flip function yet */ 01889 new_kernel=ParseKernelArray("3: 1,1,1 1,0,- -,-,0"); 01890 if (new_kernel == (KernelInfo *) NULL) 01891 return(DestroyKernelInfo(kernel)); 01892 new_kernel->type = type; 01893 ExpandRotateKernelInfo(new_kernel, 90.0); 01894 LastKernelInfo(kernel)->next = new_kernel; 01895 break; 01896 } 01897 case SkeletonKernel: 01898 { 01899 switch ( (int) args->rho ) { 01900 case 1: 01901 default: 01902 /* Traditional Skeleton... 01903 ** A cyclically rotated single kernel 01904 */ 01905 kernel=AcquireKernelInfo("ThinSE:482"); 01906 if (kernel == (KernelInfo *) NULL) 01907 return(kernel); 01908 kernel->type = type; 01909 ExpandRotateKernelInfo(kernel, 45.0); /* 8 rotations */ 01910 break; 01911 case 2: 01912 /* HIPR Variation of the cyclic skeleton 01913 ** Corners of the traditional method made more forgiving, 01914 ** but the retain the same cyclic order. 01915 */ 01916 kernel=AcquireKernelInfo("ThinSE:482; ThinSE:87x90;"); 01917 if (kernel == (KernelInfo *) NULL) 01918 return(kernel); 01919 if (kernel->next == (KernelInfo *) NULL) 01920 return(DestroyKernelInfo(kernel)); 01921 kernel->type = type; 01922 kernel->next->type = type; 01923 ExpandRotateKernelInfo(kernel, 90.0); /* 4 rotations of the 2 kernels */ 01924 break; 01925 case 3: 01926 /* Dan Bloomberg Skeleton, from his paper on 3x3 thinning SE's 01927 ** "Connectivity-Preserving Morphological Image Thransformations" 01928 ** by Dan S. Bloomberg, available on Leptonica, Selected Papers, 01929 ** http://www.leptonica.com/papers/conn.pdf 01930 */ 01931 kernel=AcquireKernelInfo( 01932 "ThinSE:41; ThinSE:42; ThinSE:43"); 01933 if (kernel == (KernelInfo *) NULL) 01934 return(kernel); 01935 kernel->type = type; 01936 kernel->next->type = type; 01937 kernel->next->next->type = type; 01938 ExpandMirrorKernelInfo(kernel); /* 12 kernels total */ 01939 break; 01940 } 01941 break; 01942 } 01943 case ThinSEKernel: 01944 { /* Special kernels for general thinning, while preserving connections 01945 ** "Connectivity-Preserving Morphological Image Thransformations" 01946 ** by Dan S. Bloomberg, available on Leptonica, Selected Papers, 01947 ** http://www.leptonica.com/papers/conn.pdf 01948 ** And 01949 ** http://tpgit.github.com/Leptonica/ccthin_8c_source.html 01950 ** 01951 ** Note kernels do not specify the origin pixel, allowing them 01952 ** to be used for both thickening and thinning operations. 01953 */ 01954 switch ( (int) args->rho ) { 01955 /* SE for 4-connected thinning */ 01956 case 41: /* SE_4_1 */ 01957 kernel=ParseKernelArray("3: -,-,1 0,-,1 -,-,1"); 01958 break; 01959 case 42: /* SE_4_2 */ 01960 kernel=ParseKernelArray("3: -,-,1 0,-,1 -,0,-"); 01961 break; 01962 case 43: /* SE_4_3 */ 01963 kernel=ParseKernelArray("3: -,0,- 0,-,1 -,-,1"); 01964 break; 01965 case 44: /* SE_4_4 */ 01966 kernel=ParseKernelArray("3: -,0,- 0,-,1 -,0,-"); 01967 break; 01968 case 45: /* SE_4_5 */ 01969 kernel=ParseKernelArray("3: -,0,1 0,-,1 -,0,-"); 01970 break; 01971 case 46: /* SE_4_6 */ 01972 kernel=ParseKernelArray("3: -,0,- 0,-,1 -,0,1"); 01973 break; 01974 case 47: /* SE_4_7 */ 01975 kernel=ParseKernelArray("3: -,1,1 0,-,1 -,0,-"); 01976 break; 01977 case 48: /* SE_4_8 */ 01978 kernel=ParseKernelArray("3: -,-,1 0,-,1 0,-,1"); 01979 break; 01980 case 49: /* SE_4_9 */ 01981 kernel=ParseKernelArray("3: 0,-,1 0,-,1 -,-,1"); 01982 break; 01983 /* SE for 8-connected thinning - negatives of the above */ 01984 case 81: /* SE_8_0 */ 01985 kernel=ParseKernelArray("3: -,1,- 0,-,1 -,1,-"); 01986 break; 01987 case 82: /* SE_8_2 */ 01988 kernel=ParseKernelArray("3: -,1,- 0,-,1 0,-,-"); 01989 break; 01990 case 83: /* SE_8_3 */ 01991 kernel=ParseKernelArray("3: 0,-,- 0,-,1 -,1,-"); 01992 break; 01993 case 84: /* SE_8_4 */ 01994 kernel=ParseKernelArray("3: 0,-,- 0,-,1 0,-,-"); 01995 break; 01996 case 85: /* SE_8_5 */ 01997 kernel=ParseKernelArray("3: 0,-,1 0,-,1 0,-,-"); 01998 break; 01999 case 86: /* SE_8_6 */ 02000 kernel=ParseKernelArray("3: 0,-,- 0,-,1 0,-,1"); 02001 break; 02002 case 87: /* SE_8_7 */ 02003 kernel=ParseKernelArray("3: -,1,- 0,-,1 0,0,-"); 02004 break; 02005 case 88: /* SE_8_8 */ 02006 kernel=ParseKernelArray("3: -,1,- 0,-,1 0,1,-"); 02007 break; 02008 case 89: /* SE_8_9 */ 02009 kernel=ParseKernelArray("3: 0,1,- 0,-,1 -,1,-"); 02010 break; 02011 /* Special combined SE kernels */ 02012 case 423: /* SE_4_2 , SE_4_3 Combined Kernel */ 02013 kernel=ParseKernelArray("3: -,-,1 0,-,- -,0,-"); 02014 break; 02015 case 823: /* SE_8_2 , SE_8_3 Combined Kernel */ 02016 kernel=ParseKernelArray("3: -,1,- -,-,1 0,-,-"); 02017 break; 02018 case 481: /* SE_48_1 - General Connected Corner Kernel */ 02019 kernel=ParseKernelArray("3: -,1,1 0,-,1 0,0,-"); 02020 break; 02021 default: 02022 case 482: /* SE_48_2 - General Edge Kernel */ 02023 kernel=ParseKernelArray("3: 0,-,1 0,-,1 0,-,1"); 02024 break; 02025 } 02026 if (kernel == (KernelInfo *) NULL) 02027 return(kernel); 02028 kernel->type = type; 02029 RotateKernelInfo(kernel, args->sigma); 02030 break; 02031 } 02032 /* 02033 Distance Measuring Kernels 02034 */ 02035 case ChebyshevKernel: 02036 { 02037 if (args->rho < 1.0) 02038 kernel->width = kernel->height = 3; /* default radius = 1 */ 02039 else 02040 kernel->width = kernel->height = ((size_t)args->rho)*2+1; 02041 kernel->x = kernel->y = (ssize_t) (kernel->width-1)/2; 02042 02043 kernel->values=(MagickRealType *) AcquireAlignedMemory(kernel->width, 02044 kernel->height*sizeof(*kernel->values)); 02045 if (kernel->values == (MagickRealType *) NULL) 02046 return(DestroyKernelInfo(kernel)); 02047 02048 for ( i=0, v=-kernel->y; v <= (ssize_t)kernel->y; v++) 02049 for ( u=-kernel->x; u <= (ssize_t)kernel->x; u++, i++) 02050 kernel->positive_range += ( kernel->values[i] = 02051 args->sigma*MagickMax(fabs((double)u),fabs((double)v)) ); 02052 kernel->maximum = kernel->values[0]; 02053 break; 02054 } 02055 case ManhattanKernel: 02056 { 02057 if (args->rho < 1.0) 02058 kernel->width = kernel->height = 3; /* default radius = 1 */ 02059 else 02060 kernel->width = kernel->height = ((size_t)args->rho)*2+1; 02061 kernel->x = kernel->y = (ssize_t) (kernel->width-1)/2; 02062 02063 kernel->values=(MagickRealType *) AcquireAlignedMemory(kernel->width, 02064 kernel->height*sizeof(*kernel->values)); 02065 if (kernel->values == (MagickRealType *) NULL) 02066 return(DestroyKernelInfo(kernel)); 02067 02068 for ( i=0, v=-kernel->y; v <= (ssize_t)kernel->y; v++) 02069 for ( u=-kernel->x; u <= (ssize_t)kernel->x; u++, i++) 02070 kernel->positive_range += ( kernel->values[i] = 02071 args->sigma*(labs((long) u)+labs((long) v)) ); 02072 kernel->maximum = kernel->values[0]; 02073 break; 02074 } 02075 case OctagonalKernel: 02076 { 02077 if (args->rho < 2.0) 02078 kernel->width = kernel->height = 5; /* default/minimum radius = 2 */ 02079 else 02080 kernel->width = kernel->height = ((size_t)args->rho)*2+1; 02081 kernel->x = kernel->y = (ssize_t) (kernel->width-1)/2; 02082 02083 kernel->values=(MagickRealType *) AcquireAlignedMemory(kernel->width, 02084 kernel->height*sizeof(*kernel->values)); 02085 if (kernel->values == (MagickRealType *) NULL) 02086 return(DestroyKernelInfo(kernel)); 02087 02088 for ( i=0, v=-kernel->y; v <= (ssize_t)kernel->y; v++) 02089 for ( u=-kernel->x; u <= (ssize_t)kernel->x; u++, i++) 02090 { 02091 double 02092 r1 = MagickMax(fabs((double)u),fabs((double)v)), 02093 r2 = floor((double)(labs((long)u)+labs((long)v)+1)/1.5); 02094 kernel->positive_range += kernel->values[i] = 02095 args->sigma*MagickMax(r1,r2); 02096 } 02097 kernel->maximum = kernel->values[0]; 02098 break; 02099 } 02100 case EuclideanKernel: 02101 { 02102 if (args->rho < 1.0) 02103 kernel->width = kernel->height = 3; /* default radius = 1 */ 02104 else 02105 kernel->width = kernel->height = ((size_t)args->rho)*2+1; 02106 kernel->x = kernel->y = (ssize_t) (kernel->width-1)/2; 02107 02108 kernel->values=(MagickRealType *) AcquireAlignedMemory(kernel->width, 02109 kernel->height*sizeof(*kernel->values)); 02110 if (kernel->values == (MagickRealType *) NULL) 02111 return(DestroyKernelInfo(kernel)); 02112 02113 for ( i=0, v=-kernel->y; v <= (ssize_t)kernel->y; v++) 02114 for ( u=-kernel->x; u <= (ssize_t)kernel->x; u++, i++) 02115 kernel->positive_range += ( kernel->values[i] = 02116 args->sigma*sqrt((double)(u*u+v*v)) ); 02117 kernel->maximum = kernel->values[0]; 02118 break; 02119 } 02120 default: 02121 { 02122 /* No-Op Kernel - Basically just a single pixel on its own */ 02123 kernel=ParseKernelArray("1:1"); 02124 if (kernel == (KernelInfo *) NULL) 02125 return(kernel); 02126 kernel->type = UndefinedKernel; 02127 break; 02128 } 02129 break; 02130 } 02131 return(kernel); 02132 } 02133 02134 /* 02135 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 02136 % % 02137 % % 02138 % % 02139 % C l o n e K e r n e l I n f o % 02140 % % 02141 % % 02142 % % 02143 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 02144 % 02145 % CloneKernelInfo() creates a new clone of the given Kernel List so that its 02146 % can be modified without effecting the original. The cloned kernel should 02147 % be destroyed using DestroyKernelInfo() when no longer needed. 02148 % 02149 % The format of the CloneKernelInfo method is: 02150 % 02151 % KernelInfo *CloneKernelInfo(const KernelInfo *kernel) 02152 % 02153 % A description of each parameter follows: 02154 % 02155 % o kernel: the Morphology/Convolution kernel to be cloned 02156 % 02157 */ 02158 MagickExport KernelInfo *CloneKernelInfo(const KernelInfo *kernel) 02159 { 02160 register ssize_t 02161 i; 02162 02163 KernelInfo 02164 *new_kernel; 02165 02166 assert(kernel != (KernelInfo *) NULL); 02167 new_kernel=(KernelInfo *) AcquireMagickMemory(sizeof(*kernel)); 02168 if (new_kernel == (KernelInfo *) NULL) 02169 return(new_kernel); 02170 *new_kernel=(*kernel); /* copy values in structure */ 02171 02172 /* replace the values with a copy of the values */ 02173 new_kernel->values=(MagickRealType *) AcquireAlignedMemory(kernel->width, 02174 kernel->height*sizeof(*kernel->values)); 02175 if (new_kernel->values == (MagickRealType *) NULL) 02176 return(DestroyKernelInfo(new_kernel)); 02177 for (i=0; i < (ssize_t) (kernel->width*kernel->height); i++) 02178 new_kernel->values[i]=kernel->values[i]; 02179 02180 /* Also clone the next kernel in the kernel list */ 02181 if ( kernel->next != (KernelInfo *) NULL ) { 02182 new_kernel->next = CloneKernelInfo(kernel->next); 02183 if ( new_kernel->next == (KernelInfo *) NULL ) 02184 return(DestroyKernelInfo(new_kernel)); 02185 } 02186 02187 return(new_kernel); 02188 } 02189 02190 /* 02191 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 02192 % % 02193 % % 02194 % % 02195 % D e s t r o y K e r n e l I n f o % 02196 % % 02197 % % 02198 % % 02199 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 02200 % 02201 % DestroyKernelInfo() frees the memory used by a Convolution/Morphology 02202 % kernel. 02203 % 02204 % The format of the DestroyKernelInfo method is: 02205 % 02206 % KernelInfo *DestroyKernelInfo(KernelInfo *kernel) 02207 % 02208 % A description of each parameter follows: 02209 % 02210 % o kernel: the Morphology/Convolution kernel to be destroyed 02211 % 02212 */ 02213 MagickExport KernelInfo *DestroyKernelInfo(KernelInfo *kernel) 02214 { 02215 assert(kernel != (KernelInfo *) NULL); 02216 if ( kernel->next != (KernelInfo *) NULL ) 02217 kernel->next=DestroyKernelInfo(kernel->next); 02218 kernel->values=(MagickRealType *) RelinquishAlignedMemory(kernel->values); 02219 kernel=(KernelInfo *) RelinquishMagickMemory(kernel); 02220 return(kernel); 02221 } 02222 02223 /* 02224 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 02225 % % 02226 % % 02227 % % 02228 + E x p a n d M i r r o r K e r n e l I n f o % 02229 % % 02230 % % 02231 % % 02232 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 02233 % 02234 % ExpandMirrorKernelInfo() takes a single kernel, and expands it into a 02235 % sequence of 90-degree rotated kernels but providing a reflected 180 02236 % rotatation, before the -/+ 90-degree rotations. 02237 % 02238 % This special rotation order produces a better, more symetrical thinning of 02239 % objects. 02240 % 02241 % The format of the ExpandMirrorKernelInfo method is: 02242 % 02243 % void ExpandMirrorKernelInfo(KernelInfo *kernel) 02244 % 02245 % A description of each parameter follows: 02246 % 02247 % o kernel: the Morphology/Convolution kernel 02248 % 02249 % This function is only internel to this module, as it is not finalized, 02250 % especially with regard to non-orthogonal angles, and rotation of larger 02251 % 2D kernels. 02252 */ 02253 02254 #if 0 02255 static void FlopKernelInfo(KernelInfo *kernel) 02256 { /* Do a Flop by reversing each row. */ 02257 size_t 02258 y; 02259 register ssize_t 02260 x,r; 02261 register double 02262 *k,t; 02263 02264 for ( y=0, k=kernel->values; y < kernel->height; y++, k+=kernel->width) 02265 for ( x=0, r=kernel->width-1; x<kernel->width/2; x++, r--) 02266 t=k[x], k[x]=k[r], k[r]=t; 02267 02268 kernel->x = kernel->width - kernel->x - 1; 02269 angle = fmod(angle+180.0, 360.0); 02270 } 02271 #endif 02272 02273 static void ExpandMirrorKernelInfo(KernelInfo *kernel) 02274 { 02275 KernelInfo 02276 *clone, 02277 *last; 02278 02279 last = kernel; 02280 02281 clone = CloneKernelInfo(last); 02282 RotateKernelInfo(clone, 180); /* flip */ 02283 LastKernelInfo(last)->next = clone; 02284 last = clone; 02285 02286 clone = CloneKernelInfo(last); 02287 RotateKernelInfo(clone, 90); /* transpose */ 02288 LastKernelInfo(last)->next = clone; 02289 last = clone; 02290 02291 clone = CloneKernelInfo(last); 02292 RotateKernelInfo(clone, 180); /* flop */ 02293 LastKernelInfo(last)->next = clone; 02294 02295 return; 02296 } 02297 02298 /* 02299 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 02300 % % 02301 % % 02302 % % 02303 + E x p a n d R o t a t e K e r n e l I n f o % 02304 % % 02305 % % 02306 % % 02307 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 02308 % 02309 % ExpandRotateKernelInfo() takes a kernel list, and expands it by rotating 02310 % incrementally by the angle given, until the kernel repeats. 02311 % 02312 % WARNING: 45 degree rotations only works for 3x3 kernels. 02313 % While 90 degree roatations only works for linear and square kernels 02314 % 02315 % The format of the ExpandRotateKernelInfo method is: 02316 % 02317 % void ExpandRotateKernelInfo(KernelInfo *kernel, double angle) 02318 % 02319 % A description of each parameter follows: 02320 % 02321 % o kernel: the Morphology/Convolution kernel 02322 % 02323 % o angle: angle to rotate in degrees 02324 % 02325 % This function is only internel to this module, as it is not finalized, 02326 % especially with regard to non-orthogonal angles, and rotation of larger 02327 % 2D kernels. 02328 */ 02329 02330 /* Internal Routine - Return true if two kernels are the same */ 02331 static MagickBooleanType SameKernelInfo(const KernelInfo *kernel1, 02332 const KernelInfo *kernel2) 02333 { 02334 register size_t 02335 i; 02336 02337 /* check size and origin location */ 02338 if ( kernel1->width != kernel2->width 02339 || kernel1->height != kernel2->height 02340 || kernel1->x != kernel2->x 02341 || kernel1->y != kernel2->y ) 02342 return MagickFalse; 02343 02344 /* check actual kernel values */ 02345 for (i=0; i < (kernel1->width*kernel1->height); i++) { 02346 /* Test for Nan equivalence */ 02347 if ( IsNan(kernel1->values[i]) && !IsNan(kernel2->values[i]) ) 02348 return MagickFalse; 02349 if ( IsNan(kernel2->values[i]) && !IsNan(kernel1->values[i]) ) 02350 return MagickFalse; 02351 /* Test actual values are equivalent */ 02352 if ( fabs(kernel1->values[i] - kernel2->values[i]) > MagickEpsilon ) 02353 return MagickFalse; 02354 } 02355 02356 return MagickTrue; 02357 } 02358 02359 static void ExpandRotateKernelInfo(KernelInfo *kernel, const double angle) 02360 { 02361 KernelInfo 02362 *clone, 02363 *last; 02364 02365 last = kernel; 02366 while(1) { 02367 clone = CloneKernelInfo(last); 02368 RotateKernelInfo(clone, angle); 02369 if ( SameKernelInfo(kernel, clone) == MagickTrue ) 02370 break; 02371 LastKernelInfo(last)->next = clone; 02372 last = clone; 02373 } 02374 clone = DestroyKernelInfo(clone); /* kernel has repeated - junk the clone */ 02375 return; 02376 } 02377 02378 /* 02379 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 02380 % % 02381 % % 02382 % % 02383 + C a l c M e t a K e r n a l I n f o % 02384 % % 02385 % % 02386 % % 02387 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 02388 % 02389 % CalcKernelMetaData() recalculate the KernelInfo meta-data of this kernel only, 02390 % using the kernel values. This should only ne used if it is not possible to 02391 % calculate that meta-data in some easier way. 02392 % 02393 % It is important that the meta-data is correct before ScaleKernelInfo() is 02394 % used to perform kernel normalization. 02395 % 02396 % The format of the CalcKernelMetaData method is: 02397 % 02398 % void CalcKernelMetaData(KernelInfo *kernel, const double scale ) 02399 % 02400 % A description of each parameter follows: 02401 % 02402 % o kernel: the Morphology/Convolution kernel to modify 02403 % 02404 % WARNING: Minimum and Maximum values are assumed to include zero, even if 02405 % zero is not part of the kernel (as in Gaussian Derived kernels). This 02406 % however is not true for flat-shaped morphological kernels. 02407 % 02408 % WARNING: Only the specific kernel pointed to is modified, not a list of 02409 % multiple kernels. 02410 % 02411 % This is an internal function and not expected to be useful outside this 02412 % module. This could change however. 02413 */ 02414 static void CalcKernelMetaData(KernelInfo *kernel) 02415 { 02416 register size_t 02417 i; 02418 02419 kernel->minimum = kernel->maximum = 0.0; 02420 kernel->negative_range = kernel->positive_range = 0.0; 02421 for (i=0; i < (kernel->width*kernel->height); i++) 02422 { 02423 if ( fabs(kernel->values[i]) < MagickEpsilon ) 02424 kernel->values[i] = 0.0; 02425 ( kernel->values[i] < 0) 02426 ? ( kernel->negative_range += kernel->values[i] ) 02427 : ( kernel->positive_range += kernel->values[i] ); 02428 Minimize(kernel->minimum, kernel->values[i]); 02429 Maximize(kernel->maximum, kernel->values[i]); 02430 } 02431 02432 return; 02433 } 02434 02435 /* 02436 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 02437 % % 02438 % % 02439 % % 02440 % M o r p h o l o g y A p p l y % 02441 % % 02442 % % 02443 % % 02444 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 02445 % 02446 % MorphologyApply() applies a morphological method, multiple times using 02447 % a list of multiple kernels. 02448 % 02449 % It is basically equivalent to as MorphologyImage() (see below) but 02450 % without any user controls. This allows internel programs to use this 02451 % function, to actually perform a specific task without possible interference 02452 % by any API user supplied settings. 02453 % 02454 % It is MorphologyImage() task to extract any such user controls, and 02455 % pass them to this function for processing. 02456 % 02457 % More specifically kernels are not normalized/scaled/blended by the 02458 % 'convolve:scale' Image Artifact (setting), nor is the convolve bias 02459 % (-bias setting or image->bias) loooked at, but must be supplied from the 02460 % function arguments. 02461 % 02462 % The format of the MorphologyApply method is: 02463 % 02464 % Image *MorphologyApply(const Image *image,MorphologyMethod method, 02465 % const ssize_t iterations,const KernelInfo *kernel, 02466 % const CompositeMethod compose,const double bias, 02467 % ExceptionInfo *exception) 02468 % 02469 % A description of each parameter follows: 02470 % 02471 % o image: the source image 02472 % 02473 % o method: the morphology method to be applied. 02474 % 02475 % o iterations: apply the operation this many times (or no change). 02476 % A value of -1 means loop until no change found. 02477 % How this is applied may depend on the morphology method. 02478 % Typically this is a value of 1. 02479 % 02480 % o channel: the channel type. 02481 % 02482 % o kernel: An array of double representing the morphology kernel. 02483 % 02484 % o compose: How to handle or merge multi-kernel results. 02485 % If 'UndefinedCompositeOp' use default for the Morphology method. 02486 % If 'NoCompositeOp' force image to be re-iterated by each kernel. 02487 % Otherwise merge the results using the compose method given. 02488 % 02489 % o bias: Convolution Output Bias. 02490 % 02491 % o exception: return any errors or warnings in this structure. 02492 % 02493 */ 02494 02495 /* Apply a Morphology Primative to an image using the given kernel. 02496 ** Two pre-created images must be provided, and no image is created. 02497 ** It returns the number of pixels that changed between the images 02498 ** for result convergence determination. 02499 */ 02500 static ssize_t MorphologyPrimitive(const Image *image,Image *morphology_image, 02501 const MorphologyMethod method,const KernelInfo *kernel,const double bias, 02502 ExceptionInfo *exception) 02503 { 02504 #define MorphologyTag "Morphology/Image" 02505 02506 CacheView 02507 *image_view, 02508 *morphology_view; 02509 02510 ssize_t 02511 y, offx, offy; 02512 02513 size_t 02514 virt_width, 02515 changed; 02516 02517 MagickBooleanType 02518 status; 02519 02520 MagickOffsetType 02521 progress; 02522 02523 assert(image != (Image *) NULL); 02524 assert(image->signature == MagickSignature); 02525 assert(morphology_image != (Image *) NULL); 02526 assert(morphology_image->signature == MagickSignature); 02527 assert(kernel != (KernelInfo *) NULL); 02528 assert(kernel->signature == MagickSignature); 02529 assert(exception != (ExceptionInfo *) NULL); 02530 assert(exception->signature == MagickSignature); 02531 02532 status=MagickTrue; 02533 changed=0; 02534 progress=0; 02535 02536 image_view=AcquireCacheView(image); 02537 morphology_view=AcquireCacheView(morphology_image); 02538 virt_width=image->columns+kernel->width-1; 02539 02540 /* Some methods (including convolve) needs use a reflected kernel. 02541 * Adjust 'origin' offsets to loop though kernel as a reflection. 02542 */ 02543 offx = kernel->x; 02544 offy = kernel->y; 02545 switch(method) { 02546 case ConvolveMorphology: 02547 case DilateMorphology: 02548 case DilateIntensityMorphology: 02549 /*case DistanceMorphology:*/ 02550 /* kernel needs to used with reflection about origin */ 02551 offx = (ssize_t) kernel->width-offx-1; 02552 offy = (ssize_t) kernel->height-offy-1; 02553 break; 02554 case ErodeMorphology: 02555 case ErodeIntensityMorphology: 02556 case HitAndMissMorphology: 02557 case ThinningMorphology: 02558 case ThickenMorphology: 02559 /* kernel is used as is, without reflection */ 02560 break; 02561 default: 02562 assert("Not a Primitive Morphology Method" != (char *) NULL); 02563 break; 02564 } 02565 02566 if ( method == ConvolveMorphology && kernel->width == 1 ) 02567 { /* Special handling (for speed) of vertical (blur) kernels. 02568 ** This performs its handling in columns rather than in rows. 02569 ** This is only done for convolve as it is the only method that 02570 ** generates very large 1-D vertical kernels (such as a 'BlurKernel') 02571 ** 02572 ** Timing tests (on single CPU laptop) 02573 ** Using a vertical 1-d Blue with normal row-by-row (below) 02574 ** time convert logo: -morphology Convolve Blur:0x10+90 null: 02575 ** 0.807u 02576 ** Using this column method 02577 ** time convert logo: -morphology Convolve Blur:0x10+90 null: 02578 ** 0.620u 02579 ** 02580 ** Anthony Thyssen, 14 June 2010 02581 */ 02582 register ssize_t 02583 x; 02584 02585 #if defined(MAGICKCORE_OPENMP_SUPPORT) 02586 #pragma omp parallel for schedule(static,4) shared(progress,status) 02587 #endif 02588 for (x=0; x < (ssize_t) image->columns; x++) 02589 { 02590 register const Quantum 02591 *restrict p; 02592 02593 register Quantum 02594 *restrict q; 02595 02596 register ssize_t 02597 y; 02598 02599 ssize_t 02600 r; 02601 02602 if (status == MagickFalse) 02603 continue; 02604 p=GetCacheViewVirtualPixels(image_view,x,-offy,1,image->rows+ 02605 kernel->height-1,exception); 02606 q=GetCacheViewAuthenticPixels(morphology_view,x,0,1, 02607 morphology_image->rows,exception); 02608 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL)) 02609 { 02610 status=MagickFalse; 02611 continue; 02612 } 02613 /* offset to origin in 'p'. while 'q' points to it directly */ 02614 r = offy; 02615 02616 for (y=0; y < (ssize_t) image->rows; y++) 02617 { 02618 PixelInfo 02619 result; 02620 02621 register ssize_t 02622 v; 02623 02624 register const MagickRealType 02625 *restrict k; 02626 02627 register const Quantum 02628 *restrict k_pixels; 02629 02630 /* Copy input image to the output image for unused channels 02631 * This removes need for 'cloning' a new image every iteration 02632 */ 02633 SetPixelRed(morphology_image,GetPixelRed(image,p+r* 02634 GetPixelChannels(image)),q); 02635 SetPixelGreen(morphology_image,GetPixelGreen(image,p+r* 02636 GetPixelChannels(image)),q); 02637 SetPixelBlue(morphology_image,GetPixelBlue(image,p+r* 02638 GetPixelChannels(image)),q); 02639 if (image->colorspace == CMYKColorspace) 02640 SetPixelBlack(morphology_image,GetPixelBlack(image,p+r* 02641 GetPixelChannels(image)),q); 02642 02643 /* Set the bias of the weighted average output */ 02644 result.red = 02645 result.green = 02646 result.blue = 02647 result.alpha = 02648 result.black = bias; 02649 02650 02651 /* Weighted Average of pixels using reflected kernel 02652 ** 02653 ** NOTE for correct working of this operation for asymetrical 02654 ** kernels, the kernel needs to be applied in its reflected form. 02655 ** That is its values needs to be reversed. 02656 */ 02657 k = &kernel->values[ kernel->height-1 ]; 02658 k_pixels = p; 02659 if ( (image->channel_mask != DefaultChannels) || (image->matte == MagickFalse) ) 02660 { /* No 'Sync' involved. 02661 ** Convolution is simple greyscale channel operation 02662 */ 02663 for (v=0; v < (ssize_t) kernel->height; v++) { 02664 if ( IsNan(*k) ) continue; 02665 result.red += (*k)*GetPixelRed(image,k_pixels); 02666 result.green += (*k)*GetPixelGreen(image,k_pixels); 02667 result.blue += (*k)*GetPixelBlue(image,k_pixels); 02668 if (image->colorspace == CMYKColorspace) 02669 result.black+=(*k)*GetPixelBlack(image,k_pixels); 02670 result.alpha += (*k)*GetPixelAlpha(image,k_pixels); 02671 k--; 02672 k_pixels+=GetPixelChannels(image); 02673 } 02674 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0) 02675 SetPixelRed(morphology_image,ClampToQuantum(result.red),q); 02676 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0) 02677 SetPixelGreen(morphology_image,ClampToQuantum(result.green),q); 02678 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0) 02679 SetPixelBlue(morphology_image,ClampToQuantum(result.blue),q); 02680 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) && 02681 (image->colorspace == CMYKColorspace)) 02682 SetPixelBlack(morphology_image,ClampToQuantum(result.black),q); 02683 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) && 02684 (image->matte == MagickTrue)) 02685 SetPixelAlpha(morphology_image,ClampToQuantum(result.alpha),q); 02686 } 02687 else 02688 { /* Channel 'Sync' Flag, and Alpha Channel enabled. 02689 ** Weight the color channels with Alpha Channel so that 02690 ** transparent pixels are not part of the results. 02691 */ 02692 MagickRealType 02693 alpha, /* alpha weighting of colors : kernel*alpha */ 02694 gamma; /* divisor, sum of color weighting values */ 02695 02696 gamma=0.0; 02697 for (v=0; v < (ssize_t) kernel->height; v++) { 02698 if ( IsNan(*k) ) continue; 02699 alpha=(*k)*(QuantumScale*GetPixelAlpha(image,k_pixels)); 02700 gamma += alpha; 02701 result.red += alpha*GetPixelRed(image,k_pixels); 02702 result.green += alpha*GetPixelGreen(image,k_pixels); 02703 result.blue += alpha*GetPixelBlue(image,k_pixels); 02704 if (image->colorspace == CMYKColorspace) 02705 result.black += alpha*GetPixelBlack(image,k_pixels); 02706 result.alpha += (*k)*GetPixelAlpha(image,k_pixels); 02707 k--; 02708 k_pixels+=GetPixelChannels(image); 02709 } 02710 /* Sync'ed channels, all channels are modified */ 02711 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma); 02712 SetPixelRed(morphology_image, 02713 ClampToQuantum(gamma*result.red),q); 02714 SetPixelGreen(morphology_image, 02715 ClampToQuantum(gamma*result.green),q); 02716 SetPixelBlue(morphology_image, 02717 ClampToQuantum(gamma*result.blue),q); 02718 if (image->colorspace == CMYKColorspace) 02719 SetPixelBlack(morphology_image, 02720 ClampToQuantum(gamma*result.black),q); 02721 SetPixelAlpha(morphology_image, 02722 ClampToQuantum(result.alpha),q); 02723 } 02724 02725 /* Count up changed pixels */ 02726 if ((GetPixelRed(image,p+r*GetPixelChannels(image)) != GetPixelRed(morphology_image,q)) 02727 || (GetPixelGreen(image,p+r*GetPixelChannels(image)) != GetPixelGreen(morphology_image,q)) 02728 || (GetPixelBlue(image,p+r*GetPixelChannels(image)) != GetPixelBlue(morphology_image,q)) 02729 || (GetPixelAlpha(image,p+r*GetPixelChannels(image)) != GetPixelAlpha(morphology_image,q)) 02730 || ((image->colorspace == CMYKColorspace) && 02731 (GetPixelBlack(image,p+r*GetPixelChannels(image)) != GetPixelBlack(morphology_image,q)))) 02732 changed++; /* The pixel was changed in some way! */ 02733 p+=GetPixelChannels(image); 02734 q+=GetPixelChannels(morphology_image); 02735 } /* y */ 02736 if ( SyncCacheViewAuthenticPixels(morphology_view,exception) == MagickFalse) 02737 status=MagickFalse; 02738 if (image->progress_monitor != (MagickProgressMonitor) NULL) 02739 { 02740 MagickBooleanType 02741 proceed; 02742 02743 #if defined(MAGICKCORE_OPENMP_SUPPORT) 02744 #pragma omp critical (MagickCore_MorphologyImage) 02745 #endif 02746 proceed=SetImageProgress(image,MorphologyTag,progress++,image->rows); 02747 if (proceed == MagickFalse) 02748 status=MagickFalse; 02749 } 02750 } /* x */ 02751 morphology_image->type=image->type; 02752 morphology_view=DestroyCacheView(morphology_view); 02753 image_view=DestroyCacheView(image_view); 02754 return(status ? (ssize_t) changed : 0); 02755 } 02756 02757 /* 02758 ** Normal handling of horizontal or rectangular kernels (row by row) 02759 */ 02760 #if defined(MAGICKCORE_OPENMP_SUPPORT) 02761 #pragma omp parallel for schedule(static,4) shared(progress,status) 02762 #endif 02763 for (y=0; y < (ssize_t) image->rows; y++) 02764 { 02765 register const Quantum 02766 *restrict p; 02767 02768 register Quantum 02769 *restrict q; 02770 02771 register ssize_t 02772 x; 02773 02774 size_t 02775 r; 02776 02777 if (status == MagickFalse) 02778 continue; 02779 p=GetCacheViewVirtualPixels(image_view, -offx, y-offy, virt_width, 02780 kernel->height, exception); 02781 q=GetCacheViewAuthenticPixels(morphology_view,0,y, 02782 morphology_image->columns,1,exception); 02783 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL)) 02784 { 02785 status=MagickFalse; 02786 continue; 02787 } 02788 /* offset to origin in 'p'. while 'q' points to it directly */ 02789 r = virt_width*offy + offx; 02790 02791 for (x=0; x < (ssize_t) image->columns; x++) 02792 { 02793 ssize_t 02794 v; 02795 02796 register ssize_t 02797 u; 02798 02799 register const MagickRealType 02800 *restrict k; 02801 02802 register const Quantum 02803 *restrict k_pixels; 02804 02805 PixelInfo 02806 result, 02807 min, 02808 max; 02809 02810 /* Copy input image to the output image for unused channels 02811 * This removes need for 'cloning' a new image every iteration 02812 */ 02813 SetPixelRed(morphology_image,GetPixelRed(image,p+r* 02814 GetPixelChannels(image)),q); 02815 SetPixelGreen(morphology_image,GetPixelGreen(image,p+r* 02816 GetPixelChannels(image)),q); 02817 SetPixelBlue(morphology_image,GetPixelBlue(image,p+r* 02818 GetPixelChannels(image)),q); 02819 if (image->colorspace == CMYKColorspace) 02820 SetPixelBlack(morphology_image,GetPixelBlack(image,p+r* 02821 GetPixelChannels(image)),q); 02822 02823 /* Defaults */ 02824 min.red = 02825 min.green = 02826 min.blue = 02827 min.alpha = 02828 min.black = (MagickRealType) QuantumRange; 02829 max.red = 02830 max.green = 02831 max.blue = 02832 max.alpha = 02833 max.black = (MagickRealType) 0; 02834 /* default result is the original pixel value */ 02835 result.red = (MagickRealType) GetPixelRed(image,p+r*GetPixelChannels(image)); 02836 result.green = (MagickRealType) GetPixelGreen(image,p+r*GetPixelChannels(image)); 02837 result.blue = (MagickRealType) GetPixelBlue(image,p+r*GetPixelChannels(image)); 02838 result.black = 0.0; 02839 if (image->colorspace == CMYKColorspace) 02840 result.black = (MagickRealType) GetPixelBlack(image,p+r*GetPixelChannels(image)); 02841 result.alpha=(MagickRealType) GetPixelAlpha(image,p+r*GetPixelChannels(image)); 02842 02843 switch (method) { 02844 case ConvolveMorphology: 02845 /* Set the bias of the weighted average output */ 02846 result.red = 02847 result.green = 02848 result.blue = 02849 result.alpha = 02850 result.black = bias; 02851 break; 02852 case DilateIntensityMorphology: 02853 case ErodeIntensityMorphology: 02854 /* use a boolean flag indicating when first match found */ 02855 result.red = 0.0; /* result is not used otherwise */ 02856 break; 02857 default: 02858 break; 02859 } 02860 02861 switch ( method ) { 02862 case ConvolveMorphology: 02863 /* Weighted Average of pixels using reflected kernel 02864 ** 02865 ** NOTE for correct working of this operation for asymetrical 02866 ** kernels, the kernel needs to be applied in its reflected form. 02867 ** That is its values needs to be reversed. 02868 ** 02869 ** Correlation is actually the same as this but without reflecting 02870 ** the kernel, and thus 'lower-level' that Convolution. However 02871 ** as Convolution is the more common method used, and it does not 02872 ** really cost us much in terms of processing to use a reflected 02873 ** kernel, so it is Convolution that is implemented. 02874 ** 02875 ** Correlation will have its kernel reflected before calling 02876 ** this function to do a Convolve. 02877 ** 02878 ** For more details of Correlation vs Convolution see 02879 ** http://www.cs.umd.edu/~djacobs/CMSC426/Convolution.pdf 02880 */ 02881 k = &kernel->values[ kernel->width*kernel->height-1 ]; 02882 k_pixels = p; 02883 if ( (image->channel_mask != DefaultChannels) || 02884 (image->matte == MagickFalse) ) 02885 { /* No 'Sync' involved. 02886 ** Convolution is simple greyscale channel operation 02887 */ 02888 for (v=0; v < (ssize_t) kernel->height; v++) { 02889 for (u=0; u < (ssize_t) kernel->width; u++, k--) { 02890 if ( IsNan(*k) ) continue; 02891 result.red += (*k)* 02892 GetPixelRed(image,k_pixels+u*GetPixelChannels(image)); 02893 result.green += (*k)* 02894 GetPixelGreen(image,k_pixels+u*GetPixelChannels(image)); 02895 result.blue += (*k)* 02896 GetPixelBlue(image,k_pixels+u*GetPixelChannels(image)); 02897 if (image->colorspace == CMYKColorspace) 02898 result.black += (*k)* 02899 GetPixelBlack(image,k_pixels+u*GetPixelChannels(image)); 02900 result.alpha += (*k)* 02901 GetPixelAlpha(image,k_pixels+u*GetPixelChannels(image)); 02902 } 02903 k_pixels += virt_width*GetPixelChannels(image); 02904 } 02905 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0) 02906 SetPixelRed(morphology_image,ClampToQuantum(result.red), 02907 q); 02908 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0) 02909 SetPixelGreen(morphology_image,ClampToQuantum(result.green), 02910 q); 02911 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0) 02912 SetPixelBlue(morphology_image,ClampToQuantum(result.blue), 02913 q); 02914 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) && 02915 (image->colorspace == CMYKColorspace)) 02916 SetPixelBlack(morphology_image,ClampToQuantum(result.black), 02917 q); 02918 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) && 02919 (image->matte == MagickTrue)) 02920 SetPixelAlpha(morphology_image,ClampToQuantum(result.alpha), 02921 q); 02922 } 02923 else 02924 { /* Channel 'Sync' Flag, and Alpha Channel enabled. 02925 ** Weight the color channels with Alpha Channel so that 02926 ** transparent pixels are not part of the results. 02927 */ 02928 MagickRealType 02929 alpha, /* alpha weighting of colors : kernel*alpha */ 02930 gamma; /* divisor, sum of color weighting values */ 02931 02932 gamma=0.0; 02933 for (v=0; v < (ssize_t) kernel->height; v++) { 02934 for (u=0; u < (ssize_t) kernel->width; u++, k--) { 02935 if ( IsNan(*k) ) continue; 02936 alpha=(*k)*(QuantumScale*GetPixelAlpha(image,k_pixels+u* 02937 GetPixelChannels(image))); 02938 gamma += alpha; 02939 result.red += alpha* 02940 GetPixelRed(image,k_pixels+u*GetPixelChannels(image)); 02941 result.green += alpha* 02942 GetPixelGreen(image,k_pixels+u*GetPixelChannels(image)); 02943 result.blue += alpha* 02944 GetPixelBlue(image,k_pixels+u*GetPixelChannels(image)); 02945 if (image->colorspace == CMYKColorspace) 02946 result.black+=alpha* 02947 GetPixelBlack(image,k_pixels+u*GetPixelChannels(image)); 02948 result.alpha += (*k)* 02949 GetPixelAlpha(image,k_pixels+u*GetPixelChannels(image)); 02950 } 02951 k_pixels += virt_width*GetPixelChannels(image); 02952 } 02953 /* Sync'ed channels, all channels are modified */ 02954 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma); 02955 SetPixelRed(morphology_image, 02956 ClampToQuantum(gamma*result.red),q); 02957 SetPixelGreen(morphology_image, 02958 ClampToQuantum(gamma*result.green),q); 02959 SetPixelBlue(morphology_image, 02960 ClampToQuantum(gamma*result.blue),q); 02961 if (image->colorspace == CMYKColorspace) 02962 SetPixelBlack(morphology_image, 02963 ClampToQuantum(gamma*result.black),q); 02964 SetPixelAlpha(morphology_image,ClampToQuantum(result.alpha),q); 02965 } 02966 break; 02967 02968 case ErodeMorphology: 02969 /* Minimum Value within kernel neighbourhood 02970 ** 02971 ** NOTE that the kernel is not reflected for this operation! 02972 ** 02973 ** NOTE: in normal Greyscale Morphology, the kernel value should 02974 ** be added to the real value, this is currently not done, due to 02975 ** the nature of the boolean kernels being used. 02976 */ 02977 k = kernel->values; 02978 k_pixels = p; 02979 for (v=0; v < (ssize_t) kernel->height; v++) { 02980 for (u=0; u < (ssize_t) kernel->width; u++, k++) { 02981 if ( IsNan(*k) || (*k) < 0.5 ) continue; 02982 Minimize(min.red, (double) 02983 GetPixelRed(image,k_pixels+u*GetPixelChannels(image))); 02984 Minimize(min.green, (double) 02985 GetPixelGreen(image,k_pixels+u*GetPixelChannels(image))); 02986 Minimize(min.blue, (double) 02987 GetPixelBlue(image,k_pixels+u*GetPixelChannels(image))); 02988 if (image->colorspace == CMYKColorspace) 02989 Minimize(min.black,(double) 02990 GetPixelBlack(image,k_pixels+u*GetPixelChannels(image))); 02991 Minimize(min.alpha,(double) 02992 GetPixelAlpha(image,k_pixels+u*GetPixelChannels(image))); 02993 } 02994 k_pixels += virt_width*GetPixelChannels(image); 02995 } 02996 break; 02997 02998 case DilateMorphology: 02999 /* Maximum Value within kernel neighbourhood 03000 ** 03001 ** NOTE for correct working of this operation for asymetrical 03002 ** kernels, the kernel needs to be applied in its reflected form. 03003 ** That is its values needs to be reversed. 03004 ** 03005 ** NOTE: in normal Greyscale Morphology, the kernel value should 03006 ** be added to the real value, this is currently not done, due to 03007 ** the nature of the boolean kernels being used. 03008 ** 03009 */ 03010 k = &kernel->values[ kernel->width*kernel->height-1 ]; 03011 k_pixels = p; 03012 for (v=0; v < (ssize_t) kernel->height; v++) { 03013 for (u=0; u < (ssize_t) kernel->width; u++, k--) { 03014 if ( IsNan(*k) || (*k) < 0.5 ) continue; 03015 Maximize(max.red, (double) 03016 GetPixelRed(image,k_pixels+u*GetPixelChannels(image))); 03017 Maximize(max.green, (double) 03018 GetPixelGreen(image,k_pixels+u*GetPixelChannels(image))); 03019 Maximize(max.blue, (double) 03020 GetPixelBlue(image,k_pixels+u*GetPixelChannels(image))); 03021 if (image->colorspace == CMYKColorspace) 03022 Maximize(max.black, (double) 03023 GetPixelBlack(image,k_pixels+u*GetPixelChannels(image))); 03024 Maximize(max.alpha,(double) 03025 GetPixelAlpha(image,k_pixels+u*GetPixelChannels(image))); 03026 } 03027 k_pixels += virt_width*GetPixelChannels(image); 03028 } 03029 break; 03030 03031 case HitAndMissMorphology: 03032 case ThinningMorphology: 03033 case ThickenMorphology: 03034 /* Minimum of Foreground Pixel minus Maxumum of Background Pixels 03035 ** 03036 ** NOTE that the kernel is not reflected for this operation, 03037 ** and consists of both foreground and background pixel 03038 ** neighbourhoods, 0.0 for background, and 1.0 for foreground 03039 ** with either Nan or 0.5 values for don't care. 03040 ** 03041 ** Note that this will never produce a meaningless negative 03042 ** result. Such results can cause Thinning/Thicken to not work 03043 ** correctly when used against a greyscale image. 03044 */ 03045 k = kernel->values; 03046 k_pixels = p; 03047 for (v=0; v < (ssize_t) kernel->height; v++) { 03048 for (u=0; u < (ssize_t) kernel->width; u++, k++) { 03049 if ( IsNan(*k) ) continue; 03050 if ( (*k) > 0.7 ) 03051 { /* minimim of foreground pixels */ 03052 Minimize(min.red, (double) 03053 GetPixelRed(image,k_pixels+u*GetPixelChannels(image))); 03054 Minimize(min.green, (double) 03055 GetPixelGreen(image,k_pixels+u*GetPixelChannels(image))); 03056 Minimize(min.blue, (double) 03057 GetPixelBlue(image,k_pixels+u*GetPixelChannels(image))); 03058 if ( image->colorspace == CMYKColorspace) 03059 Minimize(min.black,(double) 03060 GetPixelBlack(image,k_pixels+u*GetPixelChannels(image))); 03061 Minimize(min.alpha,(double) 03062 GetPixelAlpha(image,k_pixels+u*GetPixelChannels(image))); 03063 } 03064 else if ( (*k) < 0.3 ) 03065 { /* maximum of background pixels */ 03066 Maximize(max.red, (double) 03067 GetPixelRed(image,k_pixels+u*GetPixelChannels(image))); 03068 Maximize(max.green, (double) 03069 GetPixelGreen(image,k_pixels+u*GetPixelChannels(image))); 03070 Maximize(max.blue, (double) 03071 GetPixelBlue(image,k_pixels+u*GetPixelChannels(image))); 03072 if (image->colorspace == CMYKColorspace) 03073 Maximize(max.black, (double) 03074 GetPixelBlack(image,k_pixels+u*GetPixelChannels(image))); 03075 Maximize(max.alpha,(double) 03076 GetPixelAlpha(image,k_pixels+u*GetPixelChannels(image))); 03077 } 03078 } 03079 k_pixels += virt_width*GetPixelChannels(image); 03080 } 03081 /* Pattern Match if difference is positive */ 03082 min.red -= max.red; Maximize( min.red, 0.0 ); 03083 min.green -= max.green; Maximize( min.green, 0.0 ); 03084 min.blue -= max.blue; Maximize( min.blue, 0.0 ); 03085 min.black -= max.black; Maximize( min.black, 0.0 ); 03086 min.alpha -= max.alpha; Maximize( min.alpha, 0.0 ); 03087 break; 03088 03089 case ErodeIntensityMorphology: 03090 /* Select Pixel with Minimum Intensity within kernel neighbourhood 03091 ** 03092 ** WARNING: the intensity test fails for CMYK and does not 03093 ** take into account the moderating effect of the alpha channel 03094 ** on the intensity. 03095 ** 03096 ** NOTE that the kernel is not reflected for this operation! 03097 */ 03098 k = kernel->values; 03099 k_pixels = p; 03100 for (v=0; v < (ssize_t) kernel->height; v++) { 03101 for (u=0; u < (ssize_t) kernel->width; u++, k++) { 03102 if ( IsNan(*k) || (*k) < 0.5 ) continue; 03103 if ( result.red == 0.0 || 03104 GetPixelIntensity(image,k_pixels+u*GetPixelChannels(image)) < GetPixelIntensity(morphology_image,q) ) { 03105 /* copy the whole pixel - no channel selection */ 03106 SetPixelRed(morphology_image,GetPixelRed(image, 03107 k_pixels+u*GetPixelChannels(image)),q); 03108 SetPixelGreen(morphology_image,GetPixelGreen(image, 03109 k_pixels+u*GetPixelChannels(image)),q); 03110 SetPixelBlue(morphology_image,GetPixelBlue(image, 03111 k_pixels+u*GetPixelChannels(image)),q); 03112 SetPixelAlpha(morphology_image,GetPixelAlpha(image, 03113 k_pixels+u*GetPixelChannels(image)),q); 03114 if ( result.red > 0.0 ) changed++; 03115 result.red = 1.0; 03116 } 03117 } 03118 k_pixels += virt_width*GetPixelChannels(image); 03119 } 03120 break; 03121 03122 case DilateIntensityMorphology: 03123 /* Select Pixel with Maximum Intensity within kernel neighbourhood 03124 ** 03125 ** WARNING: the intensity test fails for CMYK and does not 03126 ** take into account the moderating effect of the alpha channel 03127 ** on the intensity (yet). 03128 ** 03129 ** NOTE for correct working of this operation for asymetrical 03130 ** kernels, the kernel needs to be applied in its reflected form. 03131 ** That is its values needs to be reversed. 03132 */ 03133 k = &kernel->values[ kernel->width*kernel->height-1 ]; 03134 k_pixels = p; 03135 for (v=0; v < (ssize_t) kernel->height; v++) { 03136 for (u=0; u < (ssize_t) kernel->width; u++, k--) { 03137 if ( IsNan(*k) || (*k) < 0.5 ) continue; /* boolean kernel */ 03138 if ( result.red == 0.0 || 03139 GetPixelIntensity(image,k_pixels+u*GetPixelChannels(image)) > GetPixelIntensity(morphology_image,q) ) { 03140 /* copy the whole pixel - no channel selection */ 03141 SetPixelRed(morphology_image,GetPixelRed(image, 03142 k_pixels+u*GetPixelChannels(image)),q); 03143 SetPixelGreen(morphology_image,GetPixelGreen(image, 03144 k_pixels+u*GetPixelChannels(image)),q); 03145 SetPixelBlue(morphology_image,GetPixelBlue(image, 03146 k_pixels+u*GetPixelChannels(image)),q); 03147 SetPixelAlpha(morphology_image,GetPixelAlpha(image, 03148 k_pixels+u*GetPixelChannels(image)),q); 03149 if ( result.red > 0.0 ) changed++; 03150 result.red = 1.0; 03151 } 03152 } 03153 k_pixels += virt_width*GetPixelChannels(image); 03154 } 03155 break; 03156 #if 0 03157 This code has been obsoleted by the MorphologyPrimitiveDirect() function. 03158 However it is still (almost) correct coding for Grayscale Morphology. 03159 That is... 03160 03161 GrayErode is equivalent but with kernel values subtracted from pixels 03162 without the kernel rotation 03163 GreyDilate is equivalent but using Maximum() instead of Minimum() 03164 using kernel rotation 03165 03166 It has thus been preserved for future implementation of those methods. 03167 03168 case DistanceMorphology: 03169 /* Add kernel Value and select the minimum value found. 03170 ** The result is a iterative distance from edge of image shape. 03171 ** 03172 ** All Distance Kernels are symetrical, but that may not always 03173 ** be the case. For example how about a distance from left edges? 03174 ** To work correctly with asymetrical kernels the reflected kernel 03175 ** needs to be applied. 03176 */ 03177 k = &kernel->values[ kernel->width*kernel->height-1 ]; 03178 k_pixels = p; 03179 for (v=0; v < (ssize_t) kernel->height; v++) { 03180 for (u=0; u < (ssize_t) kernel->width; u++, k--) { 03181 if ( IsNan(*k) ) continue; 03182 Minimize(result.red, (*k)+k_pixels[u].red); 03183 Minimize(result.green, (*k)+k_pixels[u].green); 03184 Minimize(result.blue, (*k)+k_pixels[u].blue); 03185 Minimize(result.alpha, (*k)+k_pixels[u].alpha); 03186 if ( image->colorspace == CMYKColorspace) 03187 Minimize(result.black,(*k)+GetPixelBlack(p_image,k_indexes+u)); 03188 } 03189 k_pixels += virt_width*GetPixelChannels(image); 03190 } 03191 break; 03192 #endif 03193 case UndefinedMorphology: 03194 default: 03195 break; /* Do nothing */ 03196 } 03197 /* Final mathematics of results (combine with original image?) 03198 ** 03199 ** NOTE: Difference Morphology operators Edge* and *Hat could also 03200 ** be done here but works better with iteration as a image difference 03201 ** in the controling function (below). Thicken and Thinning however 03202 ** should be done here so thay can be iterated correctly. 03203 */ 03204 switch ( method ) { 03205 case HitAndMissMorphology: 03206 case ErodeMorphology: 03207 result = min; /* minimum of neighbourhood */ 03208 break; 03209 case DilateMorphology: 03210 result = max; /* maximum of neighbourhood */ 03211 break; 03212 case ThinningMorphology: 03213 /* subtract pattern match from original */ 03214 result.red -= min.red; 03215 result.green -= min.green; 03216 result.blue -= min.blue; 03217 result.black -= min.black; 03218 result.alpha -= min.alpha; 03219 break; 03220 case ThickenMorphology: 03221 /* Add the pattern matchs to the original */ 03222 result.red += min.red; 03223 result.green += min.green; 03224 result.blue += min.blue; 03225 result.black += min.black; 03226 result.alpha += min.alpha; 03227 break; 03228 default: 03229 /* result directly calculated or assigned */ 03230 break; 03231 } 03232 /* Assign the resulting pixel values - Clamping Result */ 03233 switch ( method ) { 03234 case UndefinedMorphology: 03235 case ConvolveMorphology: 03236 case DilateIntensityMorphology: 03237 case ErodeIntensityMorphology: 03238 break; /* full pixel was directly assigned - not a channel method */ 03239 default: 03240 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0) 03241 SetPixelRed(morphology_image,ClampToQuantum(result.red),q); 03242 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0) 03243 SetPixelGreen(morphology_image,ClampToQuantum(result.green),q); 03244 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0) 03245 SetPixelBlue(morphology_image,ClampToQuantum(result.blue),q); 03246 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) && 03247 (image->colorspace == CMYKColorspace)) 03248 SetPixelBlack(morphology_image,ClampToQuantum(result.black),q); 03249 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) && 03250 (image->matte == MagickTrue)) 03251 SetPixelAlpha(morphology_image,ClampToQuantum(result.alpha),q); 03252 break; 03253 } 03254 /* Count up changed pixels */ 03255 if ((GetPixelRed(image,p+r*GetPixelChannels(image)) != GetPixelRed(morphology_image,q)) || 03256 (GetPixelGreen(image,p+r*GetPixelChannels(image)) != GetPixelGreen(morphology_image,q)) || 03257 (GetPixelBlue(image,p+r*GetPixelChannels(image)) != GetPixelBlue(morphology_image,q)) || 03258 (GetPixelAlpha(image,p+r*GetPixelChannels(image)) != GetPixelAlpha(morphology_image,q)) || 03259 ((image->colorspace == CMYKColorspace) && 03260 (GetPixelBlack(image,p+r*GetPixelChannels(image)) != GetPixelBlack(morphology_image,q)))) 03261 changed++; /* The pixel was changed in some way! */ 03262 p+=GetPixelChannels(image); 03263 q+=GetPixelChannels(morphology_image); 03264 } /* x */ 03265 if ( SyncCacheViewAuthenticPixels(morphology_view,exception) == MagickFalse) 03266 status=MagickFalse; 03267 if (image->progress_monitor != (MagickProgressMonitor) NULL) 03268 { 03269 MagickBooleanType 03270 proceed; 03271 03272 #if defined(MAGICKCORE_OPENMP_SUPPORT) 03273 #pragma omp critical (MagickCore_MorphologyImage) 03274 #endif 03275 proceed=SetImageProgress(image,MorphologyTag,progress++,image->rows); 03276 if (proceed == MagickFalse) 03277 status=MagickFalse; 03278 } 03279 } /* y */ 03280 morphology_view=DestroyCacheView(morphology_view); 03281 image_view=DestroyCacheView(image_view); 03282 return(status ? (ssize_t)changed : -1); 03283 } 03284 03285 /* This is almost identical to the MorphologyPrimative() function above, 03286 ** but will apply the primitive directly to the image in two passes. 03287 ** 03288 ** That is after each row is 'Sync'ed' into the image, the next row will 03289 ** make use of those values as part of the calculation of the next row. 03290 ** It then repeats, but going in the oppisite (bottom-up) direction. 03291 ** 03292 ** Because of this 'iterative' handling this function can not make use 03293 ** of multi-threaded, parellel processing. 03294 */ 03295 static ssize_t MorphologyPrimitiveDirect(Image *image, 03296 const MorphologyMethod method,const KernelInfo *kernel, 03297 ExceptionInfo *exception) 03298 { 03299 CacheView 03300 *auth_view, 03301 *virt_view; 03302 03303 MagickBooleanType 03304 status; 03305 03306 MagickOffsetType 03307 progress; 03308 03309 ssize_t 03310 y, offx, offy; 03311 03312 size_t 03313 virt_width, 03314 changed; 03315 03316 status=MagickTrue; 03317 changed=0; 03318 progress=0; 03319 03320 assert(image != (Image *) NULL); 03321 assert(image->signature == MagickSignature); 03322 assert(kernel != (KernelInfo *) NULL); 03323 assert(kernel->signature == MagickSignature); 03324 assert(exception != (ExceptionInfo *) NULL); 03325 assert(exception->signature == MagickSignature); 03326 03327 /* Some methods (including convolve) needs use a reflected kernel. 03328 * Adjust 'origin' offsets to loop though kernel as a reflection. 03329 */ 03330 offx = kernel->x; 03331 offy = kernel->y; 03332 switch(method) { 03333 case DistanceMorphology: 03334 case VoronoiMorphology: 03335 /* kernel needs to used with reflection about origin */ 03336 offx = (ssize_t) kernel->width-offx-1; 03337 offy = (ssize_t) kernel->height-offy-1; 03338 break; 03339 #if 0 03340 case ?????Morphology: 03341 /* kernel is used as is, without reflection */ 03342 break; 03343 #endif 03344 default: 03345 assert("Not a PrimativeDirect Morphology Method" != (char *) NULL); 03346 break; 03347 } 03348 03349 /* DO NOT THREAD THIS CODE! */ 03350 /* two views into same image (virtual, and actual) */ 03351 virt_view=AcquireCacheView(image); 03352 auth_view=AcquireCacheView(image); 03353 virt_width=image->columns+kernel->width-1; 03354 03355 for (y=0; y < (ssize_t) image->rows; y++) 03356 { 03357 register const Quantum 03358 *restrict p; 03359 03360 register Quantum 03361 *restrict q; 03362 03363 register ssize_t 03364 x; 03365 03366 ssize_t 03367 r; 03368 03369 /* NOTE read virtual pixels, and authentic pixels, from the same image! 03370 ** we read using virtual to get virtual pixel handling, but write back 03371 ** into the same image. 03372 ** 03373 ** Only top half of kernel is processed as we do a single pass downward 03374 ** through the image iterating the distance function as we go. 03375 */ 03376 if (status == MagickFalse) 03377 break; 03378 p=GetCacheViewVirtualPixels(virt_view,-offx,y-offy,virt_width,(size_t) 03379 offy+1,exception); 03380 q=GetCacheViewAuthenticPixels(auth_view, 0, y, image->columns, 1, 03381 exception); 03382 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL)) 03383 status=MagickFalse; 03384 if (status == MagickFalse) 03385 break; 03386 03387 /* offset to origin in 'p'. while 'q' points to it directly */ 03388 r = (ssize_t) virt_width*offy + offx; 03389 03390 for (x=0; x < (ssize_t) image->columns; x++) 03391 { 03392 ssize_t 03393 v; 03394 03395 register ssize_t 03396 u; 03397 03398 register const MagickRealType 03399 *restrict k; 03400 03401 register const Quantum 03402 *restrict k_pixels; 03403 03404 PixelInfo 03405 result; 03406 03407 /* Starting Defaults */ 03408 GetPixelInfo(image,&result); 03409 GetPixelInfoPixel(image,q,&result); 03410 if ( method != VoronoiMorphology ) 03411 result.alpha = QuantumRange - result.alpha; 03412 03413 switch ( method ) { 03414 case DistanceMorphology: 03415 /* Add kernel Value and select the minimum value found. */ 03416 k = &kernel->values[ kernel->width*kernel->height-1 ]; 03417 k_pixels = p; 03418 for (v=0; v <= (ssize_t) offy; v++) { 03419 for (u=0; u < (ssize_t) kernel->width; u++, k--) { 03420 if ( IsNan(*k) ) continue; 03421 Minimize(result.red, (*k)+ 03422 GetPixelRed(image,k_pixels+u*GetPixelChannels(image))); 03423 Minimize(result.green, (*k)+ 03424 GetPixelGreen(image,k_pixels+u*GetPixelChannels(image))); 03425 Minimize(result.blue, (*k)+ 03426 GetPixelBlue(image,k_pixels+u*GetPixelChannels(image))); 03427 if (image->colorspace == CMYKColorspace) 03428 Minimize(result.black,(*k)+ 03429 GetPixelBlue(image,k_pixels+u*GetPixelChannels(image))); 03430 Minimize(result.alpha, (*k)+ 03431 GetPixelAlpha(image,k_pixels+u*GetPixelChannels(image))); 03432 } 03433 k_pixels += virt_width*GetPixelChannels(image); 03434 } 03435 /* repeat with the just processed pixels of this row */ 03436 k = &kernel->values[ kernel->width*(kernel->y+1)-1 ]; 03437 k_pixels = q-offx*GetPixelChannels(image); 03438 for (u=0; u < (ssize_t) offx; u++, k--) { 03439 if ( x+u-offx < 0 ) continue; /* off the edge! */ 03440 if ( IsNan(*k) ) continue; 03441 Minimize(result.red, (*k)+ 03442 GetPixelRed(image,k_pixels+u*GetPixelChannels(image))); 03443 Minimize(result.green, (*k)+ 03444 GetPixelGreen(image,k_pixels+u*GetPixelChannels(image))); 03445 Minimize(result.blue, (*k)+ 03446 GetPixelBlue(image,k_pixels+u*GetPixelChannels(image))); 03447 if (image->colorspace == CMYKColorspace) 03448 Minimize(result.black,(*k)+ 03449 GetPixelBlack(image,k_pixels+u*GetPixelChannels(image))); 03450 Minimize(result.alpha,(*k)+ 03451 GetPixelAlpha(image,k_pixels+u*GetPixelChannels(image))); 03452 } 03453 break; 03454 case VoronoiMorphology: 03455 /* Apply Distance to 'Matte' channel, coping the closest color. 03456 ** 03457 ** This is experimental, and realy the 'alpha' component should 03458 ** be completely separate 'masking' channel. 03459 */ 03460 k = &kernel->values[ kernel->width*kernel->height-1 ]; 03461 k_pixels = p; 03462 for (v=0; v <= (ssize_t) offy; v++) { 03463 for (u=0; u < (ssize_t) kernel->width; u++, k--) { 03464 if ( IsNan(*k) ) continue; 03465 if( result.alpha > (*k)+GetPixelAlpha(image,k_pixels+u*GetPixelChannels(image)) ) 03466 { 03467 GetPixelInfoPixel(image,k_pixels+u*GetPixelChannels(image), 03468 &result); 03469 result.alpha += *k; 03470 } 03471 } 03472 k_pixels += virt_width*GetPixelChannels(image); 03473 } 03474 /* repeat with the just processed pixels of this row */ 03475 k = &kernel->values[ kernel->width*(kernel->y+1)-1 ]; 03476 k_pixels = q-offx*GetPixelChannels(image); 03477 for (u=0; u < (ssize_t) offx; u++, k--) { 03478 if ( x+u-offx < 0 ) continue; /* off the edge! */ 03479 if ( IsNan(*k) ) continue; 03480 if( result.alpha > (*k)+GetPixelAlpha(image,k_pixels+u*GetPixelChannels(image)) ) 03481 { 03482 GetPixelInfoPixel(image,k_pixels+u*GetPixelChannels(image), 03483 &result); 03484 result.alpha += *k; 03485 } 03486 } 03487 break; 03488 default: 03489 /* result directly calculated or assigned */ 03490 break; 03491 } 03492 /* Assign the resulting pixel values - Clamping Result */ 03493 switch ( method ) { 03494 case VoronoiMorphology: 03495 SetPixelInfoPixel(image,&result,q); 03496 break; 03497 default: 03498 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0) 03499 SetPixelRed(image,ClampToQuantum(result.red),q); 03500 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0) 03501 SetPixelGreen(image,ClampToQuantum(result.green),q); 03502 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0) 03503 SetPixelBlue(image,ClampToQuantum(result.blue),q); 03504 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) && 03505 (image->colorspace == CMYKColorspace)) 03506 SetPixelBlack(image,ClampToQuantum(result.black),q); 03507 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0 && 03508 (image->matte == MagickTrue)) 03509 SetPixelAlpha(image,ClampToQuantum(result.alpha),q); 03510 break; 03511 } 03512 /* Count up changed pixels */ 03513 if ((GetPixelRed(image,p+r*GetPixelChannels(image)) != GetPixelRed(image,q)) || 03514 (GetPixelGreen(image,p+r*GetPixelChannels(image)) != GetPixelGreen(image,q)) || 03515 (GetPixelBlue(image,p+r*GetPixelChannels(image)) != GetPixelBlue(image,q)) || 03516 (GetPixelAlpha(image,p+r*GetPixelChannels(image)) != GetPixelAlpha(image,q)) || 03517 ((image->colorspace == CMYKColorspace) && 03518 (GetPixelBlack(image,p+r*GetPixelChannels(image)) != GetPixelBlack(image,q)))) 03519 changed++; /* The pixel was changed in some way! */ 03520 03521 p+=GetPixelChannels(image); /* increment pixel buffers */ 03522 q+=GetPixelChannels(image); 03523 } /* x */ 03524 03525 if ( SyncCacheViewAuthenticPixels(auth_view,exception) == MagickFalse) 03526 status=MagickFalse; 03527 if (image->progress_monitor != (MagickProgressMonitor) NULL) 03528 if ( SetImageProgress(image,MorphologyTag,progress++,image->rows) 03529 == MagickFalse ) 03530 status=MagickFalse; 03531 03532 } /* y */ 03533 03534 /* Do the reversed pass through the image */ 03535 for (y=(ssize_t)image->rows-1; y >= 0; y--) 03536 { 03537 register const Quantum 03538 *restrict p; 03539 03540 register Quantum 03541 *restrict q; 03542 03543 register ssize_t 03544 x; 03545 03546 ssize_t 03547 r; 03548 03549 if (status == MagickFalse) 03550 break; 03551 /* NOTE read virtual pixels, and authentic pixels, from the same image! 03552 ** we read using virtual to get virtual pixel handling, but write back 03553 ** into the same image. 03554 ** 03555 ** Only the bottom half of the kernel will be processes as we 03556 ** up the image. 03557 */ 03558 p=GetCacheViewVirtualPixels(virt_view,-offx,y,virt_width,(size_t) 03559 kernel->y+1,exception); 03560 q=GetCacheViewAuthenticPixels(auth_view, 0, y, image->columns, 1, 03561 exception); 03562 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL)) 03563 status=MagickFalse; 03564 if (status == MagickFalse) 03565 break; 03566 03567 /* adjust positions to end of row */ 03568 p += (image->columns-1)*GetPixelChannels(image); 03569 q += (image->columns-1)*GetPixelChannels(image); 03570 03571 /* offset to origin in 'p'. while 'q' points to it directly */ 03572 r = offx; 03573 03574 for (x=(ssize_t)image->columns-1; x >= 0; x--) 03575 { 03576 ssize_t 03577 v; 03578 03579 register ssize_t 03580 u; 03581 03582 register const MagickRealType 03583 *restrict k; 03584 03585 register const Quantum 03586 *restrict k_pixels; 03587 03588 PixelInfo 03589 result; 03590 03591 /* Default - previously modified pixel */ 03592 GetPixelInfo(image,&result); 03593 GetPixelInfoPixel(image,q,&result); 03594 if ( method != VoronoiMorphology ) 03595 result.alpha = QuantumRange - result.alpha; 03596 03597 switch ( method ) { 03598 case DistanceMorphology: 03599 /* Add kernel Value and select the minimum value found. */ 03600 k = &kernel->values[ kernel->width*(kernel->y+1)-1 ]; 03601 k_pixels = p; 03602 for (v=offy; v < (ssize_t) kernel->height; v++) { 03603 for (u=0; u < (ssize_t) kernel->width; u++, k--) { 03604 if ( IsNan(*k) ) continue; 03605 Minimize(result.red, (*k)+ 03606 GetPixelRed(image,k_pixels+u*GetPixelChannels(image))); 03607 Minimize(result.green, (*k)+ 03608 GetPixelGreen(image,k_pixels+u*GetPixelChannels(image))); 03609 Minimize(result.blue, (*k)+ 03610 GetPixelBlue(image,k_pixels+u*GetPixelChannels(image))); 03611 if ( image->colorspace == CMYKColorspace) 03612 Minimize(result.black,(*k)+ 03613 GetPixelBlack(image,k_pixels+u*GetPixelChannels(image))); 03614 Minimize(result.alpha, (*k)+ 03615 GetPixelAlpha(image,k_pixels+u*GetPixelChannels(image))); 03616 } 03617 k_pixels += virt_width*GetPixelChannels(image); 03618 } 03619 /* repeat with the just processed pixels of this row */ 03620 k = &kernel->values[ kernel->width*(kernel->y)+kernel->x-1 ]; 03621 k_pixels = q-offx*GetPixelChannels(image); 03622 for (u=offx+1; u < (ssize_t) kernel->width; u++, k--) { 03623 if ( (x+u-offx) >= (ssize_t)image->columns ) continue; 03624 if ( IsNan(*k) ) continue; 03625 Minimize(result.red, (*k)+ 03626 GetPixelRed(image,k_pixels+u*GetPixelChannels(image))); 03627 Minimize(result.green, (*k)+ 03628 GetPixelGreen(image,k_pixels+u*GetPixelChannels(image))); 03629 Minimize(result.blue, (*k)+ 03630 GetPixelBlue(image,k_pixels+u*GetPixelChannels(image))); 03631 if ( image->colorspace == CMYKColorspace) 03632 Minimize(result.black, (*k)+ 03633 GetPixelBlack(image,k_pixels+u*GetPixelChannels(image))); 03634 Minimize(result.alpha, (*k)+ 03635 GetPixelAlpha(image,k_pixels+u*GetPixelChannels(image))); 03636 } 03637 break; 03638 case