blob: 3b5d633a5a7ed49f2e79cb5286a5229c9dc15e9f [file] [log] [blame]
cristy701db312009-11-20 03:14:08 +00001/*
2%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3% %
4% %
5% %
6% M M OOO RRRR PPPP H H OOO L OOO GGGG Y Y %
7% MM MM O O R R P P H H O O L O O G Y Y %
8% M M M O O RRRR PPPP HHHHH O O L O O G GGG Y %
9% M M O O R R P H H O O L O O G G Y %
10% M M OOO R R P H H OOO LLLLL OOO GGG Y %
11% %
12% %
13% MagickCore Morphology Methods %
14% %
15% Software Design %
16% Anthony Thyssen %
anthonyc94cdb02010-01-06 08:15:29 +000017% January 2010 %
cristy701db312009-11-20 03:14:08 +000018% %
19% %
cristy16af1cb2009-12-11 21:38:29 +000020% Copyright 1999-2010 ImageMagick Studio LLC, a non-profit organization %
cristy701db312009-11-20 03:14:08 +000021% dedicated to making software imaging solutions freely available. %
22% %
23% You may not use this file except in compliance with the License. You may %
24% obtain a copy of the License at %
25% %
26% http://www.imagemagick.org/script/license.php %
27% %
28% Unless required by applicable law or agreed to in writing, software %
29% distributed under the License is distributed on an "AS IS" BASIS, %
30% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %
31% See the License for the specific language governing permissions and %
32% limitations under the License. %
33% %
34%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
35%
anthony1b2bc0a2010-05-12 05:25:22 +000036% Morpology is the the application of various kernels, of any size and even
anthony602ab9b2010-01-05 08:06:50 +000037% shape, to a image in various ways (typically binary, but not always).
cristy701db312009-11-20 03:14:08 +000038%
anthony602ab9b2010-01-05 08:06:50 +000039% Convolution (weighted sum or average) is just one specific type of
40% morphology. Just one that is very common for image bluring and sharpening
41% effects. Not only 2D Gaussian blurring, but also 2-pass 1D Blurring.
42%
43% This module provides not only a general morphology function, and the ability
44% to apply more advanced or iterative morphologies, but also functions for the
45% generation of many different types of kernel arrays from user supplied
46% arguments. Prehaps even the generation of a kernel from a small image.
cristy701db312009-11-20 03:14:08 +000047*/
48
49/*
50 Include declarations.
51*/
52#include "magick/studio.h"
anthony602ab9b2010-01-05 08:06:50 +000053#include "magick/artifact.h"
cristy701db312009-11-20 03:14:08 +000054#include "magick/cache-view.h"
55#include "magick/color-private.h"
56#include "magick/enhance.h"
57#include "magick/exception.h"
58#include "magick/exception-private.h"
anthony602ab9b2010-01-05 08:06:50 +000059#include "magick/gem.h"
cristy701db312009-11-20 03:14:08 +000060#include "magick/hashmap.h"
61#include "magick/image.h"
cristybba804b2010-01-05 15:39:59 +000062#include "magick/image-private.h"
cristy701db312009-11-20 03:14:08 +000063#include "magick/list.h"
anthony29188a82010-01-22 10:12:34 +000064#include "magick/magick.h"
cristy701db312009-11-20 03:14:08 +000065#include "magick/memory_.h"
66#include "magick/monitor-private.h"
67#include "magick/morphology.h"
anthony602ab9b2010-01-05 08:06:50 +000068#include "magick/option.h"
cristy701db312009-11-20 03:14:08 +000069#include "magick/pixel-private.h"
70#include "magick/prepress.h"
71#include "magick/quantize.h"
72#include "magick/registry.h"
73#include "magick/semaphore.h"
74#include "magick/splay-tree.h"
75#include "magick/statistic.h"
76#include "magick/string_.h"
anthony602ab9b2010-01-05 08:06:50 +000077#include "magick/string-private.h"
78#include "magick/token.h"
cristya29d45f2010-03-05 21:14:54 +000079
anthony602ab9b2010-01-05 08:06:50 +000080/*
cristya29d45f2010-03-05 21:14:54 +000081 The following test is for special floating point numbers of value NaN (not
82 a number), that may be used within a Kernel Definition. NaN's are defined
83 as part of the IEEE standard for floating point number representation.
84
anthony1b2bc0a2010-05-12 05:25:22 +000085 These are used a Kernel value of NaN means that that kernel position is not
cristya29d45f2010-03-05 21:14:54 +000086 part of the normal convolution or morphology process, and thus allowing the
87 use of 'shaped' kernels.
88
89 Special properities two NaN's are never equal, even if they are from the
90 same variable That is the IsNaN() macro is only true if the value is NaN.
91*/
anthony602ab9b2010-01-05 08:06:50 +000092#define IsNan(a) ((a)!=(a))
93
anthony29188a82010-01-22 10:12:34 +000094/*
cristya29d45f2010-03-05 21:14:54 +000095 Other global definitions used by module.
96*/
anthony29188a82010-01-22 10:12:34 +000097static inline double MagickMin(const double x,const double y)
98{
99 return( x < y ? x : y);
100}
101static inline double MagickMax(const double x,const double y)
102{
103 return( x > y ? x : y);
104}
105#define Minimize(assign,value) assign=MagickMin(assign,value)
106#define Maximize(assign,value) assign=MagickMax(assign,value)
107
anthonyc4c86e02010-01-27 09:30:32 +0000108/* Currently these are only internal to this module */
109static void
anthony3c10fc82010-05-13 02:40:51 +0000110 ExpandKernelInfo(KernelInfo *, double),
cristyef656912010-03-05 19:54:59 +0000111 RotateKernelInfo(KernelInfo *, double);
anthony602ab9b2010-01-05 08:06:50 +0000112
anthony3dd0f622010-05-13 12:57:32 +0000113
114/* Quick function to find last kernel in a kernel list */
115static inline KernelInfo *LastKernelInfo(KernelInfo *kernel)
116{
117 while (kernel->next != (KernelInfo *) NULL)
118 kernel = kernel->next;
119 return(kernel);
120}
121
122
anthony602ab9b2010-01-05 08:06:50 +0000123/*
124%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
125% %
126% %
127% %
anthony83ba99b2010-01-24 08:48:15 +0000128% A c q u i r e K e r n e l I n f o %
anthony602ab9b2010-01-05 08:06:50 +0000129% %
130% %
131% %
132%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
133%
cristy2be15382010-01-21 02:38:03 +0000134% AcquireKernelInfo() takes the given string (generally supplied by the
anthony602ab9b2010-01-05 08:06:50 +0000135% user) and converts it into a Morphology/Convolution Kernel. This allows
136% users to specify a kernel from a number of pre-defined kernels, or to fully
137% specify their own kernel for a specific Convolution or Morphology
138% Operation.
139%
140% The kernel so generated can be any rectangular array of floating point
141% values (doubles) with the 'control point' or 'pixel being affected'
142% anywhere within that array of values.
143%
anthony83ba99b2010-01-24 08:48:15 +0000144% Previously IM was restricted to a square of odd size using the exact
145% center as origin, this is no longer the case, and any rectangular kernel
146% with any value being declared the origin. This in turn allows the use of
147% highly asymmetrical kernels.
anthony602ab9b2010-01-05 08:06:50 +0000148%
149% The floating point values in the kernel can also include a special value
anthony83ba99b2010-01-24 08:48:15 +0000150% known as 'nan' or 'not a number' to indicate that this value is not part
151% of the kernel array. This allows you to shaped the kernel within its
152% rectangular area. That is 'nan' values provide a 'mask' for the kernel
153% shape. However at least one non-nan value must be provided for correct
154% working of a kernel.
anthony602ab9b2010-01-05 08:06:50 +0000155%
anthony7a01dcf2010-05-11 12:25:52 +0000156% The returned kernel should be freed using the DestroyKernelInfo() when you
157% are finished with it. Do not free this memory yourself.
anthony602ab9b2010-01-05 08:06:50 +0000158%
159% Input kernel defintion strings can consist of any of three types.
160%
anthony29188a82010-01-22 10:12:34 +0000161% "name:args"
162% Select from one of the built in kernels, using the name and
163% geometry arguments supplied. See AcquireKernelBuiltIn()
anthony602ab9b2010-01-05 08:06:50 +0000164%
165% "WxH[+X+Y]:num, num, num ..."
anthony1b2bc0a2010-05-12 05:25:22 +0000166% a kernel of size W by H, with W*H floating point numbers following.
anthony602ab9b2010-01-05 08:06:50 +0000167% the 'center' can be optionally be defined at +X+Y (such that +0+0
anthony29188a82010-01-22 10:12:34 +0000168% is top left corner). If not defined the pixel in the center, for
169% odd sizes, or to the immediate top or left of center for even sizes
170% is automatically selected.
anthony602ab9b2010-01-05 08:06:50 +0000171%
anthony29188a82010-01-22 10:12:34 +0000172% "num, num, num, num, ..."
173% list of floating point numbers defining an 'old style' odd sized
174% square kernel. At least 9 values should be provided for a 3x3
175% square kernel, 25 for a 5x5 square kernel, 49 for 7x7, etc.
176% Values can be space or comma separated. This is not recommended.
anthony602ab9b2010-01-05 08:06:50 +0000177%
anthony83ba99b2010-01-24 08:48:15 +0000178% Note that 'name' kernels will start with an alphabetic character while the
179% new kernel specification has a ':' character in its specification string.
180% If neither is the case, it is assumed an old style of a simple list of
181% numbers generating a odd-sized square kernel has been given.
anthony602ab9b2010-01-05 08:06:50 +0000182%
anthony7a01dcf2010-05-11 12:25:52 +0000183% You can define a 'list of kernels' which can be used by some morphology
184% operators A list is defined as a semi-colon seperated list kernels.
185%
anthonydbc89892010-05-12 07:05:27 +0000186% " kernel ; kernel ; kernel ; "
anthony7a01dcf2010-05-11 12:25:52 +0000187%
anthonydbc89892010-05-12 07:05:27 +0000188% Extra ';' characters are simply ignored.
anthony7a01dcf2010-05-11 12:25:52 +0000189%
anthony602ab9b2010-01-05 08:06:50 +0000190% The format of the AcquireKernal method is:
191%
cristy2be15382010-01-21 02:38:03 +0000192% KernelInfo *AcquireKernelInfo(const char *kernel_string)
anthony602ab9b2010-01-05 08:06:50 +0000193%
194% A description of each parameter follows:
195%
196% o kernel_string: the Morphology/Convolution kernel wanted.
197%
198*/
199
anthonyc84dce52010-05-07 05:42:23 +0000200/* This was separated so that it could be used as a separate
anthony5ef8e942010-05-11 06:51:12 +0000201** array input handling function, such as for -color-matrix
anthonyc84dce52010-05-07 05:42:23 +0000202*/
anthony5ef8e942010-05-11 06:51:12 +0000203static KernelInfo *ParseKernelArray(const char *kernel_string)
anthony602ab9b2010-01-05 08:06:50 +0000204{
cristy2be15382010-01-21 02:38:03 +0000205 KernelInfo
anthony602ab9b2010-01-05 08:06:50 +0000206 *kernel;
207
208 char
209 token[MaxTextExtent];
210
anthony602ab9b2010-01-05 08:06:50 +0000211 const char
anthony5ef8e942010-05-11 06:51:12 +0000212 *p,
213 *end;
anthony602ab9b2010-01-05 08:06:50 +0000214
anthonyc84dce52010-05-07 05:42:23 +0000215 register long
216 i;
anthony602ab9b2010-01-05 08:06:50 +0000217
anthony29188a82010-01-22 10:12:34 +0000218 double
219 nan = sqrt((double)-1.0); /* Special Value : Not A Number */
220
cristy2be15382010-01-21 02:38:03 +0000221 kernel=(KernelInfo *) AcquireMagickMemory(sizeof(*kernel));
222 if (kernel == (KernelInfo *)NULL)
anthony602ab9b2010-01-05 08:06:50 +0000223 return(kernel);
224 (void) ResetMagickMemory(kernel,0,sizeof(*kernel));
anthony7a01dcf2010-05-11 12:25:52 +0000225 kernel->minimum = kernel->maximum = 0.0;
226 kernel->negative_range = kernel->positive_range = 0.0;
anthony602ab9b2010-01-05 08:06:50 +0000227 kernel->type = UserDefinedKernel;
anthony7a01dcf2010-05-11 12:25:52 +0000228 kernel->next = (KernelInfo *) NULL;
cristyd43a46b2010-01-21 02:13:41 +0000229 kernel->signature = MagickSignature;
anthony602ab9b2010-01-05 08:06:50 +0000230
anthony5ef8e942010-05-11 06:51:12 +0000231 /* find end of this specific kernel definition string */
232 end = strchr(kernel_string, ';');
233 if ( end == (char *) NULL )
234 end = strchr(kernel_string, '\0');
235
anthony602ab9b2010-01-05 08:06:50 +0000236 /* Has a ':' in argument - New user kernel specification */
237 p = strchr(kernel_string, ':');
anthony5ef8e942010-05-11 06:51:12 +0000238 if ( p != (char *) NULL && p < end)
anthony602ab9b2010-01-05 08:06:50 +0000239 {
anthonyc84dce52010-05-07 05:42:23 +0000240 MagickStatusType
241 flags;
242
243 GeometryInfo
244 args;
245
anthony602ab9b2010-01-05 08:06:50 +0000246 /* ParseGeometry() needs the geometry separated! -- Arrgghh */
cristy150989e2010-02-01 14:59:39 +0000247 memcpy(token, kernel_string, (size_t) (p-kernel_string));
anthony602ab9b2010-01-05 08:06:50 +0000248 token[p-kernel_string] = '\0';
anthonyc84dce52010-05-07 05:42:23 +0000249 SetGeometryInfo(&args);
anthony602ab9b2010-01-05 08:06:50 +0000250 flags = ParseGeometry(token, &args);
anthony602ab9b2010-01-05 08:06:50 +0000251
anthony29188a82010-01-22 10:12:34 +0000252 /* Size handling and checks of geometry settings */
anthony602ab9b2010-01-05 08:06:50 +0000253 if ( (flags & WidthValue) == 0 ) /* if no width then */
254 args.rho = args.sigma; /* then width = height */
255 if ( args.rho < 1.0 ) /* if width too small */
256 args.rho = 1.0; /* then width = 1 */
257 if ( args.sigma < 1.0 ) /* if height too small */
258 args.sigma = args.rho; /* then height = width */
259 kernel->width = (unsigned long)args.rho;
260 kernel->height = (unsigned long)args.sigma;
261
262 /* Offset Handling and Checks */
263 if ( args.xi < 0.0 || args.psi < 0.0 )
anthony83ba99b2010-01-24 08:48:15 +0000264 return(DestroyKernelInfo(kernel));
cristyc99304f2010-02-01 15:26:27 +0000265 kernel->x = ((flags & XValue)!=0) ? (long)args.xi
cristy150989e2010-02-01 14:59:39 +0000266 : (long) (kernel->width-1)/2;
cristyc99304f2010-02-01 15:26:27 +0000267 kernel->y = ((flags & YValue)!=0) ? (long)args.psi
cristy150989e2010-02-01 14:59:39 +0000268 : (long) (kernel->height-1)/2;
cristyc99304f2010-02-01 15:26:27 +0000269 if ( kernel->x >= (long) kernel->width ||
270 kernel->y >= (long) kernel->height )
anthony83ba99b2010-01-24 08:48:15 +0000271 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000272
273 p++; /* advance beyond the ':' */
274 }
275 else
anthonyc84dce52010-05-07 05:42:23 +0000276 { /* ELSE - Old old specification, forming odd-square kernel */
anthony602ab9b2010-01-05 08:06:50 +0000277 /* count up number of values given */
278 p=(const char *) kernel_string;
cristya699b172010-01-06 16:48:49 +0000279 while ((isspace((int) ((unsigned char) *p)) != 0) || (*p == '\''))
anthony29188a82010-01-22 10:12:34 +0000280 p++; /* ignore "'" chars for convolve filter usage - Cristy */
anthony5ef8e942010-05-11 06:51:12 +0000281 for (i=0; p < end; i++)
anthony602ab9b2010-01-05 08:06:50 +0000282 {
283 GetMagickToken(p,&p,token);
284 if (*token == ',')
285 GetMagickToken(p,&p,token);
286 }
287 /* set the size of the kernel - old sized square */
288 kernel->width = kernel->height= (unsigned long) sqrt((double) i+1.0);
cristyc99304f2010-02-01 15:26:27 +0000289 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony602ab9b2010-01-05 08:06:50 +0000290 p=(const char *) kernel_string;
anthony29188a82010-01-22 10:12:34 +0000291 while ((isspace((int) ((unsigned char) *p)) != 0) || (*p == '\''))
292 p++; /* ignore "'" chars for convolve filter usage - Cristy */
anthony602ab9b2010-01-05 08:06:50 +0000293 }
294
295 /* Read in the kernel values from rest of input string argument */
296 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
297 kernel->height*sizeof(double));
298 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +0000299 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000300
cristyc99304f2010-02-01 15:26:27 +0000301 kernel->minimum = +MagickHuge;
302 kernel->maximum = -MagickHuge;
303 kernel->negative_range = kernel->positive_range = 0.0;
anthonyc84dce52010-05-07 05:42:23 +0000304
anthony5ef8e942010-05-11 06:51:12 +0000305 for (i=0; (i < (long) (kernel->width*kernel->height)) && (p < end); i++)
anthony602ab9b2010-01-05 08:06:50 +0000306 {
307 GetMagickToken(p,&p,token);
308 if (*token == ',')
309 GetMagickToken(p,&p,token);
anthony29188a82010-01-22 10:12:34 +0000310 if ( LocaleCompare("nan",token) == 0
anthonyc84dce52010-05-07 05:42:23 +0000311 || LocaleCompare("-",token) == 0 ) {
anthony29188a82010-01-22 10:12:34 +0000312 kernel->values[i] = nan; /* do not include this value in kernel */
313 }
314 else {
315 kernel->values[i] = StringToDouble(token);
316 ( kernel->values[i] < 0)
cristyc99304f2010-02-01 15:26:27 +0000317 ? ( kernel->negative_range += kernel->values[i] )
318 : ( kernel->positive_range += kernel->values[i] );
319 Minimize(kernel->minimum, kernel->values[i]);
320 Maximize(kernel->maximum, kernel->values[i]);
anthony29188a82010-01-22 10:12:34 +0000321 }
anthony602ab9b2010-01-05 08:06:50 +0000322 }
anthony29188a82010-01-22 10:12:34 +0000323
anthony5ef8e942010-05-11 06:51:12 +0000324 /* sanity check -- no more values in kernel definition */
325 GetMagickToken(p,&p,token);
326 if ( *token != '\0' && *token != ';' && *token != '\'' )
327 return(DestroyKernelInfo(kernel));
328
anthonyc84dce52010-05-07 05:42:23 +0000329#if 0
330 /* this was the old method of handling a incomplete kernel */
cristy150989e2010-02-01 14:59:39 +0000331 if ( i < (long) (kernel->width*kernel->height) ) {
cristyc99304f2010-02-01 15:26:27 +0000332 Minimize(kernel->minimum, kernel->values[i]);
333 Maximize(kernel->maximum, kernel->values[i]);
cristy150989e2010-02-01 14:59:39 +0000334 for ( ; i < (long) (kernel->width*kernel->height); i++)
anthony29188a82010-01-22 10:12:34 +0000335 kernel->values[i]=0.0;
336 }
anthonyc84dce52010-05-07 05:42:23 +0000337#else
338 /* Number of values for kernel was not enough - Report Error */
339 if ( i < (long) (kernel->width*kernel->height) )
340 return(DestroyKernelInfo(kernel));
341#endif
342
343 /* check that we recieved at least one real (non-nan) value! */
344 if ( kernel->minimum == MagickHuge )
345 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000346
347 return(kernel);
348}
anthonyc84dce52010-05-07 05:42:23 +0000349
anthony5ef8e942010-05-11 06:51:12 +0000350static KernelInfo *ParseNamedKernel(const char *kernel_string)
anthonyc84dce52010-05-07 05:42:23 +0000351{
352 char
353 token[MaxTextExtent];
354
anthony5ef8e942010-05-11 06:51:12 +0000355 long
356 type;
357
anthonyc84dce52010-05-07 05:42:23 +0000358 const char
anthony7a01dcf2010-05-11 12:25:52 +0000359 *p,
360 *end;
anthonyc84dce52010-05-07 05:42:23 +0000361
362 MagickStatusType
363 flags;
364
365 GeometryInfo
366 args;
367
anthonyc84dce52010-05-07 05:42:23 +0000368 /* Parse special 'named' kernel */
anthony5ef8e942010-05-11 06:51:12 +0000369 GetMagickToken(kernel_string,&p,token);
anthonyc84dce52010-05-07 05:42:23 +0000370 type=ParseMagickOption(MagickKernelOptions,MagickFalse,token);
371 if ( type < 0 || type == UserDefinedKernel )
anthony5ef8e942010-05-11 06:51:12 +0000372 return((KernelInfo *)NULL); /* not a valid named kernel */
anthonyc84dce52010-05-07 05:42:23 +0000373
374 while (((isspace((int) ((unsigned char) *p)) != 0) ||
anthony5ef8e942010-05-11 06:51:12 +0000375 (*p == ',') || (*p == ':' )) && (*p != '\0') && (*p != ';'))
anthonyc84dce52010-05-07 05:42:23 +0000376 p++;
anthony7a01dcf2010-05-11 12:25:52 +0000377
378 end = strchr(p, ';'); /* end of this kernel defintion */
379 if ( end == (char *) NULL )
380 end = strchr(p, '\0');
381
382 /* ParseGeometry() needs the geometry separated! -- Arrgghh */
383 memcpy(token, p, (size_t) (end-p));
384 token[end-p] = '\0';
anthonyc84dce52010-05-07 05:42:23 +0000385 SetGeometryInfo(&args);
anthony7a01dcf2010-05-11 12:25:52 +0000386 flags = ParseGeometry(token, &args);
anthonyc84dce52010-05-07 05:42:23 +0000387
anthony3c10fc82010-05-13 02:40:51 +0000388#if 0
389 /* For Debugging Geometry Input */
390 fprintf(stderr, "Geometry = %04x : %lf x %lf %+lf %+lf\n",
391 flags, args.rho, args.sigma, args.xi, args.psi );
392#endif
393
anthonyc84dce52010-05-07 05:42:23 +0000394 /* special handling of missing values in input string */
395 switch( type ) {
anthony5ef8e942010-05-11 06:51:12 +0000396 case RectangleKernel:
397 if ( (flags & WidthValue) == 0 ) /* if no width then */
398 args.rho = args.sigma; /* then width = height */
399 if ( args.rho < 1.0 ) /* if width too small */
400 args.rho = 3; /* then width = 3 */
401 if ( args.sigma < 1.0 ) /* if height too small */
402 args.sigma = args.rho; /* then height = width */
403 if ( (flags & XValue) == 0 ) /* center offset if not defined */
404 args.xi = (double)(((long)args.rho-1)/2);
405 if ( (flags & YValue) == 0 )
406 args.psi = (double)(((long)args.sigma-1)/2);
407 break;
408 case SquareKernel:
409 case DiamondKernel:
410 case DiskKernel:
411 case PlusKernel:
anthony3dd0f622010-05-13 12:57:32 +0000412 case CrossKernel:
anthony5ef8e942010-05-11 06:51:12 +0000413 /* If no scale given (a 0 scale is valid! - set it to 1.0 */
414 if ( (flags & HeightValue) == 0 )
415 args.sigma = 1.0;
416 break;
anthonyc1061722010-05-14 06:23:49 +0000417 case RingKernel:
418 if ( (flags & XValue) == 0 )
419 args.xi = 1.0;
420 break;
anthony5ef8e942010-05-11 06:51:12 +0000421 case ChebyshevKernel:
422 case ManhattenKernel:
423 case EuclideanKernel:
424 if ( (flags & HeightValue) == 0 )
425 args.sigma = 100.0; /* default distance scaling */
426 else if ( (flags & AspectValue ) != 0 ) /* '!' flag */
427 args.sigma = QuantumRange/args.sigma; /* maximum pixel distance */
428 else if ( (flags & PercentValue ) != 0 ) /* '%' flag */
429 args.sigma *= QuantumRange/100.0; /* percentage of color range */
430 break;
431 default:
432 break;
anthonyc84dce52010-05-07 05:42:23 +0000433 }
434
435 return(AcquireKernelBuiltIn((KernelInfoType)type, &args));
436}
437
anthony5ef8e942010-05-11 06:51:12 +0000438MagickExport KernelInfo *AcquireKernelInfo(const char *kernel_string)
439{
anthony7a01dcf2010-05-11 12:25:52 +0000440
441 KernelInfo
anthonydbc89892010-05-12 07:05:27 +0000442 *kernel,
443 *new_kernel,
444 *last_kernel;
anthony7a01dcf2010-05-11 12:25:52 +0000445
anthony5ef8e942010-05-11 06:51:12 +0000446 char
447 token[MaxTextExtent];
448
anthony7a01dcf2010-05-11 12:25:52 +0000449 const char
anthonydbc89892010-05-12 07:05:27 +0000450 *p;
anthony7a01dcf2010-05-11 12:25:52 +0000451
anthonye108a3f2010-05-12 07:24:03 +0000452 unsigned long
453 kernel_number;
454
anthonydbc89892010-05-12 07:05:27 +0000455 p = kernel_string;
456 kernel = last_kernel = NULL;
anthonye108a3f2010-05-12 07:24:03 +0000457 kernel_number = 0;
anthony5ef8e942010-05-11 06:51:12 +0000458
anthonydbc89892010-05-12 07:05:27 +0000459 while ( GetMagickToken(p,NULL,token), *token != '\0' ) {
anthony7a01dcf2010-05-11 12:25:52 +0000460
anthonydbc89892010-05-12 07:05:27 +0000461 /* ignore multiple ';' following each other */
462 if ( *token != ';' ) {
anthony7a01dcf2010-05-11 12:25:52 +0000463
anthonydbc89892010-05-12 07:05:27 +0000464 /* tokens starting with alpha is a Named kernel */
465 if (isalpha((int) *token) == 0)
466 new_kernel = ParseKernelArray(p);
467 else /* otherwise a user defined kernel array */
468 new_kernel = ParseNamedKernel(p);
anthony7a01dcf2010-05-11 12:25:52 +0000469
anthonye108a3f2010-05-12 07:24:03 +0000470 /* Error handling -- this is not proper error handling! */
471 if ( new_kernel == (KernelInfo *) NULL ) {
472 fprintf(stderr, "Failed to parse kernel number #%lu\n", kernel_number);
473 if ( kernel != (KernelInfo *) NULL )
474 kernel=DestroyKernelInfo(kernel);
475 return((KernelInfo *) NULL);
anthonydbc89892010-05-12 07:05:27 +0000476 }
anthonye108a3f2010-05-12 07:24:03 +0000477
478 /* initialise or append the kernel list */
anthony3dd0f622010-05-13 12:57:32 +0000479 if ( kernel == (KernelInfo *) NULL )
480 kernel = new_kernel;
481 else
anthonydbc89892010-05-12 07:05:27 +0000482 last_kernel->next = new_kernel;
anthony3dd0f622010-05-13 12:57:32 +0000483 last_kernel = LastKernelInfo(new_kernel);
anthonydbc89892010-05-12 07:05:27 +0000484 }
485
486 /* look for the next kernel in list */
487 p = strchr(p, ';');
488 if ( p == (char *) NULL )
489 break;
490 p++;
491
492 }
anthony7a01dcf2010-05-11 12:25:52 +0000493 return(kernel);
anthony5ef8e942010-05-11 06:51:12 +0000494}
495
anthony602ab9b2010-01-05 08:06:50 +0000496
497/*
498%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
499% %
500% %
501% %
502% A c q u i r e K e r n e l B u i l t I n %
503% %
504% %
505% %
506%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
507%
508% AcquireKernelBuiltIn() returned one of the 'named' built-in types of
509% kernels used for special purposes such as gaussian blurring, skeleton
510% pruning, and edge distance determination.
511%
512% They take a KernelType, and a set of geometry style arguments, which were
513% typically decoded from a user supplied string, or from a more complex
514% Morphology Method that was requested.
515%
516% The format of the AcquireKernalBuiltIn method is:
517%
cristy2be15382010-01-21 02:38:03 +0000518% KernelInfo *AcquireKernelBuiltIn(const KernelInfoType type,
anthony602ab9b2010-01-05 08:06:50 +0000519% const GeometryInfo args)
520%
521% A description of each parameter follows:
522%
523% o type: the pre-defined type of kernel wanted
524%
525% o args: arguments defining or modifying the kernel
526%
527% Convolution Kernels
528%
anthony3c10fc82010-05-13 02:40:51 +0000529% Gaussian:{radius},{sigma}
530% Generate a two-dimentional gaussian kernel, as used by -gaussian.
anthonyc1061722010-05-14 06:23:49 +0000531% The sigma for the curve is required. The resulting kernel is
532% normalized,
533%
534% If 'sigma' is zero, you get a single pixel on a field of zeros.
anthony602ab9b2010-01-05 08:06:50 +0000535%
536% NOTE: that the 'radius' is optional, but if provided can limit (clip)
537% the final size of the resulting kernel to a square 2*radius+1 in size.
538% The radius should be at least 2 times that of the sigma value, or
539% sever clipping and aliasing may result. If not given or set to 0 the
540% radius will be determined so as to produce the best minimal error
541% result, which is usally much larger than is normally needed.
542%
anthonyc1061722010-05-14 06:23:49 +0000543% DOG:{radius},{sigma1},{sigma2}
544% "Difference of Gaussians" Kernel.
545% As "Gaussian" but with a gaussian produced by 'sigma2' subtracted
546% from the gaussian produced by 'sigma1'. Typically sigma2 > sigma1.
547% The result is a zero-summing kernel.
anthony602ab9b2010-01-05 08:06:50 +0000548%
anthonyc1061722010-05-14 06:23:49 +0000549% Blur:{radius},{sigma}[,{angle}]
550% Generates a 1 dimensional or linear gaussian blur, at the angle given
551% (current restricted to orthogonal angles). If a 'radius' is given the
552% kernel is clipped to a width of 2*radius+1. Kernel can be rotated
553% by a 90 degree angle.
554%
555% If 'sigma' is zero, you get a single pixel on a field of zeros.
556%
557% Note that two convolutions with two "Blur" kernels perpendicular to
558% each other, is equivelent to a far larger "Gaussian" kernel with the
559% same sigma value, However it is much faster to apply. This is how the
560% "-blur" operator actually works.
561%
562% DOB:{radius},{sigma1},{sigma2}[,{angle}]
563% "Difference of Blurs" Kernel.
564% As "Blur" but with the 1D gaussian produced by 'sigma2' subtracted
565% from thethe 1D gaussian produced by 'sigma1'.
566% The result is a zero-summing kernel.
567%
568% This can be used to generate a faster "DOG" convolution, in the same
569% way "Blur" can.
anthony602ab9b2010-01-05 08:06:50 +0000570%
anthony3c10fc82010-05-13 02:40:51 +0000571% Comet:{width},{sigma},{angle}
572% Blur in one direction only, much like how a bright object leaves
anthony602ab9b2010-01-05 08:06:50 +0000573% a comet like trail. The Kernel is actually half a gaussian curve,
anthony3c10fc82010-05-13 02:40:51 +0000574% Adding two such blurs in opposite directions produces a Blur Kernel.
575% Angle can be rotated in multiples of 90 degrees.
anthony602ab9b2010-01-05 08:06:50 +0000576%
anthony3c10fc82010-05-13 02:40:51 +0000577% Note that the first argument is the width of the kernel and not the
anthony602ab9b2010-01-05 08:06:50 +0000578% radius of the kernel.
579%
580% # Still to be implemented...
581% #
anthony4fd27e22010-02-07 08:17:18 +0000582% # Filter2D
583% # Filter1D
584% # Set kernel values using a resize filter, and given scale (sigma)
585% # Cylindrical or Linear. Is this posible with an image?
586% #
anthony602ab9b2010-01-05 08:06:50 +0000587%
anthony3c10fc82010-05-13 02:40:51 +0000588% Named Constant Convolution Kernels
589%
anthonyc1061722010-05-14 06:23:49 +0000590% All these are unscaled, zero-summing kernels by default. As such for
591% non-HDRI version of ImageMagick some form of normalization, user scaling,
592% and biasing the results is recommended, to prevent the resulting image
593% being 'clipped'.
594%
595% The 3x3 kernels (most of these) can be circularly rotated in multiples of
596% 45 degrees to generate the 8 angled varients of each of the kernels.
anthony3c10fc82010-05-13 02:40:51 +0000597%
598% Laplacian:{type}
599% Generate Lapacian kernel of the type specified. (1 is the default)
anthonyc1061722010-05-14 06:23:49 +0000600% Type 0 : 3x3 with center:8 surounded by -1 (8 neighbourhood)
601% Type 1 : 3x3 with center:4 edge:-1 corner:0 (4 neighbourhood)
602% Type 2 : 3x3 with center:4 edge:-2 corner:1
603% Type 3 : 3x3 with center:4 edge:1 corner:-2
604% Type 4 : 5x5 laplacian
605% Type 5 : 7x7 laplacian
606%
607% Sobel:{angle}
608% Sobel 3x3 'Edge' convolution kernel (3x3)
609% -1, 0, 1
610% -2, 0,-2
611% -1, 0, 1
612% Roberts:{angle}
613% Roberts 3x3 convolution kernel (3x3)
614% 0, 0, 0
615% -1, 1, 0
616% 0, 0, 0
617% Compass:{angle}
618% Prewitt's "Compass" convolution kernel (3x3)
619% -1, 1, 1
620% -1,-2, 1
621% -1, 1, 1
622% Prewitt:{angle}
623% Prewitt Edge convolution kernel (3x3)
624% -1, 0, 1
625% -1, 0, 1
626% -1, 0, 1
anthony3c10fc82010-05-13 02:40:51 +0000627%
anthony602ab9b2010-01-05 08:06:50 +0000628% Boolean Kernels
629%
anthony3c10fc82010-05-13 02:40:51 +0000630% Diamond:[{radius}[,{scale}]]
anthony1b2bc0a2010-05-12 05:25:22 +0000631% Generate a diamond shaped kernel with given radius to the points.
anthony602ab9b2010-01-05 08:06:50 +0000632% Kernel size will again be radius*2+1 square and defaults to radius 1,
633% generating a 3x3 kernel that is slightly larger than a square.
634%
anthony3c10fc82010-05-13 02:40:51 +0000635% Square:[{radius}[,{scale}]]
anthony602ab9b2010-01-05 08:06:50 +0000636% Generate a square shaped kernel of size radius*2+1, and defaulting
637% to a 3x3 (radius 1).
638%
anthonyc1061722010-05-14 06:23:49 +0000639% Note that using a larger radius for the "Square" or the "Diamond" is
640% also equivelent to iterating the basic morphological method that many
641% times. However iterating with the smaller radius is actually faster
642% than using a larger kernel radius.
643%
644% Rectangle:{geometry}
645% Simply generate a rectangle of 1's with the size given. You can also
646% specify the location of the 'control point', otherwise the closest
647% pixel to the center of the rectangle is selected.
648%
649% Properly centered and odd sized rectangles work the best.
anthony602ab9b2010-01-05 08:06:50 +0000650%
anthony3c10fc82010-05-13 02:40:51 +0000651% Disk:[{radius}[,{scale}]]
anthony602ab9b2010-01-05 08:06:50 +0000652% Generate a binary disk of the radius given, radius may be a float.
653% Kernel size will be ceil(radius)*2+1 square.
654% NOTE: Here are some disk shapes of specific interest
anthonyc1061722010-05-14 06:23:49 +0000655% "Disk:1" => "diamond" or "cross:1"
656% "Disk:1.5" => "square"
657% "Disk:2" => "diamond:2"
658% "Disk:2.5" => a general disk shape of radius 2
659% "Disk:2.9" => "square:2"
660% "Disk:3.5" => default - octagonal/disk shape of radius 3
661% "Disk:4.2" => roughly octagonal shape of radius 4
662% "Disk:4.3" => a general disk shape of radius 4
anthony602ab9b2010-01-05 08:06:50 +0000663% After this all the kernel shape becomes more and more circular.
664%
665% Because a "disk" is more circular when using a larger radius, using a
666% larger radius is preferred over iterating the morphological operation.
667%
anthonyc1061722010-05-14 06:23:49 +0000668% Symbol Dilation Kernels
669%
670% These kernel is not a good general morphological kernel, but is used
671% more for highlighting and marking any single pixels in an image using,
672% a "Dilate" method as appropriate.
673%
674% For the same reasons iterating these kernels does not produce the
675% same result as using a larger radius for the symbol.
676%
anthony3c10fc82010-05-13 02:40:51 +0000677% Plus:[{radius}[,{scale}]]
anthony3dd0f622010-05-13 12:57:32 +0000678% Cross:[{radius}[,{scale}]]
anthonyc1061722010-05-14 06:23:49 +0000679% Generate a kernel in the shape of a 'plus' or a 'cross' with
680% a each arm the length of the given radius (default 2).
anthony3dd0f622010-05-13 12:57:32 +0000681%
682% NOTE: "plus:1" is equivelent to a "Diamond" kernel.
anthony602ab9b2010-01-05 08:06:50 +0000683%
anthonyc1061722010-05-14 06:23:49 +0000684% Ring:{radius1},{radius2}[,{scale}]
685% A ring of the values given that falls between the two radii.
686% Defaults to a ring of approximataly 3 radius in a 7x7 kernel.
687% This is the 'edge' pixels of the default "Disk" kernel,
688% More specifically, "Ring" -> "Ring:2.5,3.5,1.0"
anthony602ab9b2010-01-05 08:06:50 +0000689%
anthony3dd0f622010-05-13 12:57:32 +0000690% Hit and Miss Kernels
691%
692% Peak:radius1,radius2
anthonyc1061722010-05-14 06:23:49 +0000693% Find any peak larger than the pixels the fall between the two radii.
694% The default ring of pixels is as per "Ring".
anthony3dd0f622010-05-13 12:57:32 +0000695% Corners
696% Find corners of a binary shape
697% LineEnds
698% Find end points of lines (for pruning a skeletion)
699% LineJunctions
700% Find three line junctions (in a skeletion)
701% ConvexHull
702% Octagonal thicken kernel, to generate convex hulls of 45 degrees
703% Skeleton
704% Thinning kernel, which leaves behind a skeletion of a shape
anthony602ab9b2010-01-05 08:06:50 +0000705%
706% Distance Measuring Kernels
707%
anthonyc1061722010-05-14 06:23:49 +0000708% Different types of distance measuring methods, which are used with the
709% a 'Distance' morphology method for generating a gradient based on
710% distance from an edge of a binary shape, though there is a technique
711% for handling a anti-aliased shape.
712%
713% See the 'Distance' Morphological Method, for information of how it is
714% applied.
715%
anthony3dd0f622010-05-13 12:57:32 +0000716% Chebyshev:[{radius}][x{scale}[%!]]
anthonyc94cdb02010-01-06 08:15:29 +0000717% Chebyshev Distance (also known as Tchebychev Distance) is a value of
718% one to any neighbour, orthogonal or diagonal. One why of thinking of
719% it is the number of squares a 'King' or 'Queen' in chess needs to
720% traverse reach any other position on a chess board. It results in a
721% 'square' like distance function, but one where diagonals are closer
722% than expected.
anthony602ab9b2010-01-05 08:06:50 +0000723%
anthonyc1061722010-05-14 06:23:49 +0000724% Manhatten:[{radius}][x{scale}[%!]]
anthonyc94cdb02010-01-06 08:15:29 +0000725% Manhatten Distance (also known as Rectilinear Distance, or the Taxi
726% Cab metric), is the distance needed when you can only travel in
727% orthogonal (horizontal or vertical) only. It is the distance a 'Rook'
728% in chess would travel. It results in a diamond like distances, where
729% diagonals are further than expected.
anthony602ab9b2010-01-05 08:06:50 +0000730%
anthonyc1061722010-05-14 06:23:49 +0000731% Euclidean:[{radius}][x{scale}[%!]]
anthonyc94cdb02010-01-06 08:15:29 +0000732% Euclidean Distance is the 'direct' or 'as the crow flys distance.
733% However by default the kernel size only has a radius of 1, which
734% limits the distance to 'Knight' like moves, with only orthogonal and
735% diagonal measurements being correct. As such for the default kernel
736% you will get octagonal like distance function, which is reasonally
737% accurate.
738%
739% However if you use a larger radius such as "Euclidean:4" you will
740% get a much smoother distance gradient from the edge of the shape.
741% Of course a larger kernel is slower to use, and generally not needed.
742%
743% To allow the use of fractional distances that you get with diagonals
744% the actual distance is scaled by a fixed value which the user can
745% provide. This is not actually nessary for either ""Chebyshev" or
746% "Manhatten" distance kernels, but is done for all three distance
747% kernels. If no scale is provided it is set to a value of 100,
748% allowing for a maximum distance measurement of 655 pixels using a Q16
749% version of IM, from any edge. However for small images this can
750% result in quite a dark gradient.
751%
anthony602ab9b2010-01-05 08:06:50 +0000752*/
753
cristy2be15382010-01-21 02:38:03 +0000754MagickExport KernelInfo *AcquireKernelBuiltIn(const KernelInfoType type,
anthony602ab9b2010-01-05 08:06:50 +0000755 const GeometryInfo *args)
756{
cristy2be15382010-01-21 02:38:03 +0000757 KernelInfo
anthony602ab9b2010-01-05 08:06:50 +0000758 *kernel;
759
cristy150989e2010-02-01 14:59:39 +0000760 register long
anthony602ab9b2010-01-05 08:06:50 +0000761 i;
762
763 register long
764 u,
765 v;
766
767 double
768 nan = sqrt((double)-1.0); /* Special Value : Not A Number */
769
anthonyc1061722010-05-14 06:23:49 +0000770 /* Generate a new empty kernel if needed */
771 switch(type) {
772 case GaussianKernel:
773 case DOGKernel:
774 case BlurKernel:
775 case DOBKernel:
776 case CometKernel:
777 case DiamondKernel:
778 case SquareKernel:
779 case RectangleKernel:
780 case DiskKernel:
781 case PlusKernel:
782 case CrossKernel:
783 case RingKernel:
784 case PeaksKernel:
785 case ChebyshevKernel:
786 case ManhattenKernel:
787 case EuclideanKernel:
788 kernel=(KernelInfo *) AcquireMagickMemory(sizeof(*kernel));
789 if (kernel == (KernelInfo *) NULL)
790 return(kernel);
791 (void) ResetMagickMemory(kernel,0,sizeof(*kernel));
792 kernel->minimum = kernel->maximum = 0.0;
793 kernel->negative_range = kernel->positive_range = 0.0;
794 kernel->type = type;
795 kernel->next = (KernelInfo *) NULL;
796 kernel->signature = MagickSignature;
797 default:
798 break;
799 }
anthony602ab9b2010-01-05 08:06:50 +0000800
801 switch(type) {
802 /* Convolution Kernels */
803 case GaussianKernel:
anthonyc1061722010-05-14 06:23:49 +0000804 case DOGKernel:
anthony602ab9b2010-01-05 08:06:50 +0000805 { double
anthonyc1061722010-05-14 06:23:49 +0000806 sigma = fabs(args->sigma),
807 sigma2 = fabs(args->xi),
808 gamma;
anthony602ab9b2010-01-05 08:06:50 +0000809
anthonyc1061722010-05-14 06:23:49 +0000810 if ( args->rho >= 1.0 )
811 kernel->width = (unsigned long)args->rho*2+1;
812 else if ( (type == GaussianKernel) || (sigma >= sigma2) )
813 kernel->width = GetOptimalKernelWidth2D(args->rho,sigma);
814 else
815 kernel->width = GetOptimalKernelWidth2D(args->rho,sigma2);
816 kernel->height = kernel->width;
cristyc99304f2010-02-01 15:26:27 +0000817 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony602ab9b2010-01-05 08:06:50 +0000818 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
819 kernel->height*sizeof(double));
820 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +0000821 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000822
anthonyc1061722010-05-14 06:23:49 +0000823 /* Calculate a Positive Gaussian */
824 if ( sigma > MagickEpsilon )
825 { gamma = 1.0/(2.0*sigma*sigma); /* simplify loop expressions */
826 sigma = 1.0/(MagickPI*sigma);
827 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
828 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
829 kernel->values[i] = sigma*exp(-((double)(u*u+v*v))*gamma);
830 }
831 else /* special case - generate a unity kernel */
832 { (void) ResetMagickMemory(kernel->values,0, (size_t)
833 kernel->width*kernel->height*sizeof(double));
834 kernel->values[kernel->x+kernel->y*kernel->width] = 1.0;
835 }
836 if ( type == DOGKernel )
837 { /* Subtract a Negative Gaussian for "Difference of Gaussian" */
838 if ( sigma2 > MagickEpsilon )
839 { sigma = sigma2; /* simplify loop expressions */
840 gamma = 1.0/(2.0*sigma*sigma);
841 sigma = 1.0/(MagickPI*sigma);
842 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
843 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
844 kernel->values[i] -= sigma*exp(-((double)(u*u+v*v))*gamma);
845 }
846 else /* special case - subtract the unity kernel */
847 kernel->values[kernel->x+kernel->y*kernel->width] -= 1.0;
848 }
849 /* Note the above kernel may have been 'clipped' by a user defined
850 ** radius, producing a smaller (darker) kernel. Also for very small
851 ** sigma's (> 0.1) the central value becomes larger than one, and thus
852 ** producing a very bright kernel.
853 **
854 ** Normalization will still be needed.
855 */
anthony602ab9b2010-01-05 08:06:50 +0000856
anthonyc1061722010-05-14 06:23:49 +0000857 /* Work out the meta-data about kernel */
858 kernel->minimum = kernel->maximum = 0.0;
859 kernel->negative_range = kernel->positive_range = 0.0;
860 u=(long) kernel->width*kernel->height;
861 for ( i=0; i < u; i++)
862 {
863 if ( fabs(kernel->values[i]) < MagickEpsilon )
864 kernel->values[i] = 0.0;
865 ( kernel->values[i] < 0)
866 ? ( kernel->negative_range += kernel->values[i] )
867 : ( kernel->positive_range += kernel->values[i] );
868 Minimize(kernel->minimum, kernel->values[i]);
869 Maximize(kernel->maximum, kernel->values[i]);
870 }
anthony3dd0f622010-05-13 12:57:32 +0000871 /* Normalize the 2D Gaussian Kernel
872 **
anthonyc1061722010-05-14 06:23:49 +0000873 ** NB: a CorrelateNormalize performs a normal Normalize if
874 ** there are no negative values.
anthony3dd0f622010-05-13 12:57:32 +0000875 */
anthonyc1061722010-05-14 06:23:49 +0000876 ScaleKernelInfo(kernel, 1.0, CorrelateNormalizeValue);
anthony602ab9b2010-01-05 08:06:50 +0000877
878 break;
879 }
880 case BlurKernel:
anthonyc1061722010-05-14 06:23:49 +0000881 case DOBKernel:
anthony602ab9b2010-01-05 08:06:50 +0000882 { double
anthonyc1061722010-05-14 06:23:49 +0000883 sigma = fabs(args->sigma),
884 sigma2 = fabs(args->xi),
885 gamma;
anthony602ab9b2010-01-05 08:06:50 +0000886
anthonyc1061722010-05-14 06:23:49 +0000887 if ( args->rho >= 1.0 )
888 kernel->width = (unsigned long)args->rho*2+1;
889 else if ( (type == BlurKernel) || (sigma >= sigma2) )
890 kernel->width = GetOptimalKernelWidth1D(args->rho,sigma);
891 else
892 kernel->width = GetOptimalKernelWidth1D(args->rho,sigma2);
anthony602ab9b2010-01-05 08:06:50 +0000893 kernel->height = 1;
anthonyc1061722010-05-14 06:23:49 +0000894 kernel->x = (long) (kernel->width-1)/2;
cristyc99304f2010-02-01 15:26:27 +0000895 kernel->y = 0;
896 kernel->negative_range = kernel->positive_range = 0.0;
anthony602ab9b2010-01-05 08:06:50 +0000897 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
898 kernel->height*sizeof(double));
899 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +0000900 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000901
902#if 1
903#define KernelRank 3
904 /* Formula derived from GetBlurKernel() in "effect.c" (plus bug fix).
905 ** It generates a gaussian 3 times the width, and compresses it into
906 ** the expected range. This produces a closer normalization of the
907 ** resulting kernel, especially for very low sigma values.
908 ** As such while wierd it is prefered.
909 **
910 ** I am told this method originally came from Photoshop.
911 */
anthonyc1061722010-05-14 06:23:49 +0000912
913 /* initialize */
cristy150989e2010-02-01 14:59:39 +0000914 v = (long) (kernel->width*KernelRank-1)/2; /* start/end points to fit range */
anthonyc1061722010-05-14 06:23:49 +0000915 /* Calculate a Positive 1D Gaussian */
916 if ( sigma > MagickEpsilon )
917 { sigma *= KernelRank; /* simplify loop expressions */
918 gamma = 1.0/(2.0*sigma*sigma);
919 sigma = 1.0/(MagickSQ2PI*sigma );
920 for ( u=-v; u <= v; u++) {
921 kernel->values[(u+v)/KernelRank] +=
922 sigma*exp(-((double)(u*u))*gamma);
923 }
924 }
925 else /* special case - generate a unity kernel */
926 kernel->values[kernel->x+kernel->y*kernel->width] = 1.0;
927 if ( type == DOBKernel )
928 { /* Subtract a Second 1D Gaussian for "Difference of Blur" */
929 if ( sigma2 > MagickEpsilon )
930 { sigma = sigma2*KernelRank; /* simplify loop expressions */
931 gamma = 1.0/(2.0*sigma*sigma);
932 sigma = 1.0/(MagickSQ2PI*sigma );
933 for ( u=-v; u <= v; u++)
934 kernel->values[(u+v)/KernelRank] -=
935 sigma*exp(-((double)(u*u))*gamma);
936 }
937 else /* special case - subtract a unity kernel */
938 kernel->values[kernel->x+kernel->y*kernel->width] -= 1.0;
939 }
anthony602ab9b2010-01-05 08:06:50 +0000940#else
anthonyc1061722010-05-14 06:23:49 +0000941 /* Direct calculation without curve averaging */
942
943 /* Calculate a Positive Gaussian */
944 if ( sigma > MagickEpsilon )
945 { gamma = 1.0/(2.0*sigma*sigma); /* simplify loop expressions */
946 sigma = 1.0/(MagickSQ2PI*sigma);
947 for ( i=0, u=-kernel->x; u <= (long)kernel->x; u++, i++)
948 kernel->values[i] = sigma*exp(-((double)(u*u))*gamma);
949 }
950 else /* special case - generate a unity kernel */
951 { (void) ResetMagickMemory(kernel->values,0, (size_t)
952 kernel->width*kernel->height*sizeof(double));
953 kernel->values[kernel->x+kernel->y*kernel->width] = 1.0;
954 }
955 if ( type == DOBKernel )
956 { /* Subtract a Second 1D Gaussian for "Difference of Blur" */
957 if ( sigma2 > MagickEpsilon )
958 { sigma = sigma2; /* simplify loop expressions */
959 gamma = 1.0/(2.0*sigma*sigma);
960 sigma = 1.0/(MagickSQ2PI*sigma);
961 for ( i=0, u=-kernel->x; u <= (long)kernel->x; u++, i++)
962 kernel->values[i] -= sigma*exp(-((double)(u*u))*gamma);
963 }
964 else /* special case - subtract a unity kernel */
965 kernel->values[kernel->x+kernel->y*kernel->width] -= 1.0;
966 }
anthony602ab9b2010-01-05 08:06:50 +0000967#endif
anthonyc1061722010-05-14 06:23:49 +0000968 /* Note the above kernel may have been 'clipped' by a user defined
anthonycc6c8362010-01-25 04:14:01 +0000969 ** radius, producing a smaller (darker) kernel. Also for very small
970 ** sigma's (> 0.1) the central value becomes larger than one, and thus
971 ** producing a very bright kernel.
anthonyc1061722010-05-14 06:23:49 +0000972 **
973 ** Normalization will still be needed.
anthony602ab9b2010-01-05 08:06:50 +0000974 */
anthonycc6c8362010-01-25 04:14:01 +0000975
anthonyc1061722010-05-14 06:23:49 +0000976 /* Work out the meta-data about kernel */
977 for ( i=0; i < (long) kernel->width; i++)
978 {
979 if ( fabs(kernel->values[i]) < MagickEpsilon )
980 kernel->values[i] = 0.0;
981 ( kernel->values[i] < 0)
982 ? ( kernel->negative_range += kernel->values[i] )
983 : ( kernel->positive_range += kernel->values[i] );
984 Minimize(kernel->minimum, kernel->values[i]);
985 Maximize(kernel->maximum, kernel->values[i]);
986 }
987
anthony602ab9b2010-01-05 08:06:50 +0000988 /* Normalize the 1D Gaussian Kernel
989 **
anthonyc1061722010-05-14 06:23:49 +0000990 ** NB: a CorrelateNormalize performs a normal Normalize if
991 ** there are no negative values.
anthony602ab9b2010-01-05 08:06:50 +0000992 */
anthonyc1061722010-05-14 06:23:49 +0000993 ScaleKernelInfo(kernel, 1.0, CorrelateNormalizeValue);
anthonycc6c8362010-01-25 04:14:01 +0000994
anthonyc1061722010-05-14 06:23:49 +0000995 /* rotate the 1D kernel by given angle */
996 RotateKernelInfo(kernel, (type == BlurKernel) ? args->xi : args->psi );
anthony602ab9b2010-01-05 08:06:50 +0000997 break;
998 }
999 case CometKernel:
1000 { double
1001 sigma = fabs(args->sigma);
1002
1003 sigma = (sigma <= MagickEpsilon) ? 1.0 : sigma;
anthony602ab9b2010-01-05 08:06:50 +00001004 if ( args->rho < 1.0 )
1005 kernel->width = GetOptimalKernelWidth1D(args->rho,sigma);
1006 else
1007 kernel->width = (unsigned long)args->rho;
cristyc99304f2010-02-01 15:26:27 +00001008 kernel->x = kernel->y = 0;
anthony602ab9b2010-01-05 08:06:50 +00001009 kernel->height = 1;
cristyc99304f2010-02-01 15:26:27 +00001010 kernel->negative_range = kernel->positive_range = 0.0;
anthony602ab9b2010-01-05 08:06:50 +00001011 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
1012 kernel->height*sizeof(double));
1013 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +00001014 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +00001015
anthonyc1061722010-05-14 06:23:49 +00001016 /* A comet blur is half a 1D gaussian curve, so that the object is
anthony602ab9b2010-01-05 08:06:50 +00001017 ** blurred in one direction only. This may not be quite the right
anthony3dd0f622010-05-13 12:57:32 +00001018 ** curve to use so may change in the future. The function must be
1019 ** normalised after generation, which also resolves any clipping.
anthonyc1061722010-05-14 06:23:49 +00001020 **
1021 ** As we are normalizing and not subtracting gaussians,
1022 ** there is no need for a divisor in the gaussian formula
1023 **
anthony602ab9b2010-01-05 08:06:50 +00001024 */
1025#if 1
1026#define KernelRank 3
1027 sigma *= KernelRank; /* simplify expanded curve */
cristy150989e2010-02-01 14:59:39 +00001028 v = (long) kernel->width*KernelRank; /* start/end points to fit range */
anthony602ab9b2010-01-05 08:06:50 +00001029 (void) ResetMagickMemory(kernel->values,0, (size_t)
1030 kernel->width*sizeof(double));
1031 for ( u=0; u < v; u++) {
1032 kernel->values[u/KernelRank] +=
1033 exp(-((double)(u*u))/(2.0*sigma*sigma))
1034 /* / (MagickSQ2PI*sigma/KernelRank) */ ;
1035 }
cristy150989e2010-02-01 14:59:39 +00001036 for (i=0; i < (long) kernel->width; i++)
cristyc99304f2010-02-01 15:26:27 +00001037 kernel->positive_range += kernel->values[i];
anthony602ab9b2010-01-05 08:06:50 +00001038#else
cristy150989e2010-02-01 14:59:39 +00001039 for ( i=0; i < (long) kernel->width; i++)
cristyc99304f2010-02-01 15:26:27 +00001040 kernel->positive_range += (
anthony602ab9b2010-01-05 08:06:50 +00001041 kernel->values[i] =
1042 exp(-((double)(i*i))/(2.0*sigma*sigma))
1043 /* / (MagickSQ2PI*sigma) */ );
1044#endif
cristyc99304f2010-02-01 15:26:27 +00001045 kernel->minimum = 0;
1046 kernel->maximum = kernel->values[0];
anthony602ab9b2010-01-05 08:06:50 +00001047
anthony999bb2c2010-02-18 12:38:01 +00001048 ScaleKernelInfo(kernel, 1.0, NormalizeValue); /* Normalize */
1049 RotateKernelInfo(kernel, args->xi); /* Rotate by angle */
anthony602ab9b2010-01-05 08:06:50 +00001050 break;
1051 }
anthonyc1061722010-05-14 06:23:49 +00001052
anthony3c10fc82010-05-13 02:40:51 +00001053 /* Convolution Kernels - Well Known Constants */
anthony3c10fc82010-05-13 02:40:51 +00001054 case LaplacianKernel:
anthonyc1061722010-05-14 06:23:49 +00001055 {
anthony3c10fc82010-05-13 02:40:51 +00001056 switch ( (int) args->rho ) {
anthony3dd0f622010-05-13 12:57:32 +00001057 case 0:
1058 default: /* default laplacian 'edge' filter */
anthonyc1061722010-05-14 06:23:49 +00001059 kernel=ParseKernelArray("3: -1,-1,-1 -1,8,-1 -1,-1,-1");
anthony3dd0f622010-05-13 12:57:32 +00001060 break;
anthony3c10fc82010-05-13 02:40:51 +00001061 case 1:
anthonyc1061722010-05-14 06:23:49 +00001062 kernel=ParseKernelArray("3: 0,-1,0 -1,4,-1 0,-1,0");
anthony3c10fc82010-05-13 02:40:51 +00001063 break;
1064 case 2:
anthonyc1061722010-05-14 06:23:49 +00001065 kernel=ParseKernelArray("3: 1,-2,1 -2,4,-2 1,-2,1");
anthony3c10fc82010-05-13 02:40:51 +00001066 break;
anthony3dd0f622010-05-13 12:57:32 +00001067 case 3: /* a 5x5 laplacian */
anthony3c10fc82010-05-13 02:40:51 +00001068 kernel=ParseKernelArray(
anthonyc1061722010-05-14 06:23:49 +00001069 "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");
anthony3c10fc82010-05-13 02:40:51 +00001070 break;
anthony3dd0f622010-05-13 12:57:32 +00001071 case 4: /* a 7x7 laplacian */
anthony3c10fc82010-05-13 02:40:51 +00001072 kernel=ParseKernelArray(
anthonyc1061722010-05-14 06:23:49 +00001073 "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" );
anthony3c10fc82010-05-13 02:40:51 +00001074 break;
1075 }
1076 if (kernel == (KernelInfo *) NULL)
1077 return(kernel);
1078 kernel->type = type;
1079 break;
1080 }
anthonyc1061722010-05-14 06:23:49 +00001081 case SobelKernel:
anthony602ab9b2010-01-05 08:06:50 +00001082 {
anthonyc1061722010-05-14 06:23:49 +00001083 kernel=ParseKernelArray("3: -1,0,1 -2,0,2 -1,0,1");
1084 if (kernel == (KernelInfo *) NULL)
1085 return(kernel);
1086 kernel->type = type;
1087 RotateKernelInfo(kernel, args->rho); /* Rotate by angle */
1088 break;
1089 }
1090 case RobertsKernel:
1091 {
1092 kernel=ParseKernelArray("3: 0,0,0 -1,1,0 0,0,0");
1093 if (kernel == (KernelInfo *) NULL)
1094 return(kernel);
1095 kernel->type = type;
1096 RotateKernelInfo(kernel, args->rho); /* Rotate by angle */
1097 break;
1098 }
1099 case PrewittKernel:
1100 {
1101 kernel=ParseKernelArray("3: -1,1,1 0,0,0 -1,1,1");
1102 if (kernel == (KernelInfo *) NULL)
1103 return(kernel);
1104 kernel->type = type;
1105 RotateKernelInfo(kernel, args->rho); /* Rotate by angle */
1106 break;
1107 }
1108 case CompassKernel:
1109 {
1110 kernel=ParseKernelArray("3: -1,1,1 -1,-2,1 -1,1,1");
1111 if (kernel == (KernelInfo *) NULL)
1112 return(kernel);
1113 kernel->type = type;
1114 RotateKernelInfo(kernel, args->rho); /* Rotate by angle */
1115 break;
1116 }
1117 /* Boolean Kernels */
1118 case DiamondKernel:
1119 {
1120 if (args->rho < 1.0)
1121 kernel->width = kernel->height = 3; /* default radius = 1 */
1122 else
1123 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
1124 kernel->x = kernel->y = (long) (kernel->width-1)/2;
1125
1126 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
1127 kernel->height*sizeof(double));
1128 if (kernel->values == (double *) NULL)
1129 return(DestroyKernelInfo(kernel));
1130
1131 /* set all kernel values within diamond area to scale given */
1132 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
1133 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
1134 if ((labs(u)+labs(v)) <= (long)kernel->x)
1135 kernel->positive_range += kernel->values[i] = args->sigma;
1136 else
1137 kernel->values[i] = nan;
1138 kernel->minimum = kernel->maximum = args->sigma; /* a flat shape */
1139 break;
1140 }
1141 case SquareKernel:
1142 case RectangleKernel:
1143 { double
1144 scale;
anthony602ab9b2010-01-05 08:06:50 +00001145 if ( type == SquareKernel )
1146 {
1147 if (args->rho < 1.0)
anthonyc94cdb02010-01-06 08:15:29 +00001148 kernel->width = kernel->height = 3; /* default radius = 1 */
anthony602ab9b2010-01-05 08:06:50 +00001149 else
cristy150989e2010-02-01 14:59:39 +00001150 kernel->width = kernel->height = (unsigned long) (2*args->rho+1);
cristyc99304f2010-02-01 15:26:27 +00001151 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony4fd27e22010-02-07 08:17:18 +00001152 scale = args->sigma;
anthony602ab9b2010-01-05 08:06:50 +00001153 }
1154 else {
cristy2be15382010-01-21 02:38:03 +00001155 /* NOTE: user defaults set in "AcquireKernelInfo()" */
anthony602ab9b2010-01-05 08:06:50 +00001156 if ( args->rho < 1.0 || args->sigma < 1.0 )
anthony83ba99b2010-01-24 08:48:15 +00001157 return(DestroyKernelInfo(kernel)); /* invalid args given */
anthony602ab9b2010-01-05 08:06:50 +00001158 kernel->width = (unsigned long)args->rho;
1159 kernel->height = (unsigned long)args->sigma;
1160 if ( args->xi < 0.0 || args->xi > (double)kernel->width ||
1161 args->psi < 0.0 || args->psi > (double)kernel->height )
anthony83ba99b2010-01-24 08:48:15 +00001162 return(DestroyKernelInfo(kernel)); /* invalid args given */
cristyc99304f2010-02-01 15:26:27 +00001163 kernel->x = (long) args->xi;
1164 kernel->y = (long) args->psi;
anthony4fd27e22010-02-07 08:17:18 +00001165 scale = 1.0;
anthony602ab9b2010-01-05 08:06:50 +00001166 }
1167 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
1168 kernel->height*sizeof(double));
1169 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +00001170 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +00001171
anthony3dd0f622010-05-13 12:57:32 +00001172 /* set all kernel values to scale given */
cristy150989e2010-02-01 14:59:39 +00001173 u=(long) kernel->width*kernel->height;
1174 for ( i=0; i < u; i++)
anthony4fd27e22010-02-07 08:17:18 +00001175 kernel->values[i] = scale;
1176 kernel->minimum = kernel->maximum = scale; /* a flat shape */
1177 kernel->positive_range = scale*u;
anthonycc6c8362010-01-25 04:14:01 +00001178 break;
anthony602ab9b2010-01-05 08:06:50 +00001179 }
anthony602ab9b2010-01-05 08:06:50 +00001180 case DiskKernel:
1181 {
anthonyc1061722010-05-14 06:23:49 +00001182 long limit = (long)(args->rho*args->rho);
anthony83ba99b2010-01-24 08:48:15 +00001183 if (args->rho < 0.1) /* default radius approx 3.5 */
1184 kernel->width = kernel->height = 7L, limit = 10L;
anthony602ab9b2010-01-05 08:06:50 +00001185 else
1186 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
cristyc99304f2010-02-01 15:26:27 +00001187 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony602ab9b2010-01-05 08:06:50 +00001188
1189 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
1190 kernel->height*sizeof(double));
1191 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +00001192 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +00001193
anthony3dd0f622010-05-13 12:57:32 +00001194 /* set all kernel values within disk area to scale given */
1195 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
cristyc99304f2010-02-01 15:26:27 +00001196 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
anthony602ab9b2010-01-05 08:06:50 +00001197 if ((u*u+v*v) <= limit)
anthony4fd27e22010-02-07 08:17:18 +00001198 kernel->positive_range += kernel->values[i] = args->sigma;
anthony602ab9b2010-01-05 08:06:50 +00001199 else
1200 kernel->values[i] = nan;
anthony4fd27e22010-02-07 08:17:18 +00001201 kernel->minimum = kernel->maximum = args->sigma; /* a flat shape */
anthony602ab9b2010-01-05 08:06:50 +00001202 break;
1203 }
1204 case PlusKernel:
1205 {
1206 if (args->rho < 1.0)
anthonyc94cdb02010-01-06 08:15:29 +00001207 kernel->width = kernel->height = 5; /* default radius 2 */
anthony602ab9b2010-01-05 08:06:50 +00001208 else
1209 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
cristyc99304f2010-02-01 15:26:27 +00001210 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony602ab9b2010-01-05 08:06:50 +00001211
1212 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
1213 kernel->height*sizeof(double));
1214 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +00001215 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +00001216
anthony3dd0f622010-05-13 12:57:32 +00001217 /* set all kernel values along axises to given scale */
cristyc99304f2010-02-01 15:26:27 +00001218 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
1219 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
anthony4fd27e22010-02-07 08:17:18 +00001220 kernel->values[i] = (u == 0 || v == 0) ? args->sigma : nan;
1221 kernel->minimum = kernel->maximum = args->sigma; /* a flat shape */
1222 kernel->positive_range = args->sigma*(kernel->width*2.0 - 1.0);
anthony602ab9b2010-01-05 08:06:50 +00001223 break;
1224 }
anthony3dd0f622010-05-13 12:57:32 +00001225 case CrossKernel:
1226 {
1227 if (args->rho < 1.0)
1228 kernel->width = kernel->height = 5; /* default radius 2 */
1229 else
1230 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
1231 kernel->x = kernel->y = (long) (kernel->width-1)/2;
1232
1233 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
1234 kernel->height*sizeof(double));
1235 if (kernel->values == (double *) NULL)
1236 return(DestroyKernelInfo(kernel));
1237
1238 /* set all kernel values along axises to given scale */
1239 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
1240 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
1241 kernel->values[i] = (u == v || u == -v) ? args->sigma : nan;
1242 kernel->minimum = kernel->maximum = args->sigma; /* a flat shape */
1243 kernel->positive_range = args->sigma*(kernel->width*2.0 - 1.0);
1244 break;
1245 }
1246 /* HitAndMiss Kernels */
anthonyc1061722010-05-14 06:23:49 +00001247 case RingKernel:
anthony3dd0f622010-05-13 12:57:32 +00001248 case PeaksKernel:
1249 {
1250 long
1251 limit1,
anthonyc1061722010-05-14 06:23:49 +00001252 limit2,
1253 scale;
anthony3dd0f622010-05-13 12:57:32 +00001254
1255 if (args->rho < args->sigma)
1256 {
1257 kernel->width = ((unsigned long)args->sigma)*2+1;
1258 limit1 = (long)args->rho*args->rho;
1259 limit2 = (long)args->sigma*args->sigma;
1260 }
1261 else
1262 {
1263 kernel->width = ((unsigned long)args->rho)*2+1;
1264 limit1 = (long)args->sigma*args->sigma;
1265 limit2 = (long)args->rho*args->rho;
1266 }
anthonyc1061722010-05-14 06:23:49 +00001267 if ( limit2 <= 0 )
1268 kernel->width = 7L, limit1 = 7L, limit2 = 11L;
1269
anthony3dd0f622010-05-13 12:57:32 +00001270 kernel->height = kernel->width;
1271 kernel->x = kernel->y = (long) (kernel->width-1)/2;
1272 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
1273 kernel->height*sizeof(double));
1274 if (kernel->values == (double *) NULL)
1275 return(DestroyKernelInfo(kernel));
1276
anthonyc1061722010-05-14 06:23:49 +00001277 /* set a ring of points of 'scale' ( 0.0 for PeaksKernel ) */
1278 scale = ( type == PeaksKernel) ? 0.0 : args->xi;
anthony3dd0f622010-05-13 12:57:32 +00001279 for ( i=0, v= -kernel->y; v <= (long)kernel->y; v++)
1280 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
1281 { long radius=u*u+v*v;
anthonyc1061722010-05-14 06:23:49 +00001282 if (limit1 < radius && radius <= limit2)
1283 kernel->positive_range += kernel->values[i] = scale;
anthony3dd0f622010-05-13 12:57:32 +00001284 else
1285 kernel->values[i] = nan;
1286 }
anthonyc1061722010-05-14 06:23:49 +00001287 kernel->minimum = kernel->minimum = scale;
1288 if ( type == PeaksKernel ) {
1289 /* set the central point in the middle */
1290 kernel->values[kernel->x+kernel->y*kernel->width] = 1.0;
1291 kernel->positive_range = 1.0;
1292 kernel->maximum = 1.0;
1293 }
anthony3dd0f622010-05-13 12:57:32 +00001294 break;
1295 }
1296 case CornersKernel:
1297 {
anthony4f1dcb72010-05-14 08:43:10 +00001298 kernel=ParseKernelArray("3: 0,0,- 0,1,1 -,1,-");
anthony3dd0f622010-05-13 12:57:32 +00001299 if (kernel == (KernelInfo *) NULL)
1300 return(kernel);
1301 kernel->type = type;
1302 ExpandKernelInfo(kernel, 90.0); /* Create a list of 4 rotated kernels */
1303 break;
1304 }
1305 case LineEndsKernel:
1306 {
anthony4f1dcb72010-05-14 08:43:10 +00001307 kernel=ParseKernelArray("3: 0,-,- 0,1,0 0,0,0");
anthony3dd0f622010-05-13 12:57:32 +00001308 if (kernel == (KernelInfo *) NULL)
1309 return(kernel);
1310 kernel->type = type;
1311 ExpandKernelInfo(kernel, 45.0); /* Create a list of 8 rotated kernels */
1312 break;
1313 }
1314 case LineJunctionsKernel:
1315 {
1316 KernelInfo
1317 *new_kernel;
anthony3dd0f622010-05-13 12:57:32 +00001318 /* first set of 4 kernels */
anthony4f1dcb72010-05-14 08:43:10 +00001319 kernel=ParseKernelArray("3: -,1,- -,1,- 1,-,1");
anthony3dd0f622010-05-13 12:57:32 +00001320 if (kernel == (KernelInfo *) NULL)
1321 return(kernel);
1322 kernel->type = type;
1323 ExpandKernelInfo(kernel, 90.0);
1324 /* append second set of 4 kernels */
anthony4f1dcb72010-05-14 08:43:10 +00001325 new_kernel=ParseKernelArray("3: 1,-,- -,1,- 1,-,1");
anthony3dd0f622010-05-13 12:57:32 +00001326 if (new_kernel == (KernelInfo *) NULL)
1327 return(DestroyKernelInfo(kernel));
1328 kernel->type = type;
1329 ExpandKernelInfo(new_kernel, 90.0);
1330 LastKernelInfo(kernel)->next = new_kernel;
1331 /* append Thrid set of 4 kernels */
anthony4f1dcb72010-05-14 08:43:10 +00001332 new_kernel=ParseKernelArray("3: -,1,- -,1,1 1,-,-");
anthony3dd0f622010-05-13 12:57:32 +00001333 if (new_kernel == (KernelInfo *) NULL)
1334 return(DestroyKernelInfo(kernel));
1335 kernel->type = type;
1336 ExpandKernelInfo(new_kernel, 90.0);
1337 LastKernelInfo(kernel)->next = new_kernel;
1338 break;
1339 }
anthony4f1dcb72010-05-14 08:43:10 +00001340 case ThickenKernel:
1341 { /* Thicken Kernel ?? -- Under trial */
1342 kernel=ParseKernelArray("3: 1,1,- 1,0,0 -,0,0");
1343 if (kernel == (KernelInfo *) NULL)
1344 return(kernel);
1345 kernel->type = type;
1346 ExpandKernelInfo(kernel, 45);
1347 break;
1348 }
1349 case ThinningKernel:
1350 { /* Thinning Kernel ?? -- Under trial */
1351 kernel=ParseKernelArray("3: 0,0,- 0,1,1 -,1,1");
1352 if (kernel == (KernelInfo *) NULL)
1353 return(kernel);
1354 kernel->type = type;
1355 ExpandKernelInfo(kernel, 45);
1356 break;
1357 }
anthony3dd0f622010-05-13 12:57:32 +00001358 case ConvexHullKernel:
1359 {
1360 KernelInfo
1361 *new_kernel;
anthony3dd0f622010-05-13 12:57:32 +00001362 /* first set of 4 kernels */
anthony4f1dcb72010-05-14 08:43:10 +00001363 kernel=ParseKernelArray("3: 1,1,- 1,0,- 1,-,0");
anthony3dd0f622010-05-13 12:57:32 +00001364 if (kernel == (KernelInfo *) NULL)
1365 return(kernel);
1366 kernel->type = type;
1367 ExpandKernelInfo(kernel, 90.0);
1368 /* append second set of 4 kernels */
anthony4f1dcb72010-05-14 08:43:10 +00001369 new_kernel=ParseKernelArray("3: -,1,1 -,0,1 0,-,1");
anthony3dd0f622010-05-13 12:57:32 +00001370 if (new_kernel == (KernelInfo *) NULL)
1371 return(DestroyKernelInfo(kernel));
1372 kernel->type = type;
1373 ExpandKernelInfo(new_kernel, 90.0);
1374 LastKernelInfo(kernel)->next = new_kernel;
1375 break;
1376 }
1377 case SkeletonKernel:
1378 {
1379 KernelInfo
1380 *new_kernel;
anthony3dd0f622010-05-13 12:57:32 +00001381 /* first set of 4 kernels - corners */
anthony4f1dcb72010-05-14 08:43:10 +00001382 kernel=ParseKernelArray("3: 0,0,- 0,1,1 -,1,-");
anthony3dd0f622010-05-13 12:57:32 +00001383 if (kernel == (KernelInfo *) NULL)
1384 return(kernel);
1385 kernel->type = type;
1386 ExpandKernelInfo(kernel, 90);
1387 /* append second set of 4 kernels - edge middles */
anthony4f1dcb72010-05-14 08:43:10 +00001388 new_kernel=ParseKernelArray("3: 0,0,0 -,1,- 1,1,1");
anthony3dd0f622010-05-13 12:57:32 +00001389 if (new_kernel == (KernelInfo *) NULL)
1390 return(DestroyKernelInfo(kernel));
1391 kernel->type = type;
1392 ExpandKernelInfo(new_kernel, 90);
1393 LastKernelInfo(kernel)->next = new_kernel;
1394 break;
1395 }
anthony602ab9b2010-01-05 08:06:50 +00001396 /* Distance Measuring Kernels */
1397 case ChebyshevKernel:
1398 {
anthony602ab9b2010-01-05 08:06:50 +00001399 if (args->rho < 1.0)
anthonyc94cdb02010-01-06 08:15:29 +00001400 kernel->width = kernel->height = 3; /* default radius = 1 */
anthony602ab9b2010-01-05 08:06:50 +00001401 else
1402 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
cristyc99304f2010-02-01 15:26:27 +00001403 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony602ab9b2010-01-05 08:06:50 +00001404
1405 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
1406 kernel->height*sizeof(double));
1407 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +00001408 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +00001409
cristyc99304f2010-02-01 15:26:27 +00001410 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
1411 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
1412 kernel->positive_range += ( kernel->values[i] =
anthonyc84dce52010-05-07 05:42:23 +00001413 args->sigma*((labs(u)>labs(v)) ? labs(u) : labs(v)) );
cristyc99304f2010-02-01 15:26:27 +00001414 kernel->maximum = kernel->values[0];
anthony602ab9b2010-01-05 08:06:50 +00001415 break;
1416 }
1417 case ManhattenKernel:
1418 {
anthony602ab9b2010-01-05 08:06:50 +00001419 if (args->rho < 1.0)
anthonyc94cdb02010-01-06 08:15:29 +00001420 kernel->width = kernel->height = 3; /* default radius = 1 */
anthony602ab9b2010-01-05 08:06:50 +00001421 else
1422 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
cristyc99304f2010-02-01 15:26:27 +00001423 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony602ab9b2010-01-05 08:06:50 +00001424
1425 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
1426 kernel->height*sizeof(double));
1427 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +00001428 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +00001429
cristyc99304f2010-02-01 15:26:27 +00001430 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
1431 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
1432 kernel->positive_range += ( kernel->values[i] =
anthonyc84dce52010-05-07 05:42:23 +00001433 args->sigma*(labs(u)+labs(v)) );
cristyc99304f2010-02-01 15:26:27 +00001434 kernel->maximum = kernel->values[0];
anthony602ab9b2010-01-05 08:06:50 +00001435 break;
1436 }
1437 case EuclideanKernel:
1438 {
anthony602ab9b2010-01-05 08:06:50 +00001439 if (args->rho < 1.0)
anthonyc94cdb02010-01-06 08:15:29 +00001440 kernel->width = kernel->height = 3; /* default radius = 1 */
anthony602ab9b2010-01-05 08:06:50 +00001441 else
1442 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
cristyc99304f2010-02-01 15:26:27 +00001443 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony602ab9b2010-01-05 08:06:50 +00001444
1445 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
1446 kernel->height*sizeof(double));
1447 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +00001448 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +00001449
cristyc99304f2010-02-01 15:26:27 +00001450 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
1451 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
1452 kernel->positive_range += ( kernel->values[i] =
anthonyc84dce52010-05-07 05:42:23 +00001453 args->sigma*sqrt((double)(u*u+v*v)) );
cristyc99304f2010-02-01 15:26:27 +00001454 kernel->maximum = kernel->values[0];
anthony602ab9b2010-01-05 08:06:50 +00001455 break;
1456 }
anthony602ab9b2010-01-05 08:06:50 +00001457 default:
anthonyc1061722010-05-14 06:23:49 +00001458 {
1459 /* Generate a No-Op minimal kernel - 1x1 pixel */
1460 kernel=ParseKernelArray("1");
1461 if (kernel == (KernelInfo *) NULL)
1462 return(kernel);
1463 kernel->type = UndefinedKernel;
1464 break;
1465 }
anthony602ab9b2010-01-05 08:06:50 +00001466 break;
1467 }
1468
1469 return(kernel);
1470}
anthonyc94cdb02010-01-06 08:15:29 +00001471
anthony602ab9b2010-01-05 08:06:50 +00001472/*
1473%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1474% %
1475% %
1476% %
cristy6771f1e2010-03-05 19:43:39 +00001477% C l o n e K e r n e l I n f o %
anthony4fd27e22010-02-07 08:17:18 +00001478% %
1479% %
1480% %
1481%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1482%
anthony1b2bc0a2010-05-12 05:25:22 +00001483% CloneKernelInfo() creates a new clone of the given Kernel List so that its
1484% can be modified without effecting the original. The cloned kernel should
1485% be destroyed using DestoryKernelInfo() when no longer needed.
anthony7a01dcf2010-05-11 12:25:52 +00001486%
cristye6365592010-04-02 17:31:23 +00001487% The format of the CloneKernelInfo method is:
anthony4fd27e22010-02-07 08:17:18 +00001488%
anthony930be612010-02-08 04:26:15 +00001489% KernelInfo *CloneKernelInfo(const KernelInfo *kernel)
anthony4fd27e22010-02-07 08:17:18 +00001490%
1491% A description of each parameter follows:
1492%
1493% o kernel: the Morphology/Convolution kernel to be cloned
1494%
1495*/
cristyef656912010-03-05 19:54:59 +00001496MagickExport KernelInfo *CloneKernelInfo(const KernelInfo *kernel)
anthony4fd27e22010-02-07 08:17:18 +00001497{
1498 register long
1499 i;
1500
cristy19eb6412010-04-23 14:42:29 +00001501 KernelInfo
anthony7a01dcf2010-05-11 12:25:52 +00001502 *new_kernel;
anthony4fd27e22010-02-07 08:17:18 +00001503
1504 assert(kernel != (KernelInfo *) NULL);
anthony7a01dcf2010-05-11 12:25:52 +00001505 new_kernel=(KernelInfo *) AcquireMagickMemory(sizeof(*kernel));
1506 if (new_kernel == (KernelInfo *) NULL)
1507 return(new_kernel);
1508 *new_kernel=(*kernel); /* copy values in structure */
anthony7a01dcf2010-05-11 12:25:52 +00001509
1510 /* replace the values with a copy of the values */
1511 new_kernel->values=(double *) AcquireQuantumMemory(kernel->width,
cristy19eb6412010-04-23 14:42:29 +00001512 kernel->height*sizeof(double));
anthony7a01dcf2010-05-11 12:25:52 +00001513 if (new_kernel->values == (double *) NULL)
1514 return(DestroyKernelInfo(new_kernel));
anthony4fd27e22010-02-07 08:17:18 +00001515 for (i=0; i < (long) (kernel->width*kernel->height); i++)
anthony7a01dcf2010-05-11 12:25:52 +00001516 new_kernel->values[i]=kernel->values[i];
anthony1b2bc0a2010-05-12 05:25:22 +00001517
1518 /* Also clone the next kernel in the kernel list */
1519 if ( kernel->next != (KernelInfo *) NULL ) {
1520 new_kernel->next = CloneKernelInfo(kernel->next);
1521 if ( new_kernel->next == (KernelInfo *) NULL )
1522 return(DestroyKernelInfo(new_kernel));
1523 }
1524
anthony7a01dcf2010-05-11 12:25:52 +00001525 return(new_kernel);
anthony4fd27e22010-02-07 08:17:18 +00001526}
1527
1528/*
1529%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1530% %
1531% %
1532% %
anthony83ba99b2010-01-24 08:48:15 +00001533% D e s t r o y K e r n e l I n f o %
anthony602ab9b2010-01-05 08:06:50 +00001534% %
1535% %
1536% %
1537%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1538%
anthony83ba99b2010-01-24 08:48:15 +00001539% DestroyKernelInfo() frees the memory used by a Convolution/Morphology
1540% kernel.
anthony602ab9b2010-01-05 08:06:50 +00001541%
anthony83ba99b2010-01-24 08:48:15 +00001542% The format of the DestroyKernelInfo method is:
anthony602ab9b2010-01-05 08:06:50 +00001543%
anthony83ba99b2010-01-24 08:48:15 +00001544% KernelInfo *DestroyKernelInfo(KernelInfo *kernel)
anthony602ab9b2010-01-05 08:06:50 +00001545%
1546% A description of each parameter follows:
1547%
1548% o kernel: the Morphology/Convolution kernel to be destroyed
1549%
1550*/
1551
anthony83ba99b2010-01-24 08:48:15 +00001552MagickExport KernelInfo *DestroyKernelInfo(KernelInfo *kernel)
anthony602ab9b2010-01-05 08:06:50 +00001553{
cristy2be15382010-01-21 02:38:03 +00001554 assert(kernel != (KernelInfo *) NULL);
anthony4fd27e22010-02-07 08:17:18 +00001555
anthony7a01dcf2010-05-11 12:25:52 +00001556 if ( kernel->next != (KernelInfo *) NULL )
1557 kernel->next = DestroyKernelInfo(kernel->next);
1558
1559 kernel->values = (double *)RelinquishMagickMemory(kernel->values);
1560 kernel = (KernelInfo *) RelinquishMagickMemory(kernel);
anthony602ab9b2010-01-05 08:06:50 +00001561 return(kernel);
1562}
anthonyc94cdb02010-01-06 08:15:29 +00001563
1564/*
1565%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1566% %
1567% %
1568% %
anthony3c10fc82010-05-13 02:40:51 +00001569% E x p a n d K e r n e l I n f o %
1570% %
1571% %
1572% %
1573%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1574%
1575% ExpandKernelInfo() takes a single kernel, and expands it into a list
1576% of kernels each incrementally rotated the angle given.
1577%
1578% WARNING: 45 degree rotations only works for 3x3 kernels.
1579% While 90 degree roatations only works for linear and square kernels
1580%
1581% The format of the RotateKernelInfo method is:
1582%
1583% void ExpandKernelInfo(KernelInfo *kernel, double angle)
1584%
1585% A description of each parameter follows:
1586%
1587% o kernel: the Morphology/Convolution kernel
1588%
1589% o angle: angle to rotate in degrees
1590%
1591% This function is only internel to this module, as it is not finalized,
1592% especially with regard to non-orthogonal angles, and rotation of larger
1593% 2D kernels.
1594*/
1595static void ExpandKernelInfo(KernelInfo *kernel, double angle)
1596{
1597 KernelInfo
cristya9a61ad2010-05-13 12:47:41 +00001598 *clone,
anthony3c10fc82010-05-13 02:40:51 +00001599 *last;
cristya9a61ad2010-05-13 12:47:41 +00001600
anthony3c10fc82010-05-13 02:40:51 +00001601 double
1602 a;
1603
1604 last = kernel;
1605 for (a=angle; a<355.0; a+=angle) {
cristya9a61ad2010-05-13 12:47:41 +00001606 clone = CloneKernelInfo(last);
1607 RotateKernelInfo(clone, angle);
1608 last->next = clone;
1609 last = clone;
anthony3c10fc82010-05-13 02:40:51 +00001610 }
1611}
1612
1613/*
1614%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1615% %
1616% %
1617% %
anthony29188a82010-01-22 10:12:34 +00001618% M o r p h o l o g y I m a g e C h a n n e l %
anthony602ab9b2010-01-05 08:06:50 +00001619% %
1620% %
1621% %
1622%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1623%
anthony29188a82010-01-22 10:12:34 +00001624% MorphologyImageChannel() applies a user supplied kernel to the image
1625% according to the given mophology method.
anthony602ab9b2010-01-05 08:06:50 +00001626%
1627% The given kernel is assumed to have been pre-scaled appropriatally, usally
1628% by the kernel generator.
1629%
1630% The format of the MorphologyImage method is:
1631%
cristyef656912010-03-05 19:54:59 +00001632% Image *MorphologyImage(const Image *image,MorphologyMethod method,
1633% const long iterations,KernelInfo *kernel,ExceptionInfo *exception)
anthony29188a82010-01-22 10:12:34 +00001634% Image *MorphologyImageChannel(const Image *image, const ChannelType
cristyef656912010-03-05 19:54:59 +00001635% channel,MorphologyMethod method,const long iterations,
1636% KernelInfo *kernel,ExceptionInfo *exception)
anthony602ab9b2010-01-05 08:06:50 +00001637%
1638% A description of each parameter follows:
1639%
1640% o image: the image.
1641%
1642% o method: the morphology method to be applied.
1643%
1644% o iterations: apply the operation this many times (or no change).
1645% A value of -1 means loop until no change found.
1646% How this is applied may depend on the morphology method.
1647% Typically this is a value of 1.
1648%
1649% o channel: the channel type.
1650%
1651% o kernel: An array of double representing the morphology kernel.
anthony29188a82010-01-22 10:12:34 +00001652% Warning: kernel may be normalized for the Convolve method.
anthony602ab9b2010-01-05 08:06:50 +00001653%
1654% o exception: return any errors or warnings in this structure.
1655%
1656%
1657% TODO: bias and auto-scale handling of the kernel for convolution
1658% The given kernel is assumed to have been pre-scaled appropriatally, usally
1659% by the kernel generator.
1660%
1661*/
1662
anthony930be612010-02-08 04:26:15 +00001663
anthony602ab9b2010-01-05 08:06:50 +00001664/* Internal function
anthony7a01dcf2010-05-11 12:25:52 +00001665 * Apply the low-level Morphology Method Primatives using the given Kernel
anthony930be612010-02-08 04:26:15 +00001666 * Returning the number of pixels that changed.
1667 * Two pre-created images must be provided, no image is created.
anthony602ab9b2010-01-05 08:06:50 +00001668 */
anthony7a01dcf2010-05-11 12:25:52 +00001669static unsigned long MorphologyPrimative(const Image *image, Image
anthony602ab9b2010-01-05 08:06:50 +00001670 *result_image, const MorphologyMethod method, const ChannelType channel,
cristy2be15382010-01-21 02:38:03 +00001671 const KernelInfo *kernel, ExceptionInfo *exception)
anthony602ab9b2010-01-05 08:06:50 +00001672{
cristy2be15382010-01-21 02:38:03 +00001673#define MorphologyTag "Morphology/Image"
anthony602ab9b2010-01-05 08:06:50 +00001674
1675 long
cristy150989e2010-02-01 14:59:39 +00001676 progress,
anthony29188a82010-01-22 10:12:34 +00001677 y, offx, offy,
anthony602ab9b2010-01-05 08:06:50 +00001678 changed;
1679
1680 MagickBooleanType
1681 status;
1682
1683 MagickPixelPacket
1684 bias;
1685
1686 CacheView
1687 *p_view,
1688 *q_view;
1689
anthony4fd27e22010-02-07 08:17:18 +00001690 /* Only the most basic morphology is actually performed by this routine */
anthony4fd27e22010-02-07 08:17:18 +00001691
anthony602ab9b2010-01-05 08:06:50 +00001692 /*
anthony4fd27e22010-02-07 08:17:18 +00001693 Apply Basic Morphology to Image.
anthony602ab9b2010-01-05 08:06:50 +00001694 */
1695 status=MagickTrue;
1696 changed=0;
1697 progress=0;
1698
1699 GetMagickPixelPacket(image,&bias);
1700 SetMagickPixelPacketBias(image,&bias);
anthonycc6c8362010-01-25 04:14:01 +00001701 /* Future: handle auto-bias from user, based on kernel input */
anthony602ab9b2010-01-05 08:06:50 +00001702
1703 p_view=AcquireCacheView(image);
1704 q_view=AcquireCacheView(result_image);
anthony29188a82010-01-22 10:12:34 +00001705
anthonycc6c8362010-01-25 04:14:01 +00001706 /* Some methods (including convolve) needs use a reflected kernel.
1707 * Adjust 'origin' offsets for this reflected kernel.
anthony29188a82010-01-22 10:12:34 +00001708 */
cristyc99304f2010-02-01 15:26:27 +00001709 offx = kernel->x;
1710 offy = kernel->y;
anthony29188a82010-01-22 10:12:34 +00001711 switch(method) {
anthony930be612010-02-08 04:26:15 +00001712 case ConvolveMorphology:
1713 case DilateMorphology:
1714 case DilateIntensityMorphology:
1715 case DistanceMorphology:
anthony5ef8e942010-05-11 06:51:12 +00001716 /* kernel needs to used with reflection about origin */
cristy150989e2010-02-01 14:59:39 +00001717 offx = (long) kernel->width-offx-1;
1718 offy = (long) kernel->height-offy-1;
anthony29188a82010-01-22 10:12:34 +00001719 break;
anthony5ef8e942010-05-11 06:51:12 +00001720 case ErodeMorphology:
1721 case ErodeIntensityMorphology:
1722 case HitAndMissMorphology:
1723 case ThinningMorphology:
1724 case ThickenMorphology:
1725 /* kernel is user as is, without reflection */
1726 break;
anthony930be612010-02-08 04:26:15 +00001727 default:
anthony5ef8e942010-05-11 06:51:12 +00001728 perror("Not a low level Morphology Method");
anthony930be612010-02-08 04:26:15 +00001729 break;
anthony29188a82010-01-22 10:12:34 +00001730 }
1731
anthony602ab9b2010-01-05 08:06:50 +00001732#if defined(MAGICKCORE_OPENMP_SUPPORT)
1733 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
1734#endif
cristy150989e2010-02-01 14:59:39 +00001735 for (y=0; y < (long) image->rows; y++)
anthony602ab9b2010-01-05 08:06:50 +00001736 {
1737 MagickBooleanType
1738 sync;
1739
1740 register const PixelPacket
1741 *restrict p;
1742
1743 register const IndexPacket
1744 *restrict p_indexes;
1745
1746 register PixelPacket
1747 *restrict q;
1748
1749 register IndexPacket
1750 *restrict q_indexes;
1751
cristy150989e2010-02-01 14:59:39 +00001752 register long
anthony602ab9b2010-01-05 08:06:50 +00001753 x;
1754
anthony29188a82010-01-22 10:12:34 +00001755 unsigned long
anthony602ab9b2010-01-05 08:06:50 +00001756 r;
1757
1758 if (status == MagickFalse)
1759 continue;
anthony29188a82010-01-22 10:12:34 +00001760 p=GetCacheViewVirtualPixels(p_view, -offx, y-offy,
1761 image->columns+kernel->width, kernel->height, exception);
anthony602ab9b2010-01-05 08:06:50 +00001762 q=GetCacheViewAuthenticPixels(q_view,0,y,result_image->columns,1,
1763 exception);
1764 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
1765 {
1766 status=MagickFalse;
1767 continue;
1768 }
1769 p_indexes=GetCacheViewVirtualIndexQueue(p_view);
1770 q_indexes=GetCacheViewAuthenticIndexQueue(q_view);
anthony29188a82010-01-22 10:12:34 +00001771 r = (image->columns+kernel->width)*offy+offx; /* constant */
1772
cristy150989e2010-02-01 14:59:39 +00001773 for (x=0; x < (long) image->columns; x++)
anthony602ab9b2010-01-05 08:06:50 +00001774 {
cristy150989e2010-02-01 14:59:39 +00001775 long
anthony602ab9b2010-01-05 08:06:50 +00001776 v;
1777
cristy150989e2010-02-01 14:59:39 +00001778 register long
anthony602ab9b2010-01-05 08:06:50 +00001779 u;
1780
1781 register const double
1782 *restrict k;
1783
1784 register const PixelPacket
1785 *restrict k_pixels;
1786
1787 register const IndexPacket
1788 *restrict k_indexes;
1789
1790 MagickPixelPacket
anthony5ef8e942010-05-11 06:51:12 +00001791 result,
1792 min,
1793 max;
anthony602ab9b2010-01-05 08:06:50 +00001794
anthony29188a82010-01-22 10:12:34 +00001795 /* Copy input to ouput image for unused channels
anthony83ba99b2010-01-24 08:48:15 +00001796 * This removes need for 'cloning' a new image every iteration
anthony29188a82010-01-22 10:12:34 +00001797 */
anthony602ab9b2010-01-05 08:06:50 +00001798 *q = p[r];
1799 if (image->colorspace == CMYKColorspace)
1800 q_indexes[x] = p_indexes[r];
1801
anthony5ef8e942010-05-11 06:51:12 +00001802 /* Defaults */
1803 min.red =
1804 min.green =
1805 min.blue =
1806 min.opacity =
1807 min.index = (MagickRealType) QuantumRange;
1808 max.red =
1809 max.green =
1810 max.blue =
1811 max.opacity =
1812 max.index = (MagickRealType) 0;
1813 /* original pixel value */
1814 result.red = (MagickRealType) p[r].red;
1815 result.green = (MagickRealType) p[r].green;
1816 result.blue = (MagickRealType) p[r].blue;
1817 result.opacity = QuantumRange - (MagickRealType) p[r].opacity;
cristy9b1a5442010-05-13 12:35:40 +00001818 result.index = 0;
anthony5ef8e942010-05-11 06:51:12 +00001819 if ( image->colorspace == CMYKColorspace)
1820 result.index = (MagickRealType) p_indexes[r];
1821
anthony602ab9b2010-01-05 08:06:50 +00001822 switch (method) {
1823 case ConvolveMorphology:
anthony930be612010-02-08 04:26:15 +00001824 /* Set the user defined bias of the weighted average output
1825 **
1826 ** FUTURE: provide some way for internal functions to disable
anthony5ef8e942010-05-11 06:51:12 +00001827 ** user provided bias and scaling effects.
anthony930be612010-02-08 04:26:15 +00001828 */
anthony602ab9b2010-01-05 08:06:50 +00001829 result=bias;
anthony930be612010-02-08 04:26:15 +00001830 break;
anthony4fd27e22010-02-07 08:17:18 +00001831 case DilateIntensityMorphology:
1832 case ErodeIntensityMorphology:
anthony5ef8e942010-05-11 06:51:12 +00001833 result.red = 0.0; /* flag indicating when first match found */
anthony4fd27e22010-02-07 08:17:18 +00001834 break;
anthony602ab9b2010-01-05 08:06:50 +00001835 default:
anthony602ab9b2010-01-05 08:06:50 +00001836 break;
1837 }
1838
1839 switch ( method ) {
1840 case ConvolveMorphology:
anthony930be612010-02-08 04:26:15 +00001841 /* Weighted Average of pixels using reflected kernel
1842 **
1843 ** NOTE for correct working of this operation for asymetrical
1844 ** kernels, the kernel needs to be applied in its reflected form.
1845 ** That is its values needs to be reversed.
1846 **
1847 ** Correlation is actually the same as this but without reflecting
1848 ** the kernel, and thus 'lower-level' that Convolution. However
1849 ** as Convolution is the more common method used, and it does not
1850 ** really cost us much in terms of processing to use a reflected
anthony5ef8e942010-05-11 06:51:12 +00001851 ** kernel, so it is Convolution that is implemented.
anthony930be612010-02-08 04:26:15 +00001852 **
1853 ** Correlation will have its kernel reflected before calling
1854 ** this function to do a Convolve.
1855 **
1856 ** For more details of Correlation vs Convolution see
1857 ** http://www.cs.umd.edu/~djacobs/CMSC426/Convolution.pdf
1858 */
anthony5ef8e942010-05-11 06:51:12 +00001859 if (((channel & SyncChannels) != 0 ) &&
1860 (image->matte == MagickTrue))
1861 { /* Channel has a 'Sync' Flag, and Alpha Channel enabled.
1862 ** Weight the color channels with Alpha Channel so that
1863 ** transparent pixels are not part of the results.
1864 */
anthony602ab9b2010-01-05 08:06:50 +00001865 MagickRealType
anthony5ef8e942010-05-11 06:51:12 +00001866 alpha, /* color channel weighting : kernel*alpha */
1867 gamma; /* divisor, sum of weighting values */
anthony602ab9b2010-01-05 08:06:50 +00001868
1869 gamma=0.0;
anthony29188a82010-01-22 10:12:34 +00001870 k = &kernel->values[ kernel->width*kernel->height-1 ];
anthony602ab9b2010-01-05 08:06:50 +00001871 k_pixels = p;
1872 k_indexes = p_indexes;
cristy150989e2010-02-01 14:59:39 +00001873 for (v=0; v < (long) kernel->height; v++) {
1874 for (u=0; u < (long) kernel->width; u++, k--) {
anthony602ab9b2010-01-05 08:06:50 +00001875 if ( IsNan(*k) ) continue;
1876 alpha=(*k)*(QuantumScale*(QuantumRange-
1877 k_pixels[u].opacity));
1878 gamma += alpha;
1879 result.red += alpha*k_pixels[u].red;
1880 result.green += alpha*k_pixels[u].green;
1881 result.blue += alpha*k_pixels[u].blue;
anthony83ba99b2010-01-24 08:48:15 +00001882 result.opacity += (*k)*(QuantumRange-k_pixels[u].opacity);
anthony602ab9b2010-01-05 08:06:50 +00001883 if ( image->colorspace == CMYKColorspace)
1884 result.index += alpha*k_indexes[u];
1885 }
1886 k_pixels += image->columns+kernel->width;
1887 k_indexes += image->columns+kernel->width;
1888 }
1889 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
anthony83ba99b2010-01-24 08:48:15 +00001890 result.red *= gamma;
1891 result.green *= gamma;
1892 result.blue *= gamma;
1893 result.opacity *= gamma;
1894 result.index *= gamma;
anthony602ab9b2010-01-05 08:06:50 +00001895 }
anthony5ef8e942010-05-11 06:51:12 +00001896 else
1897 {
1898 /* No 'Sync' flag, or no Alpha involved.
1899 ** Convolution is simple individual channel weigthed sum.
1900 */
1901 k = &kernel->values[ kernel->width*kernel->height-1 ];
1902 k_pixels = p;
1903 k_indexes = p_indexes;
1904 for (v=0; v < (long) kernel->height; v++) {
1905 for (u=0; u < (long) kernel->width; u++, k--) {
1906 if ( IsNan(*k) ) continue;
1907 result.red += (*k)*k_pixels[u].red;
1908 result.green += (*k)*k_pixels[u].green;
1909 result.blue += (*k)*k_pixels[u].blue;
1910 result.opacity += (*k)*(QuantumRange-k_pixels[u].opacity);
1911 if ( image->colorspace == CMYKColorspace)
1912 result.index += (*k)*k_indexes[u];
1913 }
1914 k_pixels += image->columns+kernel->width;
1915 k_indexes += image->columns+kernel->width;
1916 }
1917 }
anthony602ab9b2010-01-05 08:06:50 +00001918 break;
1919
anthony4fd27e22010-02-07 08:17:18 +00001920 case ErodeMorphology:
anthony5ef8e942010-05-11 06:51:12 +00001921 /* Minimum Value within kernel neighbourhood
anthony930be612010-02-08 04:26:15 +00001922 **
1923 ** NOTE that the kernel is not reflected for this operation!
1924 **
1925 ** NOTE: in normal Greyscale Morphology, the kernel value should
1926 ** be added to the real value, this is currently not done, due to
1927 ** the nature of the boolean kernels being used.
1928 */
anthony4fd27e22010-02-07 08:17:18 +00001929 k = kernel->values;
1930 k_pixels = p;
1931 k_indexes = p_indexes;
1932 for (v=0; v < (long) kernel->height; v++) {
1933 for (u=0; u < (long) kernel->width; u++, k++) {
1934 if ( IsNan(*k) || (*k) < 0.5 ) continue;
anthony5ef8e942010-05-11 06:51:12 +00001935 Minimize(min.red, (double) k_pixels[u].red);
1936 Minimize(min.green, (double) k_pixels[u].green);
1937 Minimize(min.blue, (double) k_pixels[u].blue);
1938 Minimize(min.opacity,
anthonyd37a5cb2010-05-07 06:37:03 +00001939 QuantumRange-(double) k_pixels[u].opacity);
anthony4fd27e22010-02-07 08:17:18 +00001940 if ( image->colorspace == CMYKColorspace)
anthony5ef8e942010-05-11 06:51:12 +00001941 Minimize(min.index, (double) k_indexes[u]);
anthony4fd27e22010-02-07 08:17:18 +00001942 }
1943 k_pixels += image->columns+kernel->width;
1944 k_indexes += image->columns+kernel->width;
1945 }
1946 break;
1947
anthony5ef8e942010-05-11 06:51:12 +00001948
anthony83ba99b2010-01-24 08:48:15 +00001949 case DilateMorphology:
anthony5ef8e942010-05-11 06:51:12 +00001950 /* Maximum Value within kernel neighbourhood
anthony930be612010-02-08 04:26:15 +00001951 **
1952 ** NOTE for correct working of this operation for asymetrical
1953 ** kernels, the kernel needs to be applied in its reflected form.
1954 ** That is its values needs to be reversed.
1955 **
1956 ** NOTE: in normal Greyscale Morphology, the kernel value should
1957 ** be added to the real value, this is currently not done, due to
1958 ** the nature of the boolean kernels being used.
1959 **
1960 */
anthony29188a82010-01-22 10:12:34 +00001961 k = &kernel->values[ kernel->width*kernel->height-1 ];
anthony602ab9b2010-01-05 08:06:50 +00001962 k_pixels = p;
1963 k_indexes = p_indexes;
cristy150989e2010-02-01 14:59:39 +00001964 for (v=0; v < (long) kernel->height; v++) {
1965 for (u=0; u < (long) kernel->width; u++, k--) {
anthony602ab9b2010-01-05 08:06:50 +00001966 if ( IsNan(*k) || (*k) < 0.5 ) continue;
anthony5ef8e942010-05-11 06:51:12 +00001967 Maximize(max.red, (double) k_pixels[u].red);
1968 Maximize(max.green, (double) k_pixels[u].green);
1969 Maximize(max.blue, (double) k_pixels[u].blue);
1970 Maximize(max.opacity,
anthonyd37a5cb2010-05-07 06:37:03 +00001971 QuantumRange-(double) k_pixels[u].opacity);
anthony602ab9b2010-01-05 08:06:50 +00001972 if ( image->colorspace == CMYKColorspace)
anthony5ef8e942010-05-11 06:51:12 +00001973 Maximize(max.index, (double) k_indexes[u]);
anthony602ab9b2010-01-05 08:06:50 +00001974 }
1975 k_pixels += image->columns+kernel->width;
1976 k_indexes += image->columns+kernel->width;
1977 }
anthony602ab9b2010-01-05 08:06:50 +00001978 break;
1979
anthony5ef8e942010-05-11 06:51:12 +00001980 case HitAndMissMorphology:
1981 case ThinningMorphology:
1982 case ThickenMorphology:
1983 /* Minimum of Foreground Pixel minus Maxumum of Background Pixels
1984 **
1985 ** NOTE that the kernel is not reflected for this operation,
1986 ** and consists of both foreground and background pixel
1987 ** neighbourhoods, 0.0 for background, and 1.0 for foreground
1988 ** with either Nan or 0.5 values for don't care.
1989 **
1990 ** Note that this can produce negative results, though really
1991 ** only a positive match has any real value.
1992 */
1993 k = kernel->values;
1994 k_pixels = p;
1995 k_indexes = p_indexes;
1996 for (v=0; v < (long) kernel->height; v++) {
1997 for (u=0; u < (long) kernel->width; u++, k++) {
1998 if ( IsNan(*k) ) continue;
1999 if ( (*k) > 0.7 )
2000 { /* minimim of foreground pixels */
2001 Minimize(min.red, (double) k_pixels[u].red);
2002 Minimize(min.green, (double) k_pixels[u].green);
2003 Minimize(min.blue, (double) k_pixels[u].blue);
2004 Minimize(min.opacity,
2005 QuantumRange-(double) k_pixels[u].opacity);
2006 if ( image->colorspace == CMYKColorspace)
2007 Minimize(min.index, (double) k_indexes[u]);
2008 }
2009 else if ( (*k) < 0.3 )
2010 { /* maximum of background pixels */
2011 Maximize(max.red, (double) k_pixels[u].red);
2012 Maximize(max.green, (double) k_pixels[u].green);
2013 Maximize(max.blue, (double) k_pixels[u].blue);
2014 Maximize(max.opacity,
2015 QuantumRange-(double) k_pixels[u].opacity);
2016 if ( image->colorspace == CMYKColorspace)
2017 Maximize(max.index, (double) k_indexes[u]);
2018 }
2019 }
2020 k_pixels += image->columns+kernel->width;
2021 k_indexes += image->columns+kernel->width;
2022 }
2023 /* Pattern Match only if min fg larger than min bg pixels */
2024 min.red -= max.red; Maximize( min.red, 0.0 );
2025 min.green -= max.green; Maximize( min.green, 0.0 );
2026 min.blue -= max.blue; Maximize( min.blue, 0.0 );
2027 min.opacity -= max.opacity; Maximize( min.opacity, 0.0 );
2028 min.index -= max.index; Maximize( min.index, 0.0 );
2029 break;
2030
anthony4fd27e22010-02-07 08:17:18 +00002031 case ErodeIntensityMorphology:
anthony930be612010-02-08 04:26:15 +00002032 /* Select Pixel with Minimum Intensity within kernel neighbourhood
2033 **
2034 ** WARNING: the intensity test fails for CMYK and does not
2035 ** take into account the moderating effect of teh alpha channel
2036 ** on the intensity.
2037 **
2038 ** NOTE that the kernel is not reflected for this operation!
2039 */
anthony602ab9b2010-01-05 08:06:50 +00002040 k = kernel->values;
2041 k_pixels = p;
2042 k_indexes = p_indexes;
cristy150989e2010-02-01 14:59:39 +00002043 for (v=0; v < (long) kernel->height; v++) {
2044 for (u=0; u < (long) kernel->width; u++, k++) {
anthony602ab9b2010-01-05 08:06:50 +00002045 if ( IsNan(*k) || (*k) < 0.5 ) continue;
anthony4fd27e22010-02-07 08:17:18 +00002046 if ( result.red == 0.0 ||
2047 PixelIntensity(&(k_pixels[u])) < PixelIntensity(q) ) {
2048 /* copy the whole pixel - no channel selection */
2049 *q = k_pixels[u];
2050 if ( result.red > 0.0 ) changed++;
2051 result.red = 1.0;
2052 }
anthony602ab9b2010-01-05 08:06:50 +00002053 }
2054 k_pixels += image->columns+kernel->width;
2055 k_indexes += image->columns+kernel->width;
2056 }
anthony602ab9b2010-01-05 08:06:50 +00002057 break;
2058
anthony83ba99b2010-01-24 08:48:15 +00002059 case DilateIntensityMorphology:
anthony930be612010-02-08 04:26:15 +00002060 /* Select Pixel with Maximum Intensity within kernel neighbourhood
2061 **
2062 ** WARNING: the intensity test fails for CMYK and does not
2063 ** take into account the moderating effect of teh alpha channel
2064 ** on the intensity.
2065 **
2066 ** NOTE for correct working of this operation for asymetrical
2067 ** kernels, the kernel needs to be applied in its reflected form.
2068 ** That is its values needs to be reversed.
2069 */
anthony29188a82010-01-22 10:12:34 +00002070 k = &kernel->values[ kernel->width*kernel->height-1 ];
anthony602ab9b2010-01-05 08:06:50 +00002071 k_pixels = p;
2072 k_indexes = p_indexes;
cristy150989e2010-02-01 14:59:39 +00002073 for (v=0; v < (long) kernel->height; v++) {
2074 for (u=0; u < (long) kernel->width; u++, k--) {
anthony29188a82010-01-22 10:12:34 +00002075 if ( IsNan(*k) || (*k) < 0.5 ) continue; /* boolean kernel */
2076 if ( result.red == 0.0 ||
2077 PixelIntensity(&(k_pixels[u])) > PixelIntensity(q) ) {
2078 /* copy the whole pixel - no channel selection */
2079 *q = k_pixels[u];
2080 if ( result.red > 0.0 ) changed++;
2081 result.red = 1.0;
2082 }
anthony602ab9b2010-01-05 08:06:50 +00002083 }
2084 k_pixels += image->columns+kernel->width;
2085 k_indexes += image->columns+kernel->width;
2086 }
anthony602ab9b2010-01-05 08:06:50 +00002087 break;
2088
anthony5ef8e942010-05-11 06:51:12 +00002089
anthony602ab9b2010-01-05 08:06:50 +00002090 case DistanceMorphology:
anthony930be612010-02-08 04:26:15 +00002091 /* Add kernel Value and select the minimum value found.
2092 ** The result is a iterative distance from edge of image shape.
2093 **
2094 ** All Distance Kernels are symetrical, but that may not always
2095 ** be the case. For example how about a distance from left edges?
2096 ** To work correctly with asymetrical kernels the reflected kernel
2097 ** needs to be applied.
anthony5ef8e942010-05-11 06:51:12 +00002098 **
2099 ** Actually this is really a GreyErode with a negative kernel!
2100 **
anthony930be612010-02-08 04:26:15 +00002101 */
anthony29188a82010-01-22 10:12:34 +00002102 k = &kernel->values[ kernel->width*kernel->height-1 ];
anthony602ab9b2010-01-05 08:06:50 +00002103 k_pixels = p;
2104 k_indexes = p_indexes;
cristy150989e2010-02-01 14:59:39 +00002105 for (v=0; v < (long) kernel->height; v++) {
2106 for (u=0; u < (long) kernel->width; u++, k--) {
anthony602ab9b2010-01-05 08:06:50 +00002107 if ( IsNan(*k) ) continue;
2108 Minimize(result.red, (*k)+k_pixels[u].red);
2109 Minimize(result.green, (*k)+k_pixels[u].green);
2110 Minimize(result.blue, (*k)+k_pixels[u].blue);
2111 Minimize(result.opacity, (*k)+QuantumRange-k_pixels[u].opacity);
2112 if ( image->colorspace == CMYKColorspace)
2113 Minimize(result.index, (*k)+k_indexes[u]);
2114 }
2115 k_pixels += image->columns+kernel->width;
2116 k_indexes += image->columns+kernel->width;
2117 }
anthony602ab9b2010-01-05 08:06:50 +00002118 break;
2119
2120 case UndefinedMorphology:
2121 default:
2122 break; /* Do nothing */
anthony83ba99b2010-01-24 08:48:15 +00002123 }
anthony5ef8e942010-05-11 06:51:12 +00002124 /* Final mathematics of results (combine with original image?)
2125 **
2126 ** NOTE: Difference Morphology operators Edge* and *Hat could also
2127 ** be done here but works better with iteration as a image difference
2128 ** in the controling function (below). Thicken and Thinning however
2129 ** should be done here so thay can be iterated correctly.
2130 */
2131 switch ( method ) {
2132 case HitAndMissMorphology:
2133 case ErodeMorphology:
2134 result = min; /* minimum of neighbourhood */
2135 break;
2136 case DilateMorphology:
2137 result = max; /* maximum of neighbourhood */
2138 break;
2139 case ThinningMorphology:
2140 /* subtract pattern match from original */
2141 result.red -= min.red;
2142 result.green -= min.green;
2143 result.blue -= min.blue;
2144 result.opacity -= min.opacity;
2145 result.index -= min.index;
2146 break;
2147 case ThickenMorphology:
2148 /* Union with original image (maximize) - or should this be + */
2149 Maximize( result.red, min.red );
2150 Maximize( result.green, min.green );
2151 Maximize( result.blue, min.blue );
2152 Maximize( result.opacity, min.opacity );
2153 Maximize( result.index, min.index );
2154 break;
2155 default:
2156 /* result directly calculated or assigned */
2157 break;
2158 }
2159 /* Assign the resulting pixel values - Clamping Result */
anthony83ba99b2010-01-24 08:48:15 +00002160 switch ( method ) {
2161 case UndefinedMorphology:
2162 case DilateIntensityMorphology:
2163 case ErodeIntensityMorphology:
anthony930be612010-02-08 04:26:15 +00002164 break; /* full pixel was directly assigned - not a channel method */
anthony83ba99b2010-01-24 08:48:15 +00002165 default:
anthony83ba99b2010-01-24 08:48:15 +00002166 if ((channel & RedChannel) != 0)
2167 q->red = ClampToQuantum(result.red);
2168 if ((channel & GreenChannel) != 0)
2169 q->green = ClampToQuantum(result.green);
2170 if ((channel & BlueChannel) != 0)
2171 q->blue = ClampToQuantum(result.blue);
2172 if ((channel & OpacityChannel) != 0
2173 && image->matte == MagickTrue )
2174 q->opacity = ClampToQuantum(QuantumRange-result.opacity);
2175 if ((channel & IndexChannel) != 0
2176 && image->colorspace == CMYKColorspace)
2177 q_indexes[x] = ClampToQuantum(result.index);
2178 break;
2179 }
anthony5ef8e942010-05-11 06:51:12 +00002180 /* Count up changed pixels */
anthony83ba99b2010-01-24 08:48:15 +00002181 if ( ( p[r].red != q->red )
2182 || ( p[r].green != q->green )
2183 || ( p[r].blue != q->blue )
2184 || ( p[r].opacity != q->opacity )
2185 || ( image->colorspace == CMYKColorspace &&
2186 p_indexes[r] != q_indexes[x] ) )
2187 changed++; /* The pixel had some value changed! */
anthony602ab9b2010-01-05 08:06:50 +00002188 p++;
2189 q++;
anthony83ba99b2010-01-24 08:48:15 +00002190 } /* x */
anthony602ab9b2010-01-05 08:06:50 +00002191 sync=SyncCacheViewAuthenticPixels(q_view,exception);
2192 if (sync == MagickFalse)
2193 status=MagickFalse;
2194 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2195 {
2196 MagickBooleanType
2197 proceed;
2198
2199#if defined(MAGICKCORE_OPENMP_SUPPORT)
2200 #pragma omp critical (MagickCore_MorphologyImage)
2201#endif
2202 proceed=SetImageProgress(image,MorphologyTag,progress++,image->rows);
2203 if (proceed == MagickFalse)
2204 status=MagickFalse;
2205 }
anthony83ba99b2010-01-24 08:48:15 +00002206 } /* y */
anthony602ab9b2010-01-05 08:06:50 +00002207 result_image->type=image->type;
2208 q_view=DestroyCacheView(q_view);
2209 p_view=DestroyCacheView(p_view);
cristy150989e2010-02-01 14:59:39 +00002210 return(status ? (unsigned long) changed : 0);
anthony602ab9b2010-01-05 08:06:50 +00002211}
2212
anthony4fd27e22010-02-07 08:17:18 +00002213
2214MagickExport Image *MorphologyImage(const Image *image, const MorphologyMethod
anthony930be612010-02-08 04:26:15 +00002215 method, const long iterations,const KernelInfo *kernel, ExceptionInfo
2216 *exception)
cristy2be15382010-01-21 02:38:03 +00002217{
2218 Image
2219 *morphology_image;
2220
2221 morphology_image=MorphologyImageChannel(image,DefaultChannels,method,
2222 iterations,kernel,exception);
2223 return(morphology_image);
2224}
2225
anthony4fd27e22010-02-07 08:17:18 +00002226
cristyef656912010-03-05 19:54:59 +00002227MagickExport Image *MorphologyImageChannel(const Image *image,
2228 const ChannelType channel,const MorphologyMethod method,
2229 const long iterations,const KernelInfo *kernel,ExceptionInfo *exception)
anthony602ab9b2010-01-05 08:06:50 +00002230{
anthony602ab9b2010-01-05 08:06:50 +00002231 Image
2232 *new_image,
anthony1b2bc0a2010-05-12 05:25:22 +00002233 *old_image,
anthony4fd27e22010-02-07 08:17:18 +00002234 *grad_image;
anthony602ab9b2010-01-05 08:06:50 +00002235
anthony4fd27e22010-02-07 08:17:18 +00002236 KernelInfo
anthony1b2bc0a2010-05-12 05:25:22 +00002237 *curr_kernel,
2238 *this_kernel;
anthony4fd27e22010-02-07 08:17:18 +00002239
2240 MorphologyMethod
2241 curr_method;
2242
anthony1b2bc0a2010-05-12 05:25:22 +00002243 unsigned long
anthony4f1dcb72010-05-14 08:43:10 +00002244 count, /* count of the number of times though kernel list */
2245 limit, /* limit of the total number of times */
2246 steps, /* grand total of number of morpholgy steps done */
2247 kernel_number, /* kernel number being applied */
2248 changed, /* pixels changed in one step */
2249 list_changed, /* changes made over one set of kernels */
2250 total_changed; /* total count of changes to image */
anthony1b2bc0a2010-05-12 05:25:22 +00002251
anthony602ab9b2010-01-05 08:06:50 +00002252 assert(image != (Image *) NULL);
2253 assert(image->signature == MagickSignature);
anthony4fd27e22010-02-07 08:17:18 +00002254 assert(kernel != (KernelInfo *) NULL);
2255 assert(kernel->signature == MagickSignature);
anthony602ab9b2010-01-05 08:06:50 +00002256 assert(exception != (ExceptionInfo *) NULL);
2257 assert(exception->signature == MagickSignature);
2258
anthony602ab9b2010-01-05 08:06:50 +00002259 if ( iterations == 0 )
2260 return((Image *)NULL); /* null operation - nothing to do! */
2261
2262 /* kernel must be valid at this point
2263 * (except maybe for posible future morphology methods like "Prune"
2264 */
cristy2be15382010-01-21 02:38:03 +00002265 assert(kernel != (KernelInfo *)NULL);
anthony602ab9b2010-01-05 08:06:50 +00002266
anthony1b2bc0a2010-05-12 05:25:22 +00002267 new_image = (Image *) NULL; /* for cleanup */
2268 old_image = (Image *) NULL;
2269 grad_image = (Image *) NULL;
2270 curr_kernel = (KernelInfo *) NULL;
anthony4f1dcb72010-05-14 08:43:10 +00002271
2272 steps = 0; /* total number of primative steps */
2273 count = 0; /* number of times through kernel list */
2274 changed = 1; /* assume something was changed! */
2275 list_changed = 0;
2276 total_changed = 0;
2277 curr_kernel = (KernelInfo *)kernel; /* allow kernel and method */
2278 curr_method = method; /* to be changed as nessary */
anthony4fd27e22010-02-07 08:17:18 +00002279
cristy150989e2010-02-01 14:59:39 +00002280 limit = (unsigned long) iterations;
anthony602ab9b2010-01-05 08:06:50 +00002281 if ( iterations < 0 )
2282 limit = image->columns > image->rows ? image->columns : image->rows;
2283
anthony4fd27e22010-02-07 08:17:18 +00002284 /* Third-level morphology methods */
2285 switch( curr_method ) {
2286 case EdgeMorphology:
2287 grad_image = MorphologyImageChannel(image, channel,
2288 DilateMorphology, iterations, curr_kernel, exception);
anthony1b2bc0a2010-05-12 05:25:22 +00002289 if ( grad_image == (Image *) NULL )
2290 goto error_cleanup;
anthony4fd27e22010-02-07 08:17:18 +00002291 /* FALL-THRU */
2292 case EdgeInMorphology:
2293 curr_method = ErodeMorphology;
anthony602ab9b2010-01-05 08:06:50 +00002294 break;
anthony4fd27e22010-02-07 08:17:18 +00002295 case EdgeOutMorphology:
2296 curr_method = DilateMorphology;
anthony602ab9b2010-01-05 08:06:50 +00002297 break;
anthony4fd27e22010-02-07 08:17:18 +00002298 case TopHatMorphology:
2299 curr_method = OpenMorphology;
2300 break;
2301 case BottomHatMorphology:
2302 curr_method = CloseMorphology;
2303 break;
2304 default:
anthony930be612010-02-08 04:26:15 +00002305 break; /* not a third-level method */
anthony4fd27e22010-02-07 08:17:18 +00002306 }
2307
2308 /* Second-level morphology methods */
2309 switch( curr_method ) {
anthony930be612010-02-08 04:26:15 +00002310 case OpenMorphology:
2311 /* Open is a Erode then a Dilate without reflection */
anthony4fd27e22010-02-07 08:17:18 +00002312 new_image = MorphologyImageChannel(image, channel,
2313 ErodeMorphology, iterations, curr_kernel, exception);
anthony602ab9b2010-01-05 08:06:50 +00002314 if (new_image == (Image *) NULL)
anthony1b2bc0a2010-05-12 05:25:22 +00002315 goto error_cleanup;
anthony4fd27e22010-02-07 08:17:18 +00002316 curr_method = DilateMorphology;
2317 break;
anthony602ab9b2010-01-05 08:06:50 +00002318 case OpenIntensityMorphology:
anthony4fd27e22010-02-07 08:17:18 +00002319 new_image = MorphologyImageChannel(image, channel,
2320 ErodeIntensityMorphology, iterations, curr_kernel, exception);
anthony602ab9b2010-01-05 08:06:50 +00002321 if (new_image == (Image *) NULL)
anthony1b2bc0a2010-05-12 05:25:22 +00002322 goto error_cleanup;
anthony4fd27e22010-02-07 08:17:18 +00002323 curr_method = DilateIntensityMorphology;
2324 break;
anthony930be612010-02-08 04:26:15 +00002325
2326 case CloseMorphology:
2327 /* Close is a Dilate then Erode using reflected kernel */
2328 /* A reflected kernel is needed for a Close */
2329 if ( curr_kernel == kernel )
2330 curr_kernel = CloneKernelInfo(kernel);
anthony1b2bc0a2010-05-12 05:25:22 +00002331 if (curr_kernel == (KernelInfo *) NULL)
2332 goto error_cleanup;
anthony930be612010-02-08 04:26:15 +00002333 RotateKernelInfo(curr_kernel,180);
2334 new_image = MorphologyImageChannel(image, channel,
2335 DilateMorphology, iterations, curr_kernel, exception);
2336 if (new_image == (Image *) NULL)
anthony1b2bc0a2010-05-12 05:25:22 +00002337 goto error_cleanup;
anthony930be612010-02-08 04:26:15 +00002338 curr_method = ErodeMorphology;
2339 break;
anthony4fd27e22010-02-07 08:17:18 +00002340 case CloseIntensityMorphology:
anthony930be612010-02-08 04:26:15 +00002341 /* A reflected kernel is needed for a Close */
2342 if ( curr_kernel == kernel )
2343 curr_kernel = CloneKernelInfo(kernel);
anthony1b2bc0a2010-05-12 05:25:22 +00002344 if (curr_kernel == (KernelInfo *) NULL)
2345 goto error_cleanup;
anthony4fd27e22010-02-07 08:17:18 +00002346 RotateKernelInfo(curr_kernel,180);
2347 new_image = MorphologyImageChannel(image, channel,
2348 DilateIntensityMorphology, iterations, curr_kernel, exception);
2349 if (new_image == (Image *) NULL)
anthony1b2bc0a2010-05-12 05:25:22 +00002350 goto error_cleanup;
anthony4fd27e22010-02-07 08:17:18 +00002351 curr_method = ErodeIntensityMorphology;
anthony602ab9b2010-01-05 08:06:50 +00002352 break;
2353
anthony930be612010-02-08 04:26:15 +00002354 case CorrelateMorphology:
2355 /* A Correlation is actually a Convolution with a reflected kernel.
2356 ** However a Convolution is a weighted sum with a reflected kernel.
2357 ** It may seem stange to convert a Correlation into a Convolution
2358 ** as the Correleation is the simplier method, but Convolution is
2359 ** much more commonly used, and it makes sense to implement it directly
2360 ** so as to avoid the need to duplicate the kernel when it is not
2361 ** required (which is typically the default).
2362 */
2363 if ( curr_kernel == kernel )
2364 curr_kernel = CloneKernelInfo(kernel);
anthony1b2bc0a2010-05-12 05:25:22 +00002365 if (curr_kernel == (KernelInfo *) NULL)
2366 goto error_cleanup;
anthony930be612010-02-08 04:26:15 +00002367 RotateKernelInfo(curr_kernel,180);
2368 curr_method = ConvolveMorphology;
2369 /* FALL-THRU into Correlate (weigthed sum without reflection) */
2370
anthonyc94cdb02010-01-06 08:15:29 +00002371 case ConvolveMorphology:
anthony1b2bc0a2010-05-12 05:25:22 +00002372 { /* Scale or Normalize kernel, according to user wishes
2373 ** before using it for the Convolve/Correlate method.
2374 **
2375 ** FUTURE: provide some way for internal functions to disable
2376 ** user bias and scaling effects.
2377 */
2378 const char
2379 *artifact = GetImageArtifact(image,"convolve:scale");
2380 if ( artifact != (char *)NULL ) {
2381 GeometryFlags
2382 flags;
2383 GeometryInfo
2384 args;
anthony999bb2c2010-02-18 12:38:01 +00002385
anthony1b2bc0a2010-05-12 05:25:22 +00002386 if ( curr_kernel == kernel )
2387 curr_kernel = CloneKernelInfo(kernel);
2388 if (curr_kernel == (KernelInfo *) NULL)
2389 goto error_cleanup;
2390 args.rho = 1.0;
2391 flags = (GeometryFlags) ParseGeometry(artifact, &args);
2392 ScaleKernelInfo(curr_kernel, args.rho, flags);
2393 }
anthony4fd27e22010-02-07 08:17:18 +00002394 }
anthony930be612010-02-08 04:26:15 +00002395 /* FALL-THRU to do the first, and typically the only iteration */
anthony4fd27e22010-02-07 08:17:18 +00002396
anthony602ab9b2010-01-05 08:06:50 +00002397 default:
anthony930be612010-02-08 04:26:15 +00002398 /* Do a single iteration using the Low-Level Morphology method!
2399 ** This ensures a "new_image" has been generated, but allows us to skip
2400 ** the creation of 'old_image' if no more iterations are needed.
2401 **
2402 ** The "curr_method" should also be set to a low-level method that is
anthony7a01dcf2010-05-11 12:25:52 +00002403 ** understood by the MorphologyPrimative() internal function.
anthony602ab9b2010-01-05 08:06:50 +00002404 */
2405 new_image=CloneImage(image,0,0,MagickTrue,exception);
2406 if (new_image == (Image *) NULL)
anthony1b2bc0a2010-05-12 05:25:22 +00002407 goto error_cleanup;
anthony602ab9b2010-01-05 08:06:50 +00002408 if (SetImageStorageClass(new_image,DirectClass) == MagickFalse)
2409 {
2410 InheritException(exception,&new_image->exception);
anthony1b2bc0a2010-05-12 05:25:22 +00002411 goto exit_cleanup;
anthony602ab9b2010-01-05 08:06:50 +00002412 }
anthony4f1dcb72010-05-14 08:43:10 +00002413 steps++; /* primative morphology steps performs */
anthony7a01dcf2010-05-11 12:25:52 +00002414 changed = MorphologyPrimative(image,new_image,curr_method,channel,
2415 curr_kernel, exception);
anthony602ab9b2010-01-05 08:06:50 +00002416 if ( GetImageArtifact(image,"verbose") != (const char *) NULL )
anthony4f1dcb72010-05-14 08:43:10 +00002417 fprintf(stderr, "Morphology %s:%lu.%lu #%lu => Changed %lu\n",
anthony4fd27e22010-02-07 08:17:18 +00002418 MagickOptionToMnemonic(MagickMorphologyOptions, curr_method),
anthony4f1dcb72010-05-14 08:43:10 +00002419 1L, 0L, steps, changed);
anthony930be612010-02-08 04:26:15 +00002420 break;
anthony602ab9b2010-01-05 08:06:50 +00002421 }
anthony1b2bc0a2010-05-12 05:25:22 +00002422 /* At this point
2423 * + "curr_method" should be set to a low-level morphology method
2424 * + "count=1" if the first iteration of the first kernel has been done.
2425 * + "new_image" is defined and contains the current resulting image
2426 */
anthony602ab9b2010-01-05 08:06:50 +00002427
anthony1b2bc0a2010-05-12 05:25:22 +00002428 /* The Hit-And-Miss Morphology is not 'iterated' against the resulting
2429 ** image from the previous morphology step. It must always be applied
2430 ** to the original image, with the results collected into a union (maximimum
2431 ** or lighten) of all the results. Multiple kernels may be applied but
2432 ** an iteration of the morphology does nothing, so is ignored.
anthony7a01dcf2010-05-11 12:25:52 +00002433 **
anthony1b2bc0a2010-05-12 05:25:22 +00002434 ** The first kernel is guranteed to have been done, so lets continue the
2435 ** process, with the other kernels in the kernel list.
anthony7a01dcf2010-05-11 12:25:52 +00002436 */
anthony1b2bc0a2010-05-12 05:25:22 +00002437 if ( method == HitAndMissMorphology )
anthony7a01dcf2010-05-11 12:25:52 +00002438 {
anthony1b2bc0a2010-05-12 05:25:22 +00002439 if ( curr_kernel->next != (KernelInfo *) NULL ) {
2440 /* create a second working image */
2441 old_image = CloneImage(image,0,0,MagickTrue,exception);
2442 if (old_image == (Image *) NULL)
2443 goto error_cleanup;
2444 if (SetImageStorageClass(old_image,DirectClass) == MagickFalse)
2445 {
2446 InheritException(exception,&old_image->exception);
2447 goto exit_cleanup;
2448 }
anthony7a01dcf2010-05-11 12:25:52 +00002449
anthony1b2bc0a2010-05-12 05:25:22 +00002450 /* loop through rest of the kernels */
2451 this_kernel=curr_kernel->next;
2452 kernel_number=1;
anthony4f1dcb72010-05-14 08:43:10 +00002453 count=1; /* it is always the first list! */
anthony1b2bc0a2010-05-12 05:25:22 +00002454 while( this_kernel != (KernelInfo *) NULL )
2455 {
anthony4f1dcb72010-05-14 08:43:10 +00002456 steps++;
anthony1b2bc0a2010-05-12 05:25:22 +00002457 changed = MorphologyPrimative(image,old_image,curr_method,channel,
2458 this_kernel,exception);
2459 (void) CompositeImageChannel(new_image,
2460 (ChannelType) (channel & ~SyncChannels), LightenCompositeOp,
2461 old_image, 0, 0);
anthony4f1dcb72010-05-14 08:43:10 +00002462 if ( kernel->next != (KernelInfo *) NULL ) /* more than one kernel? */
2463 if ( GetImageArtifact(image,"verbose") != (const char *) NULL )
2464 fprintf(stderr, "Morphology %s:%lu.%lu #%lu => Changed %lu\n",
2465 MagickOptionToMnemonic(MagickMorphologyOptions, curr_method),
2466 count, kernel_number, steps, changed);
anthony1b2bc0a2010-05-12 05:25:22 +00002467 this_kernel = this_kernel->next;
2468 kernel_number++;
2469 }
anthony4f1dcb72010-05-14 08:43:10 +00002470 if ( GetImageArtifact(image,"verbose") != (const char *) NULL )
2471 fprintf(stderr, "Morphology %s:%lu #%lu ===> Changed %lu Total %lu\n",
2472 MagickOptionToMnemonic(MagickMorphologyOptions, curr_method),
2473 count, steps, list_changed, total_changed);
anthony1b2bc0a2010-05-12 05:25:22 +00002474 old_image=DestroyImage(old_image);
2475 }
2476 goto exit_cleanup;
anthony7a01dcf2010-05-11 12:25:52 +00002477 }
2478
anthony1b2bc0a2010-05-12 05:25:22 +00002479 /* Repeat the low-level morphology over all kernels
2480 until iteration count limit or no change from any kernel is found */
anthony4f1dcb72010-05-14 08:43:10 +00002481 if ( ( steps != 0 && limit != 1 && changed > 0 ) ||
anthony1b2bc0a2010-05-12 05:25:22 +00002482 curr_kernel->next != (KernelInfo *) NULL ) {
anthony930be612010-02-08 04:26:15 +00002483
anthony4f1dcb72010-05-14 08:43:10 +00002484 /* More than one step so create a second working image */
anthony1b2bc0a2010-05-12 05:25:22 +00002485 old_image = CloneImage(image,0,0,MagickTrue,exception);
anthony602ab9b2010-01-05 08:06:50 +00002486 if (old_image == (Image *) NULL)
anthony1b2bc0a2010-05-12 05:25:22 +00002487 goto error_cleanup;
anthony602ab9b2010-01-05 08:06:50 +00002488 if (SetImageStorageClass(old_image,DirectClass) == MagickFalse)
2489 {
2490 InheritException(exception,&old_image->exception);
anthony1b2bc0a2010-05-12 05:25:22 +00002491 goto exit_cleanup;
anthony602ab9b2010-01-05 08:06:50 +00002492 }
anthony1b2bc0a2010-05-12 05:25:22 +00002493
2494 /* reset variables for the first/next iteration, or next kernel) */
anthony4f1dcb72010-05-14 08:43:10 +00002495 count = steps;
anthony1b2bc0a2010-05-12 05:25:22 +00002496 kernel_number = 0;
2497 this_kernel = curr_kernel;
anthony4f1dcb72010-05-14 08:43:10 +00002498 list_changed = (steps != 0) ? changed : 0;
2499 total_changed = 0;
2500 if ( (steps != 0) && this_kernel != (KernelInfo *) NULL ) {
2501 count = 0; /* first iteration is not yet finished! */
2502 kernel_number++;
anthony1b2bc0a2010-05-12 05:25:22 +00002503 this_kernel = curr_kernel->next;
anthony1b2bc0a2010-05-12 05:25:22 +00002504 }
2505
2506 while ( count < limit ) {
anthony4f1dcb72010-05-14 08:43:10 +00002507 count++; /* iteration though kernel list being performed */
anthony1b2bc0a2010-05-12 05:25:22 +00002508 while ( this_kernel != (KernelInfo *) NULL ) {
anthony602ab9b2010-01-05 08:06:50 +00002509 Image *tmp = old_image;
2510 old_image = new_image;
2511 new_image = tmp;
anthony4f1dcb72010-05-14 08:43:10 +00002512 steps++;
anthony7a01dcf2010-05-11 12:25:52 +00002513 changed = MorphologyPrimative(old_image,new_image,curr_method,channel,
anthony1b2bc0a2010-05-12 05:25:22 +00002514 this_kernel,exception);
anthony4f1dcb72010-05-14 08:43:10 +00002515 if ( kernel->next != (KernelInfo *) NULL ) /* more than one kernel? */
2516 if ( GetImageArtifact(image,"verbose") != (const char *) NULL )
2517 fprintf(stderr, "Morphology %s:%lu.%lu #%lu => Changed %lu\n",
2518 MagickOptionToMnemonic(MagickMorphologyOptions, curr_method),
2519 count, kernel_number, steps, changed);
2520 list_changed += changed;
anthony1b2bc0a2010-05-12 05:25:22 +00002521 this_kernel = this_kernel->next;
2522 kernel_number++;
anthony602ab9b2010-01-05 08:06:50 +00002523 }
anthony4f1dcb72010-05-14 08:43:10 +00002524 total_changed += list_changed;
2525 if ( GetImageArtifact(image,"verbose") != (const char *) NULL )
2526 fprintf(stderr, "Morphology %s:%lu #%lu ===> Changed %lu Total %lu\n",
anthony3dd0f622010-05-13 12:57:32 +00002527 MagickOptionToMnemonic(MagickMorphologyOptions, curr_method),
anthony4f1dcb72010-05-14 08:43:10 +00002528 count, steps, list_changed, total_changed);
2529 if ( list_changed == 0 )
anthony1b2bc0a2010-05-12 05:25:22 +00002530 break; /* no changes after processing all kernels - ABORT */
2531 /* prepare for next loop */
anthony4f1dcb72010-05-14 08:43:10 +00002532 list_changed = 0;
anthony1b2bc0a2010-05-12 05:25:22 +00002533 kernel_number = 0;
2534 this_kernel = curr_kernel;
2535 }
cristy150989e2010-02-01 14:59:39 +00002536 old_image=DestroyImage(old_image);
anthony602ab9b2010-01-05 08:06:50 +00002537 }
anthony930be612010-02-08 04:26:15 +00002538
anthony3dd0f622010-05-13 12:57:32 +00002539 /* finished with kernel - destroy any copy that was made */
anthony4fd27e22010-02-07 08:17:18 +00002540 if ( curr_kernel != kernel )
2541 curr_kernel=DestroyKernelInfo(curr_kernel);
2542
anthony7d10d742010-05-06 07:05:29 +00002543 /* Third-level Subtractive methods post-processing
2544 **
2545 ** The removal of any 'Sync' channel flag in the Image Compositon below
2546 ** ensures the compose method is applied in a purely mathematical way, only
2547 ** the selected channels, without any normal 'alpha blending' normally
2548 ** associated with the compose method.
2549 **
2550 ** Note "method" here is the 'original' morphological method, and not the
2551 ** 'current' morphological method used above to generate "new_image".
2552 */
anthony4fd27e22010-02-07 08:17:18 +00002553 switch( method ) {
2554 case EdgeOutMorphology:
2555 case EdgeInMorphology:
2556 case TopHatMorphology:
2557 case BottomHatMorphology:
anthony930be612010-02-08 04:26:15 +00002558 /* Get Difference relative to the original image */
cristyaeb2cbc2010-05-07 13:28:58 +00002559 (void) CompositeImageChannel(new_image, (ChannelType) (channel & ~SyncChannels),
anthony7d10d742010-05-06 07:05:29 +00002560 DifferenceCompositeOp, image, 0, 0);
anthony4fd27e22010-02-07 08:17:18 +00002561 break;
anthony7d10d742010-05-06 07:05:29 +00002562 case EdgeMorphology:
2563 /* Difference the Eroded image from the saved Dilated image */
cristyaeb2cbc2010-05-07 13:28:58 +00002564 (void) CompositeImageChannel(new_image, (ChannelType) (channel & ~SyncChannels),
anthony7d10d742010-05-06 07:05:29 +00002565 DifferenceCompositeOp, grad_image, 0, 0);
anthony4fd27e22010-02-07 08:17:18 +00002566 grad_image=DestroyImage(grad_image);
2567 break;
2568 default:
2569 break;
2570 }
anthony602ab9b2010-01-05 08:06:50 +00002571
2572 return(new_image);
anthony1b2bc0a2010-05-12 05:25:22 +00002573
2574 /* Yes goto's are bad, but in this case it makes cleanup lot more efficient */
2575error_cleanup:
2576 if ( new_image != (Image *) NULL )
2577 DestroyImage(new_image);
2578exit_cleanup:
2579 if ( old_image != (Image *) NULL )
2580 DestroyImage(old_image);
2581 if ( curr_kernel != (KernelInfo *) NULL && curr_kernel != kernel )
2582 curr_kernel=DestroyKernelInfo(curr_kernel);
2583 return(new_image);
anthony602ab9b2010-01-05 08:06:50 +00002584}
anthony83ba99b2010-01-24 08:48:15 +00002585
2586/*
2587%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2588% %
2589% %
2590% %
anthony4fd27e22010-02-07 08:17:18 +00002591+ R o t a t e K e r n e l I n f o %
anthony83ba99b2010-01-24 08:48:15 +00002592% %
2593% %
2594% %
2595%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2596%
anthony4fd27e22010-02-07 08:17:18 +00002597% RotateKernelInfo() rotates the kernel by the angle given. Currently it is
anthony83ba99b2010-01-24 08:48:15 +00002598% restricted to 90 degree angles, but this may be improved in the future.
2599%
anthony4fd27e22010-02-07 08:17:18 +00002600% The format of the RotateKernelInfo method is:
anthony83ba99b2010-01-24 08:48:15 +00002601%
anthony4fd27e22010-02-07 08:17:18 +00002602% void RotateKernelInfo(KernelInfo *kernel, double angle)
anthony83ba99b2010-01-24 08:48:15 +00002603%
2604% A description of each parameter follows:
2605%
2606% o kernel: the Morphology/Convolution kernel
2607%
2608% o angle: angle to rotate in degrees
2609%
anthonyc4c86e02010-01-27 09:30:32 +00002610% This function is only internel to this module, as it is not finalized,
2611% especially with regard to non-orthogonal angles, and rotation of larger
2612% 2D kernels.
anthony83ba99b2010-01-24 08:48:15 +00002613*/
anthony4fd27e22010-02-07 08:17:18 +00002614static void RotateKernelInfo(KernelInfo *kernel, double angle)
anthony83ba99b2010-01-24 08:48:15 +00002615{
anthony1b2bc0a2010-05-12 05:25:22 +00002616 /* angle the lower kernels first */
2617 if ( kernel->next != (KernelInfo *) NULL)
2618 RotateKernelInfo(kernel->next, angle);
2619
anthony83ba99b2010-01-24 08:48:15 +00002620 /* WARNING: Currently assumes the kernel (rightly) is horizontally symetrical
2621 **
2622 ** TODO: expand beyond simple 90 degree rotates, flips and flops
2623 */
2624
2625 /* Modulus the angle */
2626 angle = fmod(angle, 360.0);
2627 if ( angle < 0 )
2628 angle += 360.0;
2629
anthony3c10fc82010-05-13 02:40:51 +00002630 if ( 337.5 < angle || angle <= 22.5 )
anthony83ba99b2010-01-24 08:48:15 +00002631 return; /* no change! - At least at this time */
2632
anthony3dd0f622010-05-13 12:57:32 +00002633 /* Handle special cases */
anthony83ba99b2010-01-24 08:48:15 +00002634 switch (kernel->type) {
2635 /* These built-in kernels are cylindrical kernels, rotating is useless */
2636 case GaussianKernel:
anthony83ba99b2010-01-24 08:48:15 +00002637 case DOGKernel:
2638 case DiskKernel:
anthony3dd0f622010-05-13 12:57:32 +00002639 case PeaksKernel:
2640 case LaplacianKernel:
anthony83ba99b2010-01-24 08:48:15 +00002641 case ChebyshevKernel:
2642 case ManhattenKernel:
2643 case EuclideanKernel:
2644 return;
2645
2646 /* These may be rotatable at non-90 angles in the future */
2647 /* but simply rotating them in multiples of 90 degrees is useless */
2648 case SquareKernel:
2649 case DiamondKernel:
2650 case PlusKernel:
anthony3dd0f622010-05-13 12:57:32 +00002651 case CrossKernel:
anthony83ba99b2010-01-24 08:48:15 +00002652 return;
2653
2654 /* These only allows a +/-90 degree rotation (by transpose) */
2655 /* A 180 degree rotation is useless */
2656 case BlurKernel:
2657 case RectangleKernel:
2658 if ( 135.0 < angle && angle <= 225.0 )
2659 return;
2660 if ( 225.0 < angle && angle <= 315.0 )
2661 angle -= 180;
2662 break;
2663
anthony3dd0f622010-05-13 12:57:32 +00002664 default:
anthony83ba99b2010-01-24 08:48:15 +00002665 break;
2666 }
anthony3c10fc82010-05-13 02:40:51 +00002667 /* Attempt rotations by 45 degrees */
2668 if ( 22.5 < fmod(angle,90.0) && fmod(angle,90.0) <= 67.5 )
2669 {
2670 if ( kernel->width == 3 && kernel->height == 3 )
2671 { /* Rotate a 3x3 square by 45 degree angle */
2672 MagickRealType t = kernel->values[0];
2673 kernel->values[0] = kernel->values[1];
2674 kernel->values[1] = kernel->values[2];
2675 kernel->values[2] = kernel->values[5];
2676 kernel->values[5] = kernel->values[8];
2677 kernel->values[8] = kernel->values[7];
2678 kernel->values[7] = kernel->values[6];
2679 kernel->values[6] = kernel->values[3];
2680 kernel->values[3] = t;
2681 angle = fmod(angle+45.0, 360.0);
2682 }
2683 else
2684 perror("Unable to rotate non-3x3 kernel by 45 degrees");
2685 }
2686 if ( 45.0 < fmod(angle, 180.0) && fmod(angle,180.0) <= 135.0 )
2687 {
2688 if ( kernel->width == 1 || kernel->height == 1 )
2689 { /* Do a transpose of the image, which results in a 90
2690 ** degree rotation of a 1 dimentional kernel
2691 */
2692 long
2693 t;
2694 t = (long) kernel->width;
2695 kernel->width = kernel->height;
2696 kernel->height = (unsigned long) t;
2697 t = kernel->x;
2698 kernel->x = kernel->y;
2699 kernel->y = t;
2700 if ( kernel->width == 1 )
2701 angle = fmod(angle+270.0, 360.0); /* angle -90 degrees */
2702 else
2703 angle = fmod(angle+90.0, 360.0); /* angle +90 degrees */
2704 }
2705 else if ( kernel->width == kernel->height )
2706 { /* Rotate a square array of values by 90 degrees */
2707 register unsigned long
2708 i,j,x,y;
2709 register MagickRealType
2710 *k,t;
2711 k=kernel->values;
2712 for( i=0, x=kernel->width-1; i<=x; i++, x--)
2713 for( j=0, y=kernel->height-1; j<y; j++, y--)
2714 { t = k[i+j*kernel->width];
2715 k[i+j*kernel->width] = k[y+i*kernel->width];
2716 k[y+i*kernel->width] = k[x+y*kernel->width];
2717 k[x+y*kernel->width] = k[j+x*kernel->width];
2718 k[j+x*kernel->width] = t;
2719 }
2720 angle = fmod(angle+90.0, 360.0); /* angle +90 degrees */
2721 }
2722 else
2723 perror("Unable to rotate a non-square, non-linear kernel 90 degrees");
2724 }
anthony83ba99b2010-01-24 08:48:15 +00002725 if ( 135.0 < angle && angle <= 225.0 )
2726 {
2727 /* For a 180 degree rotation - also know as a reflection */
2728 /* This is actually a very very common operation! */
2729 /* Basically all that is needed is a reversal of the kernel data! */
2730 unsigned long
2731 i,j;
2732 register double
2733 *k,t;
2734
2735 k=kernel->values;
2736 for ( i=0, j=kernel->width*kernel->height-1; i<j; i++, j--)
2737 t=k[i], k[i]=k[j], k[j]=t;
2738
anthony930be612010-02-08 04:26:15 +00002739 kernel->x = (long) kernel->width - kernel->x - 1;
2740 kernel->y = (long) kernel->height - kernel->y - 1;
anthony3c10fc82010-05-13 02:40:51 +00002741 angle = fmod(angle+180.0, 360.0); /* angle+180 degrees */
anthony83ba99b2010-01-24 08:48:15 +00002742 }
anthony3c10fc82010-05-13 02:40:51 +00002743 /* At this point angle should at least between -45 (315) and +45 degrees
anthony83ba99b2010-01-24 08:48:15 +00002744 * In the future some form of non-orthogonal angled rotates could be
2745 * performed here, posibily with a linear kernel restriction.
2746 */
2747
2748#if 0
anthony3c10fc82010-05-13 02:40:51 +00002749 { /* Do a Flop by reversing each row.
anthony83ba99b2010-01-24 08:48:15 +00002750 */
2751 unsigned long
2752 y;
cristy150989e2010-02-01 14:59:39 +00002753 register long
anthony83ba99b2010-01-24 08:48:15 +00002754 x,r;
2755 register double
2756 *k,t;
2757
2758 for ( y=0, k=kernel->values; y < kernel->height; y++, k+=kernel->width)
2759 for ( x=0, r=kernel->width-1; x<kernel->width/2; x++, r--)
2760 t=k[x], k[x]=k[r], k[r]=t;
2761
cristyc99304f2010-02-01 15:26:27 +00002762 kernel->x = kernel->width - kernel->x - 1;
anthony83ba99b2010-01-24 08:48:15 +00002763 angle = fmod(angle+180.0, 360.0);
2764 }
2765#endif
2766 return;
2767}
2768
2769/*
2770%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2771% %
2772% %
2773% %
cristy6771f1e2010-03-05 19:43:39 +00002774% S c a l e K e r n e l I n f o %
anthonycc6c8362010-01-25 04:14:01 +00002775% %
2776% %
2777% %
2778%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2779%
anthony1b2bc0a2010-05-12 05:25:22 +00002780% ScaleKernelInfo() scales the given kernel list by the given amount, with or
2781% without normalization of the sum of the kernel values (as per given flags).
anthonycc6c8362010-01-25 04:14:01 +00002782%
anthony999bb2c2010-02-18 12:38:01 +00002783% By default (no flags given) the values within the kernel is scaled
anthony1b2bc0a2010-05-12 05:25:22 +00002784% directly using given scaling factor without change.
anthonycc6c8362010-01-25 04:14:01 +00002785%
anthony1b2bc0a2010-05-12 05:25:22 +00002786% If any 'normalize_flags' are given the kernel will first be normalized and
2787% then further scaled by the scaling factor value given. A 'PercentValue'
2788% flag will cause the given scaling factor to be divided by one hundred
2789% percent.
anthony999bb2c2010-02-18 12:38:01 +00002790%
2791% Kernel normalization ('normalize_flags' given) is designed to ensure that
2792% any use of the kernel scaling factor with 'Convolve' or 'Correlate'
anthony1b2bc0a2010-05-12 05:25:22 +00002793% morphology methods will fall into -1.0 to +1.0 range. Note that for
2794% non-HDRI versions of IM this may cause images to have any negative results
2795% clipped, unless some 'bias' is used.
anthony999bb2c2010-02-18 12:38:01 +00002796%
2797% More specifically. Kernels which only contain positive values (such as a
2798% 'Gaussian' kernel) will be scaled so that those values sum to +1.0,
anthony1b2bc0a2010-05-12 05:25:22 +00002799% ensuring a 0.0 to +1.0 output range for non-HDRI images.
anthony999bb2c2010-02-18 12:38:01 +00002800%
2801% For Kernels that contain some negative values, (such as 'Sharpen' kernels)
2802% the kernel will be scaled by the absolute of the sum of kernel values, so
2803% that it will generally fall within the +/- 1.0 range.
2804%
2805% For kernels whose values sum to zero, (such as 'Laplician' kernels) kernel
2806% will be scaled by just the sum of the postive values, so that its output
2807% range will again fall into the +/- 1.0 range.
2808%
2809% For special kernels designed for locating shapes using 'Correlate', (often
2810% only containing +1 and -1 values, representing foreground/brackground
2811% matching) a special normalization method is provided to scale the positive
2812% values seperatally to those of the negative values, so the kernel will be
2813% forced to become a zero-sum kernel better suited to such searches.
2814%
anthony1b2bc0a2010-05-12 05:25:22 +00002815% WARNING: Correct normalization of the kernel assumes that the '*_range'
anthony999bb2c2010-02-18 12:38:01 +00002816% attributes within the kernel structure have been correctly set during the
2817% kernels creation.
2818%
2819% NOTE: The values used for 'normalize_flags' have been selected specifically
anthony1b2bc0a2010-05-12 05:25:22 +00002820% to match the use of geometry options, so that '!' means NormalizeValue,
2821% '^' means CorrelateNormalizeValue, and '%' means PercentValue. All other
anthony999bb2c2010-02-18 12:38:01 +00002822% GeometryFlags values are ignored.
anthonycc6c8362010-01-25 04:14:01 +00002823%
anthony4fd27e22010-02-07 08:17:18 +00002824% The format of the ScaleKernelInfo method is:
anthonycc6c8362010-01-25 04:14:01 +00002825%
anthony999bb2c2010-02-18 12:38:01 +00002826% void ScaleKernelInfo(KernelInfo *kernel, const double scaling_factor,
2827% const MagickStatusType normalize_flags )
anthonycc6c8362010-01-25 04:14:01 +00002828%
2829% A description of each parameter follows:
2830%
2831% o kernel: the Morphology/Convolution kernel
2832%
anthony999bb2c2010-02-18 12:38:01 +00002833% o scaling_factor:
2834% multiply all values (after normalization) by this factor if not
2835% zero. If the kernel is normalized regardless of any flags.
2836%
2837% o normalize_flags:
2838% GeometryFlags defining normalization method to use.
2839% specifically: NormalizeValue, CorrelateNormalizeValue,
2840% and/or PercentValue
anthonycc6c8362010-01-25 04:14:01 +00002841%
anthonyc4c86e02010-01-27 09:30:32 +00002842% This function is internal to this module only at this time, but can be
2843% exported to other modules if needed.
anthonycc6c8362010-01-25 04:14:01 +00002844*/
cristy6771f1e2010-03-05 19:43:39 +00002845MagickExport void ScaleKernelInfo(KernelInfo *kernel,
2846 const double scaling_factor,const GeometryFlags normalize_flags)
anthonycc6c8362010-01-25 04:14:01 +00002847{
cristy150989e2010-02-01 14:59:39 +00002848 register long
anthonycc6c8362010-01-25 04:14:01 +00002849 i;
2850
anthony999bb2c2010-02-18 12:38:01 +00002851 register double
2852 pos_scale,
2853 neg_scale;
2854
anthony1b2bc0a2010-05-12 05:25:22 +00002855 /* scale the lower kernels first */
2856 if ( kernel->next != (KernelInfo *) NULL)
2857 ScaleKernelInfo(kernel->next, scaling_factor, normalize_flags);
2858
anthony999bb2c2010-02-18 12:38:01 +00002859 pos_scale = 1.0;
2860 if ( (normalize_flags&NormalizeValue) != 0 ) {
2861 /* normalize kernel appropriately */
2862 if ( fabs(kernel->positive_range + kernel->negative_range) > MagickEpsilon )
2863 pos_scale = fabs(kernel->positive_range + kernel->negative_range);
anthonycc6c8362010-01-25 04:14:01 +00002864 else
anthony999bb2c2010-02-18 12:38:01 +00002865 pos_scale = kernel->positive_range; /* special zero-summing kernel */
2866 }
2867 /* force kernel into being a normalized zero-summing kernel */
2868 if ( (normalize_flags&CorrelateNormalizeValue) != 0 ) {
2869 pos_scale = ( fabs(kernel->positive_range) > MagickEpsilon )
2870 ? kernel->positive_range : 1.0;
2871 neg_scale = ( fabs(kernel->negative_range) > MagickEpsilon )
2872 ? -kernel->negative_range : 1.0;
2873 }
2874 else
2875 neg_scale = pos_scale;
2876
2877 /* finialize scaling_factor for positive and negative components */
2878 pos_scale = scaling_factor/pos_scale;
2879 neg_scale = scaling_factor/neg_scale;
2880 if ( (normalize_flags&PercentValue) != 0 ) {
2881 pos_scale /= 100.0;
2882 neg_scale /= 100.0;
anthonycc6c8362010-01-25 04:14:01 +00002883 }
2884
cristy150989e2010-02-01 14:59:39 +00002885 for (i=0; i < (long) (kernel->width*kernel->height); i++)
anthonycc6c8362010-01-25 04:14:01 +00002886 if ( ! IsNan(kernel->values[i]) )
anthony999bb2c2010-02-18 12:38:01 +00002887 kernel->values[i] *= (kernel->values[i] >= 0) ? pos_scale : neg_scale;
anthonycc6c8362010-01-25 04:14:01 +00002888
anthony999bb2c2010-02-18 12:38:01 +00002889 /* convolution output range */
2890 kernel->positive_range *= pos_scale;
2891 kernel->negative_range *= neg_scale;
2892 /* maximum and minimum values in kernel */
2893 kernel->maximum *= (kernel->maximum >= 0.0) ? pos_scale : neg_scale;
2894 kernel->minimum *= (kernel->minimum >= 0.0) ? pos_scale : neg_scale;
2895
2896 /* swap kernel settings if user scaling factor is negative */
2897 if ( scaling_factor < MagickEpsilon ) {
2898 double t;
2899 t = kernel->positive_range;
2900 kernel->positive_range = kernel->negative_range;
2901 kernel->negative_range = t;
2902 t = kernel->maximum;
2903 kernel->maximum = kernel->minimum;
2904 kernel->minimum = 1;
2905 }
anthonycc6c8362010-01-25 04:14:01 +00002906
2907 return;
2908}
2909
2910/*
2911%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2912% %
2913% %
2914% %
anthony4fd27e22010-02-07 08:17:18 +00002915+ S h o w K e r n e l I n f o %
anthony83ba99b2010-01-24 08:48:15 +00002916% %
2917% %
2918% %
2919%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2920%
anthony4fd27e22010-02-07 08:17:18 +00002921% ShowKernelInfo() outputs the details of the given kernel defination to
2922% standard error, generally due to a users 'showkernel' option request.
anthony83ba99b2010-01-24 08:48:15 +00002923%
2924% The format of the ShowKernel method is:
2925%
anthony4fd27e22010-02-07 08:17:18 +00002926% void ShowKernelInfo(KernelInfo *kernel)
anthony83ba99b2010-01-24 08:48:15 +00002927%
2928% A description of each parameter follows:
2929%
2930% o kernel: the Morphology/Convolution kernel
2931%
anthonyc4c86e02010-01-27 09:30:32 +00002932% This function is internal to this module only at this time. That may change
2933% in the future.
anthony83ba99b2010-01-24 08:48:15 +00002934*/
anthony4fd27e22010-02-07 08:17:18 +00002935MagickExport void ShowKernelInfo(KernelInfo *kernel)
anthony83ba99b2010-01-24 08:48:15 +00002936{
anthony7a01dcf2010-05-11 12:25:52 +00002937 KernelInfo
2938 *k;
anthony83ba99b2010-01-24 08:48:15 +00002939
anthony7a01dcf2010-05-11 12:25:52 +00002940 long
2941 c, i, u, v;
2942
2943 for (c=0, k=kernel; k != (KernelInfo *) NULL; c++, k=k->next ) {
2944
2945 fprintf(stderr, "Kernel ");
2946 if ( kernel->next != (KernelInfo *) NULL )
2947 fprintf(stderr, " #%ld", c );
2948 fprintf(stderr, " \"%s\" of size %lux%lu%+ld%+ld ",
2949 MagickOptionToMnemonic(MagickKernelOptions, k->type),
2950 kernel->width, k->height,
2951 kernel->x, kernel->y );
2952 fprintf(stderr,
2953 " with values from %.*lg to %.*lg\n",
2954 GetMagickPrecision(), k->minimum,
2955 GetMagickPrecision(), k->maximum);
2956 fprintf(stderr, "Forming convolution output range from %.*lg to %.*lg%s\n",
2957 GetMagickPrecision(), k->negative_range,
2958 GetMagickPrecision(), k->positive_range,
2959 /*kernel->normalized == MagickTrue ? " (normalized)" : */ "" );
2960 for (i=v=0; v < (long) k->height; v++) {
2961 fprintf(stderr,"%2ld:",v);
2962 for (u=0; u < (long) k->width; u++, i++)
2963 if ( IsNan(k->values[i]) )
2964 fprintf(stderr," %*s", GetMagickPrecision()+2, "nan");
2965 else
2966 fprintf(stderr," %*.*lg", GetMagickPrecision()+2,
2967 GetMagickPrecision(), k->values[i]);
2968 fprintf(stderr,"\n");
2969 }
anthony83ba99b2010-01-24 08:48:15 +00002970 }
2971}
anthonycc6c8362010-01-25 04:14:01 +00002972
2973/*
2974%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2975% %
2976% %
2977% %
anthony4fd27e22010-02-07 08:17:18 +00002978+ Z e r o K e r n e l N a n s %
anthonycc6c8362010-01-25 04:14:01 +00002979% %
2980% %
2981% %
2982%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2983%
2984% ZeroKernelNans() replaces any special 'nan' value that may be present in
2985% the kernel with a zero value. This is typically done when the kernel will
2986% be used in special hardware (GPU) convolution processors, to simply
2987% matters.
2988%
2989% The format of the ZeroKernelNans method is:
2990%
2991% voidZeroKernelNans (KernelInfo *kernel)
2992%
2993% A description of each parameter follows:
2994%
2995% o kernel: the Morphology/Convolution kernel
2996%
2997% FUTURE: return the information in a string for API usage.
2998*/
anthonyc4c86e02010-01-27 09:30:32 +00002999MagickExport void ZeroKernelNans(KernelInfo *kernel)
anthonycc6c8362010-01-25 04:14:01 +00003000{
cristy150989e2010-02-01 14:59:39 +00003001 register long
anthonycc6c8362010-01-25 04:14:01 +00003002 i;
3003
anthony1b2bc0a2010-05-12 05:25:22 +00003004 /* scale the lower kernels first */
3005 if ( kernel->next != (KernelInfo *) NULL)
3006 ZeroKernelNans(kernel->next);
3007
cristy150989e2010-02-01 14:59:39 +00003008 for (i=0; i < (long) (kernel->width*kernel->height); i++)
anthonycc6c8362010-01-25 04:14:01 +00003009 if ( IsNan(kernel->values[i]) )
3010 kernel->values[i] = 0.0;
3011
3012 return;
3013}