blob: 69e2afdce164742fc2ea08bc327f82cd37eacca0 [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 */
cristya0ea3922010-05-14 11:39:32 +0000771 kernel=(KernelInfo *) NULL;
anthonyc1061722010-05-14 06:23:49 +0000772 switch(type) {
773 case GaussianKernel:
774 case DOGKernel:
775 case BlurKernel:
776 case DOBKernel:
777 case CometKernel:
778 case DiamondKernel:
779 case SquareKernel:
780 case RectangleKernel:
781 case DiskKernel:
782 case PlusKernel:
783 case CrossKernel:
784 case RingKernel:
785 case PeaksKernel:
786 case ChebyshevKernel:
787 case ManhattenKernel:
788 case EuclideanKernel:
789 kernel=(KernelInfo *) AcquireMagickMemory(sizeof(*kernel));
790 if (kernel == (KernelInfo *) NULL)
791 return(kernel);
792 (void) ResetMagickMemory(kernel,0,sizeof(*kernel));
793 kernel->minimum = kernel->maximum = 0.0;
794 kernel->negative_range = kernel->positive_range = 0.0;
795 kernel->type = type;
796 kernel->next = (KernelInfo *) NULL;
797 kernel->signature = MagickSignature;
798 default:
799 break;
800 }
anthony602ab9b2010-01-05 08:06:50 +0000801
802 switch(type) {
803 /* Convolution Kernels */
804 case GaussianKernel:
anthonyc1061722010-05-14 06:23:49 +0000805 case DOGKernel:
anthony602ab9b2010-01-05 08:06:50 +0000806 { double
anthonyc1061722010-05-14 06:23:49 +0000807 sigma = fabs(args->sigma),
808 sigma2 = fabs(args->xi),
809 gamma;
anthony602ab9b2010-01-05 08:06:50 +0000810
anthonyc1061722010-05-14 06:23:49 +0000811 if ( args->rho >= 1.0 )
812 kernel->width = (unsigned long)args->rho*2+1;
813 else if ( (type == GaussianKernel) || (sigma >= sigma2) )
814 kernel->width = GetOptimalKernelWidth2D(args->rho,sigma);
815 else
816 kernel->width = GetOptimalKernelWidth2D(args->rho,sigma2);
817 kernel->height = kernel->width;
cristyc99304f2010-02-01 15:26:27 +0000818 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony602ab9b2010-01-05 08:06:50 +0000819 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
820 kernel->height*sizeof(double));
821 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +0000822 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000823
anthonyc1061722010-05-14 06:23:49 +0000824 /* Calculate a Positive Gaussian */
825 if ( sigma > MagickEpsilon )
826 { gamma = 1.0/(2.0*sigma*sigma); /* simplify loop expressions */
827 sigma = 1.0/(MagickPI*sigma);
828 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
829 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
830 kernel->values[i] = sigma*exp(-((double)(u*u+v*v))*gamma);
831 }
832 else /* special case - generate a unity kernel */
833 { (void) ResetMagickMemory(kernel->values,0, (size_t)
834 kernel->width*kernel->height*sizeof(double));
835 kernel->values[kernel->x+kernel->y*kernel->width] = 1.0;
836 }
837 if ( type == DOGKernel )
838 { /* Subtract a Negative Gaussian for "Difference of Gaussian" */
839 if ( sigma2 > MagickEpsilon )
840 { sigma = sigma2; /* simplify loop expressions */
841 gamma = 1.0/(2.0*sigma*sigma);
842 sigma = 1.0/(MagickPI*sigma);
843 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
844 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
845 kernel->values[i] -= sigma*exp(-((double)(u*u+v*v))*gamma);
846 }
847 else /* special case - subtract the unity kernel */
848 kernel->values[kernel->x+kernel->y*kernel->width] -= 1.0;
849 }
850 /* Note the above kernel may have been 'clipped' by a user defined
851 ** radius, producing a smaller (darker) kernel. Also for very small
852 ** sigma's (> 0.1) the central value becomes larger than one, and thus
853 ** producing a very bright kernel.
854 **
855 ** Normalization will still be needed.
856 */
anthony602ab9b2010-01-05 08:06:50 +0000857
anthonyc1061722010-05-14 06:23:49 +0000858 /* Work out the meta-data about kernel */
859 kernel->minimum = kernel->maximum = 0.0;
860 kernel->negative_range = kernel->positive_range = 0.0;
861 u=(long) kernel->width*kernel->height;
862 for ( i=0; i < u; i++)
863 {
864 if ( fabs(kernel->values[i]) < MagickEpsilon )
865 kernel->values[i] = 0.0;
866 ( kernel->values[i] < 0)
867 ? ( kernel->negative_range += kernel->values[i] )
868 : ( kernel->positive_range += kernel->values[i] );
869 Minimize(kernel->minimum, kernel->values[i]);
870 Maximize(kernel->maximum, kernel->values[i]);
871 }
anthony3dd0f622010-05-13 12:57:32 +0000872 /* Normalize the 2D Gaussian Kernel
873 **
anthonyc1061722010-05-14 06:23:49 +0000874 ** NB: a CorrelateNormalize performs a normal Normalize if
875 ** there are no negative values.
anthony3dd0f622010-05-13 12:57:32 +0000876 */
anthonyc1061722010-05-14 06:23:49 +0000877 ScaleKernelInfo(kernel, 1.0, CorrelateNormalizeValue);
anthony602ab9b2010-01-05 08:06:50 +0000878
879 break;
880 }
881 case BlurKernel:
anthonyc1061722010-05-14 06:23:49 +0000882 case DOBKernel:
anthony602ab9b2010-01-05 08:06:50 +0000883 { double
anthonyc1061722010-05-14 06:23:49 +0000884 sigma = fabs(args->sigma),
885 sigma2 = fabs(args->xi),
886 gamma;
anthony602ab9b2010-01-05 08:06:50 +0000887
anthonyc1061722010-05-14 06:23:49 +0000888 if ( args->rho >= 1.0 )
889 kernel->width = (unsigned long)args->rho*2+1;
890 else if ( (type == BlurKernel) || (sigma >= sigma2) )
891 kernel->width = GetOptimalKernelWidth1D(args->rho,sigma);
892 else
893 kernel->width = GetOptimalKernelWidth1D(args->rho,sigma2);
anthony602ab9b2010-01-05 08:06:50 +0000894 kernel->height = 1;
anthonyc1061722010-05-14 06:23:49 +0000895 kernel->x = (long) (kernel->width-1)/2;
cristyc99304f2010-02-01 15:26:27 +0000896 kernel->y = 0;
897 kernel->negative_range = kernel->positive_range = 0.0;
anthony602ab9b2010-01-05 08:06:50 +0000898 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
899 kernel->height*sizeof(double));
900 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +0000901 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000902
903#if 1
904#define KernelRank 3
905 /* Formula derived from GetBlurKernel() in "effect.c" (plus bug fix).
906 ** It generates a gaussian 3 times the width, and compresses it into
907 ** the expected range. This produces a closer normalization of the
908 ** resulting kernel, especially for very low sigma values.
909 ** As such while wierd it is prefered.
910 **
911 ** I am told this method originally came from Photoshop.
912 */
anthonyc1061722010-05-14 06:23:49 +0000913
914 /* initialize */
cristy150989e2010-02-01 14:59:39 +0000915 v = (long) (kernel->width*KernelRank-1)/2; /* start/end points to fit range */
anthonyc1061722010-05-14 06:23:49 +0000916 /* Calculate a Positive 1D Gaussian */
917 if ( sigma > MagickEpsilon )
918 { sigma *= KernelRank; /* simplify loop expressions */
919 gamma = 1.0/(2.0*sigma*sigma);
920 sigma = 1.0/(MagickSQ2PI*sigma );
921 for ( u=-v; u <= v; u++) {
922 kernel->values[(u+v)/KernelRank] +=
923 sigma*exp(-((double)(u*u))*gamma);
924 }
925 }
926 else /* special case - generate a unity kernel */
927 kernel->values[kernel->x+kernel->y*kernel->width] = 1.0;
928 if ( type == DOBKernel )
929 { /* Subtract a Second 1D Gaussian for "Difference of Blur" */
930 if ( sigma2 > MagickEpsilon )
931 { sigma = sigma2*KernelRank; /* simplify loop expressions */
932 gamma = 1.0/(2.0*sigma*sigma);
933 sigma = 1.0/(MagickSQ2PI*sigma );
934 for ( u=-v; u <= v; u++)
935 kernel->values[(u+v)/KernelRank] -=
936 sigma*exp(-((double)(u*u))*gamma);
937 }
938 else /* special case - subtract a unity kernel */
939 kernel->values[kernel->x+kernel->y*kernel->width] -= 1.0;
940 }
anthony602ab9b2010-01-05 08:06:50 +0000941#else
anthonyc1061722010-05-14 06:23:49 +0000942 /* Direct calculation without curve averaging */
943
944 /* Calculate a Positive Gaussian */
945 if ( sigma > MagickEpsilon )
946 { gamma = 1.0/(2.0*sigma*sigma); /* simplify loop expressions */
947 sigma = 1.0/(MagickSQ2PI*sigma);
948 for ( i=0, u=-kernel->x; u <= (long)kernel->x; u++, i++)
949 kernel->values[i] = sigma*exp(-((double)(u*u))*gamma);
950 }
951 else /* special case - generate a unity kernel */
952 { (void) ResetMagickMemory(kernel->values,0, (size_t)
953 kernel->width*kernel->height*sizeof(double));
954 kernel->values[kernel->x+kernel->y*kernel->width] = 1.0;
955 }
956 if ( type == DOBKernel )
957 { /* Subtract a Second 1D Gaussian for "Difference of Blur" */
958 if ( sigma2 > MagickEpsilon )
959 { sigma = sigma2; /* simplify loop expressions */
960 gamma = 1.0/(2.0*sigma*sigma);
961 sigma = 1.0/(MagickSQ2PI*sigma);
962 for ( i=0, u=-kernel->x; u <= (long)kernel->x; u++, i++)
963 kernel->values[i] -= sigma*exp(-((double)(u*u))*gamma);
964 }
965 else /* special case - subtract a unity kernel */
966 kernel->values[kernel->x+kernel->y*kernel->width] -= 1.0;
967 }
anthony602ab9b2010-01-05 08:06:50 +0000968#endif
anthonyc1061722010-05-14 06:23:49 +0000969 /* Note the above kernel may have been 'clipped' by a user defined
anthonycc6c8362010-01-25 04:14:01 +0000970 ** radius, producing a smaller (darker) kernel. Also for very small
971 ** sigma's (> 0.1) the central value becomes larger than one, and thus
972 ** producing a very bright kernel.
anthonyc1061722010-05-14 06:23:49 +0000973 **
974 ** Normalization will still be needed.
anthony602ab9b2010-01-05 08:06:50 +0000975 */
anthonycc6c8362010-01-25 04:14:01 +0000976
anthonyc1061722010-05-14 06:23:49 +0000977 /* Work out the meta-data about kernel */
978 for ( i=0; i < (long) kernel->width; i++)
979 {
980 if ( fabs(kernel->values[i]) < MagickEpsilon )
981 kernel->values[i] = 0.0;
982 ( kernel->values[i] < 0)
983 ? ( kernel->negative_range += kernel->values[i] )
984 : ( kernel->positive_range += kernel->values[i] );
985 Minimize(kernel->minimum, kernel->values[i]);
986 Maximize(kernel->maximum, kernel->values[i]);
987 }
988
anthony602ab9b2010-01-05 08:06:50 +0000989 /* Normalize the 1D Gaussian Kernel
990 **
anthonyc1061722010-05-14 06:23:49 +0000991 ** NB: a CorrelateNormalize performs a normal Normalize if
992 ** there are no negative values.
anthony602ab9b2010-01-05 08:06:50 +0000993 */
anthonyc1061722010-05-14 06:23:49 +0000994 ScaleKernelInfo(kernel, 1.0, CorrelateNormalizeValue);
anthonycc6c8362010-01-25 04:14:01 +0000995
anthonyc1061722010-05-14 06:23:49 +0000996 /* rotate the 1D kernel by given angle */
997 RotateKernelInfo(kernel, (type == BlurKernel) ? args->xi : args->psi );
anthony602ab9b2010-01-05 08:06:50 +0000998 break;
999 }
1000 case CometKernel:
1001 { double
1002 sigma = fabs(args->sigma);
1003
1004 sigma = (sigma <= MagickEpsilon) ? 1.0 : sigma;
anthony602ab9b2010-01-05 08:06:50 +00001005 if ( args->rho < 1.0 )
1006 kernel->width = GetOptimalKernelWidth1D(args->rho,sigma);
1007 else
1008 kernel->width = (unsigned long)args->rho;
cristyc99304f2010-02-01 15:26:27 +00001009 kernel->x = kernel->y = 0;
anthony602ab9b2010-01-05 08:06:50 +00001010 kernel->height = 1;
cristyc99304f2010-02-01 15:26:27 +00001011 kernel->negative_range = kernel->positive_range = 0.0;
anthony602ab9b2010-01-05 08:06:50 +00001012 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
1013 kernel->height*sizeof(double));
1014 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +00001015 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +00001016
anthonyc1061722010-05-14 06:23:49 +00001017 /* A comet blur is half a 1D gaussian curve, so that the object is
anthony602ab9b2010-01-05 08:06:50 +00001018 ** blurred in one direction only. This may not be quite the right
anthony3dd0f622010-05-13 12:57:32 +00001019 ** curve to use so may change in the future. The function must be
1020 ** normalised after generation, which also resolves any clipping.
anthonyc1061722010-05-14 06:23:49 +00001021 **
1022 ** As we are normalizing and not subtracting gaussians,
1023 ** there is no need for a divisor in the gaussian formula
1024 **
anthony602ab9b2010-01-05 08:06:50 +00001025 */
1026#if 1
1027#define KernelRank 3
1028 sigma *= KernelRank; /* simplify expanded curve */
cristy150989e2010-02-01 14:59:39 +00001029 v = (long) kernel->width*KernelRank; /* start/end points to fit range */
anthony602ab9b2010-01-05 08:06:50 +00001030 (void) ResetMagickMemory(kernel->values,0, (size_t)
1031 kernel->width*sizeof(double));
1032 for ( u=0; u < v; u++) {
1033 kernel->values[u/KernelRank] +=
1034 exp(-((double)(u*u))/(2.0*sigma*sigma))
1035 /* / (MagickSQ2PI*sigma/KernelRank) */ ;
1036 }
cristy150989e2010-02-01 14:59:39 +00001037 for (i=0; i < (long) kernel->width; i++)
cristyc99304f2010-02-01 15:26:27 +00001038 kernel->positive_range += kernel->values[i];
anthony602ab9b2010-01-05 08:06:50 +00001039#else
cristy150989e2010-02-01 14:59:39 +00001040 for ( i=0; i < (long) kernel->width; i++)
cristyc99304f2010-02-01 15:26:27 +00001041 kernel->positive_range += (
anthony602ab9b2010-01-05 08:06:50 +00001042 kernel->values[i] =
1043 exp(-((double)(i*i))/(2.0*sigma*sigma))
1044 /* / (MagickSQ2PI*sigma) */ );
1045#endif
cristyc99304f2010-02-01 15:26:27 +00001046 kernel->minimum = 0;
1047 kernel->maximum = kernel->values[0];
anthony602ab9b2010-01-05 08:06:50 +00001048
anthony999bb2c2010-02-18 12:38:01 +00001049 ScaleKernelInfo(kernel, 1.0, NormalizeValue); /* Normalize */
1050 RotateKernelInfo(kernel, args->xi); /* Rotate by angle */
anthony602ab9b2010-01-05 08:06:50 +00001051 break;
1052 }
anthonyc1061722010-05-14 06:23:49 +00001053
anthony3c10fc82010-05-13 02:40:51 +00001054 /* Convolution Kernels - Well Known Constants */
anthony3c10fc82010-05-13 02:40:51 +00001055 case LaplacianKernel:
anthonyc1061722010-05-14 06:23:49 +00001056 {
anthony3c10fc82010-05-13 02:40:51 +00001057 switch ( (int) args->rho ) {
anthony3dd0f622010-05-13 12:57:32 +00001058 case 0:
1059 default: /* default laplacian 'edge' filter */
anthonyc1061722010-05-14 06:23:49 +00001060 kernel=ParseKernelArray("3: -1,-1,-1 -1,8,-1 -1,-1,-1");
anthony3dd0f622010-05-13 12:57:32 +00001061 break;
anthony3c10fc82010-05-13 02:40:51 +00001062 case 1:
anthonyc1061722010-05-14 06:23:49 +00001063 kernel=ParseKernelArray("3: 0,-1,0 -1,4,-1 0,-1,0");
anthony3c10fc82010-05-13 02:40:51 +00001064 break;
1065 case 2:
anthonyc1061722010-05-14 06:23:49 +00001066 kernel=ParseKernelArray("3: 1,-2,1 -2,4,-2 1,-2,1");
anthony3c10fc82010-05-13 02:40:51 +00001067 break;
anthony3dd0f622010-05-13 12:57:32 +00001068 case 3: /* a 5x5 laplacian */
anthony3c10fc82010-05-13 02:40:51 +00001069 kernel=ParseKernelArray(
anthonyc1061722010-05-14 06:23:49 +00001070 "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 +00001071 break;
anthony3dd0f622010-05-13 12:57:32 +00001072 case 4: /* a 7x7 laplacian */
anthony3c10fc82010-05-13 02:40:51 +00001073 kernel=ParseKernelArray(
anthonyc1061722010-05-14 06:23:49 +00001074 "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 +00001075 break;
1076 }
1077 if (kernel == (KernelInfo *) NULL)
1078 return(kernel);
1079 kernel->type = type;
1080 break;
1081 }
anthonyc1061722010-05-14 06:23:49 +00001082 case SobelKernel:
anthony602ab9b2010-01-05 08:06:50 +00001083 {
anthonyc1061722010-05-14 06:23:49 +00001084 kernel=ParseKernelArray("3: -1,0,1 -2,0,2 -1,0,1");
1085 if (kernel == (KernelInfo *) NULL)
1086 return(kernel);
1087 kernel->type = type;
1088 RotateKernelInfo(kernel, args->rho); /* Rotate by angle */
1089 break;
1090 }
1091 case RobertsKernel:
1092 {
1093 kernel=ParseKernelArray("3: 0,0,0 -1,1,0 0,0,0");
1094 if (kernel == (KernelInfo *) NULL)
1095 return(kernel);
1096 kernel->type = type;
1097 RotateKernelInfo(kernel, args->rho); /* Rotate by angle */
1098 break;
1099 }
1100 case PrewittKernel:
1101 {
1102 kernel=ParseKernelArray("3: -1,1,1 0,0,0 -1,1,1");
1103 if (kernel == (KernelInfo *) NULL)
1104 return(kernel);
1105 kernel->type = type;
1106 RotateKernelInfo(kernel, args->rho); /* Rotate by angle */
1107 break;
1108 }
1109 case CompassKernel:
1110 {
1111 kernel=ParseKernelArray("3: -1,1,1 -1,-2,1 -1,1,1");
1112 if (kernel == (KernelInfo *) NULL)
1113 return(kernel);
1114 kernel->type = type;
1115 RotateKernelInfo(kernel, args->rho); /* Rotate by angle */
1116 break;
1117 }
1118 /* Boolean Kernels */
1119 case DiamondKernel:
1120 {
1121 if (args->rho < 1.0)
1122 kernel->width = kernel->height = 3; /* default radius = 1 */
1123 else
1124 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
1125 kernel->x = kernel->y = (long) (kernel->width-1)/2;
1126
1127 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
1128 kernel->height*sizeof(double));
1129 if (kernel->values == (double *) NULL)
1130 return(DestroyKernelInfo(kernel));
1131
1132 /* set all kernel values within diamond area to scale given */
1133 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
1134 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
1135 if ((labs(u)+labs(v)) <= (long)kernel->x)
1136 kernel->positive_range += kernel->values[i] = args->sigma;
1137 else
1138 kernel->values[i] = nan;
1139 kernel->minimum = kernel->maximum = args->sigma; /* a flat shape */
1140 break;
1141 }
1142 case SquareKernel:
1143 case RectangleKernel:
1144 { double
1145 scale;
anthony602ab9b2010-01-05 08:06:50 +00001146 if ( type == SquareKernel )
1147 {
1148 if (args->rho < 1.0)
anthonyc94cdb02010-01-06 08:15:29 +00001149 kernel->width = kernel->height = 3; /* default radius = 1 */
anthony602ab9b2010-01-05 08:06:50 +00001150 else
cristy150989e2010-02-01 14:59:39 +00001151 kernel->width = kernel->height = (unsigned long) (2*args->rho+1);
cristyc99304f2010-02-01 15:26:27 +00001152 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony4fd27e22010-02-07 08:17:18 +00001153 scale = args->sigma;
anthony602ab9b2010-01-05 08:06:50 +00001154 }
1155 else {
cristy2be15382010-01-21 02:38:03 +00001156 /* NOTE: user defaults set in "AcquireKernelInfo()" */
anthony602ab9b2010-01-05 08:06:50 +00001157 if ( args->rho < 1.0 || args->sigma < 1.0 )
anthony83ba99b2010-01-24 08:48:15 +00001158 return(DestroyKernelInfo(kernel)); /* invalid args given */
anthony602ab9b2010-01-05 08:06:50 +00001159 kernel->width = (unsigned long)args->rho;
1160 kernel->height = (unsigned long)args->sigma;
1161 if ( args->xi < 0.0 || args->xi > (double)kernel->width ||
1162 args->psi < 0.0 || args->psi > (double)kernel->height )
anthony83ba99b2010-01-24 08:48:15 +00001163 return(DestroyKernelInfo(kernel)); /* invalid args given */
cristyc99304f2010-02-01 15:26:27 +00001164 kernel->x = (long) args->xi;
1165 kernel->y = (long) args->psi;
anthony4fd27e22010-02-07 08:17:18 +00001166 scale = 1.0;
anthony602ab9b2010-01-05 08:06:50 +00001167 }
1168 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
1169 kernel->height*sizeof(double));
1170 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +00001171 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +00001172
anthony3dd0f622010-05-13 12:57:32 +00001173 /* set all kernel values to scale given */
cristy150989e2010-02-01 14:59:39 +00001174 u=(long) kernel->width*kernel->height;
1175 for ( i=0; i < u; i++)
anthony4fd27e22010-02-07 08:17:18 +00001176 kernel->values[i] = scale;
1177 kernel->minimum = kernel->maximum = scale; /* a flat shape */
1178 kernel->positive_range = scale*u;
anthonycc6c8362010-01-25 04:14:01 +00001179 break;
anthony602ab9b2010-01-05 08:06:50 +00001180 }
anthony602ab9b2010-01-05 08:06:50 +00001181 case DiskKernel:
1182 {
anthonyc1061722010-05-14 06:23:49 +00001183 long limit = (long)(args->rho*args->rho);
anthony83ba99b2010-01-24 08:48:15 +00001184 if (args->rho < 0.1) /* default radius approx 3.5 */
1185 kernel->width = kernel->height = 7L, limit = 10L;
anthony602ab9b2010-01-05 08:06:50 +00001186 else
1187 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
cristyc99304f2010-02-01 15:26:27 +00001188 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony602ab9b2010-01-05 08:06:50 +00001189
1190 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
1191 kernel->height*sizeof(double));
1192 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +00001193 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +00001194
anthony3dd0f622010-05-13 12:57:32 +00001195 /* set all kernel values within disk area to scale given */
1196 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
cristyc99304f2010-02-01 15:26:27 +00001197 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
anthony602ab9b2010-01-05 08:06:50 +00001198 if ((u*u+v*v) <= limit)
anthony4fd27e22010-02-07 08:17:18 +00001199 kernel->positive_range += kernel->values[i] = args->sigma;
anthony602ab9b2010-01-05 08:06:50 +00001200 else
1201 kernel->values[i] = nan;
anthony4fd27e22010-02-07 08:17:18 +00001202 kernel->minimum = kernel->maximum = args->sigma; /* a flat shape */
anthony602ab9b2010-01-05 08:06:50 +00001203 break;
1204 }
1205 case PlusKernel:
1206 {
1207 if (args->rho < 1.0)
anthonyc94cdb02010-01-06 08:15:29 +00001208 kernel->width = kernel->height = 5; /* default radius 2 */
anthony602ab9b2010-01-05 08:06:50 +00001209 else
1210 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
cristyc99304f2010-02-01 15:26:27 +00001211 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony602ab9b2010-01-05 08:06:50 +00001212
1213 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
1214 kernel->height*sizeof(double));
1215 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +00001216 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +00001217
anthony3dd0f622010-05-13 12:57:32 +00001218 /* set all kernel values along axises to given scale */
cristyc99304f2010-02-01 15:26:27 +00001219 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
1220 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
anthony4fd27e22010-02-07 08:17:18 +00001221 kernel->values[i] = (u == 0 || v == 0) ? args->sigma : nan;
1222 kernel->minimum = kernel->maximum = args->sigma; /* a flat shape */
1223 kernel->positive_range = args->sigma*(kernel->width*2.0 - 1.0);
anthony602ab9b2010-01-05 08:06:50 +00001224 break;
1225 }
anthony3dd0f622010-05-13 12:57:32 +00001226 case CrossKernel:
1227 {
1228 if (args->rho < 1.0)
1229 kernel->width = kernel->height = 5; /* default radius 2 */
1230 else
1231 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
1232 kernel->x = kernel->y = (long) (kernel->width-1)/2;
1233
1234 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
1235 kernel->height*sizeof(double));
1236 if (kernel->values == (double *) NULL)
1237 return(DestroyKernelInfo(kernel));
1238
1239 /* set all kernel values along axises to given scale */
1240 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
1241 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
1242 kernel->values[i] = (u == v || u == -v) ? args->sigma : nan;
1243 kernel->minimum = kernel->maximum = args->sigma; /* a flat shape */
1244 kernel->positive_range = args->sigma*(kernel->width*2.0 - 1.0);
1245 break;
1246 }
1247 /* HitAndMiss Kernels */
anthonyc1061722010-05-14 06:23:49 +00001248 case RingKernel:
anthony3dd0f622010-05-13 12:57:32 +00001249 case PeaksKernel:
1250 {
1251 long
1252 limit1,
anthonyc1061722010-05-14 06:23:49 +00001253 limit2,
1254 scale;
anthony3dd0f622010-05-13 12:57:32 +00001255
1256 if (args->rho < args->sigma)
1257 {
1258 kernel->width = ((unsigned long)args->sigma)*2+1;
1259 limit1 = (long)args->rho*args->rho;
1260 limit2 = (long)args->sigma*args->sigma;
1261 }
1262 else
1263 {
1264 kernel->width = ((unsigned long)args->rho)*2+1;
1265 limit1 = (long)args->sigma*args->sigma;
1266 limit2 = (long)args->rho*args->rho;
1267 }
anthonyc1061722010-05-14 06:23:49 +00001268 if ( limit2 <= 0 )
1269 kernel->width = 7L, limit1 = 7L, limit2 = 11L;
1270
anthony3dd0f622010-05-13 12:57:32 +00001271 kernel->height = kernel->width;
1272 kernel->x = kernel->y = (long) (kernel->width-1)/2;
1273 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
1274 kernel->height*sizeof(double));
1275 if (kernel->values == (double *) NULL)
1276 return(DestroyKernelInfo(kernel));
1277
anthonyc1061722010-05-14 06:23:49 +00001278 /* set a ring of points of 'scale' ( 0.0 for PeaksKernel ) */
1279 scale = ( type == PeaksKernel) ? 0.0 : args->xi;
anthony3dd0f622010-05-13 12:57:32 +00001280 for ( i=0, v= -kernel->y; v <= (long)kernel->y; v++)
1281 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
1282 { long radius=u*u+v*v;
anthonyc1061722010-05-14 06:23:49 +00001283 if (limit1 < radius && radius <= limit2)
1284 kernel->positive_range += kernel->values[i] = scale;
anthony3dd0f622010-05-13 12:57:32 +00001285 else
1286 kernel->values[i] = nan;
1287 }
anthonyc1061722010-05-14 06:23:49 +00001288 kernel->minimum = kernel->minimum = scale;
1289 if ( type == PeaksKernel ) {
1290 /* set the central point in the middle */
1291 kernel->values[kernel->x+kernel->y*kernel->width] = 1.0;
1292 kernel->positive_range = 1.0;
1293 kernel->maximum = 1.0;
1294 }
anthony3dd0f622010-05-13 12:57:32 +00001295 break;
1296 }
1297 case CornersKernel:
1298 {
anthony4f1dcb72010-05-14 08:43:10 +00001299 kernel=ParseKernelArray("3: 0,0,- 0,1,1 -,1,-");
anthony3dd0f622010-05-13 12:57:32 +00001300 if (kernel == (KernelInfo *) NULL)
1301 return(kernel);
1302 kernel->type = type;
1303 ExpandKernelInfo(kernel, 90.0); /* Create a list of 4 rotated kernels */
1304 break;
1305 }
1306 case LineEndsKernel:
1307 {
anthony4f1dcb72010-05-14 08:43:10 +00001308 kernel=ParseKernelArray("3: 0,-,- 0,1,0 0,0,0");
anthony3dd0f622010-05-13 12:57:32 +00001309 if (kernel == (KernelInfo *) NULL)
1310 return(kernel);
1311 kernel->type = type;
1312 ExpandKernelInfo(kernel, 45.0); /* Create a list of 8 rotated kernels */
1313 break;
1314 }
1315 case LineJunctionsKernel:
1316 {
1317 KernelInfo
1318 *new_kernel;
anthony3dd0f622010-05-13 12:57:32 +00001319 /* first set of 4 kernels */
anthony4f1dcb72010-05-14 08:43:10 +00001320 kernel=ParseKernelArray("3: -,1,- -,1,- 1,-,1");
anthony3dd0f622010-05-13 12:57:32 +00001321 if (kernel == (KernelInfo *) NULL)
1322 return(kernel);
1323 kernel->type = type;
1324 ExpandKernelInfo(kernel, 90.0);
1325 /* append second set of 4 kernels */
anthony4f1dcb72010-05-14 08:43:10 +00001326 new_kernel=ParseKernelArray("3: 1,-,- -,1,- 1,-,1");
anthony3dd0f622010-05-13 12:57:32 +00001327 if (new_kernel == (KernelInfo *) NULL)
1328 return(DestroyKernelInfo(kernel));
1329 kernel->type = type;
1330 ExpandKernelInfo(new_kernel, 90.0);
1331 LastKernelInfo(kernel)->next = new_kernel;
1332 /* append Thrid set of 4 kernels */
anthony4f1dcb72010-05-14 08:43:10 +00001333 new_kernel=ParseKernelArray("3: -,1,- -,1,1 1,-,-");
anthony3dd0f622010-05-13 12:57:32 +00001334 if (new_kernel == (KernelInfo *) NULL)
1335 return(DestroyKernelInfo(kernel));
1336 kernel->type = type;
1337 ExpandKernelInfo(new_kernel, 90.0);
1338 LastKernelInfo(kernel)->next = new_kernel;
1339 break;
1340 }
anthony4f1dcb72010-05-14 08:43:10 +00001341 case ThickenKernel:
1342 { /* Thicken Kernel ?? -- Under trial */
1343 kernel=ParseKernelArray("3: 1,1,- 1,0,0 -,0,0");
1344 if (kernel == (KernelInfo *) NULL)
1345 return(kernel);
1346 kernel->type = type;
1347 ExpandKernelInfo(kernel, 45);
1348 break;
1349 }
1350 case ThinningKernel:
1351 { /* Thinning Kernel ?? -- Under trial */
1352 kernel=ParseKernelArray("3: 0,0,- 0,1,1 -,1,1");
1353 if (kernel == (KernelInfo *) NULL)
1354 return(kernel);
1355 kernel->type = type;
1356 ExpandKernelInfo(kernel, 45);
1357 break;
1358 }
anthony3dd0f622010-05-13 12:57:32 +00001359 case ConvexHullKernel:
1360 {
1361 KernelInfo
1362 *new_kernel;
anthony3dd0f622010-05-13 12:57:32 +00001363 /* first set of 4 kernels */
anthony4f1dcb72010-05-14 08:43:10 +00001364 kernel=ParseKernelArray("3: 1,1,- 1,0,- 1,-,0");
anthony3dd0f622010-05-13 12:57:32 +00001365 if (kernel == (KernelInfo *) NULL)
1366 return(kernel);
1367 kernel->type = type;
1368 ExpandKernelInfo(kernel, 90.0);
1369 /* append second set of 4 kernels */
anthony4f1dcb72010-05-14 08:43:10 +00001370 new_kernel=ParseKernelArray("3: -,1,1 -,0,1 0,-,1");
anthony3dd0f622010-05-13 12:57:32 +00001371 if (new_kernel == (KernelInfo *) NULL)
1372 return(DestroyKernelInfo(kernel));
1373 kernel->type = type;
1374 ExpandKernelInfo(new_kernel, 90.0);
1375 LastKernelInfo(kernel)->next = new_kernel;
1376 break;
1377 }
1378 case SkeletonKernel:
1379 {
1380 KernelInfo
1381 *new_kernel;
anthony3dd0f622010-05-13 12:57:32 +00001382 /* first set of 4 kernels - corners */
anthony4f1dcb72010-05-14 08:43:10 +00001383 kernel=ParseKernelArray("3: 0,0,- 0,1,1 -,1,-");
anthony3dd0f622010-05-13 12:57:32 +00001384 if (kernel == (KernelInfo *) NULL)
1385 return(kernel);
1386 kernel->type = type;
1387 ExpandKernelInfo(kernel, 90);
1388 /* append second set of 4 kernels - edge middles */
anthony4f1dcb72010-05-14 08:43:10 +00001389 new_kernel=ParseKernelArray("3: 0,0,0 -,1,- 1,1,1");
anthony3dd0f622010-05-13 12:57:32 +00001390 if (new_kernel == (KernelInfo *) NULL)
1391 return(DestroyKernelInfo(kernel));
1392 kernel->type = type;
1393 ExpandKernelInfo(new_kernel, 90);
1394 LastKernelInfo(kernel)->next = new_kernel;
1395 break;
1396 }
anthony602ab9b2010-01-05 08:06:50 +00001397 /* Distance Measuring Kernels */
1398 case ChebyshevKernel:
1399 {
anthony602ab9b2010-01-05 08:06:50 +00001400 if (args->rho < 1.0)
anthonyc94cdb02010-01-06 08:15:29 +00001401 kernel->width = kernel->height = 3; /* default radius = 1 */
anthony602ab9b2010-01-05 08:06:50 +00001402 else
1403 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
cristyc99304f2010-02-01 15:26:27 +00001404 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony602ab9b2010-01-05 08:06:50 +00001405
1406 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
1407 kernel->height*sizeof(double));
1408 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +00001409 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +00001410
cristyc99304f2010-02-01 15:26:27 +00001411 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
1412 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
1413 kernel->positive_range += ( kernel->values[i] =
anthonyc84dce52010-05-07 05:42:23 +00001414 args->sigma*((labs(u)>labs(v)) ? labs(u) : labs(v)) );
cristyc99304f2010-02-01 15:26:27 +00001415 kernel->maximum = kernel->values[0];
anthony602ab9b2010-01-05 08:06:50 +00001416 break;
1417 }
1418 case ManhattenKernel:
1419 {
anthony602ab9b2010-01-05 08:06:50 +00001420 if (args->rho < 1.0)
anthonyc94cdb02010-01-06 08:15:29 +00001421 kernel->width = kernel->height = 3; /* default radius = 1 */
anthony602ab9b2010-01-05 08:06:50 +00001422 else
1423 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
cristyc99304f2010-02-01 15:26:27 +00001424 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony602ab9b2010-01-05 08:06:50 +00001425
1426 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
1427 kernel->height*sizeof(double));
1428 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +00001429 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +00001430
cristyc99304f2010-02-01 15:26:27 +00001431 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
1432 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
1433 kernel->positive_range += ( kernel->values[i] =
anthonyc84dce52010-05-07 05:42:23 +00001434 args->sigma*(labs(u)+labs(v)) );
cristyc99304f2010-02-01 15:26:27 +00001435 kernel->maximum = kernel->values[0];
anthony602ab9b2010-01-05 08:06:50 +00001436 break;
1437 }
1438 case EuclideanKernel:
1439 {
anthony602ab9b2010-01-05 08:06:50 +00001440 if (args->rho < 1.0)
anthonyc94cdb02010-01-06 08:15:29 +00001441 kernel->width = kernel->height = 3; /* default radius = 1 */
anthony602ab9b2010-01-05 08:06:50 +00001442 else
1443 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
cristyc99304f2010-02-01 15:26:27 +00001444 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony602ab9b2010-01-05 08:06:50 +00001445
1446 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
1447 kernel->height*sizeof(double));
1448 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +00001449 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +00001450
cristyc99304f2010-02-01 15:26:27 +00001451 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
1452 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
1453 kernel->positive_range += ( kernel->values[i] =
anthonyc84dce52010-05-07 05:42:23 +00001454 args->sigma*sqrt((double)(u*u+v*v)) );
cristyc99304f2010-02-01 15:26:27 +00001455 kernel->maximum = kernel->values[0];
anthony602ab9b2010-01-05 08:06:50 +00001456 break;
1457 }
anthony602ab9b2010-01-05 08:06:50 +00001458 default:
anthonyc1061722010-05-14 06:23:49 +00001459 {
1460 /* Generate a No-Op minimal kernel - 1x1 pixel */
1461 kernel=ParseKernelArray("1");
1462 if (kernel == (KernelInfo *) NULL)
1463 return(kernel);
1464 kernel->type = UndefinedKernel;
1465 break;
1466 }
anthony602ab9b2010-01-05 08:06:50 +00001467 break;
1468 }
1469
1470 return(kernel);
1471}
anthonyc94cdb02010-01-06 08:15:29 +00001472
anthony602ab9b2010-01-05 08:06:50 +00001473/*
1474%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1475% %
1476% %
1477% %
cristy6771f1e2010-03-05 19:43:39 +00001478% C l o n e K e r n e l I n f o %
anthony4fd27e22010-02-07 08:17:18 +00001479% %
1480% %
1481% %
1482%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1483%
anthony1b2bc0a2010-05-12 05:25:22 +00001484% CloneKernelInfo() creates a new clone of the given Kernel List so that its
1485% can be modified without effecting the original. The cloned kernel should
1486% be destroyed using DestoryKernelInfo() when no longer needed.
anthony7a01dcf2010-05-11 12:25:52 +00001487%
cristye6365592010-04-02 17:31:23 +00001488% The format of the CloneKernelInfo method is:
anthony4fd27e22010-02-07 08:17:18 +00001489%
anthony930be612010-02-08 04:26:15 +00001490% KernelInfo *CloneKernelInfo(const KernelInfo *kernel)
anthony4fd27e22010-02-07 08:17:18 +00001491%
1492% A description of each parameter follows:
1493%
1494% o kernel: the Morphology/Convolution kernel to be cloned
1495%
1496*/
cristyef656912010-03-05 19:54:59 +00001497MagickExport KernelInfo *CloneKernelInfo(const KernelInfo *kernel)
anthony4fd27e22010-02-07 08:17:18 +00001498{
1499 register long
1500 i;
1501
cristy19eb6412010-04-23 14:42:29 +00001502 KernelInfo
anthony7a01dcf2010-05-11 12:25:52 +00001503 *new_kernel;
anthony4fd27e22010-02-07 08:17:18 +00001504
1505 assert(kernel != (KernelInfo *) NULL);
anthony7a01dcf2010-05-11 12:25:52 +00001506 new_kernel=(KernelInfo *) AcquireMagickMemory(sizeof(*kernel));
1507 if (new_kernel == (KernelInfo *) NULL)
1508 return(new_kernel);
1509 *new_kernel=(*kernel); /* copy values in structure */
anthony7a01dcf2010-05-11 12:25:52 +00001510
1511 /* replace the values with a copy of the values */
1512 new_kernel->values=(double *) AcquireQuantumMemory(kernel->width,
cristy19eb6412010-04-23 14:42:29 +00001513 kernel->height*sizeof(double));
anthony7a01dcf2010-05-11 12:25:52 +00001514 if (new_kernel->values == (double *) NULL)
1515 return(DestroyKernelInfo(new_kernel));
anthony4fd27e22010-02-07 08:17:18 +00001516 for (i=0; i < (long) (kernel->width*kernel->height); i++)
anthony7a01dcf2010-05-11 12:25:52 +00001517 new_kernel->values[i]=kernel->values[i];
anthony1b2bc0a2010-05-12 05:25:22 +00001518
1519 /* Also clone the next kernel in the kernel list */
1520 if ( kernel->next != (KernelInfo *) NULL ) {
1521 new_kernel->next = CloneKernelInfo(kernel->next);
1522 if ( new_kernel->next == (KernelInfo *) NULL )
1523 return(DestroyKernelInfo(new_kernel));
1524 }
1525
anthony7a01dcf2010-05-11 12:25:52 +00001526 return(new_kernel);
anthony4fd27e22010-02-07 08:17:18 +00001527}
1528
1529/*
1530%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1531% %
1532% %
1533% %
anthony83ba99b2010-01-24 08:48:15 +00001534% D e s t r o y K e r n e l I n f o %
anthony602ab9b2010-01-05 08:06:50 +00001535% %
1536% %
1537% %
1538%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1539%
anthony83ba99b2010-01-24 08:48:15 +00001540% DestroyKernelInfo() frees the memory used by a Convolution/Morphology
1541% kernel.
anthony602ab9b2010-01-05 08:06:50 +00001542%
anthony83ba99b2010-01-24 08:48:15 +00001543% The format of the DestroyKernelInfo method is:
anthony602ab9b2010-01-05 08:06:50 +00001544%
anthony83ba99b2010-01-24 08:48:15 +00001545% KernelInfo *DestroyKernelInfo(KernelInfo *kernel)
anthony602ab9b2010-01-05 08:06:50 +00001546%
1547% A description of each parameter follows:
1548%
1549% o kernel: the Morphology/Convolution kernel to be destroyed
1550%
1551*/
1552
anthony83ba99b2010-01-24 08:48:15 +00001553MagickExport KernelInfo *DestroyKernelInfo(KernelInfo *kernel)
anthony602ab9b2010-01-05 08:06:50 +00001554{
cristy2be15382010-01-21 02:38:03 +00001555 assert(kernel != (KernelInfo *) NULL);
anthony4fd27e22010-02-07 08:17:18 +00001556
anthony7a01dcf2010-05-11 12:25:52 +00001557 if ( kernel->next != (KernelInfo *) NULL )
1558 kernel->next = DestroyKernelInfo(kernel->next);
1559
1560 kernel->values = (double *)RelinquishMagickMemory(kernel->values);
1561 kernel = (KernelInfo *) RelinquishMagickMemory(kernel);
anthony602ab9b2010-01-05 08:06:50 +00001562 return(kernel);
1563}
anthonyc94cdb02010-01-06 08:15:29 +00001564
1565/*
1566%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1567% %
1568% %
1569% %
anthony3c10fc82010-05-13 02:40:51 +00001570% E x p a n d K e r n e l I n f o %
1571% %
1572% %
1573% %
1574%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1575%
1576% ExpandKernelInfo() takes a single kernel, and expands it into a list
1577% of kernels each incrementally rotated the angle given.
1578%
1579% WARNING: 45 degree rotations only works for 3x3 kernels.
1580% While 90 degree roatations only works for linear and square kernels
1581%
1582% The format of the RotateKernelInfo method is:
1583%
1584% void ExpandKernelInfo(KernelInfo *kernel, double angle)
1585%
1586% A description of each parameter follows:
1587%
1588% o kernel: the Morphology/Convolution kernel
1589%
1590% o angle: angle to rotate in degrees
1591%
1592% This function is only internel to this module, as it is not finalized,
1593% especially with regard to non-orthogonal angles, and rotation of larger
1594% 2D kernels.
1595*/
1596static void ExpandKernelInfo(KernelInfo *kernel, double angle)
1597{
1598 KernelInfo
cristya9a61ad2010-05-13 12:47:41 +00001599 *clone,
anthony3c10fc82010-05-13 02:40:51 +00001600 *last;
cristya9a61ad2010-05-13 12:47:41 +00001601
anthony3c10fc82010-05-13 02:40:51 +00001602 double
1603 a;
1604
1605 last = kernel;
1606 for (a=angle; a<355.0; a+=angle) {
cristya9a61ad2010-05-13 12:47:41 +00001607 clone = CloneKernelInfo(last);
1608 RotateKernelInfo(clone, angle);
1609 last->next = clone;
1610 last = clone;
anthony3c10fc82010-05-13 02:40:51 +00001611 }
1612}
1613
1614/*
1615%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1616% %
1617% %
1618% %
anthony29188a82010-01-22 10:12:34 +00001619% 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 +00001620% %
1621% %
1622% %
1623%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1624%
anthony29188a82010-01-22 10:12:34 +00001625% MorphologyImageChannel() applies a user supplied kernel to the image
1626% according to the given mophology method.
anthony602ab9b2010-01-05 08:06:50 +00001627%
1628% The given kernel is assumed to have been pre-scaled appropriatally, usally
1629% by the kernel generator.
1630%
1631% The format of the MorphologyImage method is:
1632%
cristyef656912010-03-05 19:54:59 +00001633% Image *MorphologyImage(const Image *image,MorphologyMethod method,
1634% const long iterations,KernelInfo *kernel,ExceptionInfo *exception)
anthony29188a82010-01-22 10:12:34 +00001635% Image *MorphologyImageChannel(const Image *image, const ChannelType
cristyef656912010-03-05 19:54:59 +00001636% channel,MorphologyMethod method,const long iterations,
1637% KernelInfo *kernel,ExceptionInfo *exception)
anthony602ab9b2010-01-05 08:06:50 +00001638%
1639% A description of each parameter follows:
1640%
1641% o image: the image.
1642%
1643% o method: the morphology method to be applied.
1644%
1645% o iterations: apply the operation this many times (or no change).
1646% A value of -1 means loop until no change found.
1647% How this is applied may depend on the morphology method.
1648% Typically this is a value of 1.
1649%
1650% o channel: the channel type.
1651%
1652% o kernel: An array of double representing the morphology kernel.
anthony29188a82010-01-22 10:12:34 +00001653% Warning: kernel may be normalized for the Convolve method.
anthony602ab9b2010-01-05 08:06:50 +00001654%
1655% o exception: return any errors or warnings in this structure.
1656%
1657%
1658% TODO: bias and auto-scale handling of the kernel for convolution
1659% The given kernel is assumed to have been pre-scaled appropriatally, usally
1660% by the kernel generator.
1661%
1662*/
1663
anthony930be612010-02-08 04:26:15 +00001664
anthony602ab9b2010-01-05 08:06:50 +00001665/* Internal function
anthony7a01dcf2010-05-11 12:25:52 +00001666 * Apply the low-level Morphology Method Primatives using the given Kernel
anthony930be612010-02-08 04:26:15 +00001667 * Returning the number of pixels that changed.
1668 * Two pre-created images must be provided, no image is created.
anthony602ab9b2010-01-05 08:06:50 +00001669 */
anthony7a01dcf2010-05-11 12:25:52 +00001670static unsigned long MorphologyPrimative(const Image *image, Image
anthony602ab9b2010-01-05 08:06:50 +00001671 *result_image, const MorphologyMethod method, const ChannelType channel,
cristy2be15382010-01-21 02:38:03 +00001672 const KernelInfo *kernel, ExceptionInfo *exception)
anthony602ab9b2010-01-05 08:06:50 +00001673{
cristy2be15382010-01-21 02:38:03 +00001674#define MorphologyTag "Morphology/Image"
anthony602ab9b2010-01-05 08:06:50 +00001675
1676 long
cristy150989e2010-02-01 14:59:39 +00001677 progress,
anthony29188a82010-01-22 10:12:34 +00001678 y, offx, offy,
anthony602ab9b2010-01-05 08:06:50 +00001679 changed;
1680
1681 MagickBooleanType
1682 status;
1683
1684 MagickPixelPacket
1685 bias;
1686
1687 CacheView
1688 *p_view,
1689 *q_view;
1690
anthony4fd27e22010-02-07 08:17:18 +00001691 /* Only the most basic morphology is actually performed by this routine */
anthony4fd27e22010-02-07 08:17:18 +00001692
anthony602ab9b2010-01-05 08:06:50 +00001693 /*
anthony4fd27e22010-02-07 08:17:18 +00001694 Apply Basic Morphology to Image.
anthony602ab9b2010-01-05 08:06:50 +00001695 */
1696 status=MagickTrue;
1697 changed=0;
1698 progress=0;
1699
1700 GetMagickPixelPacket(image,&bias);
1701 SetMagickPixelPacketBias(image,&bias);
anthonycc6c8362010-01-25 04:14:01 +00001702 /* Future: handle auto-bias from user, based on kernel input */
anthony602ab9b2010-01-05 08:06:50 +00001703
1704 p_view=AcquireCacheView(image);
1705 q_view=AcquireCacheView(result_image);
anthony29188a82010-01-22 10:12:34 +00001706
anthonycc6c8362010-01-25 04:14:01 +00001707 /* Some methods (including convolve) needs use a reflected kernel.
1708 * Adjust 'origin' offsets for this reflected kernel.
anthony29188a82010-01-22 10:12:34 +00001709 */
cristyc99304f2010-02-01 15:26:27 +00001710 offx = kernel->x;
1711 offy = kernel->y;
anthony29188a82010-01-22 10:12:34 +00001712 switch(method) {
anthony930be612010-02-08 04:26:15 +00001713 case ConvolveMorphology:
1714 case DilateMorphology:
1715 case DilateIntensityMorphology:
1716 case DistanceMorphology:
anthony5ef8e942010-05-11 06:51:12 +00001717 /* kernel needs to used with reflection about origin */
cristy150989e2010-02-01 14:59:39 +00001718 offx = (long) kernel->width-offx-1;
1719 offy = (long) kernel->height-offy-1;
anthony29188a82010-01-22 10:12:34 +00001720 break;
anthony5ef8e942010-05-11 06:51:12 +00001721 case ErodeMorphology:
1722 case ErodeIntensityMorphology:
1723 case HitAndMissMorphology:
1724 case ThinningMorphology:
1725 case ThickenMorphology:
1726 /* kernel is user as is, without reflection */
1727 break;
anthony930be612010-02-08 04:26:15 +00001728 default:
anthony5ef8e942010-05-11 06:51:12 +00001729 perror("Not a low level Morphology Method");
anthony930be612010-02-08 04:26:15 +00001730 break;
anthony29188a82010-01-22 10:12:34 +00001731 }
1732
anthony602ab9b2010-01-05 08:06:50 +00001733#if defined(MAGICKCORE_OPENMP_SUPPORT)
1734 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
1735#endif
cristy150989e2010-02-01 14:59:39 +00001736 for (y=0; y < (long) image->rows; y++)
anthony602ab9b2010-01-05 08:06:50 +00001737 {
1738 MagickBooleanType
1739 sync;
1740
1741 register const PixelPacket
1742 *restrict p;
1743
1744 register const IndexPacket
1745 *restrict p_indexes;
1746
1747 register PixelPacket
1748 *restrict q;
1749
1750 register IndexPacket
1751 *restrict q_indexes;
1752
cristy150989e2010-02-01 14:59:39 +00001753 register long
anthony602ab9b2010-01-05 08:06:50 +00001754 x;
1755
anthony29188a82010-01-22 10:12:34 +00001756 unsigned long
anthony602ab9b2010-01-05 08:06:50 +00001757 r;
1758
1759 if (status == MagickFalse)
1760 continue;
anthony29188a82010-01-22 10:12:34 +00001761 p=GetCacheViewVirtualPixels(p_view, -offx, y-offy,
1762 image->columns+kernel->width, kernel->height, exception);
anthony602ab9b2010-01-05 08:06:50 +00001763 q=GetCacheViewAuthenticPixels(q_view,0,y,result_image->columns,1,
1764 exception);
1765 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
1766 {
1767 status=MagickFalse;
1768 continue;
1769 }
1770 p_indexes=GetCacheViewVirtualIndexQueue(p_view);
1771 q_indexes=GetCacheViewAuthenticIndexQueue(q_view);
anthony29188a82010-01-22 10:12:34 +00001772 r = (image->columns+kernel->width)*offy+offx; /* constant */
1773
cristy150989e2010-02-01 14:59:39 +00001774 for (x=0; x < (long) image->columns; x++)
anthony602ab9b2010-01-05 08:06:50 +00001775 {
cristy150989e2010-02-01 14:59:39 +00001776 long
anthony602ab9b2010-01-05 08:06:50 +00001777 v;
1778
cristy150989e2010-02-01 14:59:39 +00001779 register long
anthony602ab9b2010-01-05 08:06:50 +00001780 u;
1781
1782 register const double
1783 *restrict k;
1784
1785 register const PixelPacket
1786 *restrict k_pixels;
1787
1788 register const IndexPacket
1789 *restrict k_indexes;
1790
1791 MagickPixelPacket
anthony5ef8e942010-05-11 06:51:12 +00001792 result,
1793 min,
1794 max;
anthony602ab9b2010-01-05 08:06:50 +00001795
anthony29188a82010-01-22 10:12:34 +00001796 /* Copy input to ouput image for unused channels
anthony83ba99b2010-01-24 08:48:15 +00001797 * This removes need for 'cloning' a new image every iteration
anthony29188a82010-01-22 10:12:34 +00001798 */
anthony602ab9b2010-01-05 08:06:50 +00001799 *q = p[r];
1800 if (image->colorspace == CMYKColorspace)
1801 q_indexes[x] = p_indexes[r];
1802
anthony5ef8e942010-05-11 06:51:12 +00001803 /* Defaults */
1804 min.red =
1805 min.green =
1806 min.blue =
1807 min.opacity =
1808 min.index = (MagickRealType) QuantumRange;
1809 max.red =
1810 max.green =
1811 max.blue =
1812 max.opacity =
1813 max.index = (MagickRealType) 0;
1814 /* original pixel value */
1815 result.red = (MagickRealType) p[r].red;
1816 result.green = (MagickRealType) p[r].green;
1817 result.blue = (MagickRealType) p[r].blue;
1818 result.opacity = QuantumRange - (MagickRealType) p[r].opacity;
cristy9b1a5442010-05-13 12:35:40 +00001819 result.index = 0;
anthony5ef8e942010-05-11 06:51:12 +00001820 if ( image->colorspace == CMYKColorspace)
1821 result.index = (MagickRealType) p_indexes[r];
1822
anthony602ab9b2010-01-05 08:06:50 +00001823 switch (method) {
1824 case ConvolveMorphology:
anthony930be612010-02-08 04:26:15 +00001825 /* Set the user defined bias of the weighted average output
1826 **
1827 ** FUTURE: provide some way for internal functions to disable
anthony5ef8e942010-05-11 06:51:12 +00001828 ** user provided bias and scaling effects.
anthony930be612010-02-08 04:26:15 +00001829 */
anthony602ab9b2010-01-05 08:06:50 +00001830 result=bias;
anthony930be612010-02-08 04:26:15 +00001831 break;
anthony4fd27e22010-02-07 08:17:18 +00001832 case DilateIntensityMorphology:
1833 case ErodeIntensityMorphology:
anthony5ef8e942010-05-11 06:51:12 +00001834 result.red = 0.0; /* flag indicating when first match found */
anthony4fd27e22010-02-07 08:17:18 +00001835 break;
anthony602ab9b2010-01-05 08:06:50 +00001836 default:
anthony602ab9b2010-01-05 08:06:50 +00001837 break;
1838 }
1839
1840 switch ( method ) {
1841 case ConvolveMorphology:
anthony930be612010-02-08 04:26:15 +00001842 /* Weighted Average of pixels using reflected kernel
1843 **
1844 ** NOTE for correct working of this operation for asymetrical
1845 ** kernels, the kernel needs to be applied in its reflected form.
1846 ** That is its values needs to be reversed.
1847 **
1848 ** Correlation is actually the same as this but without reflecting
1849 ** the kernel, and thus 'lower-level' that Convolution. However
1850 ** as Convolution is the more common method used, and it does not
1851 ** really cost us much in terms of processing to use a reflected
anthony5ef8e942010-05-11 06:51:12 +00001852 ** kernel, so it is Convolution that is implemented.
anthony930be612010-02-08 04:26:15 +00001853 **
1854 ** Correlation will have its kernel reflected before calling
1855 ** this function to do a Convolve.
1856 **
1857 ** For more details of Correlation vs Convolution see
1858 ** http://www.cs.umd.edu/~djacobs/CMSC426/Convolution.pdf
1859 */
anthony5ef8e942010-05-11 06:51:12 +00001860 if (((channel & SyncChannels) != 0 ) &&
1861 (image->matte == MagickTrue))
1862 { /* Channel has a 'Sync' Flag, and Alpha Channel enabled.
1863 ** Weight the color channels with Alpha Channel so that
1864 ** transparent pixels are not part of the results.
1865 */
anthony602ab9b2010-01-05 08:06:50 +00001866 MagickRealType
anthony5ef8e942010-05-11 06:51:12 +00001867 alpha, /* color channel weighting : kernel*alpha */
1868 gamma; /* divisor, sum of weighting values */
anthony602ab9b2010-01-05 08:06:50 +00001869
1870 gamma=0.0;
anthony29188a82010-01-22 10:12:34 +00001871 k = &kernel->values[ kernel->width*kernel->height-1 ];
anthony602ab9b2010-01-05 08:06:50 +00001872 k_pixels = p;
1873 k_indexes = p_indexes;
cristy150989e2010-02-01 14:59:39 +00001874 for (v=0; v < (long) kernel->height; v++) {
1875 for (u=0; u < (long) kernel->width; u++, k--) {
anthony602ab9b2010-01-05 08:06:50 +00001876 if ( IsNan(*k) ) continue;
1877 alpha=(*k)*(QuantumScale*(QuantumRange-
1878 k_pixels[u].opacity));
1879 gamma += alpha;
1880 result.red += alpha*k_pixels[u].red;
1881 result.green += alpha*k_pixels[u].green;
1882 result.blue += alpha*k_pixels[u].blue;
anthony83ba99b2010-01-24 08:48:15 +00001883 result.opacity += (*k)*(QuantumRange-k_pixels[u].opacity);
anthony602ab9b2010-01-05 08:06:50 +00001884 if ( image->colorspace == CMYKColorspace)
1885 result.index += alpha*k_indexes[u];
1886 }
1887 k_pixels += image->columns+kernel->width;
1888 k_indexes += image->columns+kernel->width;
1889 }
1890 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
anthony83ba99b2010-01-24 08:48:15 +00001891 result.red *= gamma;
1892 result.green *= gamma;
1893 result.blue *= gamma;
1894 result.opacity *= gamma;
1895 result.index *= gamma;
anthony602ab9b2010-01-05 08:06:50 +00001896 }
anthony5ef8e942010-05-11 06:51:12 +00001897 else
1898 {
1899 /* No 'Sync' flag, or no Alpha involved.
1900 ** Convolution is simple individual channel weigthed sum.
1901 */
1902 k = &kernel->values[ kernel->width*kernel->height-1 ];
1903 k_pixels = p;
1904 k_indexes = p_indexes;
1905 for (v=0; v < (long) kernel->height; v++) {
1906 for (u=0; u < (long) kernel->width; u++, k--) {
1907 if ( IsNan(*k) ) continue;
1908 result.red += (*k)*k_pixels[u].red;
1909 result.green += (*k)*k_pixels[u].green;
1910 result.blue += (*k)*k_pixels[u].blue;
1911 result.opacity += (*k)*(QuantumRange-k_pixels[u].opacity);
1912 if ( image->colorspace == CMYKColorspace)
1913 result.index += (*k)*k_indexes[u];
1914 }
1915 k_pixels += image->columns+kernel->width;
1916 k_indexes += image->columns+kernel->width;
1917 }
1918 }
anthony602ab9b2010-01-05 08:06:50 +00001919 break;
1920
anthony4fd27e22010-02-07 08:17:18 +00001921 case ErodeMorphology:
anthony5ef8e942010-05-11 06:51:12 +00001922 /* Minimum Value within kernel neighbourhood
anthony930be612010-02-08 04:26:15 +00001923 **
1924 ** NOTE that the kernel is not reflected for this operation!
1925 **
1926 ** NOTE: in normal Greyscale Morphology, the kernel value should
1927 ** be added to the real value, this is currently not done, due to
1928 ** the nature of the boolean kernels being used.
1929 */
anthony4fd27e22010-02-07 08:17:18 +00001930 k = kernel->values;
1931 k_pixels = p;
1932 k_indexes = p_indexes;
1933 for (v=0; v < (long) kernel->height; v++) {
1934 for (u=0; u < (long) kernel->width; u++, k++) {
1935 if ( IsNan(*k) || (*k) < 0.5 ) continue;
anthony5ef8e942010-05-11 06:51:12 +00001936 Minimize(min.red, (double) k_pixels[u].red);
1937 Minimize(min.green, (double) k_pixels[u].green);
1938 Minimize(min.blue, (double) k_pixels[u].blue);
1939 Minimize(min.opacity,
anthonyd37a5cb2010-05-07 06:37:03 +00001940 QuantumRange-(double) k_pixels[u].opacity);
anthony4fd27e22010-02-07 08:17:18 +00001941 if ( image->colorspace == CMYKColorspace)
anthony5ef8e942010-05-11 06:51:12 +00001942 Minimize(min.index, (double) k_indexes[u]);
anthony4fd27e22010-02-07 08:17:18 +00001943 }
1944 k_pixels += image->columns+kernel->width;
1945 k_indexes += image->columns+kernel->width;
1946 }
1947 break;
1948
anthony5ef8e942010-05-11 06:51:12 +00001949
anthony83ba99b2010-01-24 08:48:15 +00001950 case DilateMorphology:
anthony5ef8e942010-05-11 06:51:12 +00001951 /* Maximum Value within kernel neighbourhood
anthony930be612010-02-08 04:26:15 +00001952 **
1953 ** NOTE for correct working of this operation for asymetrical
1954 ** kernels, the kernel needs to be applied in its reflected form.
1955 ** That is its values needs to be reversed.
1956 **
1957 ** NOTE: in normal Greyscale Morphology, the kernel value should
1958 ** be added to the real value, this is currently not done, due to
1959 ** the nature of the boolean kernels being used.
1960 **
1961 */
anthony29188a82010-01-22 10:12:34 +00001962 k = &kernel->values[ kernel->width*kernel->height-1 ];
anthony602ab9b2010-01-05 08:06:50 +00001963 k_pixels = p;
1964 k_indexes = p_indexes;
cristy150989e2010-02-01 14:59:39 +00001965 for (v=0; v < (long) kernel->height; v++) {
1966 for (u=0; u < (long) kernel->width; u++, k--) {
anthony602ab9b2010-01-05 08:06:50 +00001967 if ( IsNan(*k) || (*k) < 0.5 ) continue;
anthony5ef8e942010-05-11 06:51:12 +00001968 Maximize(max.red, (double) k_pixels[u].red);
1969 Maximize(max.green, (double) k_pixels[u].green);
1970 Maximize(max.blue, (double) k_pixels[u].blue);
1971 Maximize(max.opacity,
anthonyd37a5cb2010-05-07 06:37:03 +00001972 QuantumRange-(double) k_pixels[u].opacity);
anthony602ab9b2010-01-05 08:06:50 +00001973 if ( image->colorspace == CMYKColorspace)
anthony5ef8e942010-05-11 06:51:12 +00001974 Maximize(max.index, (double) k_indexes[u]);
anthony602ab9b2010-01-05 08:06:50 +00001975 }
1976 k_pixels += image->columns+kernel->width;
1977 k_indexes += image->columns+kernel->width;
1978 }
anthony602ab9b2010-01-05 08:06:50 +00001979 break;
1980
anthony5ef8e942010-05-11 06:51:12 +00001981 case HitAndMissMorphology:
1982 case ThinningMorphology:
1983 case ThickenMorphology:
1984 /* Minimum of Foreground Pixel minus Maxumum of Background Pixels
1985 **
1986 ** NOTE that the kernel is not reflected for this operation,
1987 ** and consists of both foreground and background pixel
1988 ** neighbourhoods, 0.0 for background, and 1.0 for foreground
1989 ** with either Nan or 0.5 values for don't care.
1990 **
1991 ** Note that this can produce negative results, though really
1992 ** only a positive match has any real value.
1993 */
1994 k = kernel->values;
1995 k_pixels = p;
1996 k_indexes = p_indexes;
1997 for (v=0; v < (long) kernel->height; v++) {
1998 for (u=0; u < (long) kernel->width; u++, k++) {
1999 if ( IsNan(*k) ) continue;
2000 if ( (*k) > 0.7 )
2001 { /* minimim of foreground pixels */
2002 Minimize(min.red, (double) k_pixels[u].red);
2003 Minimize(min.green, (double) k_pixels[u].green);
2004 Minimize(min.blue, (double) k_pixels[u].blue);
2005 Minimize(min.opacity,
2006 QuantumRange-(double) k_pixels[u].opacity);
2007 if ( image->colorspace == CMYKColorspace)
2008 Minimize(min.index, (double) k_indexes[u]);
2009 }
2010 else if ( (*k) < 0.3 )
2011 { /* maximum of background pixels */
2012 Maximize(max.red, (double) k_pixels[u].red);
2013 Maximize(max.green, (double) k_pixels[u].green);
2014 Maximize(max.blue, (double) k_pixels[u].blue);
2015 Maximize(max.opacity,
2016 QuantumRange-(double) k_pixels[u].opacity);
2017 if ( image->colorspace == CMYKColorspace)
2018 Maximize(max.index, (double) k_indexes[u]);
2019 }
2020 }
2021 k_pixels += image->columns+kernel->width;
2022 k_indexes += image->columns+kernel->width;
2023 }
2024 /* Pattern Match only if min fg larger than min bg pixels */
2025 min.red -= max.red; Maximize( min.red, 0.0 );
2026 min.green -= max.green; Maximize( min.green, 0.0 );
2027 min.blue -= max.blue; Maximize( min.blue, 0.0 );
2028 min.opacity -= max.opacity; Maximize( min.opacity, 0.0 );
2029 min.index -= max.index; Maximize( min.index, 0.0 );
2030 break;
2031
anthony4fd27e22010-02-07 08:17:18 +00002032 case ErodeIntensityMorphology:
anthony930be612010-02-08 04:26:15 +00002033 /* Select Pixel with Minimum Intensity within kernel neighbourhood
2034 **
2035 ** WARNING: the intensity test fails for CMYK and does not
2036 ** take into account the moderating effect of teh alpha channel
2037 ** on the intensity.
2038 **
2039 ** NOTE that the kernel is not reflected for this operation!
2040 */
anthony602ab9b2010-01-05 08:06:50 +00002041 k = kernel->values;
2042 k_pixels = p;
2043 k_indexes = p_indexes;
cristy150989e2010-02-01 14:59:39 +00002044 for (v=0; v < (long) kernel->height; v++) {
2045 for (u=0; u < (long) kernel->width; u++, k++) {
anthony602ab9b2010-01-05 08:06:50 +00002046 if ( IsNan(*k) || (*k) < 0.5 ) continue;
anthony4fd27e22010-02-07 08:17:18 +00002047 if ( result.red == 0.0 ||
2048 PixelIntensity(&(k_pixels[u])) < PixelIntensity(q) ) {
2049 /* copy the whole pixel - no channel selection */
2050 *q = k_pixels[u];
2051 if ( result.red > 0.0 ) changed++;
2052 result.red = 1.0;
2053 }
anthony602ab9b2010-01-05 08:06:50 +00002054 }
2055 k_pixels += image->columns+kernel->width;
2056 k_indexes += image->columns+kernel->width;
2057 }
anthony602ab9b2010-01-05 08:06:50 +00002058 break;
2059
anthony83ba99b2010-01-24 08:48:15 +00002060 case DilateIntensityMorphology:
anthony930be612010-02-08 04:26:15 +00002061 /* Select Pixel with Maximum Intensity within kernel neighbourhood
2062 **
2063 ** WARNING: the intensity test fails for CMYK and does not
2064 ** take into account the moderating effect of teh alpha channel
2065 ** on the intensity.
2066 **
2067 ** NOTE for correct working of this operation for asymetrical
2068 ** kernels, the kernel needs to be applied in its reflected form.
2069 ** That is its values needs to be reversed.
2070 */
anthony29188a82010-01-22 10:12:34 +00002071 k = &kernel->values[ kernel->width*kernel->height-1 ];
anthony602ab9b2010-01-05 08:06:50 +00002072 k_pixels = p;
2073 k_indexes = p_indexes;
cristy150989e2010-02-01 14:59:39 +00002074 for (v=0; v < (long) kernel->height; v++) {
2075 for (u=0; u < (long) kernel->width; u++, k--) {
anthony29188a82010-01-22 10:12:34 +00002076 if ( IsNan(*k) || (*k) < 0.5 ) continue; /* boolean kernel */
2077 if ( result.red == 0.0 ||
2078 PixelIntensity(&(k_pixels[u])) > PixelIntensity(q) ) {
2079 /* copy the whole pixel - no channel selection */
2080 *q = k_pixels[u];
2081 if ( result.red > 0.0 ) changed++;
2082 result.red = 1.0;
2083 }
anthony602ab9b2010-01-05 08:06:50 +00002084 }
2085 k_pixels += image->columns+kernel->width;
2086 k_indexes += image->columns+kernel->width;
2087 }
anthony602ab9b2010-01-05 08:06:50 +00002088 break;
2089
anthony5ef8e942010-05-11 06:51:12 +00002090
anthony602ab9b2010-01-05 08:06:50 +00002091 case DistanceMorphology:
anthony930be612010-02-08 04:26:15 +00002092 /* Add kernel Value and select the minimum value found.
2093 ** The result is a iterative distance from edge of image shape.
2094 **
2095 ** All Distance Kernels are symetrical, but that may not always
2096 ** be the case. For example how about a distance from left edges?
2097 ** To work correctly with asymetrical kernels the reflected kernel
2098 ** needs to be applied.
anthony5ef8e942010-05-11 06:51:12 +00002099 **
2100 ** Actually this is really a GreyErode with a negative kernel!
2101 **
anthony930be612010-02-08 04:26:15 +00002102 */
anthony29188a82010-01-22 10:12:34 +00002103 k = &kernel->values[ kernel->width*kernel->height-1 ];
anthony602ab9b2010-01-05 08:06:50 +00002104 k_pixels = p;
2105 k_indexes = p_indexes;
cristy150989e2010-02-01 14:59:39 +00002106 for (v=0; v < (long) kernel->height; v++) {
2107 for (u=0; u < (long) kernel->width; u++, k--) {
anthony602ab9b2010-01-05 08:06:50 +00002108 if ( IsNan(*k) ) continue;
2109 Minimize(result.red, (*k)+k_pixels[u].red);
2110 Minimize(result.green, (*k)+k_pixels[u].green);
2111 Minimize(result.blue, (*k)+k_pixels[u].blue);
2112 Minimize(result.opacity, (*k)+QuantumRange-k_pixels[u].opacity);
2113 if ( image->colorspace == CMYKColorspace)
2114 Minimize(result.index, (*k)+k_indexes[u]);
2115 }
2116 k_pixels += image->columns+kernel->width;
2117 k_indexes += image->columns+kernel->width;
2118 }
anthony602ab9b2010-01-05 08:06:50 +00002119 break;
2120
2121 case UndefinedMorphology:
2122 default:
2123 break; /* Do nothing */
anthony83ba99b2010-01-24 08:48:15 +00002124 }
anthony5ef8e942010-05-11 06:51:12 +00002125 /* Final mathematics of results (combine with original image?)
2126 **
2127 ** NOTE: Difference Morphology operators Edge* and *Hat could also
2128 ** be done here but works better with iteration as a image difference
2129 ** in the controling function (below). Thicken and Thinning however
2130 ** should be done here so thay can be iterated correctly.
2131 */
2132 switch ( method ) {
2133 case HitAndMissMorphology:
2134 case ErodeMorphology:
2135 result = min; /* minimum of neighbourhood */
2136 break;
2137 case DilateMorphology:
2138 result = max; /* maximum of neighbourhood */
2139 break;
2140 case ThinningMorphology:
2141 /* subtract pattern match from original */
2142 result.red -= min.red;
2143 result.green -= min.green;
2144 result.blue -= min.blue;
2145 result.opacity -= min.opacity;
2146 result.index -= min.index;
2147 break;
2148 case ThickenMorphology:
2149 /* Union with original image (maximize) - or should this be + */
2150 Maximize( result.red, min.red );
2151 Maximize( result.green, min.green );
2152 Maximize( result.blue, min.blue );
2153 Maximize( result.opacity, min.opacity );
2154 Maximize( result.index, min.index );
2155 break;
2156 default:
2157 /* result directly calculated or assigned */
2158 break;
2159 }
2160 /* Assign the resulting pixel values - Clamping Result */
anthony83ba99b2010-01-24 08:48:15 +00002161 switch ( method ) {
2162 case UndefinedMorphology:
2163 case DilateIntensityMorphology:
2164 case ErodeIntensityMorphology:
anthony930be612010-02-08 04:26:15 +00002165 break; /* full pixel was directly assigned - not a channel method */
anthony83ba99b2010-01-24 08:48:15 +00002166 default:
anthony83ba99b2010-01-24 08:48:15 +00002167 if ((channel & RedChannel) != 0)
2168 q->red = ClampToQuantum(result.red);
2169 if ((channel & GreenChannel) != 0)
2170 q->green = ClampToQuantum(result.green);
2171 if ((channel & BlueChannel) != 0)
2172 q->blue = ClampToQuantum(result.blue);
2173 if ((channel & OpacityChannel) != 0
2174 && image->matte == MagickTrue )
2175 q->opacity = ClampToQuantum(QuantumRange-result.opacity);
2176 if ((channel & IndexChannel) != 0
2177 && image->colorspace == CMYKColorspace)
2178 q_indexes[x] = ClampToQuantum(result.index);
2179 break;
2180 }
anthony5ef8e942010-05-11 06:51:12 +00002181 /* Count up changed pixels */
anthony83ba99b2010-01-24 08:48:15 +00002182 if ( ( p[r].red != q->red )
2183 || ( p[r].green != q->green )
2184 || ( p[r].blue != q->blue )
2185 || ( p[r].opacity != q->opacity )
2186 || ( image->colorspace == CMYKColorspace &&
2187 p_indexes[r] != q_indexes[x] ) )
2188 changed++; /* The pixel had some value changed! */
anthony602ab9b2010-01-05 08:06:50 +00002189 p++;
2190 q++;
anthony83ba99b2010-01-24 08:48:15 +00002191 } /* x */
anthony602ab9b2010-01-05 08:06:50 +00002192 sync=SyncCacheViewAuthenticPixels(q_view,exception);
2193 if (sync == MagickFalse)
2194 status=MagickFalse;
2195 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2196 {
2197 MagickBooleanType
2198 proceed;
2199
2200#if defined(MAGICKCORE_OPENMP_SUPPORT)
2201 #pragma omp critical (MagickCore_MorphologyImage)
2202#endif
2203 proceed=SetImageProgress(image,MorphologyTag,progress++,image->rows);
2204 if (proceed == MagickFalse)
2205 status=MagickFalse;
2206 }
anthony83ba99b2010-01-24 08:48:15 +00002207 } /* y */
anthony602ab9b2010-01-05 08:06:50 +00002208 result_image->type=image->type;
2209 q_view=DestroyCacheView(q_view);
2210 p_view=DestroyCacheView(p_view);
cristy150989e2010-02-01 14:59:39 +00002211 return(status ? (unsigned long) changed : 0);
anthony602ab9b2010-01-05 08:06:50 +00002212}
2213
anthony4fd27e22010-02-07 08:17:18 +00002214
2215MagickExport Image *MorphologyImage(const Image *image, const MorphologyMethod
anthony930be612010-02-08 04:26:15 +00002216 method, const long iterations,const KernelInfo *kernel, ExceptionInfo
2217 *exception)
cristy2be15382010-01-21 02:38:03 +00002218{
2219 Image
2220 *morphology_image;
2221
2222 morphology_image=MorphologyImageChannel(image,DefaultChannels,method,
2223 iterations,kernel,exception);
2224 return(morphology_image);
2225}
2226
anthony4fd27e22010-02-07 08:17:18 +00002227
cristyef656912010-03-05 19:54:59 +00002228MagickExport Image *MorphologyImageChannel(const Image *image,
2229 const ChannelType channel,const MorphologyMethod method,
2230 const long iterations,const KernelInfo *kernel,ExceptionInfo *exception)
anthony602ab9b2010-01-05 08:06:50 +00002231{
anthony602ab9b2010-01-05 08:06:50 +00002232 Image
2233 *new_image,
anthony1b2bc0a2010-05-12 05:25:22 +00002234 *old_image,
anthony4fd27e22010-02-07 08:17:18 +00002235 *grad_image;
anthony602ab9b2010-01-05 08:06:50 +00002236
anthony4fd27e22010-02-07 08:17:18 +00002237 KernelInfo
anthony1b2bc0a2010-05-12 05:25:22 +00002238 *curr_kernel,
2239 *this_kernel;
anthony4fd27e22010-02-07 08:17:18 +00002240
2241 MorphologyMethod
2242 curr_method;
2243
anthony1b2bc0a2010-05-12 05:25:22 +00002244 unsigned long
anthony4f1dcb72010-05-14 08:43:10 +00002245 count, /* count of the number of times though kernel list */
2246 limit, /* limit of the total number of times */
2247 steps, /* grand total of number of morpholgy steps done */
2248 kernel_number, /* kernel number being applied */
2249 changed, /* pixels changed in one step */
2250 list_changed, /* changes made over one set of kernels */
2251 total_changed; /* total count of changes to image */
anthony1b2bc0a2010-05-12 05:25:22 +00002252
anthony602ab9b2010-01-05 08:06:50 +00002253 assert(image != (Image *) NULL);
2254 assert(image->signature == MagickSignature);
anthony4fd27e22010-02-07 08:17:18 +00002255 assert(kernel != (KernelInfo *) NULL);
2256 assert(kernel->signature == MagickSignature);
anthony602ab9b2010-01-05 08:06:50 +00002257 assert(exception != (ExceptionInfo *) NULL);
2258 assert(exception->signature == MagickSignature);
2259
anthony602ab9b2010-01-05 08:06:50 +00002260 if ( iterations == 0 )
2261 return((Image *)NULL); /* null operation - nothing to do! */
2262
2263 /* kernel must be valid at this point
2264 * (except maybe for posible future morphology methods like "Prune"
2265 */
cristy2be15382010-01-21 02:38:03 +00002266 assert(kernel != (KernelInfo *)NULL);
anthony602ab9b2010-01-05 08:06:50 +00002267
anthony1b2bc0a2010-05-12 05:25:22 +00002268 new_image = (Image *) NULL; /* for cleanup */
2269 old_image = (Image *) NULL;
2270 grad_image = (Image *) NULL;
2271 curr_kernel = (KernelInfo *) NULL;
anthony4f1dcb72010-05-14 08:43:10 +00002272
2273 steps = 0; /* total number of primative steps */
2274 count = 0; /* number of times through kernel list */
2275 changed = 1; /* assume something was changed! */
2276 list_changed = 0;
2277 total_changed = 0;
2278 curr_kernel = (KernelInfo *)kernel; /* allow kernel and method */
2279 curr_method = method; /* to be changed as nessary */
anthony4fd27e22010-02-07 08:17:18 +00002280
cristy150989e2010-02-01 14:59:39 +00002281 limit = (unsigned long) iterations;
anthony602ab9b2010-01-05 08:06:50 +00002282 if ( iterations < 0 )
2283 limit = image->columns > image->rows ? image->columns : image->rows;
2284
anthony4fd27e22010-02-07 08:17:18 +00002285 /* Third-level morphology methods */
2286 switch( curr_method ) {
2287 case EdgeMorphology:
2288 grad_image = MorphologyImageChannel(image, channel,
2289 DilateMorphology, iterations, curr_kernel, exception);
anthony1b2bc0a2010-05-12 05:25:22 +00002290 if ( grad_image == (Image *) NULL )
2291 goto error_cleanup;
anthony4fd27e22010-02-07 08:17:18 +00002292 /* FALL-THRU */
2293 case EdgeInMorphology:
2294 curr_method = ErodeMorphology;
anthony602ab9b2010-01-05 08:06:50 +00002295 break;
anthony4fd27e22010-02-07 08:17:18 +00002296 case EdgeOutMorphology:
2297 curr_method = DilateMorphology;
anthony602ab9b2010-01-05 08:06:50 +00002298 break;
anthony4fd27e22010-02-07 08:17:18 +00002299 case TopHatMorphology:
2300 curr_method = OpenMorphology;
2301 break;
2302 case BottomHatMorphology:
2303 curr_method = CloseMorphology;
2304 break;
2305 default:
anthony930be612010-02-08 04:26:15 +00002306 break; /* not a third-level method */
anthony4fd27e22010-02-07 08:17:18 +00002307 }
2308
2309 /* Second-level morphology methods */
2310 switch( curr_method ) {
anthony930be612010-02-08 04:26:15 +00002311 case OpenMorphology:
2312 /* Open is a Erode then a Dilate without reflection */
anthony4fd27e22010-02-07 08:17:18 +00002313 new_image = MorphologyImageChannel(image, channel,
2314 ErodeMorphology, iterations, curr_kernel, exception);
anthony602ab9b2010-01-05 08:06:50 +00002315 if (new_image == (Image *) NULL)
anthony1b2bc0a2010-05-12 05:25:22 +00002316 goto error_cleanup;
anthony4fd27e22010-02-07 08:17:18 +00002317 curr_method = DilateMorphology;
2318 break;
anthony602ab9b2010-01-05 08:06:50 +00002319 case OpenIntensityMorphology:
anthony4fd27e22010-02-07 08:17:18 +00002320 new_image = MorphologyImageChannel(image, channel,
2321 ErodeIntensityMorphology, iterations, curr_kernel, exception);
anthony602ab9b2010-01-05 08:06:50 +00002322 if (new_image == (Image *) NULL)
anthony1b2bc0a2010-05-12 05:25:22 +00002323 goto error_cleanup;
anthony4fd27e22010-02-07 08:17:18 +00002324 curr_method = DilateIntensityMorphology;
2325 break;
anthony930be612010-02-08 04:26:15 +00002326
2327 case CloseMorphology:
2328 /* Close is a Dilate then Erode using reflected kernel */
2329 /* A reflected kernel is needed for a Close */
2330 if ( curr_kernel == kernel )
2331 curr_kernel = CloneKernelInfo(kernel);
anthony1b2bc0a2010-05-12 05:25:22 +00002332 if (curr_kernel == (KernelInfo *) NULL)
2333 goto error_cleanup;
anthony930be612010-02-08 04:26:15 +00002334 RotateKernelInfo(curr_kernel,180);
2335 new_image = MorphologyImageChannel(image, channel,
2336 DilateMorphology, iterations, curr_kernel, exception);
2337 if (new_image == (Image *) NULL)
anthony1b2bc0a2010-05-12 05:25:22 +00002338 goto error_cleanup;
anthony930be612010-02-08 04:26:15 +00002339 curr_method = ErodeMorphology;
2340 break;
anthony4fd27e22010-02-07 08:17:18 +00002341 case CloseIntensityMorphology:
anthony930be612010-02-08 04:26:15 +00002342 /* A reflected kernel is needed for a Close */
2343 if ( curr_kernel == kernel )
2344 curr_kernel = CloneKernelInfo(kernel);
anthony1b2bc0a2010-05-12 05:25:22 +00002345 if (curr_kernel == (KernelInfo *) NULL)
2346 goto error_cleanup;
anthony4fd27e22010-02-07 08:17:18 +00002347 RotateKernelInfo(curr_kernel,180);
2348 new_image = MorphologyImageChannel(image, channel,
2349 DilateIntensityMorphology, iterations, curr_kernel, exception);
2350 if (new_image == (Image *) NULL)
anthony1b2bc0a2010-05-12 05:25:22 +00002351 goto error_cleanup;
anthony4fd27e22010-02-07 08:17:18 +00002352 curr_method = ErodeIntensityMorphology;
anthony602ab9b2010-01-05 08:06:50 +00002353 break;
2354
anthony930be612010-02-08 04:26:15 +00002355 case CorrelateMorphology:
2356 /* A Correlation is actually a Convolution with a reflected kernel.
2357 ** However a Convolution is a weighted sum with a reflected kernel.
2358 ** It may seem stange to convert a Correlation into a Convolution
2359 ** as the Correleation is the simplier method, but Convolution is
2360 ** much more commonly used, and it makes sense to implement it directly
2361 ** so as to avoid the need to duplicate the kernel when it is not
2362 ** required (which is typically the default).
2363 */
2364 if ( curr_kernel == kernel )
2365 curr_kernel = CloneKernelInfo(kernel);
anthony1b2bc0a2010-05-12 05:25:22 +00002366 if (curr_kernel == (KernelInfo *) NULL)
2367 goto error_cleanup;
anthony930be612010-02-08 04:26:15 +00002368 RotateKernelInfo(curr_kernel,180);
2369 curr_method = ConvolveMorphology;
2370 /* FALL-THRU into Correlate (weigthed sum without reflection) */
2371
anthonyc94cdb02010-01-06 08:15:29 +00002372 case ConvolveMorphology:
anthony1b2bc0a2010-05-12 05:25:22 +00002373 { /* Scale or Normalize kernel, according to user wishes
2374 ** before using it for the Convolve/Correlate method.
2375 **
2376 ** FUTURE: provide some way for internal functions to disable
2377 ** user bias and scaling effects.
2378 */
2379 const char
2380 *artifact = GetImageArtifact(image,"convolve:scale");
2381 if ( artifact != (char *)NULL ) {
2382 GeometryFlags
2383 flags;
2384 GeometryInfo
2385 args;
anthony999bb2c2010-02-18 12:38:01 +00002386
anthony1b2bc0a2010-05-12 05:25:22 +00002387 if ( curr_kernel == kernel )
2388 curr_kernel = CloneKernelInfo(kernel);
2389 if (curr_kernel == (KernelInfo *) NULL)
2390 goto error_cleanup;
2391 args.rho = 1.0;
2392 flags = (GeometryFlags) ParseGeometry(artifact, &args);
2393 ScaleKernelInfo(curr_kernel, args.rho, flags);
2394 }
anthony4fd27e22010-02-07 08:17:18 +00002395 }
anthony930be612010-02-08 04:26:15 +00002396 /* FALL-THRU to do the first, and typically the only iteration */
anthony4fd27e22010-02-07 08:17:18 +00002397
anthony602ab9b2010-01-05 08:06:50 +00002398 default:
anthony930be612010-02-08 04:26:15 +00002399 /* Do a single iteration using the Low-Level Morphology method!
2400 ** This ensures a "new_image" has been generated, but allows us to skip
2401 ** the creation of 'old_image' if no more iterations are needed.
2402 **
2403 ** The "curr_method" should also be set to a low-level method that is
anthony7a01dcf2010-05-11 12:25:52 +00002404 ** understood by the MorphologyPrimative() internal function.
anthony602ab9b2010-01-05 08:06:50 +00002405 */
2406 new_image=CloneImage(image,0,0,MagickTrue,exception);
2407 if (new_image == (Image *) NULL)
anthony1b2bc0a2010-05-12 05:25:22 +00002408 goto error_cleanup;
anthony602ab9b2010-01-05 08:06:50 +00002409 if (SetImageStorageClass(new_image,DirectClass) == MagickFalse)
2410 {
2411 InheritException(exception,&new_image->exception);
anthony1b2bc0a2010-05-12 05:25:22 +00002412 goto exit_cleanup;
anthony602ab9b2010-01-05 08:06:50 +00002413 }
anthony4f1dcb72010-05-14 08:43:10 +00002414 steps++; /* primative morphology steps performs */
anthony7a01dcf2010-05-11 12:25:52 +00002415 changed = MorphologyPrimative(image,new_image,curr_method,channel,
2416 curr_kernel, exception);
anthony602ab9b2010-01-05 08:06:50 +00002417 if ( GetImageArtifact(image,"verbose") != (const char *) NULL )
anthony4f1dcb72010-05-14 08:43:10 +00002418 fprintf(stderr, "Morphology %s:%lu.%lu #%lu => Changed %lu\n",
anthony4fd27e22010-02-07 08:17:18 +00002419 MagickOptionToMnemonic(MagickMorphologyOptions, curr_method),
anthony4f1dcb72010-05-14 08:43:10 +00002420 1L, 0L, steps, changed);
anthony930be612010-02-08 04:26:15 +00002421 break;
anthony602ab9b2010-01-05 08:06:50 +00002422 }
anthony1b2bc0a2010-05-12 05:25:22 +00002423 /* At this point
2424 * + "curr_method" should be set to a low-level morphology method
2425 * + "count=1" if the first iteration of the first kernel has been done.
2426 * + "new_image" is defined and contains the current resulting image
2427 */
anthony602ab9b2010-01-05 08:06:50 +00002428
anthony1b2bc0a2010-05-12 05:25:22 +00002429 /* The Hit-And-Miss Morphology is not 'iterated' against the resulting
2430 ** image from the previous morphology step. It must always be applied
2431 ** to the original image, with the results collected into a union (maximimum
2432 ** or lighten) of all the results. Multiple kernels may be applied but
2433 ** an iteration of the morphology does nothing, so is ignored.
anthony7a01dcf2010-05-11 12:25:52 +00002434 **
anthony1b2bc0a2010-05-12 05:25:22 +00002435 ** The first kernel is guranteed to have been done, so lets continue the
2436 ** process, with the other kernels in the kernel list.
anthony7a01dcf2010-05-11 12:25:52 +00002437 */
anthony1b2bc0a2010-05-12 05:25:22 +00002438 if ( method == HitAndMissMorphology )
anthony7a01dcf2010-05-11 12:25:52 +00002439 {
anthony1b2bc0a2010-05-12 05:25:22 +00002440 if ( curr_kernel->next != (KernelInfo *) NULL ) {
2441 /* create a second working image */
2442 old_image = CloneImage(image,0,0,MagickTrue,exception);
2443 if (old_image == (Image *) NULL)
2444 goto error_cleanup;
2445 if (SetImageStorageClass(old_image,DirectClass) == MagickFalse)
2446 {
2447 InheritException(exception,&old_image->exception);
2448 goto exit_cleanup;
2449 }
anthony7a01dcf2010-05-11 12:25:52 +00002450
anthony1b2bc0a2010-05-12 05:25:22 +00002451 /* loop through rest of the kernels */
2452 this_kernel=curr_kernel->next;
2453 kernel_number=1;
anthony4f1dcb72010-05-14 08:43:10 +00002454 count=1; /* it is always the first list! */
anthony1b2bc0a2010-05-12 05:25:22 +00002455 while( this_kernel != (KernelInfo *) NULL )
2456 {
anthony4f1dcb72010-05-14 08:43:10 +00002457 steps++;
anthony1b2bc0a2010-05-12 05:25:22 +00002458 changed = MorphologyPrimative(image,old_image,curr_method,channel,
2459 this_kernel,exception);
2460 (void) CompositeImageChannel(new_image,
2461 (ChannelType) (channel & ~SyncChannels), LightenCompositeOp,
2462 old_image, 0, 0);
anthony4f1dcb72010-05-14 08:43:10 +00002463 if ( kernel->next != (KernelInfo *) NULL ) /* more than one kernel? */
2464 if ( GetImageArtifact(image,"verbose") != (const char *) NULL )
2465 fprintf(stderr, "Morphology %s:%lu.%lu #%lu => Changed %lu\n",
2466 MagickOptionToMnemonic(MagickMorphologyOptions, curr_method),
2467 count, kernel_number, steps, changed);
anthony1b2bc0a2010-05-12 05:25:22 +00002468 this_kernel = this_kernel->next;
2469 kernel_number++;
2470 }
anthony4f1dcb72010-05-14 08:43:10 +00002471 if ( GetImageArtifact(image,"verbose") != (const char *) NULL )
2472 fprintf(stderr, "Morphology %s:%lu #%lu ===> Changed %lu Total %lu\n",
2473 MagickOptionToMnemonic(MagickMorphologyOptions, curr_method),
2474 count, steps, list_changed, total_changed);
anthony1b2bc0a2010-05-12 05:25:22 +00002475 old_image=DestroyImage(old_image);
2476 }
2477 goto exit_cleanup;
anthony7a01dcf2010-05-11 12:25:52 +00002478 }
2479
anthony1b2bc0a2010-05-12 05:25:22 +00002480 /* Repeat the low-level morphology over all kernels
2481 until iteration count limit or no change from any kernel is found */
anthony4f1dcb72010-05-14 08:43:10 +00002482 if ( ( steps != 0 && limit != 1 && changed > 0 ) ||
anthony1b2bc0a2010-05-12 05:25:22 +00002483 curr_kernel->next != (KernelInfo *) NULL ) {
anthony930be612010-02-08 04:26:15 +00002484
anthony4f1dcb72010-05-14 08:43:10 +00002485 /* More than one step so create a second working image */
anthony1b2bc0a2010-05-12 05:25:22 +00002486 old_image = CloneImage(image,0,0,MagickTrue,exception);
anthony602ab9b2010-01-05 08:06:50 +00002487 if (old_image == (Image *) NULL)
anthony1b2bc0a2010-05-12 05:25:22 +00002488 goto error_cleanup;
anthony602ab9b2010-01-05 08:06:50 +00002489 if (SetImageStorageClass(old_image,DirectClass) == MagickFalse)
2490 {
2491 InheritException(exception,&old_image->exception);
anthony1b2bc0a2010-05-12 05:25:22 +00002492 goto exit_cleanup;
anthony602ab9b2010-01-05 08:06:50 +00002493 }
anthony1b2bc0a2010-05-12 05:25:22 +00002494
2495 /* reset variables for the first/next iteration, or next kernel) */
anthony4f1dcb72010-05-14 08:43:10 +00002496 count = steps;
anthony1b2bc0a2010-05-12 05:25:22 +00002497 kernel_number = 0;
2498 this_kernel = curr_kernel;
anthony4f1dcb72010-05-14 08:43:10 +00002499 list_changed = (steps != 0) ? changed : 0;
2500 total_changed = 0;
2501 if ( (steps != 0) && this_kernel != (KernelInfo *) NULL ) {
2502 count = 0; /* first iteration is not yet finished! */
2503 kernel_number++;
anthony1b2bc0a2010-05-12 05:25:22 +00002504 this_kernel = curr_kernel->next;
anthony1b2bc0a2010-05-12 05:25:22 +00002505 }
2506
2507 while ( count < limit ) {
anthony4f1dcb72010-05-14 08:43:10 +00002508 count++; /* iteration though kernel list being performed */
anthony1b2bc0a2010-05-12 05:25:22 +00002509 while ( this_kernel != (KernelInfo *) NULL ) {
anthony602ab9b2010-01-05 08:06:50 +00002510 Image *tmp = old_image;
2511 old_image = new_image;
2512 new_image = tmp;
anthony4f1dcb72010-05-14 08:43:10 +00002513 steps++;
anthony7a01dcf2010-05-11 12:25:52 +00002514 changed = MorphologyPrimative(old_image,new_image,curr_method,channel,
anthony1b2bc0a2010-05-12 05:25:22 +00002515 this_kernel,exception);
anthony4f1dcb72010-05-14 08:43:10 +00002516 if ( kernel->next != (KernelInfo *) NULL ) /* more than one kernel? */
2517 if ( GetImageArtifact(image,"verbose") != (const char *) NULL )
2518 fprintf(stderr, "Morphology %s:%lu.%lu #%lu => Changed %lu\n",
2519 MagickOptionToMnemonic(MagickMorphologyOptions, curr_method),
2520 count, kernel_number, steps, changed);
2521 list_changed += changed;
anthony1b2bc0a2010-05-12 05:25:22 +00002522 this_kernel = this_kernel->next;
2523 kernel_number++;
anthony602ab9b2010-01-05 08:06:50 +00002524 }
anthony4f1dcb72010-05-14 08:43:10 +00002525 total_changed += list_changed;
2526 if ( GetImageArtifact(image,"verbose") != (const char *) NULL )
2527 fprintf(stderr, "Morphology %s:%lu #%lu ===> Changed %lu Total %lu\n",
anthony3dd0f622010-05-13 12:57:32 +00002528 MagickOptionToMnemonic(MagickMorphologyOptions, curr_method),
anthony4f1dcb72010-05-14 08:43:10 +00002529 count, steps, list_changed, total_changed);
2530 if ( list_changed == 0 )
anthony1b2bc0a2010-05-12 05:25:22 +00002531 break; /* no changes after processing all kernels - ABORT */
2532 /* prepare for next loop */
anthony4f1dcb72010-05-14 08:43:10 +00002533 list_changed = 0;
anthony1b2bc0a2010-05-12 05:25:22 +00002534 kernel_number = 0;
2535 this_kernel = curr_kernel;
2536 }
cristy150989e2010-02-01 14:59:39 +00002537 old_image=DestroyImage(old_image);
anthony602ab9b2010-01-05 08:06:50 +00002538 }
anthony930be612010-02-08 04:26:15 +00002539
anthony3dd0f622010-05-13 12:57:32 +00002540 /* finished with kernel - destroy any copy that was made */
anthony4fd27e22010-02-07 08:17:18 +00002541 if ( curr_kernel != kernel )
2542 curr_kernel=DestroyKernelInfo(curr_kernel);
2543
anthony7d10d742010-05-06 07:05:29 +00002544 /* Third-level Subtractive methods post-processing
2545 **
2546 ** The removal of any 'Sync' channel flag in the Image Compositon below
2547 ** ensures the compose method is applied in a purely mathematical way, only
2548 ** the selected channels, without any normal 'alpha blending' normally
2549 ** associated with the compose method.
2550 **
2551 ** Note "method" here is the 'original' morphological method, and not the
2552 ** 'current' morphological method used above to generate "new_image".
2553 */
anthony4fd27e22010-02-07 08:17:18 +00002554 switch( method ) {
2555 case EdgeOutMorphology:
2556 case EdgeInMorphology:
2557 case TopHatMorphology:
2558 case BottomHatMorphology:
anthony930be612010-02-08 04:26:15 +00002559 /* Get Difference relative to the original image */
cristyaeb2cbc2010-05-07 13:28:58 +00002560 (void) CompositeImageChannel(new_image, (ChannelType) (channel & ~SyncChannels),
anthony7d10d742010-05-06 07:05:29 +00002561 DifferenceCompositeOp, image, 0, 0);
anthony4fd27e22010-02-07 08:17:18 +00002562 break;
anthony7d10d742010-05-06 07:05:29 +00002563 case EdgeMorphology:
2564 /* Difference the Eroded image from the saved Dilated image */
cristyaeb2cbc2010-05-07 13:28:58 +00002565 (void) CompositeImageChannel(new_image, (ChannelType) (channel & ~SyncChannels),
anthony7d10d742010-05-06 07:05:29 +00002566 DifferenceCompositeOp, grad_image, 0, 0);
anthony4fd27e22010-02-07 08:17:18 +00002567 grad_image=DestroyImage(grad_image);
2568 break;
2569 default:
2570 break;
2571 }
anthony602ab9b2010-01-05 08:06:50 +00002572
2573 return(new_image);
anthony1b2bc0a2010-05-12 05:25:22 +00002574
2575 /* Yes goto's are bad, but in this case it makes cleanup lot more efficient */
2576error_cleanup:
2577 if ( new_image != (Image *) NULL )
2578 DestroyImage(new_image);
2579exit_cleanup:
2580 if ( old_image != (Image *) NULL )
2581 DestroyImage(old_image);
2582 if ( curr_kernel != (KernelInfo *) NULL && curr_kernel != kernel )
2583 curr_kernel=DestroyKernelInfo(curr_kernel);
2584 return(new_image);
anthony602ab9b2010-01-05 08:06:50 +00002585}
anthony83ba99b2010-01-24 08:48:15 +00002586
2587/*
2588%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2589% %
2590% %
2591% %
anthony4fd27e22010-02-07 08:17:18 +00002592+ R o t a t e K e r n e l I n f o %
anthony83ba99b2010-01-24 08:48:15 +00002593% %
2594% %
2595% %
2596%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2597%
anthony4fd27e22010-02-07 08:17:18 +00002598% RotateKernelInfo() rotates the kernel by the angle given. Currently it is
anthony83ba99b2010-01-24 08:48:15 +00002599% restricted to 90 degree angles, but this may be improved in the future.
2600%
anthony4fd27e22010-02-07 08:17:18 +00002601% The format of the RotateKernelInfo method is:
anthony83ba99b2010-01-24 08:48:15 +00002602%
anthony4fd27e22010-02-07 08:17:18 +00002603% void RotateKernelInfo(KernelInfo *kernel, double angle)
anthony83ba99b2010-01-24 08:48:15 +00002604%
2605% A description of each parameter follows:
2606%
2607% o kernel: the Morphology/Convolution kernel
2608%
2609% o angle: angle to rotate in degrees
2610%
anthonyc4c86e02010-01-27 09:30:32 +00002611% This function is only internel to this module, as it is not finalized,
2612% especially with regard to non-orthogonal angles, and rotation of larger
2613% 2D kernels.
anthony83ba99b2010-01-24 08:48:15 +00002614*/
anthony4fd27e22010-02-07 08:17:18 +00002615static void RotateKernelInfo(KernelInfo *kernel, double angle)
anthony83ba99b2010-01-24 08:48:15 +00002616{
anthony1b2bc0a2010-05-12 05:25:22 +00002617 /* angle the lower kernels first */
2618 if ( kernel->next != (KernelInfo *) NULL)
2619 RotateKernelInfo(kernel->next, angle);
2620
anthony83ba99b2010-01-24 08:48:15 +00002621 /* WARNING: Currently assumes the kernel (rightly) is horizontally symetrical
2622 **
2623 ** TODO: expand beyond simple 90 degree rotates, flips and flops
2624 */
2625
2626 /* Modulus the angle */
2627 angle = fmod(angle, 360.0);
2628 if ( angle < 0 )
2629 angle += 360.0;
2630
anthony3c10fc82010-05-13 02:40:51 +00002631 if ( 337.5 < angle || angle <= 22.5 )
anthony83ba99b2010-01-24 08:48:15 +00002632 return; /* no change! - At least at this time */
2633
anthony3dd0f622010-05-13 12:57:32 +00002634 /* Handle special cases */
anthony83ba99b2010-01-24 08:48:15 +00002635 switch (kernel->type) {
2636 /* These built-in kernels are cylindrical kernels, rotating is useless */
2637 case GaussianKernel:
anthony83ba99b2010-01-24 08:48:15 +00002638 case DOGKernel:
2639 case DiskKernel:
anthony3dd0f622010-05-13 12:57:32 +00002640 case PeaksKernel:
2641 case LaplacianKernel:
anthony83ba99b2010-01-24 08:48:15 +00002642 case ChebyshevKernel:
2643 case ManhattenKernel:
2644 case EuclideanKernel:
2645 return;
2646
2647 /* These may be rotatable at non-90 angles in the future */
2648 /* but simply rotating them in multiples of 90 degrees is useless */
2649 case SquareKernel:
2650 case DiamondKernel:
2651 case PlusKernel:
anthony3dd0f622010-05-13 12:57:32 +00002652 case CrossKernel:
anthony83ba99b2010-01-24 08:48:15 +00002653 return;
2654
2655 /* These only allows a +/-90 degree rotation (by transpose) */
2656 /* A 180 degree rotation is useless */
2657 case BlurKernel:
2658 case RectangleKernel:
2659 if ( 135.0 < angle && angle <= 225.0 )
2660 return;
2661 if ( 225.0 < angle && angle <= 315.0 )
2662 angle -= 180;
2663 break;
2664
anthony3dd0f622010-05-13 12:57:32 +00002665 default:
anthony83ba99b2010-01-24 08:48:15 +00002666 break;
2667 }
anthony3c10fc82010-05-13 02:40:51 +00002668 /* Attempt rotations by 45 degrees */
2669 if ( 22.5 < fmod(angle,90.0) && fmod(angle,90.0) <= 67.5 )
2670 {
2671 if ( kernel->width == 3 && kernel->height == 3 )
2672 { /* Rotate a 3x3 square by 45 degree angle */
2673 MagickRealType t = kernel->values[0];
2674 kernel->values[0] = kernel->values[1];
2675 kernel->values[1] = kernel->values[2];
2676 kernel->values[2] = kernel->values[5];
2677 kernel->values[5] = kernel->values[8];
2678 kernel->values[8] = kernel->values[7];
2679 kernel->values[7] = kernel->values[6];
2680 kernel->values[6] = kernel->values[3];
2681 kernel->values[3] = t;
2682 angle = fmod(angle+45.0, 360.0);
2683 }
2684 else
2685 perror("Unable to rotate non-3x3 kernel by 45 degrees");
2686 }
2687 if ( 45.0 < fmod(angle, 180.0) && fmod(angle,180.0) <= 135.0 )
2688 {
2689 if ( kernel->width == 1 || kernel->height == 1 )
2690 { /* Do a transpose of the image, which results in a 90
2691 ** degree rotation of a 1 dimentional kernel
2692 */
2693 long
2694 t;
2695 t = (long) kernel->width;
2696 kernel->width = kernel->height;
2697 kernel->height = (unsigned long) t;
2698 t = kernel->x;
2699 kernel->x = kernel->y;
2700 kernel->y = t;
2701 if ( kernel->width == 1 )
2702 angle = fmod(angle+270.0, 360.0); /* angle -90 degrees */
2703 else
2704 angle = fmod(angle+90.0, 360.0); /* angle +90 degrees */
2705 }
2706 else if ( kernel->width == kernel->height )
2707 { /* Rotate a square array of values by 90 degrees */
2708 register unsigned long
2709 i,j,x,y;
2710 register MagickRealType
2711 *k,t;
2712 k=kernel->values;
2713 for( i=0, x=kernel->width-1; i<=x; i++, x--)
2714 for( j=0, y=kernel->height-1; j<y; j++, y--)
2715 { t = k[i+j*kernel->width];
2716 k[i+j*kernel->width] = k[y+i*kernel->width];
2717 k[y+i*kernel->width] = k[x+y*kernel->width];
2718 k[x+y*kernel->width] = k[j+x*kernel->width];
2719 k[j+x*kernel->width] = t;
2720 }
2721 angle = fmod(angle+90.0, 360.0); /* angle +90 degrees */
2722 }
2723 else
2724 perror("Unable to rotate a non-square, non-linear kernel 90 degrees");
2725 }
anthony83ba99b2010-01-24 08:48:15 +00002726 if ( 135.0 < angle && angle <= 225.0 )
2727 {
2728 /* For a 180 degree rotation - also know as a reflection */
2729 /* This is actually a very very common operation! */
2730 /* Basically all that is needed is a reversal of the kernel data! */
2731 unsigned long
2732 i,j;
2733 register double
2734 *k,t;
2735
2736 k=kernel->values;
2737 for ( i=0, j=kernel->width*kernel->height-1; i<j; i++, j--)
2738 t=k[i], k[i]=k[j], k[j]=t;
2739
anthony930be612010-02-08 04:26:15 +00002740 kernel->x = (long) kernel->width - kernel->x - 1;
2741 kernel->y = (long) kernel->height - kernel->y - 1;
anthony3c10fc82010-05-13 02:40:51 +00002742 angle = fmod(angle+180.0, 360.0); /* angle+180 degrees */
anthony83ba99b2010-01-24 08:48:15 +00002743 }
anthony3c10fc82010-05-13 02:40:51 +00002744 /* At this point angle should at least between -45 (315) and +45 degrees
anthony83ba99b2010-01-24 08:48:15 +00002745 * In the future some form of non-orthogonal angled rotates could be
2746 * performed here, posibily with a linear kernel restriction.
2747 */
2748
2749#if 0
anthony3c10fc82010-05-13 02:40:51 +00002750 { /* Do a Flop by reversing each row.
anthony83ba99b2010-01-24 08:48:15 +00002751 */
2752 unsigned long
2753 y;
cristy150989e2010-02-01 14:59:39 +00002754 register long
anthony83ba99b2010-01-24 08:48:15 +00002755 x,r;
2756 register double
2757 *k,t;
2758
2759 for ( y=0, k=kernel->values; y < kernel->height; y++, k+=kernel->width)
2760 for ( x=0, r=kernel->width-1; x<kernel->width/2; x++, r--)
2761 t=k[x], k[x]=k[r], k[r]=t;
2762
cristyc99304f2010-02-01 15:26:27 +00002763 kernel->x = kernel->width - kernel->x - 1;
anthony83ba99b2010-01-24 08:48:15 +00002764 angle = fmod(angle+180.0, 360.0);
2765 }
2766#endif
2767 return;
2768}
2769
2770/*
2771%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2772% %
2773% %
2774% %
cristy6771f1e2010-03-05 19:43:39 +00002775% S c a l e K e r n e l I n f o %
anthonycc6c8362010-01-25 04:14:01 +00002776% %
2777% %
2778% %
2779%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2780%
anthony1b2bc0a2010-05-12 05:25:22 +00002781% ScaleKernelInfo() scales the given kernel list by the given amount, with or
2782% without normalization of the sum of the kernel values (as per given flags).
anthonycc6c8362010-01-25 04:14:01 +00002783%
anthony999bb2c2010-02-18 12:38:01 +00002784% By default (no flags given) the values within the kernel is scaled
anthony1b2bc0a2010-05-12 05:25:22 +00002785% directly using given scaling factor without change.
anthonycc6c8362010-01-25 04:14:01 +00002786%
anthony1b2bc0a2010-05-12 05:25:22 +00002787% If any 'normalize_flags' are given the kernel will first be normalized and
2788% then further scaled by the scaling factor value given. A 'PercentValue'
2789% flag will cause the given scaling factor to be divided by one hundred
2790% percent.
anthony999bb2c2010-02-18 12:38:01 +00002791%
2792% Kernel normalization ('normalize_flags' given) is designed to ensure that
2793% any use of the kernel scaling factor with 'Convolve' or 'Correlate'
anthony1b2bc0a2010-05-12 05:25:22 +00002794% morphology methods will fall into -1.0 to +1.0 range. Note that for
2795% non-HDRI versions of IM this may cause images to have any negative results
2796% clipped, unless some 'bias' is used.
anthony999bb2c2010-02-18 12:38:01 +00002797%
2798% More specifically. Kernels which only contain positive values (such as a
2799% 'Gaussian' kernel) will be scaled so that those values sum to +1.0,
anthony1b2bc0a2010-05-12 05:25:22 +00002800% ensuring a 0.0 to +1.0 output range for non-HDRI images.
anthony999bb2c2010-02-18 12:38:01 +00002801%
2802% For Kernels that contain some negative values, (such as 'Sharpen' kernels)
2803% the kernel will be scaled by the absolute of the sum of kernel values, so
2804% that it will generally fall within the +/- 1.0 range.
2805%
2806% For kernels whose values sum to zero, (such as 'Laplician' kernels) kernel
2807% will be scaled by just the sum of the postive values, so that its output
2808% range will again fall into the +/- 1.0 range.
2809%
2810% For special kernels designed for locating shapes using 'Correlate', (often
2811% only containing +1 and -1 values, representing foreground/brackground
2812% matching) a special normalization method is provided to scale the positive
2813% values seperatally to those of the negative values, so the kernel will be
2814% forced to become a zero-sum kernel better suited to such searches.
2815%
anthony1b2bc0a2010-05-12 05:25:22 +00002816% WARNING: Correct normalization of the kernel assumes that the '*_range'
anthony999bb2c2010-02-18 12:38:01 +00002817% attributes within the kernel structure have been correctly set during the
2818% kernels creation.
2819%
2820% NOTE: The values used for 'normalize_flags' have been selected specifically
anthony1b2bc0a2010-05-12 05:25:22 +00002821% to match the use of geometry options, so that '!' means NormalizeValue,
2822% '^' means CorrelateNormalizeValue, and '%' means PercentValue. All other
anthony999bb2c2010-02-18 12:38:01 +00002823% GeometryFlags values are ignored.
anthonycc6c8362010-01-25 04:14:01 +00002824%
anthony4fd27e22010-02-07 08:17:18 +00002825% The format of the ScaleKernelInfo method is:
anthonycc6c8362010-01-25 04:14:01 +00002826%
anthony999bb2c2010-02-18 12:38:01 +00002827% void ScaleKernelInfo(KernelInfo *kernel, const double scaling_factor,
2828% const MagickStatusType normalize_flags )
anthonycc6c8362010-01-25 04:14:01 +00002829%
2830% A description of each parameter follows:
2831%
2832% o kernel: the Morphology/Convolution kernel
2833%
anthony999bb2c2010-02-18 12:38:01 +00002834% o scaling_factor:
2835% multiply all values (after normalization) by this factor if not
2836% zero. If the kernel is normalized regardless of any flags.
2837%
2838% o normalize_flags:
2839% GeometryFlags defining normalization method to use.
2840% specifically: NormalizeValue, CorrelateNormalizeValue,
2841% and/or PercentValue
anthonycc6c8362010-01-25 04:14:01 +00002842%
anthonyc4c86e02010-01-27 09:30:32 +00002843% This function is internal to this module only at this time, but can be
2844% exported to other modules if needed.
anthonycc6c8362010-01-25 04:14:01 +00002845*/
cristy6771f1e2010-03-05 19:43:39 +00002846MagickExport void ScaleKernelInfo(KernelInfo *kernel,
2847 const double scaling_factor,const GeometryFlags normalize_flags)
anthonycc6c8362010-01-25 04:14:01 +00002848{
cristy150989e2010-02-01 14:59:39 +00002849 register long
anthonycc6c8362010-01-25 04:14:01 +00002850 i;
2851
anthony999bb2c2010-02-18 12:38:01 +00002852 register double
2853 pos_scale,
2854 neg_scale;
2855
anthony1b2bc0a2010-05-12 05:25:22 +00002856 /* scale the lower kernels first */
2857 if ( kernel->next != (KernelInfo *) NULL)
2858 ScaleKernelInfo(kernel->next, scaling_factor, normalize_flags);
2859
anthony999bb2c2010-02-18 12:38:01 +00002860 pos_scale = 1.0;
2861 if ( (normalize_flags&NormalizeValue) != 0 ) {
2862 /* normalize kernel appropriately */
2863 if ( fabs(kernel->positive_range + kernel->negative_range) > MagickEpsilon )
2864 pos_scale = fabs(kernel->positive_range + kernel->negative_range);
anthonycc6c8362010-01-25 04:14:01 +00002865 else
anthony999bb2c2010-02-18 12:38:01 +00002866 pos_scale = kernel->positive_range; /* special zero-summing kernel */
2867 }
2868 /* force kernel into being a normalized zero-summing kernel */
2869 if ( (normalize_flags&CorrelateNormalizeValue) != 0 ) {
2870 pos_scale = ( fabs(kernel->positive_range) > MagickEpsilon )
2871 ? kernel->positive_range : 1.0;
2872 neg_scale = ( fabs(kernel->negative_range) > MagickEpsilon )
2873 ? -kernel->negative_range : 1.0;
2874 }
2875 else
2876 neg_scale = pos_scale;
2877
2878 /* finialize scaling_factor for positive and negative components */
2879 pos_scale = scaling_factor/pos_scale;
2880 neg_scale = scaling_factor/neg_scale;
2881 if ( (normalize_flags&PercentValue) != 0 ) {
2882 pos_scale /= 100.0;
2883 neg_scale /= 100.0;
anthonycc6c8362010-01-25 04:14:01 +00002884 }
2885
cristy150989e2010-02-01 14:59:39 +00002886 for (i=0; i < (long) (kernel->width*kernel->height); i++)
anthonycc6c8362010-01-25 04:14:01 +00002887 if ( ! IsNan(kernel->values[i]) )
anthony999bb2c2010-02-18 12:38:01 +00002888 kernel->values[i] *= (kernel->values[i] >= 0) ? pos_scale : neg_scale;
anthonycc6c8362010-01-25 04:14:01 +00002889
anthony999bb2c2010-02-18 12:38:01 +00002890 /* convolution output range */
2891 kernel->positive_range *= pos_scale;
2892 kernel->negative_range *= neg_scale;
2893 /* maximum and minimum values in kernel */
2894 kernel->maximum *= (kernel->maximum >= 0.0) ? pos_scale : neg_scale;
2895 kernel->minimum *= (kernel->minimum >= 0.0) ? pos_scale : neg_scale;
2896
2897 /* swap kernel settings if user scaling factor is negative */
2898 if ( scaling_factor < MagickEpsilon ) {
2899 double t;
2900 t = kernel->positive_range;
2901 kernel->positive_range = kernel->negative_range;
2902 kernel->negative_range = t;
2903 t = kernel->maximum;
2904 kernel->maximum = kernel->minimum;
2905 kernel->minimum = 1;
2906 }
anthonycc6c8362010-01-25 04:14:01 +00002907
2908 return;
2909}
2910
2911/*
2912%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2913% %
2914% %
2915% %
anthony4fd27e22010-02-07 08:17:18 +00002916+ S h o w K e r n e l I n f o %
anthony83ba99b2010-01-24 08:48:15 +00002917% %
2918% %
2919% %
2920%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2921%
anthony4fd27e22010-02-07 08:17:18 +00002922% ShowKernelInfo() outputs the details of the given kernel defination to
2923% standard error, generally due to a users 'showkernel' option request.
anthony83ba99b2010-01-24 08:48:15 +00002924%
2925% The format of the ShowKernel method is:
2926%
anthony4fd27e22010-02-07 08:17:18 +00002927% void ShowKernelInfo(KernelInfo *kernel)
anthony83ba99b2010-01-24 08:48:15 +00002928%
2929% A description of each parameter follows:
2930%
2931% o kernel: the Morphology/Convolution kernel
2932%
anthonyc4c86e02010-01-27 09:30:32 +00002933% This function is internal to this module only at this time. That may change
2934% in the future.
anthony83ba99b2010-01-24 08:48:15 +00002935*/
anthony4fd27e22010-02-07 08:17:18 +00002936MagickExport void ShowKernelInfo(KernelInfo *kernel)
anthony83ba99b2010-01-24 08:48:15 +00002937{
anthony7a01dcf2010-05-11 12:25:52 +00002938 KernelInfo
2939 *k;
anthony83ba99b2010-01-24 08:48:15 +00002940
anthony7a01dcf2010-05-11 12:25:52 +00002941 long
2942 c, i, u, v;
2943
2944 for (c=0, k=kernel; k != (KernelInfo *) NULL; c++, k=k->next ) {
2945
2946 fprintf(stderr, "Kernel ");
2947 if ( kernel->next != (KernelInfo *) NULL )
2948 fprintf(stderr, " #%ld", c );
2949 fprintf(stderr, " \"%s\" of size %lux%lu%+ld%+ld ",
2950 MagickOptionToMnemonic(MagickKernelOptions, k->type),
2951 kernel->width, k->height,
2952 kernel->x, kernel->y );
2953 fprintf(stderr,
2954 " with values from %.*lg to %.*lg\n",
2955 GetMagickPrecision(), k->minimum,
2956 GetMagickPrecision(), k->maximum);
2957 fprintf(stderr, "Forming convolution output range from %.*lg to %.*lg%s\n",
2958 GetMagickPrecision(), k->negative_range,
2959 GetMagickPrecision(), k->positive_range,
2960 /*kernel->normalized == MagickTrue ? " (normalized)" : */ "" );
2961 for (i=v=0; v < (long) k->height; v++) {
2962 fprintf(stderr,"%2ld:",v);
2963 for (u=0; u < (long) k->width; u++, i++)
2964 if ( IsNan(k->values[i]) )
2965 fprintf(stderr," %*s", GetMagickPrecision()+2, "nan");
2966 else
2967 fprintf(stderr," %*.*lg", GetMagickPrecision()+2,
2968 GetMagickPrecision(), k->values[i]);
2969 fprintf(stderr,"\n");
2970 }
anthony83ba99b2010-01-24 08:48:15 +00002971 }
2972}
anthonycc6c8362010-01-25 04:14:01 +00002973
2974/*
2975%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2976% %
2977% %
2978% %
anthony4fd27e22010-02-07 08:17:18 +00002979+ Z e r o K e r n e l N a n s %
anthonycc6c8362010-01-25 04:14:01 +00002980% %
2981% %
2982% %
2983%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2984%
2985% ZeroKernelNans() replaces any special 'nan' value that may be present in
2986% the kernel with a zero value. This is typically done when the kernel will
2987% be used in special hardware (GPU) convolution processors, to simply
2988% matters.
2989%
2990% The format of the ZeroKernelNans method is:
2991%
2992% voidZeroKernelNans (KernelInfo *kernel)
2993%
2994% A description of each parameter follows:
2995%
2996% o kernel: the Morphology/Convolution kernel
2997%
2998% FUTURE: return the information in a string for API usage.
2999*/
anthonyc4c86e02010-01-27 09:30:32 +00003000MagickExport void ZeroKernelNans(KernelInfo *kernel)
anthonycc6c8362010-01-25 04:14:01 +00003001{
cristy150989e2010-02-01 14:59:39 +00003002 register long
anthonycc6c8362010-01-25 04:14:01 +00003003 i;
3004
anthony1b2bc0a2010-05-12 05:25:22 +00003005 /* scale the lower kernels first */
3006 if ( kernel->next != (KernelInfo *) NULL)
3007 ZeroKernelNans(kernel->next);
3008
cristy150989e2010-02-01 14:59:39 +00003009 for (i=0; i < (long) (kernel->width*kernel->height); i++)
anthonycc6c8362010-01-25 04:14:01 +00003010 if ( IsNan(kernel->values[i]) )
3011 kernel->values[i] = 0.0;
3012
3013 return;
3014}