blob: 3010a83cf4c5debbfeef0d7f4f4e72ab0bdef438 [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
113/*
114%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
115% %
116% %
117% %
anthony83ba99b2010-01-24 08:48:15 +0000118% A c q u i r e K e r n e l I n f o %
anthony602ab9b2010-01-05 08:06:50 +0000119% %
120% %
121% %
122%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
123%
cristy2be15382010-01-21 02:38:03 +0000124% AcquireKernelInfo() takes the given string (generally supplied by the
anthony602ab9b2010-01-05 08:06:50 +0000125% user) and converts it into a Morphology/Convolution Kernel. This allows
126% users to specify a kernel from a number of pre-defined kernels, or to fully
127% specify their own kernel for a specific Convolution or Morphology
128% Operation.
129%
130% The kernel so generated can be any rectangular array of floating point
131% values (doubles) with the 'control point' or 'pixel being affected'
132% anywhere within that array of values.
133%
anthony83ba99b2010-01-24 08:48:15 +0000134% Previously IM was restricted to a square of odd size using the exact
135% center as origin, this is no longer the case, and any rectangular kernel
136% with any value being declared the origin. This in turn allows the use of
137% highly asymmetrical kernels.
anthony602ab9b2010-01-05 08:06:50 +0000138%
139% The floating point values in the kernel can also include a special value
anthony83ba99b2010-01-24 08:48:15 +0000140% known as 'nan' or 'not a number' to indicate that this value is not part
141% of the kernel array. This allows you to shaped the kernel within its
142% rectangular area. That is 'nan' values provide a 'mask' for the kernel
143% shape. However at least one non-nan value must be provided for correct
144% working of a kernel.
anthony602ab9b2010-01-05 08:06:50 +0000145%
anthony7a01dcf2010-05-11 12:25:52 +0000146% The returned kernel should be freed using the DestroyKernelInfo() when you
147% are finished with it. Do not free this memory yourself.
anthony602ab9b2010-01-05 08:06:50 +0000148%
149% Input kernel defintion strings can consist of any of three types.
150%
anthony29188a82010-01-22 10:12:34 +0000151% "name:args"
152% Select from one of the built in kernels, using the name and
153% geometry arguments supplied. See AcquireKernelBuiltIn()
anthony602ab9b2010-01-05 08:06:50 +0000154%
155% "WxH[+X+Y]:num, num, num ..."
anthony1b2bc0a2010-05-12 05:25:22 +0000156% a kernel of size W by H, with W*H floating point numbers following.
anthony602ab9b2010-01-05 08:06:50 +0000157% the 'center' can be optionally be defined at +X+Y (such that +0+0
anthony29188a82010-01-22 10:12:34 +0000158% is top left corner). If not defined the pixel in the center, for
159% odd sizes, or to the immediate top or left of center for even sizes
160% is automatically selected.
anthony602ab9b2010-01-05 08:06:50 +0000161%
anthony29188a82010-01-22 10:12:34 +0000162% "num, num, num, num, ..."
163% list of floating point numbers defining an 'old style' odd sized
164% square kernel. At least 9 values should be provided for a 3x3
165% square kernel, 25 for a 5x5 square kernel, 49 for 7x7, etc.
166% Values can be space or comma separated. This is not recommended.
anthony602ab9b2010-01-05 08:06:50 +0000167%
anthony83ba99b2010-01-24 08:48:15 +0000168% Note that 'name' kernels will start with an alphabetic character while the
169% new kernel specification has a ':' character in its specification string.
170% If neither is the case, it is assumed an old style of a simple list of
171% numbers generating a odd-sized square kernel has been given.
anthony602ab9b2010-01-05 08:06:50 +0000172%
anthony7a01dcf2010-05-11 12:25:52 +0000173% You can define a 'list of kernels' which can be used by some morphology
174% operators A list is defined as a semi-colon seperated list kernels.
175%
anthonydbc89892010-05-12 07:05:27 +0000176% " kernel ; kernel ; kernel ; "
anthony7a01dcf2010-05-11 12:25:52 +0000177%
anthonydbc89892010-05-12 07:05:27 +0000178% Extra ';' characters are simply ignored.
anthony7a01dcf2010-05-11 12:25:52 +0000179%
anthony602ab9b2010-01-05 08:06:50 +0000180% The format of the AcquireKernal method is:
181%
cristy2be15382010-01-21 02:38:03 +0000182% KernelInfo *AcquireKernelInfo(const char *kernel_string)
anthony602ab9b2010-01-05 08:06:50 +0000183%
184% A description of each parameter follows:
185%
186% o kernel_string: the Morphology/Convolution kernel wanted.
187%
188*/
189
anthonyc84dce52010-05-07 05:42:23 +0000190/* This was separated so that it could be used as a separate
anthony5ef8e942010-05-11 06:51:12 +0000191** array input handling function, such as for -color-matrix
anthonyc84dce52010-05-07 05:42:23 +0000192*/
anthony5ef8e942010-05-11 06:51:12 +0000193static KernelInfo *ParseKernelArray(const char *kernel_string)
anthony602ab9b2010-01-05 08:06:50 +0000194{
cristy2be15382010-01-21 02:38:03 +0000195 KernelInfo
anthony602ab9b2010-01-05 08:06:50 +0000196 *kernel;
197
198 char
199 token[MaxTextExtent];
200
anthony602ab9b2010-01-05 08:06:50 +0000201 const char
anthony5ef8e942010-05-11 06:51:12 +0000202 *p,
203 *end;
anthony602ab9b2010-01-05 08:06:50 +0000204
anthonyc84dce52010-05-07 05:42:23 +0000205 register long
206 i;
anthony602ab9b2010-01-05 08:06:50 +0000207
anthony29188a82010-01-22 10:12:34 +0000208 double
209 nan = sqrt((double)-1.0); /* Special Value : Not A Number */
210
cristy2be15382010-01-21 02:38:03 +0000211 kernel=(KernelInfo *) AcquireMagickMemory(sizeof(*kernel));
212 if (kernel == (KernelInfo *)NULL)
anthony602ab9b2010-01-05 08:06:50 +0000213 return(kernel);
214 (void) ResetMagickMemory(kernel,0,sizeof(*kernel));
anthony7a01dcf2010-05-11 12:25:52 +0000215 kernel->minimum = kernel->maximum = 0.0;
216 kernel->negative_range = kernel->positive_range = 0.0;
anthony602ab9b2010-01-05 08:06:50 +0000217 kernel->type = UserDefinedKernel;
anthony7a01dcf2010-05-11 12:25:52 +0000218 kernel->next = (KernelInfo *) NULL;
cristyd43a46b2010-01-21 02:13:41 +0000219 kernel->signature = MagickSignature;
anthony602ab9b2010-01-05 08:06:50 +0000220
anthony5ef8e942010-05-11 06:51:12 +0000221 /* find end of this specific kernel definition string */
222 end = strchr(kernel_string, ';');
223 if ( end == (char *) NULL )
224 end = strchr(kernel_string, '\0');
225
anthony602ab9b2010-01-05 08:06:50 +0000226 /* Has a ':' in argument - New user kernel specification */
227 p = strchr(kernel_string, ':');
anthony5ef8e942010-05-11 06:51:12 +0000228 if ( p != (char *) NULL && p < end)
anthony602ab9b2010-01-05 08:06:50 +0000229 {
anthonyc84dce52010-05-07 05:42:23 +0000230 MagickStatusType
231 flags;
232
233 GeometryInfo
234 args;
235
anthony602ab9b2010-01-05 08:06:50 +0000236 /* ParseGeometry() needs the geometry separated! -- Arrgghh */
cristy150989e2010-02-01 14:59:39 +0000237 memcpy(token, kernel_string, (size_t) (p-kernel_string));
anthony602ab9b2010-01-05 08:06:50 +0000238 token[p-kernel_string] = '\0';
anthonyc84dce52010-05-07 05:42:23 +0000239 SetGeometryInfo(&args);
anthony602ab9b2010-01-05 08:06:50 +0000240 flags = ParseGeometry(token, &args);
anthony602ab9b2010-01-05 08:06:50 +0000241
anthony29188a82010-01-22 10:12:34 +0000242 /* Size handling and checks of geometry settings */
anthony602ab9b2010-01-05 08:06:50 +0000243 if ( (flags & WidthValue) == 0 ) /* if no width then */
244 args.rho = args.sigma; /* then width = height */
245 if ( args.rho < 1.0 ) /* if width too small */
246 args.rho = 1.0; /* then width = 1 */
247 if ( args.sigma < 1.0 ) /* if height too small */
248 args.sigma = args.rho; /* then height = width */
249 kernel->width = (unsigned long)args.rho;
250 kernel->height = (unsigned long)args.sigma;
251
252 /* Offset Handling and Checks */
253 if ( args.xi < 0.0 || args.psi < 0.0 )
anthony83ba99b2010-01-24 08:48:15 +0000254 return(DestroyKernelInfo(kernel));
cristyc99304f2010-02-01 15:26:27 +0000255 kernel->x = ((flags & XValue)!=0) ? (long)args.xi
cristy150989e2010-02-01 14:59:39 +0000256 : (long) (kernel->width-1)/2;
cristyc99304f2010-02-01 15:26:27 +0000257 kernel->y = ((flags & YValue)!=0) ? (long)args.psi
cristy150989e2010-02-01 14:59:39 +0000258 : (long) (kernel->height-1)/2;
cristyc99304f2010-02-01 15:26:27 +0000259 if ( kernel->x >= (long) kernel->width ||
260 kernel->y >= (long) kernel->height )
anthony83ba99b2010-01-24 08:48:15 +0000261 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000262
263 p++; /* advance beyond the ':' */
264 }
265 else
anthonyc84dce52010-05-07 05:42:23 +0000266 { /* ELSE - Old old specification, forming odd-square kernel */
anthony602ab9b2010-01-05 08:06:50 +0000267 /* count up number of values given */
268 p=(const char *) kernel_string;
cristya699b172010-01-06 16:48:49 +0000269 while ((isspace((int) ((unsigned char) *p)) != 0) || (*p == '\''))
anthony29188a82010-01-22 10:12:34 +0000270 p++; /* ignore "'" chars for convolve filter usage - Cristy */
anthony5ef8e942010-05-11 06:51:12 +0000271 for (i=0; p < end; i++)
anthony602ab9b2010-01-05 08:06:50 +0000272 {
273 GetMagickToken(p,&p,token);
274 if (*token == ',')
275 GetMagickToken(p,&p,token);
276 }
277 /* set the size of the kernel - old sized square */
278 kernel->width = kernel->height= (unsigned long) sqrt((double) i+1.0);
cristyc99304f2010-02-01 15:26:27 +0000279 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony602ab9b2010-01-05 08:06:50 +0000280 p=(const char *) kernel_string;
anthony29188a82010-01-22 10:12:34 +0000281 while ((isspace((int) ((unsigned char) *p)) != 0) || (*p == '\''))
282 p++; /* ignore "'" chars for convolve filter usage - Cristy */
anthony602ab9b2010-01-05 08:06:50 +0000283 }
284
285 /* Read in the kernel values from rest of input string argument */
286 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
287 kernel->height*sizeof(double));
288 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +0000289 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000290
cristyc99304f2010-02-01 15:26:27 +0000291 kernel->minimum = +MagickHuge;
292 kernel->maximum = -MagickHuge;
293 kernel->negative_range = kernel->positive_range = 0.0;
anthonyc84dce52010-05-07 05:42:23 +0000294
anthony5ef8e942010-05-11 06:51:12 +0000295 for (i=0; (i < (long) (kernel->width*kernel->height)) && (p < end); i++)
anthony602ab9b2010-01-05 08:06:50 +0000296 {
297 GetMagickToken(p,&p,token);
298 if (*token == ',')
299 GetMagickToken(p,&p,token);
anthony29188a82010-01-22 10:12:34 +0000300 if ( LocaleCompare("nan",token) == 0
anthonyc84dce52010-05-07 05:42:23 +0000301 || LocaleCompare("-",token) == 0 ) {
anthony29188a82010-01-22 10:12:34 +0000302 kernel->values[i] = nan; /* do not include this value in kernel */
303 }
304 else {
305 kernel->values[i] = StringToDouble(token);
306 ( kernel->values[i] < 0)
cristyc99304f2010-02-01 15:26:27 +0000307 ? ( kernel->negative_range += kernel->values[i] )
308 : ( kernel->positive_range += kernel->values[i] );
309 Minimize(kernel->minimum, kernel->values[i]);
310 Maximize(kernel->maximum, kernel->values[i]);
anthony29188a82010-01-22 10:12:34 +0000311 }
anthony602ab9b2010-01-05 08:06:50 +0000312 }
anthony29188a82010-01-22 10:12:34 +0000313
anthony5ef8e942010-05-11 06:51:12 +0000314 /* sanity check -- no more values in kernel definition */
315 GetMagickToken(p,&p,token);
316 if ( *token != '\0' && *token != ';' && *token != '\'' )
317 return(DestroyKernelInfo(kernel));
318
anthonyc84dce52010-05-07 05:42:23 +0000319#if 0
320 /* this was the old method of handling a incomplete kernel */
cristy150989e2010-02-01 14:59:39 +0000321 if ( i < (long) (kernel->width*kernel->height) ) {
cristyc99304f2010-02-01 15:26:27 +0000322 Minimize(kernel->minimum, kernel->values[i]);
323 Maximize(kernel->maximum, kernel->values[i]);
cristy150989e2010-02-01 14:59:39 +0000324 for ( ; i < (long) (kernel->width*kernel->height); i++)
anthony29188a82010-01-22 10:12:34 +0000325 kernel->values[i]=0.0;
326 }
anthonyc84dce52010-05-07 05:42:23 +0000327#else
328 /* Number of values for kernel was not enough - Report Error */
329 if ( i < (long) (kernel->width*kernel->height) )
330 return(DestroyKernelInfo(kernel));
331#endif
332
333 /* check that we recieved at least one real (non-nan) value! */
334 if ( kernel->minimum == MagickHuge )
335 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000336
337 return(kernel);
338}
anthonyc84dce52010-05-07 05:42:23 +0000339
anthony5ef8e942010-05-11 06:51:12 +0000340static KernelInfo *ParseNamedKernel(const char *kernel_string)
anthonyc84dce52010-05-07 05:42:23 +0000341{
342 char
343 token[MaxTextExtent];
344
anthony5ef8e942010-05-11 06:51:12 +0000345 long
346 type;
347
anthonyc84dce52010-05-07 05:42:23 +0000348 const char
anthony7a01dcf2010-05-11 12:25:52 +0000349 *p,
350 *end;
anthonyc84dce52010-05-07 05:42:23 +0000351
352 MagickStatusType
353 flags;
354
355 GeometryInfo
356 args;
357
anthonyc84dce52010-05-07 05:42:23 +0000358 /* Parse special 'named' kernel */
anthony5ef8e942010-05-11 06:51:12 +0000359 GetMagickToken(kernel_string,&p,token);
anthonyc84dce52010-05-07 05:42:23 +0000360 type=ParseMagickOption(MagickKernelOptions,MagickFalse,token);
361 if ( type < 0 || type == UserDefinedKernel )
anthony5ef8e942010-05-11 06:51:12 +0000362 return((KernelInfo *)NULL); /* not a valid named kernel */
anthonyc84dce52010-05-07 05:42:23 +0000363
364 while (((isspace((int) ((unsigned char) *p)) != 0) ||
anthony5ef8e942010-05-11 06:51:12 +0000365 (*p == ',') || (*p == ':' )) && (*p != '\0') && (*p != ';'))
anthonyc84dce52010-05-07 05:42:23 +0000366 p++;
anthony7a01dcf2010-05-11 12:25:52 +0000367
368 end = strchr(p, ';'); /* end of this kernel defintion */
369 if ( end == (char *) NULL )
370 end = strchr(p, '\0');
371
372 /* ParseGeometry() needs the geometry separated! -- Arrgghh */
373 memcpy(token, p, (size_t) (end-p));
374 token[end-p] = '\0';
anthonyc84dce52010-05-07 05:42:23 +0000375 SetGeometryInfo(&args);
anthony7a01dcf2010-05-11 12:25:52 +0000376 flags = ParseGeometry(token, &args);
anthonyc84dce52010-05-07 05:42:23 +0000377
anthony3c10fc82010-05-13 02:40:51 +0000378#if 0
379 /* For Debugging Geometry Input */
380 fprintf(stderr, "Geometry = %04x : %lf x %lf %+lf %+lf\n",
381 flags, args.rho, args.sigma, args.xi, args.psi );
382#endif
383
anthonyc84dce52010-05-07 05:42:23 +0000384 /* special handling of missing values in input string */
385 switch( type ) {
anthony5ef8e942010-05-11 06:51:12 +0000386 case RectangleKernel:
387 if ( (flags & WidthValue) == 0 ) /* if no width then */
388 args.rho = args.sigma; /* then width = height */
389 if ( args.rho < 1.0 ) /* if width too small */
390 args.rho = 3; /* then width = 3 */
391 if ( args.sigma < 1.0 ) /* if height too small */
392 args.sigma = args.rho; /* then height = width */
393 if ( (flags & XValue) == 0 ) /* center offset if not defined */
394 args.xi = (double)(((long)args.rho-1)/2);
395 if ( (flags & YValue) == 0 )
396 args.psi = (double)(((long)args.sigma-1)/2);
397 break;
398 case SquareKernel:
399 case DiamondKernel:
400 case DiskKernel:
401 case PlusKernel:
402 /* If no scale given (a 0 scale is valid! - set it to 1.0 */
403 if ( (flags & HeightValue) == 0 )
404 args.sigma = 1.0;
405 break;
406 case ChebyshevKernel:
407 case ManhattenKernel:
408 case EuclideanKernel:
409 if ( (flags & HeightValue) == 0 )
410 args.sigma = 100.0; /* default distance scaling */
411 else if ( (flags & AspectValue ) != 0 ) /* '!' flag */
412 args.sigma = QuantumRange/args.sigma; /* maximum pixel distance */
413 else if ( (flags & PercentValue ) != 0 ) /* '%' flag */
414 args.sigma *= QuantumRange/100.0; /* percentage of color range */
415 break;
416 default:
417 break;
anthonyc84dce52010-05-07 05:42:23 +0000418 }
419
420 return(AcquireKernelBuiltIn((KernelInfoType)type, &args));
421}
422
anthony5ef8e942010-05-11 06:51:12 +0000423MagickExport KernelInfo *AcquireKernelInfo(const char *kernel_string)
424{
anthony7a01dcf2010-05-11 12:25:52 +0000425
426 KernelInfo
anthonydbc89892010-05-12 07:05:27 +0000427 *kernel,
428 *new_kernel,
429 *last_kernel;
anthony7a01dcf2010-05-11 12:25:52 +0000430
anthony5ef8e942010-05-11 06:51:12 +0000431 char
432 token[MaxTextExtent];
433
anthony7a01dcf2010-05-11 12:25:52 +0000434 const char
anthonydbc89892010-05-12 07:05:27 +0000435 *p;
anthony7a01dcf2010-05-11 12:25:52 +0000436
anthonye108a3f2010-05-12 07:24:03 +0000437 unsigned long
438 kernel_number;
439
anthonydbc89892010-05-12 07:05:27 +0000440 p = kernel_string;
441 kernel = last_kernel = NULL;
anthonye108a3f2010-05-12 07:24:03 +0000442 kernel_number = 0;
anthony5ef8e942010-05-11 06:51:12 +0000443
anthonydbc89892010-05-12 07:05:27 +0000444 while ( GetMagickToken(p,NULL,token), *token != '\0' ) {
anthony7a01dcf2010-05-11 12:25:52 +0000445
anthonydbc89892010-05-12 07:05:27 +0000446 /* ignore multiple ';' following each other */
447 if ( *token != ';' ) {
anthony7a01dcf2010-05-11 12:25:52 +0000448
anthonydbc89892010-05-12 07:05:27 +0000449 /* tokens starting with alpha is a Named kernel */
450 if (isalpha((int) *token) == 0)
451 new_kernel = ParseKernelArray(p);
452 else /* otherwise a user defined kernel array */
453 new_kernel = ParseNamedKernel(p);
anthony7a01dcf2010-05-11 12:25:52 +0000454
anthonye108a3f2010-05-12 07:24:03 +0000455 /* Error handling -- this is not proper error handling! */
456 if ( new_kernel == (KernelInfo *) NULL ) {
457 fprintf(stderr, "Failed to parse kernel number #%lu\n", kernel_number);
458 if ( kernel != (KernelInfo *) NULL )
459 kernel=DestroyKernelInfo(kernel);
460 return((KernelInfo *) NULL);
anthonydbc89892010-05-12 07:05:27 +0000461 }
anthonye108a3f2010-05-12 07:24:03 +0000462
463 /* initialise or append the kernel list */
464 if ( last_kernel == (KernelInfo *) NULL )
465 kernel = last_kernel = new_kernel;
anthonydbc89892010-05-12 07:05:27 +0000466 else {
anthonydbc89892010-05-12 07:05:27 +0000467 last_kernel->next = new_kernel;
468 last_kernel = new_kernel;
469 }
470 }
471
472 /* look for the next kernel in list */
473 p = strchr(p, ';');
474 if ( p == (char *) NULL )
475 break;
476 p++;
477
478 }
anthony7a01dcf2010-05-11 12:25:52 +0000479 return(kernel);
anthony5ef8e942010-05-11 06:51:12 +0000480}
481
anthony602ab9b2010-01-05 08:06:50 +0000482
483/*
484%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
485% %
486% %
487% %
488% A c q u i r e K e r n e l B u i l t I n %
489% %
490% %
491% %
492%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
493%
494% AcquireKernelBuiltIn() returned one of the 'named' built-in types of
495% kernels used for special purposes such as gaussian blurring, skeleton
496% pruning, and edge distance determination.
497%
498% They take a KernelType, and a set of geometry style arguments, which were
499% typically decoded from a user supplied string, or from a more complex
500% Morphology Method that was requested.
501%
502% The format of the AcquireKernalBuiltIn method is:
503%
cristy2be15382010-01-21 02:38:03 +0000504% KernelInfo *AcquireKernelBuiltIn(const KernelInfoType type,
anthony602ab9b2010-01-05 08:06:50 +0000505% const GeometryInfo args)
506%
507% A description of each parameter follows:
508%
509% o type: the pre-defined type of kernel wanted
510%
511% o args: arguments defining or modifying the kernel
512%
513% Convolution Kernels
514%
anthony3c10fc82010-05-13 02:40:51 +0000515% Gaussian:{radius},{sigma}
516% Generate a two-dimentional gaussian kernel, as used by -gaussian.
517% A sigma is required.
anthony602ab9b2010-01-05 08:06:50 +0000518%
519% NOTE: that the 'radius' is optional, but if provided can limit (clip)
520% the final size of the resulting kernel to a square 2*radius+1 in size.
521% The radius should be at least 2 times that of the sigma value, or
522% sever clipping and aliasing may result. If not given or set to 0 the
523% radius will be determined so as to produce the best minimal error
524% result, which is usally much larger than is normally needed.
525%
anthony3c10fc82010-05-13 02:40:51 +0000526% Blur:{radius},{sigma},{angle}
anthony602ab9b2010-01-05 08:06:50 +0000527% As per Gaussian, but generates a 1 dimensional or linear gaussian
528% blur, at the angle given (current restricted to orthogonal angles).
529% If a 'radius' is given the kernel is clipped to a width of 2*radius+1.
anthony3c10fc82010-05-13 02:40:51 +0000530% Angle can be rotated in multiples of 90 degrees.
anthony602ab9b2010-01-05 08:06:50 +0000531%
anthony3c10fc82010-05-13 02:40:51 +0000532% Note that two such blurs perpendicular to each other is equivelent to
533% the far large "Gaussian" kernel, but much faster to apply. This is how
534% the -blur operator works.
anthony602ab9b2010-01-05 08:06:50 +0000535%
anthony3c10fc82010-05-13 02:40:51 +0000536% Comet:{width},{sigma},{angle}
537% Blur in one direction only, much like how a bright object leaves
anthony602ab9b2010-01-05 08:06:50 +0000538% a comet like trail. The Kernel is actually half a gaussian curve,
anthony3c10fc82010-05-13 02:40:51 +0000539% Adding two such blurs in opposite directions produces a Blur Kernel.
540% Angle can be rotated in multiples of 90 degrees.
anthony602ab9b2010-01-05 08:06:50 +0000541%
anthony3c10fc82010-05-13 02:40:51 +0000542% Note that the first argument is the width of the kernel and not the
anthony602ab9b2010-01-05 08:06:50 +0000543% radius of the kernel.
544%
545% # Still to be implemented...
546% #
anthony3c10fc82010-05-13 02:40:51 +0000547% # Sharpen:{radius},{sigma}
anthony4fd27e22010-02-07 08:17:18 +0000548% # Negated Gaussian (center zeroed and re-normalized),
549% # with a 2 unit positive peak. -- Check On line documentation
550% #
anthony3c10fc82010-05-13 02:40:51 +0000551% # LOG:{radius},{sigma1},{sigma2}
anthony602ab9b2010-01-05 08:06:50 +0000552% # Laplacian of Gaussian
553% #
anthony3c10fc82010-05-13 02:40:51 +0000554% # DOG:{radius},{sigma1},{sigma2}
anthony4fd27e22010-02-07 08:17:18 +0000555% # Difference of two Gaussians
556% #
557% # Filter2D
558% # Filter1D
559% # Set kernel values using a resize filter, and given scale (sigma)
560% # Cylindrical or Linear. Is this posible with an image?
561% #
anthony602ab9b2010-01-05 08:06:50 +0000562%
anthony3c10fc82010-05-13 02:40:51 +0000563% Named Constant Convolution Kernels
564%
565% Sobel:[angle]
566% The 3x3 sobel convolution kernel. Angle may be given in multiples
567% of 45 degrees. Kernel is unscaled by default so some normalization
568% may be required to ensure results are not clipped.
569% Default kernel is -1,0,1
570% -2,0,2
571% -1,0,1
572%
573% Laplacian:{type}
574% Generate Lapacian kernel of the type specified. (1 is the default)
575% Type 0 default square laplacian 3x3: all -1's with central 8
576% Type 1 3x3: central 4 edge -1 corner 0
577% Type 2 3x3: central 4 edge 1 corner -2
578% Type 3 a 5x5 laplacian
579% Type 5 a 7x7 laplacian
580%
anthony602ab9b2010-01-05 08:06:50 +0000581% Boolean Kernels
582%
anthony3c10fc82010-05-13 02:40:51 +0000583% Rectangle:{geometry}
anthony602ab9b2010-01-05 08:06:50 +0000584% Simply generate a rectangle of 1's with the size given. You can also
585% specify the location of the 'control point', otherwise the closest
586% pixel to the center of the rectangle is selected.
587%
588% Properly centered and odd sized rectangles work the best.
589%
anthony3c10fc82010-05-13 02:40:51 +0000590% Diamond:[{radius}[,{scale}]]
anthony1b2bc0a2010-05-12 05:25:22 +0000591% Generate a diamond shaped kernel with given radius to the points.
anthony602ab9b2010-01-05 08:06:50 +0000592% Kernel size will again be radius*2+1 square and defaults to radius 1,
593% generating a 3x3 kernel that is slightly larger than a square.
594%
anthony3c10fc82010-05-13 02:40:51 +0000595% Square:[{radius}[,{scale}]]
anthony602ab9b2010-01-05 08:06:50 +0000596% Generate a square shaped kernel of size radius*2+1, and defaulting
597% to a 3x3 (radius 1).
598%
599% Note that using a larger radius for the "Square" or the "Diamond"
600% is also equivelent to iterating the basic morphological method
601% that many times. However However iterating with the smaller radius 1
602% default is actually faster than using a larger kernel radius.
603%
anthony3c10fc82010-05-13 02:40:51 +0000604% Disk:[{radius}[,{scale}]]
anthony602ab9b2010-01-05 08:06:50 +0000605% Generate a binary disk of the radius given, radius may be a float.
606% Kernel size will be ceil(radius)*2+1 square.
607% NOTE: Here are some disk shapes of specific interest
608% "disk:1" => "diamond" or "cross:1"
609% "disk:1.5" => "square"
610% "disk:2" => "diamond:2"
anthony83ba99b2010-01-24 08:48:15 +0000611% "disk:2.5" => a general disk shape of radius 2
anthony602ab9b2010-01-05 08:06:50 +0000612% "disk:2.9" => "square:2"
anthony83ba99b2010-01-24 08:48:15 +0000613% "disk:3.5" => default - octagonal/disk shape of radius 3
anthony602ab9b2010-01-05 08:06:50 +0000614% "disk:4.2" => roughly octagonal shape of radius 4
anthony83ba99b2010-01-24 08:48:15 +0000615% "disk:4.3" => a general disk shape of radius 4
anthony602ab9b2010-01-05 08:06:50 +0000616% After this all the kernel shape becomes more and more circular.
617%
618% Because a "disk" is more circular when using a larger radius, using a
619% larger radius is preferred over iterating the morphological operation.
620%
anthony3c10fc82010-05-13 02:40:51 +0000621% Plus:[{radius}[,{scale}]]
anthony602ab9b2010-01-05 08:06:50 +0000622% Generate a kernel in the shape of a 'plus' sign. The length of each
623% arm is also the radius, which defaults to 2.
624%
625% This kernel is not a good general morphological kernel, but is used
626% more for highlighting and marking any single pixels in an image using,
627% a "Dilate" or "Erode" method as appropriate.
anthonyc94cdb02010-01-06 08:15:29 +0000628%
anthony602ab9b2010-01-05 08:06:50 +0000629% NOTE: "plus:1" is equivelent to a "Diamond" kernel.
630%
631% Note that unlike other kernels iterating a plus does not produce the
632% same result as using a larger radius for the cross.
633%
634% Distance Measuring Kernels
635%
anthonyc84dce52010-05-07 05:42:23 +0000636% Chebyshev "[{radius}][x{scale}[%!]]"
637% Manhatten "[{radius}][x{scale}[%!]]"
638% Euclidean "[{radius}][x{scale}[%!]]"
anthony602ab9b2010-01-05 08:06:50 +0000639%
640% Different types of distance measuring methods, which are used with the
641% a 'Distance' morphology method for generating a gradient based on
642% distance from an edge of a binary shape, though there is a technique
643% for handling a anti-aliased shape.
644%
anthonyc94cdb02010-01-06 08:15:29 +0000645% Chebyshev Distance (also known as Tchebychev Distance) is a value of
646% one to any neighbour, orthogonal or diagonal. One why of thinking of
647% it is the number of squares a 'King' or 'Queen' in chess needs to
648% traverse reach any other position on a chess board. It results in a
649% 'square' like distance function, but one where diagonals are closer
650% than expected.
anthony602ab9b2010-01-05 08:06:50 +0000651%
anthonyc94cdb02010-01-06 08:15:29 +0000652% Manhatten Distance (also known as Rectilinear Distance, or the Taxi
653% Cab metric), is the distance needed when you can only travel in
654% orthogonal (horizontal or vertical) only. It is the distance a 'Rook'
655% in chess would travel. It results in a diamond like distances, where
656% diagonals are further than expected.
anthony602ab9b2010-01-05 08:06:50 +0000657%
anthonyc94cdb02010-01-06 08:15:29 +0000658% Euclidean Distance is the 'direct' or 'as the crow flys distance.
659% However by default the kernel size only has a radius of 1, which
660% limits the distance to 'Knight' like moves, with only orthogonal and
661% diagonal measurements being correct. As such for the default kernel
662% you will get octagonal like distance function, which is reasonally
663% accurate.
664%
665% However if you use a larger radius such as "Euclidean:4" you will
666% get a much smoother distance gradient from the edge of the shape.
667% Of course a larger kernel is slower to use, and generally not needed.
668%
669% To allow the use of fractional distances that you get with diagonals
670% the actual distance is scaled by a fixed value which the user can
671% provide. This is not actually nessary for either ""Chebyshev" or
672% "Manhatten" distance kernels, but is done for all three distance
673% kernels. If no scale is provided it is set to a value of 100,
674% allowing for a maximum distance measurement of 655 pixels using a Q16
675% version of IM, from any edge. However for small images this can
676% result in quite a dark gradient.
677%
678% See the 'Distance' Morphological Method, for information of how it is
679% applied.
anthony602ab9b2010-01-05 08:06:50 +0000680%
anthony4fd27e22010-02-07 08:17:18 +0000681% # Hit-n-Miss Kernel-Lists -- Still to be implemented
682% #
683% # specifically for Pruning, Thinning, Thickening
684% #
anthony602ab9b2010-01-05 08:06:50 +0000685*/
686
cristy2be15382010-01-21 02:38:03 +0000687MagickExport KernelInfo *AcquireKernelBuiltIn(const KernelInfoType type,
anthony602ab9b2010-01-05 08:06:50 +0000688 const GeometryInfo *args)
689{
cristy2be15382010-01-21 02:38:03 +0000690 KernelInfo
anthony602ab9b2010-01-05 08:06:50 +0000691 *kernel;
692
cristy150989e2010-02-01 14:59:39 +0000693 register long
anthony602ab9b2010-01-05 08:06:50 +0000694 i;
695
696 register long
697 u,
698 v;
699
700 double
701 nan = sqrt((double)-1.0); /* Special Value : Not A Number */
702
cristy2be15382010-01-21 02:38:03 +0000703 kernel=(KernelInfo *) AcquireMagickMemory(sizeof(*kernel));
704 if (kernel == (KernelInfo *) NULL)
anthony602ab9b2010-01-05 08:06:50 +0000705 return(kernel);
706 (void) ResetMagickMemory(kernel,0,sizeof(*kernel));
cristyc99304f2010-02-01 15:26:27 +0000707 kernel->minimum = kernel->maximum = 0.0;
708 kernel->negative_range = kernel->positive_range = 0.0;
anthony602ab9b2010-01-05 08:06:50 +0000709 kernel->type = type;
anthony7a01dcf2010-05-11 12:25:52 +0000710 kernel->next = (KernelInfo *) NULL;
cristyd43a46b2010-01-21 02:13:41 +0000711 kernel->signature = MagickSignature;
anthony602ab9b2010-01-05 08:06:50 +0000712
713 switch(type) {
714 /* Convolution Kernels */
715 case GaussianKernel:
716 { double
717 sigma = fabs(args->sigma);
718
719 sigma = (sigma <= MagickEpsilon) ? 1.0 : sigma;
720
721 kernel->width = kernel->height =
722 GetOptimalKernelWidth2D(args->rho,sigma);
cristyc99304f2010-02-01 15:26:27 +0000723 kernel->x = kernel->y = (long) (kernel->width-1)/2;
724 kernel->negative_range = kernel->positive_range = 0.0;
anthony602ab9b2010-01-05 08:06:50 +0000725 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
726 kernel->height*sizeof(double));
727 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +0000728 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000729
730 sigma = 2.0*sigma*sigma; /* simplify the expression */
cristyc99304f2010-02-01 15:26:27 +0000731 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
732 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
733 kernel->positive_range += (
anthony602ab9b2010-01-05 08:06:50 +0000734 kernel->values[i] =
735 exp(-((double)(u*u+v*v))/sigma)
736 /* / (MagickPI*sigma) */ );
cristyc99304f2010-02-01 15:26:27 +0000737 kernel->minimum = 0;
738 kernel->maximum = kernel->values[
739 kernel->y*kernel->width+kernel->x ];
anthony602ab9b2010-01-05 08:06:50 +0000740
anthony999bb2c2010-02-18 12:38:01 +0000741 ScaleKernelInfo(kernel, 1.0, NormalizeValue); /* Normalize */
anthony602ab9b2010-01-05 08:06:50 +0000742
743 break;
744 }
745 case BlurKernel:
746 { double
747 sigma = fabs(args->sigma);
748
749 sigma = (sigma <= MagickEpsilon) ? 1.0 : sigma;
750
751 kernel->width = GetOptimalKernelWidth1D(args->rho,sigma);
cristyc99304f2010-02-01 15:26:27 +0000752 kernel->x = (long) (kernel->width-1)/2;
anthony602ab9b2010-01-05 08:06:50 +0000753 kernel->height = 1;
cristyc99304f2010-02-01 15:26:27 +0000754 kernel->y = 0;
755 kernel->negative_range = kernel->positive_range = 0.0;
anthony602ab9b2010-01-05 08:06:50 +0000756 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
757 kernel->height*sizeof(double));
758 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +0000759 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000760
761#if 1
762#define KernelRank 3
763 /* Formula derived from GetBlurKernel() in "effect.c" (plus bug fix).
764 ** It generates a gaussian 3 times the width, and compresses it into
765 ** the expected range. This produces a closer normalization of the
766 ** resulting kernel, especially for very low sigma values.
767 ** As such while wierd it is prefered.
768 **
769 ** I am told this method originally came from Photoshop.
770 */
771 sigma *= KernelRank; /* simplify expanded curve */
cristy150989e2010-02-01 14:59:39 +0000772 v = (long) (kernel->width*KernelRank-1)/2; /* start/end points to fit range */
anthony602ab9b2010-01-05 08:06:50 +0000773 (void) ResetMagickMemory(kernel->values,0, (size_t)
774 kernel->width*sizeof(double));
775 for ( u=-v; u <= v; u++) {
776 kernel->values[(u+v)/KernelRank] +=
777 exp(-((double)(u*u))/(2.0*sigma*sigma))
778 /* / (MagickSQ2PI*sigma/KernelRank) */ ;
779 }
cristy150989e2010-02-01 14:59:39 +0000780 for (i=0; i < (long) kernel->width; i++)
cristyc99304f2010-02-01 15:26:27 +0000781 kernel->positive_range += kernel->values[i];
anthony602ab9b2010-01-05 08:06:50 +0000782#else
cristyc99304f2010-02-01 15:26:27 +0000783 for ( i=0, u=-kernel->x; i < kernel->width; i++, u++)
784 kernel->positive_range += (
anthony602ab9b2010-01-05 08:06:50 +0000785 kernel->values[i] =
786 exp(-((double)(u*u))/(2.0*sigma*sigma))
787 /* / (MagickSQ2PI*sigma) */ );
788#endif
cristyc99304f2010-02-01 15:26:27 +0000789 kernel->minimum = 0;
790 kernel->maximum = kernel->values[ kernel->x ];
anthonycc6c8362010-01-25 04:14:01 +0000791 /* Note that neither methods above generate a normalized kernel,
792 ** though it gets close. The kernel may be 'clipped' by a user defined
793 ** radius, producing a smaller (darker) kernel. Also for very small
794 ** sigma's (> 0.1) the central value becomes larger than one, and thus
795 ** producing a very bright kernel.
anthony602ab9b2010-01-05 08:06:50 +0000796 */
anthonycc6c8362010-01-25 04:14:01 +0000797
anthony602ab9b2010-01-05 08:06:50 +0000798 /* Normalize the 1D Gaussian Kernel
799 **
800 ** Because of this the divisor in the above kernel generator is
anthonyc94cdb02010-01-06 08:15:29 +0000801 ** not needed, so is not done above.
anthony602ab9b2010-01-05 08:06:50 +0000802 */
anthony999bb2c2010-02-18 12:38:01 +0000803 ScaleKernelInfo(kernel, 1.0, NormalizeValue); /* Normalize */
anthonycc6c8362010-01-25 04:14:01 +0000804
anthony602ab9b2010-01-05 08:06:50 +0000805 /* rotate the kernel by given angle */
anthony4fd27e22010-02-07 08:17:18 +0000806 RotateKernelInfo(kernel, args->xi);
anthony602ab9b2010-01-05 08:06:50 +0000807 break;
808 }
809 case CometKernel:
810 { double
811 sigma = fabs(args->sigma);
812
813 sigma = (sigma <= MagickEpsilon) ? 1.0 : sigma;
814
815 if ( args->rho < 1.0 )
816 kernel->width = GetOptimalKernelWidth1D(args->rho,sigma);
817 else
818 kernel->width = (unsigned long)args->rho;
cristyc99304f2010-02-01 15:26:27 +0000819 kernel->x = kernel->y = 0;
anthony602ab9b2010-01-05 08:06:50 +0000820 kernel->height = 1;
cristyc99304f2010-02-01 15:26:27 +0000821 kernel->negative_range = kernel->positive_range = 0.0;
anthony602ab9b2010-01-05 08:06:50 +0000822 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
823 kernel->height*sizeof(double));
824 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +0000825 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000826
827 /* A comet blur is half a gaussian curve, so that the object is
828 ** blurred in one direction only. This may not be quite the right
829 ** curve so may change in the future. The function must be normalised.
830 */
831#if 1
832#define KernelRank 3
833 sigma *= KernelRank; /* simplify expanded curve */
cristy150989e2010-02-01 14:59:39 +0000834 v = (long) kernel->width*KernelRank; /* start/end points to fit range */
anthony602ab9b2010-01-05 08:06:50 +0000835 (void) ResetMagickMemory(kernel->values,0, (size_t)
836 kernel->width*sizeof(double));
837 for ( u=0; u < v; u++) {
838 kernel->values[u/KernelRank] +=
839 exp(-((double)(u*u))/(2.0*sigma*sigma))
840 /* / (MagickSQ2PI*sigma/KernelRank) */ ;
841 }
cristy150989e2010-02-01 14:59:39 +0000842 for (i=0; i < (long) kernel->width; i++)
cristyc99304f2010-02-01 15:26:27 +0000843 kernel->positive_range += kernel->values[i];
anthony602ab9b2010-01-05 08:06:50 +0000844#else
cristy150989e2010-02-01 14:59:39 +0000845 for ( i=0; i < (long) kernel->width; i++)
cristyc99304f2010-02-01 15:26:27 +0000846 kernel->positive_range += (
anthony602ab9b2010-01-05 08:06:50 +0000847 kernel->values[i] =
848 exp(-((double)(i*i))/(2.0*sigma*sigma))
849 /* / (MagickSQ2PI*sigma) */ );
850#endif
cristyc99304f2010-02-01 15:26:27 +0000851 kernel->minimum = 0;
852 kernel->maximum = kernel->values[0];
anthony602ab9b2010-01-05 08:06:50 +0000853
anthony999bb2c2010-02-18 12:38:01 +0000854 ScaleKernelInfo(kernel, 1.0, NormalizeValue); /* Normalize */
855 RotateKernelInfo(kernel, args->xi); /* Rotate by angle */
anthony602ab9b2010-01-05 08:06:50 +0000856 break;
857 }
anthony3c10fc82010-05-13 02:40:51 +0000858 /* Convolution Kernels - Well Known Constants */
859 case SobelKernel:
860 { kernel=DestroyKernelInfo(kernel); /* default kernel is not needed */
861 kernel=ParseKernelArray("3x3:-1,0,1 -2,0,2 -1,0,1");
862 if (kernel == (KernelInfo *) NULL)
863 return(kernel);
864 kernel->type = type;
865 RotateKernelInfo(kernel, args->rho); /* Rotate by angle */
866 break;
867 }
868 case LaplacianKernel:
869 { kernel=DestroyKernelInfo(kernel); /* default kernel is not needed */
870 switch ( (int) args->rho ) {
871 case 1:
872 kernel=ParseKernelArray("3x3: 0,-1,0 -1,4,-1 0,-1,0");
873 break;
874 case 2:
875 kernel=ParseKernelArray("3x3: -2,1,-2 1,4,1 -2,1,-2");
876 break;
877 case 3:
878 default: /* default laplacian 'edge' filter */
879 kernel=ParseKernelArray("3x3: -1,-1,-1 -1,8,-1 -1,-1,-1");
880 break;
881 case 4: /* a 5x5 laplacian */
882 kernel=ParseKernelArray(
883 "5x5: -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");
884 break;
885 case 5: /* a 7x7 laplacian */
886 kernel=ParseKernelArray(
887 "7x7:-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" );
888 break;
889 }
890 if (kernel == (KernelInfo *) NULL)
891 return(kernel);
892 kernel->type = type;
893 break;
894 }
anthony602ab9b2010-01-05 08:06:50 +0000895 /* Boolean Kernels */
896 case RectangleKernel:
897 case SquareKernel:
898 {
anthony4fd27e22010-02-07 08:17:18 +0000899 double scale;
anthony602ab9b2010-01-05 08:06:50 +0000900 if ( type == SquareKernel )
901 {
902 if (args->rho < 1.0)
anthonyc94cdb02010-01-06 08:15:29 +0000903 kernel->width = kernel->height = 3; /* default radius = 1 */
anthony602ab9b2010-01-05 08:06:50 +0000904 else
cristy150989e2010-02-01 14:59:39 +0000905 kernel->width = kernel->height = (unsigned long) (2*args->rho+1);
cristyc99304f2010-02-01 15:26:27 +0000906 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony4fd27e22010-02-07 08:17:18 +0000907 scale = args->sigma;
anthony602ab9b2010-01-05 08:06:50 +0000908 }
909 else {
cristy2be15382010-01-21 02:38:03 +0000910 /* NOTE: user defaults set in "AcquireKernelInfo()" */
anthony602ab9b2010-01-05 08:06:50 +0000911 if ( args->rho < 1.0 || args->sigma < 1.0 )
anthony83ba99b2010-01-24 08:48:15 +0000912 return(DestroyKernelInfo(kernel)); /* invalid args given */
anthony602ab9b2010-01-05 08:06:50 +0000913 kernel->width = (unsigned long)args->rho;
914 kernel->height = (unsigned long)args->sigma;
915 if ( args->xi < 0.0 || args->xi > (double)kernel->width ||
916 args->psi < 0.0 || args->psi > (double)kernel->height )
anthony83ba99b2010-01-24 08:48:15 +0000917 return(DestroyKernelInfo(kernel)); /* invalid args given */
cristyc99304f2010-02-01 15:26:27 +0000918 kernel->x = (long) args->xi;
919 kernel->y = (long) args->psi;
anthony4fd27e22010-02-07 08:17:18 +0000920 scale = 1.0;
anthony602ab9b2010-01-05 08:06:50 +0000921 }
922 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
923 kernel->height*sizeof(double));
924 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +0000925 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000926
anthonycc6c8362010-01-25 04:14:01 +0000927 /* set all kernel values to 1.0 */
cristy150989e2010-02-01 14:59:39 +0000928 u=(long) kernel->width*kernel->height;
929 for ( i=0; i < u; i++)
anthony4fd27e22010-02-07 08:17:18 +0000930 kernel->values[i] = scale;
931 kernel->minimum = kernel->maximum = scale; /* a flat shape */
932 kernel->positive_range = scale*u;
anthonycc6c8362010-01-25 04:14:01 +0000933 break;
anthony602ab9b2010-01-05 08:06:50 +0000934 }
935 case DiamondKernel:
936 {
937 if (args->rho < 1.0)
anthonyc94cdb02010-01-06 08:15:29 +0000938 kernel->width = kernel->height = 3; /* default radius = 1 */
anthony602ab9b2010-01-05 08:06:50 +0000939 else
940 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
cristyc99304f2010-02-01 15:26:27 +0000941 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony602ab9b2010-01-05 08:06:50 +0000942
943 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
944 kernel->height*sizeof(double));
945 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +0000946 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000947
anthony4fd27e22010-02-07 08:17:18 +0000948 /* set all kernel values within diamond area to scale given */
cristyc99304f2010-02-01 15:26:27 +0000949 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
950 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
951 if ((labs(u)+labs(v)) <= (long)kernel->x)
anthony4fd27e22010-02-07 08:17:18 +0000952 kernel->positive_range += kernel->values[i] = args->sigma;
anthony602ab9b2010-01-05 08:06:50 +0000953 else
954 kernel->values[i] = nan;
anthony4fd27e22010-02-07 08:17:18 +0000955 kernel->minimum = kernel->maximum = args->sigma; /* a flat shape */
anthony602ab9b2010-01-05 08:06:50 +0000956 break;
957 }
958 case DiskKernel:
959 {
960 long
961 limit;
962
963 limit = (long)(args->rho*args->rho);
anthony83ba99b2010-01-24 08:48:15 +0000964 if (args->rho < 0.1) /* default radius approx 3.5 */
965 kernel->width = kernel->height = 7L, limit = 10L;
anthony602ab9b2010-01-05 08:06:50 +0000966 else
967 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
cristyc99304f2010-02-01 15:26:27 +0000968 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony602ab9b2010-01-05 08:06:50 +0000969
970 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
971 kernel->height*sizeof(double));
972 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +0000973 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000974
anthonycc6c8362010-01-25 04:14:01 +0000975 /* set all kernel values within disk area to 1.0 */
cristyc99304f2010-02-01 15:26:27 +0000976 for ( i=0, v= -kernel->y; v <= (long)kernel->y; v++)
977 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
anthony602ab9b2010-01-05 08:06:50 +0000978 if ((u*u+v*v) <= limit)
anthony4fd27e22010-02-07 08:17:18 +0000979 kernel->positive_range += kernel->values[i] = args->sigma;
anthony602ab9b2010-01-05 08:06:50 +0000980 else
981 kernel->values[i] = nan;
anthony4fd27e22010-02-07 08:17:18 +0000982 kernel->minimum = kernel->maximum = args->sigma; /* a flat shape */
anthony602ab9b2010-01-05 08:06:50 +0000983 break;
984 }
985 case PlusKernel:
986 {
987 if (args->rho < 1.0)
anthonyc94cdb02010-01-06 08:15:29 +0000988 kernel->width = kernel->height = 5; /* default radius 2 */
anthony602ab9b2010-01-05 08:06:50 +0000989 else
990 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
cristyc99304f2010-02-01 15:26:27 +0000991 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony602ab9b2010-01-05 08:06:50 +0000992
993 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
994 kernel->height*sizeof(double));
995 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +0000996 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000997
anthonycc6c8362010-01-25 04:14:01 +0000998 /* set all kernel values along axises to 1.0 */
cristyc99304f2010-02-01 15:26:27 +0000999 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
1000 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
anthony4fd27e22010-02-07 08:17:18 +00001001 kernel->values[i] = (u == 0 || v == 0) ? args->sigma : nan;
1002 kernel->minimum = kernel->maximum = args->sigma; /* a flat shape */
1003 kernel->positive_range = args->sigma*(kernel->width*2.0 - 1.0);
anthony602ab9b2010-01-05 08:06:50 +00001004 break;
1005 }
1006 /* Distance Measuring Kernels */
1007 case ChebyshevKernel:
1008 {
anthony602ab9b2010-01-05 08:06:50 +00001009 if (args->rho < 1.0)
anthonyc94cdb02010-01-06 08:15:29 +00001010 kernel->width = kernel->height = 3; /* default radius = 1 */
anthony602ab9b2010-01-05 08:06:50 +00001011 else
1012 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
cristyc99304f2010-02-01 15:26:27 +00001013 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony602ab9b2010-01-05 08:06:50 +00001014
1015 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
1016 kernel->height*sizeof(double));
1017 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +00001018 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +00001019
cristyc99304f2010-02-01 15:26:27 +00001020 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
1021 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
1022 kernel->positive_range += ( kernel->values[i] =
anthonyc84dce52010-05-07 05:42:23 +00001023 args->sigma*((labs(u)>labs(v)) ? labs(u) : labs(v)) );
cristyc99304f2010-02-01 15:26:27 +00001024 kernel->maximum = kernel->values[0];
anthony602ab9b2010-01-05 08:06:50 +00001025 break;
1026 }
1027 case ManhattenKernel:
1028 {
anthony602ab9b2010-01-05 08:06:50 +00001029 if (args->rho < 1.0)
anthonyc94cdb02010-01-06 08:15:29 +00001030 kernel->width = kernel->height = 3; /* default radius = 1 */
anthony602ab9b2010-01-05 08:06:50 +00001031 else
1032 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
cristyc99304f2010-02-01 15:26:27 +00001033 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony602ab9b2010-01-05 08:06:50 +00001034
1035 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
1036 kernel->height*sizeof(double));
1037 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +00001038 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +00001039
cristyc99304f2010-02-01 15:26:27 +00001040 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
1041 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
1042 kernel->positive_range += ( kernel->values[i] =
anthonyc84dce52010-05-07 05:42:23 +00001043 args->sigma*(labs(u)+labs(v)) );
cristyc99304f2010-02-01 15:26:27 +00001044 kernel->maximum = kernel->values[0];
anthony602ab9b2010-01-05 08:06:50 +00001045 break;
1046 }
1047 case EuclideanKernel:
1048 {
anthony602ab9b2010-01-05 08:06:50 +00001049 if (args->rho < 1.0)
anthonyc94cdb02010-01-06 08:15:29 +00001050 kernel->width = kernel->height = 3; /* default radius = 1 */
anthony602ab9b2010-01-05 08:06:50 +00001051 else
1052 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
cristyc99304f2010-02-01 15:26:27 +00001053 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony602ab9b2010-01-05 08:06:50 +00001054
1055 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
1056 kernel->height*sizeof(double));
1057 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +00001058 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +00001059
cristyc99304f2010-02-01 15:26:27 +00001060 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
1061 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
1062 kernel->positive_range += ( kernel->values[i] =
anthonyc84dce52010-05-07 05:42:23 +00001063 args->sigma*sqrt((double)(u*u+v*v)) );
cristyc99304f2010-02-01 15:26:27 +00001064 kernel->maximum = kernel->values[0];
anthony602ab9b2010-01-05 08:06:50 +00001065 break;
1066 }
1067 /* Undefined Kernels */
anthony602ab9b2010-01-05 08:06:50 +00001068 case LOGKernel:
1069 case DOGKernel:
anthony3c10fc82010-05-13 02:40:51 +00001070 perror("Kernel Type has not been defined yet\n");
anthony602ab9b2010-01-05 08:06:50 +00001071 /* FALL THRU */
1072 default:
1073 /* Generate a No-Op minimal kernel - 1x1 pixel */
1074 kernel->values=(double *)AcquireQuantumMemory((size_t)1,sizeof(double));
1075 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +00001076 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +00001077 kernel->width = kernel->height = 1;
cristyc99304f2010-02-01 15:26:27 +00001078 kernel->x = kernel->x = 0;
anthony602ab9b2010-01-05 08:06:50 +00001079 kernel->type = UndefinedKernel;
cristyc99304f2010-02-01 15:26:27 +00001080 kernel->maximum =
1081 kernel->positive_range =
anthonyc94cdb02010-01-06 08:15:29 +00001082 kernel->values[0] = 1.0; /* a flat single-point no-op kernel! */
anthony602ab9b2010-01-05 08:06:50 +00001083 break;
1084 }
1085
1086 return(kernel);
1087}
anthonyc94cdb02010-01-06 08:15:29 +00001088
anthony602ab9b2010-01-05 08:06:50 +00001089/*
1090%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1091% %
1092% %
1093% %
cristy6771f1e2010-03-05 19:43:39 +00001094% C l o n e K e r n e l I n f o %
anthony4fd27e22010-02-07 08:17:18 +00001095% %
1096% %
1097% %
1098%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1099%
anthony1b2bc0a2010-05-12 05:25:22 +00001100% CloneKernelInfo() creates a new clone of the given Kernel List so that its
1101% can be modified without effecting the original. The cloned kernel should
1102% be destroyed using DestoryKernelInfo() when no longer needed.
anthony7a01dcf2010-05-11 12:25:52 +00001103%
cristye6365592010-04-02 17:31:23 +00001104% The format of the CloneKernelInfo method is:
anthony4fd27e22010-02-07 08:17:18 +00001105%
anthony930be612010-02-08 04:26:15 +00001106% KernelInfo *CloneKernelInfo(const KernelInfo *kernel)
anthony4fd27e22010-02-07 08:17:18 +00001107%
1108% A description of each parameter follows:
1109%
1110% o kernel: the Morphology/Convolution kernel to be cloned
1111%
1112*/
cristyef656912010-03-05 19:54:59 +00001113MagickExport KernelInfo *CloneKernelInfo(const KernelInfo *kernel)
anthony4fd27e22010-02-07 08:17:18 +00001114{
1115 register long
1116 i;
1117
cristy19eb6412010-04-23 14:42:29 +00001118 KernelInfo
anthony7a01dcf2010-05-11 12:25:52 +00001119 *new_kernel;
anthony4fd27e22010-02-07 08:17:18 +00001120
1121 assert(kernel != (KernelInfo *) NULL);
anthony7a01dcf2010-05-11 12:25:52 +00001122 new_kernel=(KernelInfo *) AcquireMagickMemory(sizeof(*kernel));
1123 if (new_kernel == (KernelInfo *) NULL)
1124 return(new_kernel);
1125 *new_kernel=(*kernel); /* copy values in structure */
anthony7a01dcf2010-05-11 12:25:52 +00001126
1127 /* replace the values with a copy of the values */
1128 new_kernel->values=(double *) AcquireQuantumMemory(kernel->width,
cristy19eb6412010-04-23 14:42:29 +00001129 kernel->height*sizeof(double));
anthony7a01dcf2010-05-11 12:25:52 +00001130 if (new_kernel->values == (double *) NULL)
1131 return(DestroyKernelInfo(new_kernel));
anthony4fd27e22010-02-07 08:17:18 +00001132 for (i=0; i < (long) (kernel->width*kernel->height); i++)
anthony7a01dcf2010-05-11 12:25:52 +00001133 new_kernel->values[i]=kernel->values[i];
anthony1b2bc0a2010-05-12 05:25:22 +00001134
1135 /* Also clone the next kernel in the kernel list */
1136 if ( kernel->next != (KernelInfo *) NULL ) {
1137 new_kernel->next = CloneKernelInfo(kernel->next);
1138 if ( new_kernel->next == (KernelInfo *) NULL )
1139 return(DestroyKernelInfo(new_kernel));
1140 }
1141
anthony7a01dcf2010-05-11 12:25:52 +00001142 return(new_kernel);
anthony4fd27e22010-02-07 08:17:18 +00001143}
1144
1145/*
1146%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1147% %
1148% %
1149% %
anthony83ba99b2010-01-24 08:48:15 +00001150% D e s t r o y K e r n e l I n f o %
anthony602ab9b2010-01-05 08:06:50 +00001151% %
1152% %
1153% %
1154%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1155%
anthony83ba99b2010-01-24 08:48:15 +00001156% DestroyKernelInfo() frees the memory used by a Convolution/Morphology
1157% kernel.
anthony602ab9b2010-01-05 08:06:50 +00001158%
anthony83ba99b2010-01-24 08:48:15 +00001159% The format of the DestroyKernelInfo method is:
anthony602ab9b2010-01-05 08:06:50 +00001160%
anthony83ba99b2010-01-24 08:48:15 +00001161% KernelInfo *DestroyKernelInfo(KernelInfo *kernel)
anthony602ab9b2010-01-05 08:06:50 +00001162%
1163% A description of each parameter follows:
1164%
1165% o kernel: the Morphology/Convolution kernel to be destroyed
1166%
1167*/
1168
anthony83ba99b2010-01-24 08:48:15 +00001169MagickExport KernelInfo *DestroyKernelInfo(KernelInfo *kernel)
anthony602ab9b2010-01-05 08:06:50 +00001170{
cristy2be15382010-01-21 02:38:03 +00001171 assert(kernel != (KernelInfo *) NULL);
anthony4fd27e22010-02-07 08:17:18 +00001172
anthony7a01dcf2010-05-11 12:25:52 +00001173 if ( kernel->next != (KernelInfo *) NULL )
1174 kernel->next = DestroyKernelInfo(kernel->next);
1175
1176 kernel->values = (double *)RelinquishMagickMemory(kernel->values);
1177 kernel = (KernelInfo *) RelinquishMagickMemory(kernel);
anthony602ab9b2010-01-05 08:06:50 +00001178 return(kernel);
1179}
anthonyc94cdb02010-01-06 08:15:29 +00001180
1181/*
1182%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1183% %
1184% %
1185% %
anthony3c10fc82010-05-13 02:40:51 +00001186% E x p a n d K e r n e l I n f o %
1187% %
1188% %
1189% %
1190%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1191%
1192% ExpandKernelInfo() takes a single kernel, and expands it into a list
1193% of kernels each incrementally rotated the angle given.
1194%
1195% WARNING: 45 degree rotations only works for 3x3 kernels.
1196% While 90 degree roatations only works for linear and square kernels
1197%
1198% The format of the RotateKernelInfo method is:
1199%
1200% void ExpandKernelInfo(KernelInfo *kernel, double angle)
1201%
1202% A description of each parameter follows:
1203%
1204% o kernel: the Morphology/Convolution kernel
1205%
1206% o angle: angle to rotate in degrees
1207%
1208% This function is only internel to this module, as it is not finalized,
1209% especially with regard to non-orthogonal angles, and rotation of larger
1210% 2D kernels.
1211*/
1212static void ExpandKernelInfo(KernelInfo *kernel, double angle)
1213{
1214 KernelInfo
1215 *new,
1216 *last;
1217 double
1218 a;
1219
1220 last = kernel;
1221 for (a=angle; a<355.0; a+=angle) {
1222 new = CloneKernelInfo(last);
1223 RotateKernelInfo(new, angle);
1224 last->next = new;
1225 last = new;
1226 }
1227}
1228
1229/*
1230%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1231% %
1232% %
1233% %
anthony29188a82010-01-22 10:12:34 +00001234% 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 +00001235% %
1236% %
1237% %
1238%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1239%
anthony29188a82010-01-22 10:12:34 +00001240% MorphologyImageChannel() applies a user supplied kernel to the image
1241% according to the given mophology method.
anthony602ab9b2010-01-05 08:06:50 +00001242%
1243% The given kernel is assumed to have been pre-scaled appropriatally, usally
1244% by the kernel generator.
1245%
1246% The format of the MorphologyImage method is:
1247%
cristyef656912010-03-05 19:54:59 +00001248% Image *MorphologyImage(const Image *image,MorphologyMethod method,
1249% const long iterations,KernelInfo *kernel,ExceptionInfo *exception)
anthony29188a82010-01-22 10:12:34 +00001250% Image *MorphologyImageChannel(const Image *image, const ChannelType
cristyef656912010-03-05 19:54:59 +00001251% channel,MorphologyMethod method,const long iterations,
1252% KernelInfo *kernel,ExceptionInfo *exception)
anthony602ab9b2010-01-05 08:06:50 +00001253%
1254% A description of each parameter follows:
1255%
1256% o image: the image.
1257%
1258% o method: the morphology method to be applied.
1259%
1260% o iterations: apply the operation this many times (or no change).
1261% A value of -1 means loop until no change found.
1262% How this is applied may depend on the morphology method.
1263% Typically this is a value of 1.
1264%
1265% o channel: the channel type.
1266%
1267% o kernel: An array of double representing the morphology kernel.
anthony29188a82010-01-22 10:12:34 +00001268% Warning: kernel may be normalized for the Convolve method.
anthony602ab9b2010-01-05 08:06:50 +00001269%
1270% o exception: return any errors or warnings in this structure.
1271%
1272%
1273% TODO: bias and auto-scale handling of the kernel for convolution
1274% The given kernel is assumed to have been pre-scaled appropriatally, usally
1275% by the kernel generator.
1276%
1277*/
1278
anthony930be612010-02-08 04:26:15 +00001279
anthony602ab9b2010-01-05 08:06:50 +00001280/* Internal function
anthony7a01dcf2010-05-11 12:25:52 +00001281 * Apply the low-level Morphology Method Primatives using the given Kernel
anthony930be612010-02-08 04:26:15 +00001282 * Returning the number of pixels that changed.
1283 * Two pre-created images must be provided, no image is created.
anthony602ab9b2010-01-05 08:06:50 +00001284 */
anthony7a01dcf2010-05-11 12:25:52 +00001285static unsigned long MorphologyPrimative(const Image *image, Image
anthony602ab9b2010-01-05 08:06:50 +00001286 *result_image, const MorphologyMethod method, const ChannelType channel,
cristy2be15382010-01-21 02:38:03 +00001287 const KernelInfo *kernel, ExceptionInfo *exception)
anthony602ab9b2010-01-05 08:06:50 +00001288{
cristy2be15382010-01-21 02:38:03 +00001289#define MorphologyTag "Morphology/Image"
anthony602ab9b2010-01-05 08:06:50 +00001290
1291 long
cristy150989e2010-02-01 14:59:39 +00001292 progress,
anthony29188a82010-01-22 10:12:34 +00001293 y, offx, offy,
anthony602ab9b2010-01-05 08:06:50 +00001294 changed;
1295
1296 MagickBooleanType
1297 status;
1298
1299 MagickPixelPacket
1300 bias;
1301
1302 CacheView
1303 *p_view,
1304 *q_view;
1305
anthony4fd27e22010-02-07 08:17:18 +00001306 /* Only the most basic morphology is actually performed by this routine */
anthony4fd27e22010-02-07 08:17:18 +00001307
anthony602ab9b2010-01-05 08:06:50 +00001308 /*
anthony4fd27e22010-02-07 08:17:18 +00001309 Apply Basic Morphology to Image.
anthony602ab9b2010-01-05 08:06:50 +00001310 */
1311 status=MagickTrue;
1312 changed=0;
1313 progress=0;
1314
1315 GetMagickPixelPacket(image,&bias);
1316 SetMagickPixelPacketBias(image,&bias);
anthonycc6c8362010-01-25 04:14:01 +00001317 /* Future: handle auto-bias from user, based on kernel input */
anthony602ab9b2010-01-05 08:06:50 +00001318
1319 p_view=AcquireCacheView(image);
1320 q_view=AcquireCacheView(result_image);
anthony29188a82010-01-22 10:12:34 +00001321
anthonycc6c8362010-01-25 04:14:01 +00001322 /* Some methods (including convolve) needs use a reflected kernel.
1323 * Adjust 'origin' offsets for this reflected kernel.
anthony29188a82010-01-22 10:12:34 +00001324 */
cristyc99304f2010-02-01 15:26:27 +00001325 offx = kernel->x;
1326 offy = kernel->y;
anthony29188a82010-01-22 10:12:34 +00001327 switch(method) {
anthony930be612010-02-08 04:26:15 +00001328 case ConvolveMorphology:
1329 case DilateMorphology:
1330 case DilateIntensityMorphology:
1331 case DistanceMorphology:
anthony5ef8e942010-05-11 06:51:12 +00001332 /* kernel needs to used with reflection about origin */
cristy150989e2010-02-01 14:59:39 +00001333 offx = (long) kernel->width-offx-1;
1334 offy = (long) kernel->height-offy-1;
anthony29188a82010-01-22 10:12:34 +00001335 break;
anthony5ef8e942010-05-11 06:51:12 +00001336 case ErodeMorphology:
1337 case ErodeIntensityMorphology:
1338 case HitAndMissMorphology:
1339 case ThinningMorphology:
1340 case ThickenMorphology:
1341 /* kernel is user as is, without reflection */
1342 break;
anthony930be612010-02-08 04:26:15 +00001343 default:
anthony5ef8e942010-05-11 06:51:12 +00001344 perror("Not a low level Morphology Method");
anthony930be612010-02-08 04:26:15 +00001345 break;
anthony29188a82010-01-22 10:12:34 +00001346 }
1347
anthony602ab9b2010-01-05 08:06:50 +00001348#if defined(MAGICKCORE_OPENMP_SUPPORT)
1349 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
1350#endif
cristy150989e2010-02-01 14:59:39 +00001351 for (y=0; y < (long) image->rows; y++)
anthony602ab9b2010-01-05 08:06:50 +00001352 {
1353 MagickBooleanType
1354 sync;
1355
1356 register const PixelPacket
1357 *restrict p;
1358
1359 register const IndexPacket
1360 *restrict p_indexes;
1361
1362 register PixelPacket
1363 *restrict q;
1364
1365 register IndexPacket
1366 *restrict q_indexes;
1367
cristy150989e2010-02-01 14:59:39 +00001368 register long
anthony602ab9b2010-01-05 08:06:50 +00001369 x;
1370
anthony29188a82010-01-22 10:12:34 +00001371 unsigned long
anthony602ab9b2010-01-05 08:06:50 +00001372 r;
1373
1374 if (status == MagickFalse)
1375 continue;
anthony29188a82010-01-22 10:12:34 +00001376 p=GetCacheViewVirtualPixels(p_view, -offx, y-offy,
1377 image->columns+kernel->width, kernel->height, exception);
anthony602ab9b2010-01-05 08:06:50 +00001378 q=GetCacheViewAuthenticPixels(q_view,0,y,result_image->columns,1,
1379 exception);
1380 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
1381 {
1382 status=MagickFalse;
1383 continue;
1384 }
1385 p_indexes=GetCacheViewVirtualIndexQueue(p_view);
1386 q_indexes=GetCacheViewAuthenticIndexQueue(q_view);
anthony29188a82010-01-22 10:12:34 +00001387 r = (image->columns+kernel->width)*offy+offx; /* constant */
1388
cristy150989e2010-02-01 14:59:39 +00001389 for (x=0; x < (long) image->columns; x++)
anthony602ab9b2010-01-05 08:06:50 +00001390 {
cristy150989e2010-02-01 14:59:39 +00001391 long
anthony602ab9b2010-01-05 08:06:50 +00001392 v;
1393
cristy150989e2010-02-01 14:59:39 +00001394 register long
anthony602ab9b2010-01-05 08:06:50 +00001395 u;
1396
1397 register const double
1398 *restrict k;
1399
1400 register const PixelPacket
1401 *restrict k_pixels;
1402
1403 register const IndexPacket
1404 *restrict k_indexes;
1405
1406 MagickPixelPacket
anthony5ef8e942010-05-11 06:51:12 +00001407 result,
1408 min,
1409 max;
anthony602ab9b2010-01-05 08:06:50 +00001410
anthony29188a82010-01-22 10:12:34 +00001411 /* Copy input to ouput image for unused channels
anthony83ba99b2010-01-24 08:48:15 +00001412 * This removes need for 'cloning' a new image every iteration
anthony29188a82010-01-22 10:12:34 +00001413 */
anthony602ab9b2010-01-05 08:06:50 +00001414 *q = p[r];
1415 if (image->colorspace == CMYKColorspace)
1416 q_indexes[x] = p_indexes[r];
1417
anthony5ef8e942010-05-11 06:51:12 +00001418 /* Defaults */
1419 min.red =
1420 min.green =
1421 min.blue =
1422 min.opacity =
1423 min.index = (MagickRealType) QuantumRange;
1424 max.red =
1425 max.green =
1426 max.blue =
1427 max.opacity =
1428 max.index = (MagickRealType) 0;
1429 /* original pixel value */
1430 result.red = (MagickRealType) p[r].red;
1431 result.green = (MagickRealType) p[r].green;
1432 result.blue = (MagickRealType) p[r].blue;
1433 result.opacity = QuantumRange - (MagickRealType) p[r].opacity;
1434 if ( image->colorspace == CMYKColorspace)
1435 result.index = (MagickRealType) p_indexes[r];
1436
anthony602ab9b2010-01-05 08:06:50 +00001437 switch (method) {
1438 case ConvolveMorphology:
anthony930be612010-02-08 04:26:15 +00001439 /* Set the user defined bias of the weighted average output
1440 **
1441 ** FUTURE: provide some way for internal functions to disable
anthony5ef8e942010-05-11 06:51:12 +00001442 ** user provided bias and scaling effects.
anthony930be612010-02-08 04:26:15 +00001443 */
anthony602ab9b2010-01-05 08:06:50 +00001444 result=bias;
anthony930be612010-02-08 04:26:15 +00001445 break;
anthony4fd27e22010-02-07 08:17:18 +00001446 case DilateIntensityMorphology:
1447 case ErodeIntensityMorphology:
anthony5ef8e942010-05-11 06:51:12 +00001448 result.red = 0.0; /* flag indicating when first match found */
anthony4fd27e22010-02-07 08:17:18 +00001449 break;
anthony602ab9b2010-01-05 08:06:50 +00001450 default:
anthony602ab9b2010-01-05 08:06:50 +00001451 break;
1452 }
1453
1454 switch ( method ) {
1455 case ConvolveMorphology:
anthony930be612010-02-08 04:26:15 +00001456 /* Weighted Average of pixels using reflected kernel
1457 **
1458 ** NOTE for correct working of this operation for asymetrical
1459 ** kernels, the kernel needs to be applied in its reflected form.
1460 ** That is its values needs to be reversed.
1461 **
1462 ** Correlation is actually the same as this but without reflecting
1463 ** the kernel, and thus 'lower-level' that Convolution. However
1464 ** as Convolution is the more common method used, and it does not
1465 ** really cost us much in terms of processing to use a reflected
anthony5ef8e942010-05-11 06:51:12 +00001466 ** kernel, so it is Convolution that is implemented.
anthony930be612010-02-08 04:26:15 +00001467 **
1468 ** Correlation will have its kernel reflected before calling
1469 ** this function to do a Convolve.
1470 **
1471 ** For more details of Correlation vs Convolution see
1472 ** http://www.cs.umd.edu/~djacobs/CMSC426/Convolution.pdf
1473 */
anthony5ef8e942010-05-11 06:51:12 +00001474 if (((channel & SyncChannels) != 0 ) &&
1475 (image->matte == MagickTrue))
1476 { /* Channel has a 'Sync' Flag, and Alpha Channel enabled.
1477 ** Weight the color channels with Alpha Channel so that
1478 ** transparent pixels are not part of the results.
1479 */
anthony602ab9b2010-01-05 08:06:50 +00001480 MagickRealType
anthony5ef8e942010-05-11 06:51:12 +00001481 alpha, /* color channel weighting : kernel*alpha */
1482 gamma; /* divisor, sum of weighting values */
anthony602ab9b2010-01-05 08:06:50 +00001483
1484 gamma=0.0;
anthony29188a82010-01-22 10:12:34 +00001485 k = &kernel->values[ kernel->width*kernel->height-1 ];
anthony602ab9b2010-01-05 08:06:50 +00001486 k_pixels = p;
1487 k_indexes = p_indexes;
cristy150989e2010-02-01 14:59:39 +00001488 for (v=0; v < (long) kernel->height; v++) {
1489 for (u=0; u < (long) kernel->width; u++, k--) {
anthony602ab9b2010-01-05 08:06:50 +00001490 if ( IsNan(*k) ) continue;
1491 alpha=(*k)*(QuantumScale*(QuantumRange-
1492 k_pixels[u].opacity));
1493 gamma += alpha;
1494 result.red += alpha*k_pixels[u].red;
1495 result.green += alpha*k_pixels[u].green;
1496 result.blue += alpha*k_pixels[u].blue;
anthony83ba99b2010-01-24 08:48:15 +00001497 result.opacity += (*k)*(QuantumRange-k_pixels[u].opacity);
anthony602ab9b2010-01-05 08:06:50 +00001498 if ( image->colorspace == CMYKColorspace)
1499 result.index += alpha*k_indexes[u];
1500 }
1501 k_pixels += image->columns+kernel->width;
1502 k_indexes += image->columns+kernel->width;
1503 }
1504 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
anthony83ba99b2010-01-24 08:48:15 +00001505 result.red *= gamma;
1506 result.green *= gamma;
1507 result.blue *= gamma;
1508 result.opacity *= gamma;
1509 result.index *= gamma;
anthony602ab9b2010-01-05 08:06:50 +00001510 }
anthony5ef8e942010-05-11 06:51:12 +00001511 else
1512 {
1513 /* No 'Sync' flag, or no Alpha involved.
1514 ** Convolution is simple individual channel weigthed sum.
1515 */
1516 k = &kernel->values[ kernel->width*kernel->height-1 ];
1517 k_pixels = p;
1518 k_indexes = p_indexes;
1519 for (v=0; v < (long) kernel->height; v++) {
1520 for (u=0; u < (long) kernel->width; u++, k--) {
1521 if ( IsNan(*k) ) continue;
1522 result.red += (*k)*k_pixels[u].red;
1523 result.green += (*k)*k_pixels[u].green;
1524 result.blue += (*k)*k_pixels[u].blue;
1525 result.opacity += (*k)*(QuantumRange-k_pixels[u].opacity);
1526 if ( image->colorspace == CMYKColorspace)
1527 result.index += (*k)*k_indexes[u];
1528 }
1529 k_pixels += image->columns+kernel->width;
1530 k_indexes += image->columns+kernel->width;
1531 }
1532 }
anthony602ab9b2010-01-05 08:06:50 +00001533 break;
1534
anthony4fd27e22010-02-07 08:17:18 +00001535 case ErodeMorphology:
anthony5ef8e942010-05-11 06:51:12 +00001536 /* Minimum Value within kernel neighbourhood
anthony930be612010-02-08 04:26:15 +00001537 **
1538 ** NOTE that the kernel is not reflected for this operation!
1539 **
1540 ** NOTE: in normal Greyscale Morphology, the kernel value should
1541 ** be added to the real value, this is currently not done, due to
1542 ** the nature of the boolean kernels being used.
1543 */
anthony4fd27e22010-02-07 08:17:18 +00001544 k = kernel->values;
1545 k_pixels = p;
1546 k_indexes = p_indexes;
1547 for (v=0; v < (long) kernel->height; v++) {
1548 for (u=0; u < (long) kernel->width; u++, k++) {
1549 if ( IsNan(*k) || (*k) < 0.5 ) continue;
anthony5ef8e942010-05-11 06:51:12 +00001550 Minimize(min.red, (double) k_pixels[u].red);
1551 Minimize(min.green, (double) k_pixels[u].green);
1552 Minimize(min.blue, (double) k_pixels[u].blue);
1553 Minimize(min.opacity,
anthonyd37a5cb2010-05-07 06:37:03 +00001554 QuantumRange-(double) k_pixels[u].opacity);
anthony4fd27e22010-02-07 08:17:18 +00001555 if ( image->colorspace == CMYKColorspace)
anthony5ef8e942010-05-11 06:51:12 +00001556 Minimize(min.index, (double) k_indexes[u]);
anthony4fd27e22010-02-07 08:17:18 +00001557 }
1558 k_pixels += image->columns+kernel->width;
1559 k_indexes += image->columns+kernel->width;
1560 }
1561 break;
1562
anthony5ef8e942010-05-11 06:51:12 +00001563
anthony83ba99b2010-01-24 08:48:15 +00001564 case DilateMorphology:
anthony5ef8e942010-05-11 06:51:12 +00001565 /* Maximum Value within kernel neighbourhood
anthony930be612010-02-08 04:26:15 +00001566 **
1567 ** NOTE for correct working of this operation for asymetrical
1568 ** kernels, the kernel needs to be applied in its reflected form.
1569 ** That is its values needs to be reversed.
1570 **
1571 ** NOTE: in normal Greyscale Morphology, the kernel value should
1572 ** be added to the real value, this is currently not done, due to
1573 ** the nature of the boolean kernels being used.
1574 **
1575 */
anthony29188a82010-01-22 10:12:34 +00001576 k = &kernel->values[ kernel->width*kernel->height-1 ];
anthony602ab9b2010-01-05 08:06:50 +00001577 k_pixels = p;
1578 k_indexes = p_indexes;
cristy150989e2010-02-01 14:59:39 +00001579 for (v=0; v < (long) kernel->height; v++) {
1580 for (u=0; u < (long) kernel->width; u++, k--) {
anthony602ab9b2010-01-05 08:06:50 +00001581 if ( IsNan(*k) || (*k) < 0.5 ) continue;
anthony5ef8e942010-05-11 06:51:12 +00001582 Maximize(max.red, (double) k_pixels[u].red);
1583 Maximize(max.green, (double) k_pixels[u].green);
1584 Maximize(max.blue, (double) k_pixels[u].blue);
1585 Maximize(max.opacity,
anthonyd37a5cb2010-05-07 06:37:03 +00001586 QuantumRange-(double) k_pixels[u].opacity);
anthony602ab9b2010-01-05 08:06:50 +00001587 if ( image->colorspace == CMYKColorspace)
anthony5ef8e942010-05-11 06:51:12 +00001588 Maximize(max.index, (double) k_indexes[u]);
anthony602ab9b2010-01-05 08:06:50 +00001589 }
1590 k_pixels += image->columns+kernel->width;
1591 k_indexes += image->columns+kernel->width;
1592 }
anthony602ab9b2010-01-05 08:06:50 +00001593 break;
1594
anthony5ef8e942010-05-11 06:51:12 +00001595 case HitAndMissMorphology:
1596 case ThinningMorphology:
1597 case ThickenMorphology:
1598 /* Minimum of Foreground Pixel minus Maxumum of Background Pixels
1599 **
1600 ** NOTE that the kernel is not reflected for this operation,
1601 ** and consists of both foreground and background pixel
1602 ** neighbourhoods, 0.0 for background, and 1.0 for foreground
1603 ** with either Nan or 0.5 values for don't care.
1604 **
1605 ** Note that this can produce negative results, though really
1606 ** only a positive match has any real value.
1607 */
1608 k = kernel->values;
1609 k_pixels = p;
1610 k_indexes = p_indexes;
1611 for (v=0; v < (long) kernel->height; v++) {
1612 for (u=0; u < (long) kernel->width; u++, k++) {
1613 if ( IsNan(*k) ) continue;
1614 if ( (*k) > 0.7 )
1615 { /* minimim of foreground pixels */
1616 Minimize(min.red, (double) k_pixels[u].red);
1617 Minimize(min.green, (double) k_pixels[u].green);
1618 Minimize(min.blue, (double) k_pixels[u].blue);
1619 Minimize(min.opacity,
1620 QuantumRange-(double) k_pixels[u].opacity);
1621 if ( image->colorspace == CMYKColorspace)
1622 Minimize(min.index, (double) k_indexes[u]);
1623 }
1624 else if ( (*k) < 0.3 )
1625 { /* maximum of background pixels */
1626 Maximize(max.red, (double) k_pixels[u].red);
1627 Maximize(max.green, (double) k_pixels[u].green);
1628 Maximize(max.blue, (double) k_pixels[u].blue);
1629 Maximize(max.opacity,
1630 QuantumRange-(double) k_pixels[u].opacity);
1631 if ( image->colorspace == CMYKColorspace)
1632 Maximize(max.index, (double) k_indexes[u]);
1633 }
1634 }
1635 k_pixels += image->columns+kernel->width;
1636 k_indexes += image->columns+kernel->width;
1637 }
1638 /* Pattern Match only if min fg larger than min bg pixels */
1639 min.red -= max.red; Maximize( min.red, 0.0 );
1640 min.green -= max.green; Maximize( min.green, 0.0 );
1641 min.blue -= max.blue; Maximize( min.blue, 0.0 );
1642 min.opacity -= max.opacity; Maximize( min.opacity, 0.0 );
1643 min.index -= max.index; Maximize( min.index, 0.0 );
1644 break;
1645
anthony4fd27e22010-02-07 08:17:18 +00001646 case ErodeIntensityMorphology:
anthony930be612010-02-08 04:26:15 +00001647 /* Select Pixel with Minimum Intensity within kernel neighbourhood
1648 **
1649 ** WARNING: the intensity test fails for CMYK and does not
1650 ** take into account the moderating effect of teh alpha channel
1651 ** on the intensity.
1652 **
1653 ** NOTE that the kernel is not reflected for this operation!
1654 */
anthony602ab9b2010-01-05 08:06:50 +00001655 k = kernel->values;
1656 k_pixels = p;
1657 k_indexes = p_indexes;
cristy150989e2010-02-01 14:59:39 +00001658 for (v=0; v < (long) kernel->height; v++) {
1659 for (u=0; u < (long) kernel->width; u++, k++) {
anthony602ab9b2010-01-05 08:06:50 +00001660 if ( IsNan(*k) || (*k) < 0.5 ) continue;
anthony4fd27e22010-02-07 08:17:18 +00001661 if ( result.red == 0.0 ||
1662 PixelIntensity(&(k_pixels[u])) < PixelIntensity(q) ) {
1663 /* copy the whole pixel - no channel selection */
1664 *q = k_pixels[u];
1665 if ( result.red > 0.0 ) changed++;
1666 result.red = 1.0;
1667 }
anthony602ab9b2010-01-05 08:06:50 +00001668 }
1669 k_pixels += image->columns+kernel->width;
1670 k_indexes += image->columns+kernel->width;
1671 }
anthony602ab9b2010-01-05 08:06:50 +00001672 break;
1673
anthony83ba99b2010-01-24 08:48:15 +00001674 case DilateIntensityMorphology:
anthony930be612010-02-08 04:26:15 +00001675 /* Select Pixel with Maximum Intensity within kernel neighbourhood
1676 **
1677 ** WARNING: the intensity test fails for CMYK and does not
1678 ** take into account the moderating effect of teh alpha channel
1679 ** on the intensity.
1680 **
1681 ** NOTE for correct working of this operation for asymetrical
1682 ** kernels, the kernel needs to be applied in its reflected form.
1683 ** That is its values needs to be reversed.
1684 */
anthony29188a82010-01-22 10:12:34 +00001685 k = &kernel->values[ kernel->width*kernel->height-1 ];
anthony602ab9b2010-01-05 08:06:50 +00001686 k_pixels = p;
1687 k_indexes = p_indexes;
cristy150989e2010-02-01 14:59:39 +00001688 for (v=0; v < (long) kernel->height; v++) {
1689 for (u=0; u < (long) kernel->width; u++, k--) {
anthony29188a82010-01-22 10:12:34 +00001690 if ( IsNan(*k) || (*k) < 0.5 ) continue; /* boolean kernel */
1691 if ( result.red == 0.0 ||
1692 PixelIntensity(&(k_pixels[u])) > PixelIntensity(q) ) {
1693 /* copy the whole pixel - no channel selection */
1694 *q = k_pixels[u];
1695 if ( result.red > 0.0 ) changed++;
1696 result.red = 1.0;
1697 }
anthony602ab9b2010-01-05 08:06:50 +00001698 }
1699 k_pixels += image->columns+kernel->width;
1700 k_indexes += image->columns+kernel->width;
1701 }
anthony602ab9b2010-01-05 08:06:50 +00001702 break;
1703
anthony5ef8e942010-05-11 06:51:12 +00001704
anthony602ab9b2010-01-05 08:06:50 +00001705 case DistanceMorphology:
anthony930be612010-02-08 04:26:15 +00001706 /* Add kernel Value and select the minimum value found.
1707 ** The result is a iterative distance from edge of image shape.
1708 **
1709 ** All Distance Kernels are symetrical, but that may not always
1710 ** be the case. For example how about a distance from left edges?
1711 ** To work correctly with asymetrical kernels the reflected kernel
1712 ** needs to be applied.
anthony5ef8e942010-05-11 06:51:12 +00001713 **
1714 ** Actually this is really a GreyErode with a negative kernel!
1715 **
anthony930be612010-02-08 04:26:15 +00001716 */
anthony29188a82010-01-22 10:12:34 +00001717 k = &kernel->values[ kernel->width*kernel->height-1 ];
anthony602ab9b2010-01-05 08:06:50 +00001718 k_pixels = p;
1719 k_indexes = p_indexes;
cristy150989e2010-02-01 14:59:39 +00001720 for (v=0; v < (long) kernel->height; v++) {
1721 for (u=0; u < (long) kernel->width; u++, k--) {
anthony602ab9b2010-01-05 08:06:50 +00001722 if ( IsNan(*k) ) continue;
1723 Minimize(result.red, (*k)+k_pixels[u].red);
1724 Minimize(result.green, (*k)+k_pixels[u].green);
1725 Minimize(result.blue, (*k)+k_pixels[u].blue);
1726 Minimize(result.opacity, (*k)+QuantumRange-k_pixels[u].opacity);
1727 if ( image->colorspace == CMYKColorspace)
1728 Minimize(result.index, (*k)+k_indexes[u]);
1729 }
1730 k_pixels += image->columns+kernel->width;
1731 k_indexes += image->columns+kernel->width;
1732 }
anthony602ab9b2010-01-05 08:06:50 +00001733 break;
1734
1735 case UndefinedMorphology:
1736 default:
1737 break; /* Do nothing */
anthony83ba99b2010-01-24 08:48:15 +00001738 }
anthony5ef8e942010-05-11 06:51:12 +00001739 /* Final mathematics of results (combine with original image?)
1740 **
1741 ** NOTE: Difference Morphology operators Edge* and *Hat could also
1742 ** be done here but works better with iteration as a image difference
1743 ** in the controling function (below). Thicken and Thinning however
1744 ** should be done here so thay can be iterated correctly.
1745 */
1746 switch ( method ) {
1747 case HitAndMissMorphology:
1748 case ErodeMorphology:
1749 result = min; /* minimum of neighbourhood */
1750 break;
1751 case DilateMorphology:
1752 result = max; /* maximum of neighbourhood */
1753 break;
1754 case ThinningMorphology:
1755 /* subtract pattern match from original */
1756 result.red -= min.red;
1757 result.green -= min.green;
1758 result.blue -= min.blue;
1759 result.opacity -= min.opacity;
1760 result.index -= min.index;
1761 break;
1762 case ThickenMorphology:
1763 /* Union with original image (maximize) - or should this be + */
1764 Maximize( result.red, min.red );
1765 Maximize( result.green, min.green );
1766 Maximize( result.blue, min.blue );
1767 Maximize( result.opacity, min.opacity );
1768 Maximize( result.index, min.index );
1769 break;
1770 default:
1771 /* result directly calculated or assigned */
1772 break;
1773 }
1774 /* Assign the resulting pixel values - Clamping Result */
anthony83ba99b2010-01-24 08:48:15 +00001775 switch ( method ) {
1776 case UndefinedMorphology:
1777 case DilateIntensityMorphology:
1778 case ErodeIntensityMorphology:
anthony930be612010-02-08 04:26:15 +00001779 break; /* full pixel was directly assigned - not a channel method */
anthony83ba99b2010-01-24 08:48:15 +00001780 default:
anthony83ba99b2010-01-24 08:48:15 +00001781 if ((channel & RedChannel) != 0)
1782 q->red = ClampToQuantum(result.red);
1783 if ((channel & GreenChannel) != 0)
1784 q->green = ClampToQuantum(result.green);
1785 if ((channel & BlueChannel) != 0)
1786 q->blue = ClampToQuantum(result.blue);
1787 if ((channel & OpacityChannel) != 0
1788 && image->matte == MagickTrue )
1789 q->opacity = ClampToQuantum(QuantumRange-result.opacity);
1790 if ((channel & IndexChannel) != 0
1791 && image->colorspace == CMYKColorspace)
1792 q_indexes[x] = ClampToQuantum(result.index);
1793 break;
1794 }
anthony5ef8e942010-05-11 06:51:12 +00001795 /* Count up changed pixels */
anthony83ba99b2010-01-24 08:48:15 +00001796 if ( ( p[r].red != q->red )
1797 || ( p[r].green != q->green )
1798 || ( p[r].blue != q->blue )
1799 || ( p[r].opacity != q->opacity )
1800 || ( image->colorspace == CMYKColorspace &&
1801 p_indexes[r] != q_indexes[x] ) )
1802 changed++; /* The pixel had some value changed! */
anthony602ab9b2010-01-05 08:06:50 +00001803 p++;
1804 q++;
anthony83ba99b2010-01-24 08:48:15 +00001805 } /* x */
anthony602ab9b2010-01-05 08:06:50 +00001806 sync=SyncCacheViewAuthenticPixels(q_view,exception);
1807 if (sync == MagickFalse)
1808 status=MagickFalse;
1809 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1810 {
1811 MagickBooleanType
1812 proceed;
1813
1814#if defined(MAGICKCORE_OPENMP_SUPPORT)
1815 #pragma omp critical (MagickCore_MorphologyImage)
1816#endif
1817 proceed=SetImageProgress(image,MorphologyTag,progress++,image->rows);
1818 if (proceed == MagickFalse)
1819 status=MagickFalse;
1820 }
anthony83ba99b2010-01-24 08:48:15 +00001821 } /* y */
anthony602ab9b2010-01-05 08:06:50 +00001822 result_image->type=image->type;
1823 q_view=DestroyCacheView(q_view);
1824 p_view=DestroyCacheView(p_view);
cristy150989e2010-02-01 14:59:39 +00001825 return(status ? (unsigned long) changed : 0);
anthony602ab9b2010-01-05 08:06:50 +00001826}
1827
anthony4fd27e22010-02-07 08:17:18 +00001828
1829MagickExport Image *MorphologyImage(const Image *image, const MorphologyMethod
anthony930be612010-02-08 04:26:15 +00001830 method, const long iterations,const KernelInfo *kernel, ExceptionInfo
1831 *exception)
cristy2be15382010-01-21 02:38:03 +00001832{
1833 Image
1834 *morphology_image;
1835
1836 morphology_image=MorphologyImageChannel(image,DefaultChannels,method,
1837 iterations,kernel,exception);
1838 return(morphology_image);
1839}
1840
anthony4fd27e22010-02-07 08:17:18 +00001841
cristyef656912010-03-05 19:54:59 +00001842MagickExport Image *MorphologyImageChannel(const Image *image,
1843 const ChannelType channel,const MorphologyMethod method,
1844 const long iterations,const KernelInfo *kernel,ExceptionInfo *exception)
anthony602ab9b2010-01-05 08:06:50 +00001845{
anthony602ab9b2010-01-05 08:06:50 +00001846 Image
1847 *new_image,
anthony1b2bc0a2010-05-12 05:25:22 +00001848 *old_image,
anthony4fd27e22010-02-07 08:17:18 +00001849 *grad_image;
anthony602ab9b2010-01-05 08:06:50 +00001850
anthony4fd27e22010-02-07 08:17:18 +00001851 KernelInfo
anthony1b2bc0a2010-05-12 05:25:22 +00001852 *curr_kernel,
1853 *this_kernel;
anthony4fd27e22010-02-07 08:17:18 +00001854
1855 MorphologyMethod
1856 curr_method;
1857
anthony1b2bc0a2010-05-12 05:25:22 +00001858 unsigned long
1859 count,
1860 limit,
1861 changed,
1862 total_changed,
1863 kernel_number;
1864
anthony602ab9b2010-01-05 08:06:50 +00001865 assert(image != (Image *) NULL);
1866 assert(image->signature == MagickSignature);
anthony4fd27e22010-02-07 08:17:18 +00001867 assert(kernel != (KernelInfo *) NULL);
1868 assert(kernel->signature == MagickSignature);
anthony602ab9b2010-01-05 08:06:50 +00001869 assert(exception != (ExceptionInfo *) NULL);
1870 assert(exception->signature == MagickSignature);
1871
anthony602ab9b2010-01-05 08:06:50 +00001872 if ( iterations == 0 )
1873 return((Image *)NULL); /* null operation - nothing to do! */
1874
1875 /* kernel must be valid at this point
1876 * (except maybe for posible future morphology methods like "Prune"
1877 */
cristy2be15382010-01-21 02:38:03 +00001878 assert(kernel != (KernelInfo *)NULL);
anthony602ab9b2010-01-05 08:06:50 +00001879
anthony1b2bc0a2010-05-12 05:25:22 +00001880 new_image = (Image *) NULL; /* for cleanup */
1881 old_image = (Image *) NULL;
1882 grad_image = (Image *) NULL;
1883 curr_kernel = (KernelInfo *) NULL;
1884 count = 0; /* interation count */
1885 changed = 1; /* was last run succesfull */
anthony930be612010-02-08 04:26:15 +00001886 curr_kernel = (KernelInfo *)kernel; /* allow kernel and method */
1887 curr_method = method; /* to be changed as nessary */
anthony4fd27e22010-02-07 08:17:18 +00001888
cristy150989e2010-02-01 14:59:39 +00001889 limit = (unsigned long) iterations;
anthony602ab9b2010-01-05 08:06:50 +00001890 if ( iterations < 0 )
1891 limit = image->columns > image->rows ? image->columns : image->rows;
1892
anthony4fd27e22010-02-07 08:17:18 +00001893 /* Third-level morphology methods */
1894 switch( curr_method ) {
1895 case EdgeMorphology:
1896 grad_image = MorphologyImageChannel(image, channel,
1897 DilateMorphology, iterations, curr_kernel, exception);
anthony1b2bc0a2010-05-12 05:25:22 +00001898 if ( grad_image == (Image *) NULL )
1899 goto error_cleanup;
anthony4fd27e22010-02-07 08:17:18 +00001900 /* FALL-THRU */
1901 case EdgeInMorphology:
1902 curr_method = ErodeMorphology;
anthony602ab9b2010-01-05 08:06:50 +00001903 break;
anthony4fd27e22010-02-07 08:17:18 +00001904 case EdgeOutMorphology:
1905 curr_method = DilateMorphology;
anthony602ab9b2010-01-05 08:06:50 +00001906 break;
anthony4fd27e22010-02-07 08:17:18 +00001907 case TopHatMorphology:
1908 curr_method = OpenMorphology;
1909 break;
1910 case BottomHatMorphology:
1911 curr_method = CloseMorphology;
1912 break;
1913 default:
anthony930be612010-02-08 04:26:15 +00001914 break; /* not a third-level method */
anthony4fd27e22010-02-07 08:17:18 +00001915 }
1916
1917 /* Second-level morphology methods */
1918 switch( curr_method ) {
anthony930be612010-02-08 04:26:15 +00001919 case OpenMorphology:
1920 /* Open is a Erode then a Dilate without reflection */
anthony4fd27e22010-02-07 08:17:18 +00001921 new_image = MorphologyImageChannel(image, channel,
1922 ErodeMorphology, iterations, curr_kernel, exception);
anthony602ab9b2010-01-05 08:06:50 +00001923 if (new_image == (Image *) NULL)
anthony1b2bc0a2010-05-12 05:25:22 +00001924 goto error_cleanup;
anthony4fd27e22010-02-07 08:17:18 +00001925 curr_method = DilateMorphology;
1926 break;
anthony602ab9b2010-01-05 08:06:50 +00001927 case OpenIntensityMorphology:
anthony4fd27e22010-02-07 08:17:18 +00001928 new_image = MorphologyImageChannel(image, channel,
1929 ErodeIntensityMorphology, iterations, curr_kernel, exception);
anthony602ab9b2010-01-05 08:06:50 +00001930 if (new_image == (Image *) NULL)
anthony1b2bc0a2010-05-12 05:25:22 +00001931 goto error_cleanup;
anthony4fd27e22010-02-07 08:17:18 +00001932 curr_method = DilateIntensityMorphology;
1933 break;
anthony930be612010-02-08 04:26:15 +00001934
1935 case CloseMorphology:
1936 /* Close is a Dilate then Erode using reflected kernel */
1937 /* A reflected kernel is needed for a Close */
1938 if ( curr_kernel == kernel )
1939 curr_kernel = CloneKernelInfo(kernel);
anthony1b2bc0a2010-05-12 05:25:22 +00001940 if (curr_kernel == (KernelInfo *) NULL)
1941 goto error_cleanup;
anthony930be612010-02-08 04:26:15 +00001942 RotateKernelInfo(curr_kernel,180);
1943 new_image = MorphologyImageChannel(image, channel,
1944 DilateMorphology, iterations, curr_kernel, exception);
1945 if (new_image == (Image *) NULL)
anthony1b2bc0a2010-05-12 05:25:22 +00001946 goto error_cleanup;
anthony930be612010-02-08 04:26:15 +00001947 curr_method = ErodeMorphology;
1948 break;
anthony4fd27e22010-02-07 08:17:18 +00001949 case CloseIntensityMorphology:
anthony930be612010-02-08 04:26:15 +00001950 /* A reflected kernel is needed for a Close */
1951 if ( curr_kernel == kernel )
1952 curr_kernel = CloneKernelInfo(kernel);
anthony1b2bc0a2010-05-12 05:25:22 +00001953 if (curr_kernel == (KernelInfo *) NULL)
1954 goto error_cleanup;
anthony4fd27e22010-02-07 08:17:18 +00001955 RotateKernelInfo(curr_kernel,180);
1956 new_image = MorphologyImageChannel(image, channel,
1957 DilateIntensityMorphology, iterations, curr_kernel, exception);
1958 if (new_image == (Image *) NULL)
anthony1b2bc0a2010-05-12 05:25:22 +00001959 goto error_cleanup;
anthony4fd27e22010-02-07 08:17:18 +00001960 curr_method = ErodeIntensityMorphology;
anthony602ab9b2010-01-05 08:06:50 +00001961 break;
1962
anthony930be612010-02-08 04:26:15 +00001963 case CorrelateMorphology:
1964 /* A Correlation is actually a Convolution with a reflected kernel.
1965 ** However a Convolution is a weighted sum with a reflected kernel.
1966 ** It may seem stange to convert a Correlation into a Convolution
1967 ** as the Correleation is the simplier method, but Convolution is
1968 ** much more commonly used, and it makes sense to implement it directly
1969 ** so as to avoid the need to duplicate the kernel when it is not
1970 ** required (which is typically the default).
1971 */
1972 if ( curr_kernel == kernel )
1973 curr_kernel = CloneKernelInfo(kernel);
anthony1b2bc0a2010-05-12 05:25:22 +00001974 if (curr_kernel == (KernelInfo *) NULL)
1975 goto error_cleanup;
anthony930be612010-02-08 04:26:15 +00001976 RotateKernelInfo(curr_kernel,180);
1977 curr_method = ConvolveMorphology;
1978 /* FALL-THRU into Correlate (weigthed sum without reflection) */
1979
anthonyc94cdb02010-01-06 08:15:29 +00001980 case ConvolveMorphology:
anthony1b2bc0a2010-05-12 05:25:22 +00001981 { /* Scale or Normalize kernel, according to user wishes
1982 ** before using it for the Convolve/Correlate method.
1983 **
1984 ** FUTURE: provide some way for internal functions to disable
1985 ** user bias and scaling effects.
1986 */
1987 const char
1988 *artifact = GetImageArtifact(image,"convolve:scale");
1989 if ( artifact != (char *)NULL ) {
1990 GeometryFlags
1991 flags;
1992 GeometryInfo
1993 args;
anthony999bb2c2010-02-18 12:38:01 +00001994
anthony1b2bc0a2010-05-12 05:25:22 +00001995 if ( curr_kernel == kernel )
1996 curr_kernel = CloneKernelInfo(kernel);
1997 if (curr_kernel == (KernelInfo *) NULL)
1998 goto error_cleanup;
1999 args.rho = 1.0;
2000 flags = (GeometryFlags) ParseGeometry(artifact, &args);
2001 ScaleKernelInfo(curr_kernel, args.rho, flags);
2002 }
anthony4fd27e22010-02-07 08:17:18 +00002003 }
anthony930be612010-02-08 04:26:15 +00002004 /* FALL-THRU to do the first, and typically the only iteration */
anthony4fd27e22010-02-07 08:17:18 +00002005
anthony602ab9b2010-01-05 08:06:50 +00002006 default:
anthony930be612010-02-08 04:26:15 +00002007 /* Do a single iteration using the Low-Level Morphology method!
2008 ** This ensures a "new_image" has been generated, but allows us to skip
2009 ** the creation of 'old_image' if no more iterations are needed.
2010 **
2011 ** The "curr_method" should also be set to a low-level method that is
anthony7a01dcf2010-05-11 12:25:52 +00002012 ** understood by the MorphologyPrimative() internal function.
anthony602ab9b2010-01-05 08:06:50 +00002013 */
2014 new_image=CloneImage(image,0,0,MagickTrue,exception);
2015 if (new_image == (Image *) NULL)
anthony1b2bc0a2010-05-12 05:25:22 +00002016 goto error_cleanup;
anthony602ab9b2010-01-05 08:06:50 +00002017 if (SetImageStorageClass(new_image,DirectClass) == MagickFalse)
2018 {
2019 InheritException(exception,&new_image->exception);
anthony1b2bc0a2010-05-12 05:25:22 +00002020 goto exit_cleanup;
anthony602ab9b2010-01-05 08:06:50 +00002021 }
anthony1b2bc0a2010-05-12 05:25:22 +00002022 count++; /* iteration count */
anthony7a01dcf2010-05-11 12:25:52 +00002023 changed = MorphologyPrimative(image,new_image,curr_method,channel,
2024 curr_kernel, exception);
anthony602ab9b2010-01-05 08:06:50 +00002025 if ( GetImageArtifact(image,"verbose") != (const char *) NULL )
anthony1b2bc0a2010-05-12 05:25:22 +00002026 fprintf(stderr, "Morphology %s:%lu.%lu => Changed %lu\n",
anthony4fd27e22010-02-07 08:17:18 +00002027 MagickOptionToMnemonic(MagickMorphologyOptions, curr_method),
anthony1b2bc0a2010-05-12 05:25:22 +00002028 count, 0L, changed);
anthony930be612010-02-08 04:26:15 +00002029 break;
anthony602ab9b2010-01-05 08:06:50 +00002030 }
anthony1b2bc0a2010-05-12 05:25:22 +00002031 /* At this point
2032 * + "curr_method" should be set to a low-level morphology method
2033 * + "count=1" if the first iteration of the first kernel has been done.
2034 * + "new_image" is defined and contains the current resulting image
2035 */
anthony602ab9b2010-01-05 08:06:50 +00002036
anthony1b2bc0a2010-05-12 05:25:22 +00002037 /* The Hit-And-Miss Morphology is not 'iterated' against the resulting
2038 ** image from the previous morphology step. It must always be applied
2039 ** to the original image, with the results collected into a union (maximimum
2040 ** or lighten) of all the results. Multiple kernels may be applied but
2041 ** an iteration of the morphology does nothing, so is ignored.
anthony7a01dcf2010-05-11 12:25:52 +00002042 **
anthony1b2bc0a2010-05-12 05:25:22 +00002043 ** The first kernel is guranteed to have been done, so lets continue the
2044 ** process, with the other kernels in the kernel list.
anthony7a01dcf2010-05-11 12:25:52 +00002045 */
anthony1b2bc0a2010-05-12 05:25:22 +00002046 if ( method == HitAndMissMorphology )
anthony7a01dcf2010-05-11 12:25:52 +00002047 {
anthony1b2bc0a2010-05-12 05:25:22 +00002048 if ( curr_kernel->next != (KernelInfo *) NULL ) {
2049 /* create a second working image */
2050 old_image = CloneImage(image,0,0,MagickTrue,exception);
2051 if (old_image == (Image *) NULL)
2052 goto error_cleanup;
2053 if (SetImageStorageClass(old_image,DirectClass) == MagickFalse)
2054 {
2055 InheritException(exception,&old_image->exception);
2056 goto exit_cleanup;
2057 }
anthony7a01dcf2010-05-11 12:25:52 +00002058
anthony1b2bc0a2010-05-12 05:25:22 +00002059 /* loop through rest of the kernels */
2060 this_kernel=curr_kernel->next;
2061 kernel_number=1;
2062 while( this_kernel != (KernelInfo *) NULL )
2063 {
2064 changed = MorphologyPrimative(image,old_image,curr_method,channel,
2065 this_kernel,exception);
2066 (void) CompositeImageChannel(new_image,
2067 (ChannelType) (channel & ~SyncChannels), LightenCompositeOp,
2068 old_image, 0, 0);
2069 if ( GetImageArtifact(image,"verbose") != (const char *) NULL )
2070 fprintf(stderr, "Morphology %s:%lu.%lu => Changed %lu\n",
2071 MagickOptionToMnemonic(MagickMorphologyOptions, curr_method),
2072 count, kernel_number, changed);
2073 this_kernel = this_kernel->next;
2074 kernel_number++;
2075 }
2076 old_image=DestroyImage(old_image);
2077 }
2078 goto exit_cleanup;
anthony7a01dcf2010-05-11 12:25:52 +00002079 }
2080
anthony1b2bc0a2010-05-12 05:25:22 +00002081 /* Repeat the low-level morphology over all kernels
2082 until iteration count limit or no change from any kernel is found */
2083 if ( ( count < limit && changed > 0 ) ||
2084 curr_kernel->next != (KernelInfo *) NULL ) {
anthony930be612010-02-08 04:26:15 +00002085
anthony1b2bc0a2010-05-12 05:25:22 +00002086 /* create a second working image */
2087 old_image = CloneImage(image,0,0,MagickTrue,exception);
anthony602ab9b2010-01-05 08:06:50 +00002088 if (old_image == (Image *) NULL)
anthony1b2bc0a2010-05-12 05:25:22 +00002089 goto error_cleanup;
anthony602ab9b2010-01-05 08:06:50 +00002090 if (SetImageStorageClass(old_image,DirectClass) == MagickFalse)
2091 {
2092 InheritException(exception,&old_image->exception);
anthony1b2bc0a2010-05-12 05:25:22 +00002093 goto exit_cleanup;
anthony602ab9b2010-01-05 08:06:50 +00002094 }
anthony1b2bc0a2010-05-12 05:25:22 +00002095
2096 /* reset variables for the first/next iteration, or next kernel) */
2097 kernel_number = 0;
2098 this_kernel = curr_kernel;
2099 total_changed = count != 0 ? changed : 0;
2100 if ( count != 0 && this_kernel != (KernelInfo *) NULL ) {
2101 count = 0; /* first iteration is not yet finished! */
2102 this_kernel = curr_kernel->next;
2103 kernel_number = 1;
2104 total_changed = changed;
2105 }
2106
2107 while ( count < limit ) {
2108 count++;
2109 while ( this_kernel != (KernelInfo *) NULL ) {
anthony602ab9b2010-01-05 08:06:50 +00002110 Image *tmp = old_image;
2111 old_image = new_image;
2112 new_image = tmp;
anthony7a01dcf2010-05-11 12:25:52 +00002113 changed = MorphologyPrimative(old_image,new_image,curr_method,channel,
anthony1b2bc0a2010-05-12 05:25:22 +00002114 this_kernel,exception);
anthony602ab9b2010-01-05 08:06:50 +00002115 if ( GetImageArtifact(image,"verbose") != (const char *) NULL )
anthony1b2bc0a2010-05-12 05:25:22 +00002116 fprintf(stderr, "Morphology %s:%lu.%lu => Changed %lu\n",
anthony4fd27e22010-02-07 08:17:18 +00002117 MagickOptionToMnemonic(MagickMorphologyOptions, curr_method),
anthony1b2bc0a2010-05-12 05:25:22 +00002118 count, kernel_number, changed);
2119 total_changed += changed;
2120 this_kernel = this_kernel->next;
2121 kernel_number++;
anthony602ab9b2010-01-05 08:06:50 +00002122 }
anthony1b2bc0a2010-05-12 05:25:22 +00002123 if ( total_changed == 0 )
2124 break; /* no changes after processing all kernels - ABORT */
2125 /* prepare for next loop */
2126 total_changed = 0;
2127 kernel_number = 0;
2128 this_kernel = curr_kernel;
2129 }
cristy150989e2010-02-01 14:59:39 +00002130 old_image=DestroyImage(old_image);
anthony602ab9b2010-01-05 08:06:50 +00002131 }
anthony930be612010-02-08 04:26:15 +00002132
anthony1b2bc0a2010-05-12 05:25:22 +00002133 /* finished with kernel - destary any copy that was made */
anthony4fd27e22010-02-07 08:17:18 +00002134 if ( curr_kernel != kernel )
2135 curr_kernel=DestroyKernelInfo(curr_kernel);
2136
anthony7d10d742010-05-06 07:05:29 +00002137 /* Third-level Subtractive methods post-processing
2138 **
2139 ** The removal of any 'Sync' channel flag in the Image Compositon below
2140 ** ensures the compose method is applied in a purely mathematical way, only
2141 ** the selected channels, without any normal 'alpha blending' normally
2142 ** associated with the compose method.
2143 **
2144 ** Note "method" here is the 'original' morphological method, and not the
2145 ** 'current' morphological method used above to generate "new_image".
2146 */
anthony4fd27e22010-02-07 08:17:18 +00002147 switch( method ) {
2148 case EdgeOutMorphology:
2149 case EdgeInMorphology:
2150 case TopHatMorphology:
2151 case BottomHatMorphology:
anthony930be612010-02-08 04:26:15 +00002152 /* Get Difference relative to the original image */
cristyaeb2cbc2010-05-07 13:28:58 +00002153 (void) CompositeImageChannel(new_image, (ChannelType) (channel & ~SyncChannels),
anthony7d10d742010-05-06 07:05:29 +00002154 DifferenceCompositeOp, image, 0, 0);
anthony4fd27e22010-02-07 08:17:18 +00002155 break;
anthony7d10d742010-05-06 07:05:29 +00002156 case EdgeMorphology:
2157 /* Difference the Eroded image from the saved Dilated image */
cristyaeb2cbc2010-05-07 13:28:58 +00002158 (void) CompositeImageChannel(new_image, (ChannelType) (channel & ~SyncChannels),
anthony7d10d742010-05-06 07:05:29 +00002159 DifferenceCompositeOp, grad_image, 0, 0);
anthony4fd27e22010-02-07 08:17:18 +00002160 grad_image=DestroyImage(grad_image);
2161 break;
2162 default:
2163 break;
2164 }
anthony602ab9b2010-01-05 08:06:50 +00002165
2166 return(new_image);
anthony1b2bc0a2010-05-12 05:25:22 +00002167
2168 /* Yes goto's are bad, but in this case it makes cleanup lot more efficient */
2169error_cleanup:
2170 if ( new_image != (Image *) NULL )
2171 DestroyImage(new_image);
2172exit_cleanup:
2173 if ( old_image != (Image *) NULL )
2174 DestroyImage(old_image);
2175 if ( curr_kernel != (KernelInfo *) NULL && curr_kernel != kernel )
2176 curr_kernel=DestroyKernelInfo(curr_kernel);
2177 return(new_image);
anthony602ab9b2010-01-05 08:06:50 +00002178}
anthony83ba99b2010-01-24 08:48:15 +00002179
2180/*
2181%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2182% %
2183% %
2184% %
anthony4fd27e22010-02-07 08:17:18 +00002185+ R o t a t e K e r n e l I n f o %
anthony83ba99b2010-01-24 08:48:15 +00002186% %
2187% %
2188% %
2189%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2190%
anthony4fd27e22010-02-07 08:17:18 +00002191% RotateKernelInfo() rotates the kernel by the angle given. Currently it is
anthony83ba99b2010-01-24 08:48:15 +00002192% restricted to 90 degree angles, but this may be improved in the future.
2193%
anthony4fd27e22010-02-07 08:17:18 +00002194% The format of the RotateKernelInfo method is:
anthony83ba99b2010-01-24 08:48:15 +00002195%
anthony4fd27e22010-02-07 08:17:18 +00002196% void RotateKernelInfo(KernelInfo *kernel, double angle)
anthony83ba99b2010-01-24 08:48:15 +00002197%
2198% A description of each parameter follows:
2199%
2200% o kernel: the Morphology/Convolution kernel
2201%
2202% o angle: angle to rotate in degrees
2203%
anthonyc4c86e02010-01-27 09:30:32 +00002204% This function is only internel to this module, as it is not finalized,
2205% especially with regard to non-orthogonal angles, and rotation of larger
2206% 2D kernels.
anthony83ba99b2010-01-24 08:48:15 +00002207*/
anthony4fd27e22010-02-07 08:17:18 +00002208static void RotateKernelInfo(KernelInfo *kernel, double angle)
anthony83ba99b2010-01-24 08:48:15 +00002209{
anthony1b2bc0a2010-05-12 05:25:22 +00002210 /* angle the lower kernels first */
2211 if ( kernel->next != (KernelInfo *) NULL)
2212 RotateKernelInfo(kernel->next, angle);
2213
anthony83ba99b2010-01-24 08:48:15 +00002214 /* WARNING: Currently assumes the kernel (rightly) is horizontally symetrical
2215 **
2216 ** TODO: expand beyond simple 90 degree rotates, flips and flops
2217 */
2218
2219 /* Modulus the angle */
2220 angle = fmod(angle, 360.0);
2221 if ( angle < 0 )
2222 angle += 360.0;
2223
anthony3c10fc82010-05-13 02:40:51 +00002224 if ( 337.5 < angle || angle <= 22.5 )
anthony83ba99b2010-01-24 08:48:15 +00002225 return; /* no change! - At least at this time */
2226
2227 switch (kernel->type) {
2228 /* These built-in kernels are cylindrical kernels, rotating is useless */
2229 case GaussianKernel:
2230 case LaplacianKernel:
2231 case LOGKernel:
2232 case DOGKernel:
2233 case DiskKernel:
2234 case ChebyshevKernel:
2235 case ManhattenKernel:
2236 case EuclideanKernel:
2237 return;
2238
2239 /* These may be rotatable at non-90 angles in the future */
2240 /* but simply rotating them in multiples of 90 degrees is useless */
2241 case SquareKernel:
2242 case DiamondKernel:
2243 case PlusKernel:
2244 return;
2245
2246 /* These only allows a +/-90 degree rotation (by transpose) */
2247 /* A 180 degree rotation is useless */
2248 case BlurKernel:
2249 case RectangleKernel:
2250 if ( 135.0 < angle && angle <= 225.0 )
2251 return;
2252 if ( 225.0 < angle && angle <= 315.0 )
2253 angle -= 180;
2254 break;
2255
2256 /* these are freely rotatable in 90 degree units */
anthony3c10fc82010-05-13 02:40:51 +00002257 case SobelKernel:
anthony83ba99b2010-01-24 08:48:15 +00002258 case CometKernel:
2259 case UndefinedKernel:
2260 case UserDefinedKernel:
2261 break;
2262 }
anthony3c10fc82010-05-13 02:40:51 +00002263 /* Attempt rotations by 45 degrees */
2264 if ( 22.5 < fmod(angle,90.0) && fmod(angle,90.0) <= 67.5 )
2265 {
2266 if ( kernel->width == 3 && kernel->height == 3 )
2267 { /* Rotate a 3x3 square by 45 degree angle */
2268 MagickRealType t = kernel->values[0];
2269 kernel->values[0] = kernel->values[1];
2270 kernel->values[1] = kernel->values[2];
2271 kernel->values[2] = kernel->values[5];
2272 kernel->values[5] = kernel->values[8];
2273 kernel->values[8] = kernel->values[7];
2274 kernel->values[7] = kernel->values[6];
2275 kernel->values[6] = kernel->values[3];
2276 kernel->values[3] = t;
2277 angle = fmod(angle+45.0, 360.0);
2278 }
2279 else
2280 perror("Unable to rotate non-3x3 kernel by 45 degrees");
2281 }
2282 if ( 45.0 < fmod(angle, 180.0) && fmod(angle,180.0) <= 135.0 )
2283 {
2284 if ( kernel->width == 1 || kernel->height == 1 )
2285 { /* Do a transpose of the image, which results in a 90
2286 ** degree rotation of a 1 dimentional kernel
2287 */
2288 long
2289 t;
2290 t = (long) kernel->width;
2291 kernel->width = kernel->height;
2292 kernel->height = (unsigned long) t;
2293 t = kernel->x;
2294 kernel->x = kernel->y;
2295 kernel->y = t;
2296 if ( kernel->width == 1 )
2297 angle = fmod(angle+270.0, 360.0); /* angle -90 degrees */
2298 else
2299 angle = fmod(angle+90.0, 360.0); /* angle +90 degrees */
2300 }
2301 else if ( kernel->width == kernel->height )
2302 { /* Rotate a square array of values by 90 degrees */
2303 register unsigned long
2304 i,j,x,y;
2305 register MagickRealType
2306 *k,t;
2307 k=kernel->values;
2308 for( i=0, x=kernel->width-1; i<=x; i++, x--)
2309 for( j=0, y=kernel->height-1; j<y; j++, y--)
2310 { t = k[i+j*kernel->width];
2311 k[i+j*kernel->width] = k[y+i*kernel->width];
2312 k[y+i*kernel->width] = k[x+y*kernel->width];
2313 k[x+y*kernel->width] = k[j+x*kernel->width];
2314 k[j+x*kernel->width] = t;
2315 }
2316 angle = fmod(angle+90.0, 360.0); /* angle +90 degrees */
2317 }
2318 else
2319 perror("Unable to rotate a non-square, non-linear kernel 90 degrees");
2320 }
anthony83ba99b2010-01-24 08:48:15 +00002321 if ( 135.0 < angle && angle <= 225.0 )
2322 {
2323 /* For a 180 degree rotation - also know as a reflection */
2324 /* This is actually a very very common operation! */
2325 /* Basically all that is needed is a reversal of the kernel data! */
2326 unsigned long
2327 i,j;
2328 register double
2329 *k,t;
2330
2331 k=kernel->values;
2332 for ( i=0, j=kernel->width*kernel->height-1; i<j; i++, j--)
2333 t=k[i], k[i]=k[j], k[j]=t;
2334
anthony930be612010-02-08 04:26:15 +00002335 kernel->x = (long) kernel->width - kernel->x - 1;
2336 kernel->y = (long) kernel->height - kernel->y - 1;
anthony3c10fc82010-05-13 02:40:51 +00002337 angle = fmod(angle+180.0, 360.0); /* angle+180 degrees */
anthony83ba99b2010-01-24 08:48:15 +00002338 }
anthony3c10fc82010-05-13 02:40:51 +00002339 /* At this point angle should at least between -45 (315) and +45 degrees
anthony83ba99b2010-01-24 08:48:15 +00002340 * In the future some form of non-orthogonal angled rotates could be
2341 * performed here, posibily with a linear kernel restriction.
2342 */
2343
2344#if 0
anthony3c10fc82010-05-13 02:40:51 +00002345 { /* Do a Flop by reversing each row.
anthony83ba99b2010-01-24 08:48:15 +00002346 */
2347 unsigned long
2348 y;
cristy150989e2010-02-01 14:59:39 +00002349 register long
anthony83ba99b2010-01-24 08:48:15 +00002350 x,r;
2351 register double
2352 *k,t;
2353
2354 for ( y=0, k=kernel->values; y < kernel->height; y++, k+=kernel->width)
2355 for ( x=0, r=kernel->width-1; x<kernel->width/2; x++, r--)
2356 t=k[x], k[x]=k[r], k[r]=t;
2357
cristyc99304f2010-02-01 15:26:27 +00002358 kernel->x = kernel->width - kernel->x - 1;
anthony83ba99b2010-01-24 08:48:15 +00002359 angle = fmod(angle+180.0, 360.0);
2360 }
2361#endif
2362 return;
2363}
2364
2365/*
2366%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2367% %
2368% %
2369% %
cristy6771f1e2010-03-05 19:43:39 +00002370% S c a l e K e r n e l I n f o %
anthonycc6c8362010-01-25 04:14:01 +00002371% %
2372% %
2373% %
2374%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2375%
anthony1b2bc0a2010-05-12 05:25:22 +00002376% ScaleKernelInfo() scales the given kernel list by the given amount, with or
2377% without normalization of the sum of the kernel values (as per given flags).
anthonycc6c8362010-01-25 04:14:01 +00002378%
anthony999bb2c2010-02-18 12:38:01 +00002379% By default (no flags given) the values within the kernel is scaled
anthony1b2bc0a2010-05-12 05:25:22 +00002380% directly using given scaling factor without change.
anthonycc6c8362010-01-25 04:14:01 +00002381%
anthony1b2bc0a2010-05-12 05:25:22 +00002382% If any 'normalize_flags' are given the kernel will first be normalized and
2383% then further scaled by the scaling factor value given. A 'PercentValue'
2384% flag will cause the given scaling factor to be divided by one hundred
2385% percent.
anthony999bb2c2010-02-18 12:38:01 +00002386%
2387% Kernel normalization ('normalize_flags' given) is designed to ensure that
2388% any use of the kernel scaling factor with 'Convolve' or 'Correlate'
anthony1b2bc0a2010-05-12 05:25:22 +00002389% morphology methods will fall into -1.0 to +1.0 range. Note that for
2390% non-HDRI versions of IM this may cause images to have any negative results
2391% clipped, unless some 'bias' is used.
anthony999bb2c2010-02-18 12:38:01 +00002392%
2393% More specifically. Kernels which only contain positive values (such as a
2394% 'Gaussian' kernel) will be scaled so that those values sum to +1.0,
anthony1b2bc0a2010-05-12 05:25:22 +00002395% ensuring a 0.0 to +1.0 output range for non-HDRI images.
anthony999bb2c2010-02-18 12:38:01 +00002396%
2397% For Kernels that contain some negative values, (such as 'Sharpen' kernels)
2398% the kernel will be scaled by the absolute of the sum of kernel values, so
2399% that it will generally fall within the +/- 1.0 range.
2400%
2401% For kernels whose values sum to zero, (such as 'Laplician' kernels) kernel
2402% will be scaled by just the sum of the postive values, so that its output
2403% range will again fall into the +/- 1.0 range.
2404%
2405% For special kernels designed for locating shapes using 'Correlate', (often
2406% only containing +1 and -1 values, representing foreground/brackground
2407% matching) a special normalization method is provided to scale the positive
2408% values seperatally to those of the negative values, so the kernel will be
2409% forced to become a zero-sum kernel better suited to such searches.
2410%
anthony1b2bc0a2010-05-12 05:25:22 +00002411% WARNING: Correct normalization of the kernel assumes that the '*_range'
anthony999bb2c2010-02-18 12:38:01 +00002412% attributes within the kernel structure have been correctly set during the
2413% kernels creation.
2414%
2415% NOTE: The values used for 'normalize_flags' have been selected specifically
anthony1b2bc0a2010-05-12 05:25:22 +00002416% to match the use of geometry options, so that '!' means NormalizeValue,
2417% '^' means CorrelateNormalizeValue, and '%' means PercentValue. All other
anthony999bb2c2010-02-18 12:38:01 +00002418% GeometryFlags values are ignored.
anthonycc6c8362010-01-25 04:14:01 +00002419%
anthony4fd27e22010-02-07 08:17:18 +00002420% The format of the ScaleKernelInfo method is:
anthonycc6c8362010-01-25 04:14:01 +00002421%
anthony999bb2c2010-02-18 12:38:01 +00002422% void ScaleKernelInfo(KernelInfo *kernel, const double scaling_factor,
2423% const MagickStatusType normalize_flags )
anthonycc6c8362010-01-25 04:14:01 +00002424%
2425% A description of each parameter follows:
2426%
2427% o kernel: the Morphology/Convolution kernel
2428%
anthony999bb2c2010-02-18 12:38:01 +00002429% o scaling_factor:
2430% multiply all values (after normalization) by this factor if not
2431% zero. If the kernel is normalized regardless of any flags.
2432%
2433% o normalize_flags:
2434% GeometryFlags defining normalization method to use.
2435% specifically: NormalizeValue, CorrelateNormalizeValue,
2436% and/or PercentValue
anthonycc6c8362010-01-25 04:14:01 +00002437%
anthonyc4c86e02010-01-27 09:30:32 +00002438% This function is internal to this module only at this time, but can be
2439% exported to other modules if needed.
anthonycc6c8362010-01-25 04:14:01 +00002440*/
cristy6771f1e2010-03-05 19:43:39 +00002441MagickExport void ScaleKernelInfo(KernelInfo *kernel,
2442 const double scaling_factor,const GeometryFlags normalize_flags)
anthonycc6c8362010-01-25 04:14:01 +00002443{
cristy150989e2010-02-01 14:59:39 +00002444 register long
anthonycc6c8362010-01-25 04:14:01 +00002445 i;
2446
anthony999bb2c2010-02-18 12:38:01 +00002447 register double
2448 pos_scale,
2449 neg_scale;
2450
anthony1b2bc0a2010-05-12 05:25:22 +00002451 /* scale the lower kernels first */
2452 if ( kernel->next != (KernelInfo *) NULL)
2453 ScaleKernelInfo(kernel->next, scaling_factor, normalize_flags);
2454
anthony999bb2c2010-02-18 12:38:01 +00002455 pos_scale = 1.0;
2456 if ( (normalize_flags&NormalizeValue) != 0 ) {
2457 /* normalize kernel appropriately */
2458 if ( fabs(kernel->positive_range + kernel->negative_range) > MagickEpsilon )
2459 pos_scale = fabs(kernel->positive_range + kernel->negative_range);
anthonycc6c8362010-01-25 04:14:01 +00002460 else
anthony999bb2c2010-02-18 12:38:01 +00002461 pos_scale = kernel->positive_range; /* special zero-summing kernel */
2462 }
2463 /* force kernel into being a normalized zero-summing kernel */
2464 if ( (normalize_flags&CorrelateNormalizeValue) != 0 ) {
2465 pos_scale = ( fabs(kernel->positive_range) > MagickEpsilon )
2466 ? kernel->positive_range : 1.0;
2467 neg_scale = ( fabs(kernel->negative_range) > MagickEpsilon )
2468 ? -kernel->negative_range : 1.0;
2469 }
2470 else
2471 neg_scale = pos_scale;
2472
2473 /* finialize scaling_factor for positive and negative components */
2474 pos_scale = scaling_factor/pos_scale;
2475 neg_scale = scaling_factor/neg_scale;
2476 if ( (normalize_flags&PercentValue) != 0 ) {
2477 pos_scale /= 100.0;
2478 neg_scale /= 100.0;
anthonycc6c8362010-01-25 04:14:01 +00002479 }
2480
cristy150989e2010-02-01 14:59:39 +00002481 for (i=0; i < (long) (kernel->width*kernel->height); i++)
anthonycc6c8362010-01-25 04:14:01 +00002482 if ( ! IsNan(kernel->values[i]) )
anthony999bb2c2010-02-18 12:38:01 +00002483 kernel->values[i] *= (kernel->values[i] >= 0) ? pos_scale : neg_scale;
anthonycc6c8362010-01-25 04:14:01 +00002484
anthony999bb2c2010-02-18 12:38:01 +00002485 /* convolution output range */
2486 kernel->positive_range *= pos_scale;
2487 kernel->negative_range *= neg_scale;
2488 /* maximum and minimum values in kernel */
2489 kernel->maximum *= (kernel->maximum >= 0.0) ? pos_scale : neg_scale;
2490 kernel->minimum *= (kernel->minimum >= 0.0) ? pos_scale : neg_scale;
2491
2492 /* swap kernel settings if user scaling factor is negative */
2493 if ( scaling_factor < MagickEpsilon ) {
2494 double t;
2495 t = kernel->positive_range;
2496 kernel->positive_range = kernel->negative_range;
2497 kernel->negative_range = t;
2498 t = kernel->maximum;
2499 kernel->maximum = kernel->minimum;
2500 kernel->minimum = 1;
2501 }
anthonycc6c8362010-01-25 04:14:01 +00002502
2503 return;
2504}
2505
2506/*
2507%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2508% %
2509% %
2510% %
anthony4fd27e22010-02-07 08:17:18 +00002511+ S h o w K e r n e l I n f o %
anthony83ba99b2010-01-24 08:48:15 +00002512% %
2513% %
2514% %
2515%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2516%
anthony4fd27e22010-02-07 08:17:18 +00002517% ShowKernelInfo() outputs the details of the given kernel defination to
2518% standard error, generally due to a users 'showkernel' option request.
anthony83ba99b2010-01-24 08:48:15 +00002519%
2520% The format of the ShowKernel method is:
2521%
anthony4fd27e22010-02-07 08:17:18 +00002522% void ShowKernelInfo(KernelInfo *kernel)
anthony83ba99b2010-01-24 08:48:15 +00002523%
2524% A description of each parameter follows:
2525%
2526% o kernel: the Morphology/Convolution kernel
2527%
anthonyc4c86e02010-01-27 09:30:32 +00002528% This function is internal to this module only at this time. That may change
2529% in the future.
anthony83ba99b2010-01-24 08:48:15 +00002530*/
anthony4fd27e22010-02-07 08:17:18 +00002531MagickExport void ShowKernelInfo(KernelInfo *kernel)
anthony83ba99b2010-01-24 08:48:15 +00002532{
anthony7a01dcf2010-05-11 12:25:52 +00002533 KernelInfo
2534 *k;
anthony83ba99b2010-01-24 08:48:15 +00002535
anthony7a01dcf2010-05-11 12:25:52 +00002536 long
2537 c, i, u, v;
2538
2539 for (c=0, k=kernel; k != (KernelInfo *) NULL; c++, k=k->next ) {
2540
2541 fprintf(stderr, "Kernel ");
2542 if ( kernel->next != (KernelInfo *) NULL )
2543 fprintf(stderr, " #%ld", c );
2544 fprintf(stderr, " \"%s\" of size %lux%lu%+ld%+ld ",
2545 MagickOptionToMnemonic(MagickKernelOptions, k->type),
2546 kernel->width, k->height,
2547 kernel->x, kernel->y );
2548 fprintf(stderr,
2549 " with values from %.*lg to %.*lg\n",
2550 GetMagickPrecision(), k->minimum,
2551 GetMagickPrecision(), k->maximum);
2552 fprintf(stderr, "Forming convolution output range from %.*lg to %.*lg%s\n",
2553 GetMagickPrecision(), k->negative_range,
2554 GetMagickPrecision(), k->positive_range,
2555 /*kernel->normalized == MagickTrue ? " (normalized)" : */ "" );
2556 for (i=v=0; v < (long) k->height; v++) {
2557 fprintf(stderr,"%2ld:",v);
2558 for (u=0; u < (long) k->width; u++, i++)
2559 if ( IsNan(k->values[i]) )
2560 fprintf(stderr," %*s", GetMagickPrecision()+2, "nan");
2561 else
2562 fprintf(stderr," %*.*lg", GetMagickPrecision()+2,
2563 GetMagickPrecision(), k->values[i]);
2564 fprintf(stderr,"\n");
2565 }
anthony83ba99b2010-01-24 08:48:15 +00002566 }
2567}
anthonycc6c8362010-01-25 04:14:01 +00002568
2569/*
2570%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2571% %
2572% %
2573% %
anthony4fd27e22010-02-07 08:17:18 +00002574+ Z e r o K e r n e l N a n s %
anthonycc6c8362010-01-25 04:14:01 +00002575% %
2576% %
2577% %
2578%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2579%
2580% ZeroKernelNans() replaces any special 'nan' value that may be present in
2581% the kernel with a zero value. This is typically done when the kernel will
2582% be used in special hardware (GPU) convolution processors, to simply
2583% matters.
2584%
2585% The format of the ZeroKernelNans method is:
2586%
2587% voidZeroKernelNans (KernelInfo *kernel)
2588%
2589% A description of each parameter follows:
2590%
2591% o kernel: the Morphology/Convolution kernel
2592%
2593% FUTURE: return the information in a string for API usage.
2594*/
anthonyc4c86e02010-01-27 09:30:32 +00002595MagickExport void ZeroKernelNans(KernelInfo *kernel)
anthonycc6c8362010-01-25 04:14:01 +00002596{
cristy150989e2010-02-01 14:59:39 +00002597 register long
anthonycc6c8362010-01-25 04:14:01 +00002598 i;
2599
anthony1b2bc0a2010-05-12 05:25:22 +00002600 /* scale the lower kernels first */
2601 if ( kernel->next != (KernelInfo *) NULL)
2602 ZeroKernelNans(kernel->next);
2603
cristy150989e2010-02-01 14:59:39 +00002604 for (i=0; i < (long) (kernel->width*kernel->height); i++)
anthonycc6c8362010-01-25 04:14:01 +00002605 if ( IsNan(kernel->values[i]) )
2606 kernel->values[i] = 0.0;
2607
2608 return;
2609}