blob: 07afc17427fcd4966cc8ab65f09431dced4f666f [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
cristya9a61ad2010-05-13 12:47:41 +00001215 *clone,
anthony3c10fc82010-05-13 02:40:51 +00001216 *last;
cristya9a61ad2010-05-13 12:47:41 +00001217
anthony3c10fc82010-05-13 02:40:51 +00001218 double
1219 a;
1220
1221 last = kernel;
1222 for (a=angle; a<355.0; a+=angle) {
cristya9a61ad2010-05-13 12:47:41 +00001223 clone = CloneKernelInfo(last);
1224 RotateKernelInfo(clone, angle);
1225 last->next = clone;
1226 last = clone;
anthony3c10fc82010-05-13 02:40:51 +00001227 }
1228}
1229
1230/*
1231%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1232% %
1233% %
1234% %
anthony29188a82010-01-22 10:12:34 +00001235% 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 +00001236% %
1237% %
1238% %
1239%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1240%
anthony29188a82010-01-22 10:12:34 +00001241% MorphologyImageChannel() applies a user supplied kernel to the image
1242% according to the given mophology method.
anthony602ab9b2010-01-05 08:06:50 +00001243%
1244% The given kernel is assumed to have been pre-scaled appropriatally, usally
1245% by the kernel generator.
1246%
1247% The format of the MorphologyImage method is:
1248%
cristyef656912010-03-05 19:54:59 +00001249% Image *MorphologyImage(const Image *image,MorphologyMethod method,
1250% const long iterations,KernelInfo *kernel,ExceptionInfo *exception)
anthony29188a82010-01-22 10:12:34 +00001251% Image *MorphologyImageChannel(const Image *image, const ChannelType
cristyef656912010-03-05 19:54:59 +00001252% channel,MorphologyMethod method,const long iterations,
1253% KernelInfo *kernel,ExceptionInfo *exception)
anthony602ab9b2010-01-05 08:06:50 +00001254%
1255% A description of each parameter follows:
1256%
1257% o image: the image.
1258%
1259% o method: the morphology method to be applied.
1260%
1261% o iterations: apply the operation this many times (or no change).
1262% A value of -1 means loop until no change found.
1263% How this is applied may depend on the morphology method.
1264% Typically this is a value of 1.
1265%
1266% o channel: the channel type.
1267%
1268% o kernel: An array of double representing the morphology kernel.
anthony29188a82010-01-22 10:12:34 +00001269% Warning: kernel may be normalized for the Convolve method.
anthony602ab9b2010-01-05 08:06:50 +00001270%
1271% o exception: return any errors or warnings in this structure.
1272%
1273%
1274% TODO: bias and auto-scale handling of the kernel for convolution
1275% The given kernel is assumed to have been pre-scaled appropriatally, usally
1276% by the kernel generator.
1277%
1278*/
1279
anthony930be612010-02-08 04:26:15 +00001280
anthony602ab9b2010-01-05 08:06:50 +00001281/* Internal function
anthony7a01dcf2010-05-11 12:25:52 +00001282 * Apply the low-level Morphology Method Primatives using the given Kernel
anthony930be612010-02-08 04:26:15 +00001283 * Returning the number of pixels that changed.
1284 * Two pre-created images must be provided, no image is created.
anthony602ab9b2010-01-05 08:06:50 +00001285 */
anthony7a01dcf2010-05-11 12:25:52 +00001286static unsigned long MorphologyPrimative(const Image *image, Image
anthony602ab9b2010-01-05 08:06:50 +00001287 *result_image, const MorphologyMethod method, const ChannelType channel,
cristy2be15382010-01-21 02:38:03 +00001288 const KernelInfo *kernel, ExceptionInfo *exception)
anthony602ab9b2010-01-05 08:06:50 +00001289{
cristy2be15382010-01-21 02:38:03 +00001290#define MorphologyTag "Morphology/Image"
anthony602ab9b2010-01-05 08:06:50 +00001291
1292 long
cristy150989e2010-02-01 14:59:39 +00001293 progress,
anthony29188a82010-01-22 10:12:34 +00001294 y, offx, offy,
anthony602ab9b2010-01-05 08:06:50 +00001295 changed;
1296
1297 MagickBooleanType
1298 status;
1299
1300 MagickPixelPacket
1301 bias;
1302
1303 CacheView
1304 *p_view,
1305 *q_view;
1306
anthony4fd27e22010-02-07 08:17:18 +00001307 /* Only the most basic morphology is actually performed by this routine */
anthony4fd27e22010-02-07 08:17:18 +00001308
anthony602ab9b2010-01-05 08:06:50 +00001309 /*
anthony4fd27e22010-02-07 08:17:18 +00001310 Apply Basic Morphology to Image.
anthony602ab9b2010-01-05 08:06:50 +00001311 */
1312 status=MagickTrue;
1313 changed=0;
1314 progress=0;
1315
1316 GetMagickPixelPacket(image,&bias);
1317 SetMagickPixelPacketBias(image,&bias);
anthonycc6c8362010-01-25 04:14:01 +00001318 /* Future: handle auto-bias from user, based on kernel input */
anthony602ab9b2010-01-05 08:06:50 +00001319
1320 p_view=AcquireCacheView(image);
1321 q_view=AcquireCacheView(result_image);
anthony29188a82010-01-22 10:12:34 +00001322
anthonycc6c8362010-01-25 04:14:01 +00001323 /* Some methods (including convolve) needs use a reflected kernel.
1324 * Adjust 'origin' offsets for this reflected kernel.
anthony29188a82010-01-22 10:12:34 +00001325 */
cristyc99304f2010-02-01 15:26:27 +00001326 offx = kernel->x;
1327 offy = kernel->y;
anthony29188a82010-01-22 10:12:34 +00001328 switch(method) {
anthony930be612010-02-08 04:26:15 +00001329 case ConvolveMorphology:
1330 case DilateMorphology:
1331 case DilateIntensityMorphology:
1332 case DistanceMorphology:
anthony5ef8e942010-05-11 06:51:12 +00001333 /* kernel needs to used with reflection about origin */
cristy150989e2010-02-01 14:59:39 +00001334 offx = (long) kernel->width-offx-1;
1335 offy = (long) kernel->height-offy-1;
anthony29188a82010-01-22 10:12:34 +00001336 break;
anthony5ef8e942010-05-11 06:51:12 +00001337 case ErodeMorphology:
1338 case ErodeIntensityMorphology:
1339 case HitAndMissMorphology:
1340 case ThinningMorphology:
1341 case ThickenMorphology:
1342 /* kernel is user as is, without reflection */
1343 break;
anthony930be612010-02-08 04:26:15 +00001344 default:
anthony5ef8e942010-05-11 06:51:12 +00001345 perror("Not a low level Morphology Method");
anthony930be612010-02-08 04:26:15 +00001346 break;
anthony29188a82010-01-22 10:12:34 +00001347 }
1348
anthony602ab9b2010-01-05 08:06:50 +00001349#if defined(MAGICKCORE_OPENMP_SUPPORT)
1350 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
1351#endif
cristy150989e2010-02-01 14:59:39 +00001352 for (y=0; y < (long) image->rows; y++)
anthony602ab9b2010-01-05 08:06:50 +00001353 {
1354 MagickBooleanType
1355 sync;
1356
1357 register const PixelPacket
1358 *restrict p;
1359
1360 register const IndexPacket
1361 *restrict p_indexes;
1362
1363 register PixelPacket
1364 *restrict q;
1365
1366 register IndexPacket
1367 *restrict q_indexes;
1368
cristy150989e2010-02-01 14:59:39 +00001369 register long
anthony602ab9b2010-01-05 08:06:50 +00001370 x;
1371
anthony29188a82010-01-22 10:12:34 +00001372 unsigned long
anthony602ab9b2010-01-05 08:06:50 +00001373 r;
1374
1375 if (status == MagickFalse)
1376 continue;
anthony29188a82010-01-22 10:12:34 +00001377 p=GetCacheViewVirtualPixels(p_view, -offx, y-offy,
1378 image->columns+kernel->width, kernel->height, exception);
anthony602ab9b2010-01-05 08:06:50 +00001379 q=GetCacheViewAuthenticPixels(q_view,0,y,result_image->columns,1,
1380 exception);
1381 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
1382 {
1383 status=MagickFalse;
1384 continue;
1385 }
1386 p_indexes=GetCacheViewVirtualIndexQueue(p_view);
1387 q_indexes=GetCacheViewAuthenticIndexQueue(q_view);
anthony29188a82010-01-22 10:12:34 +00001388 r = (image->columns+kernel->width)*offy+offx; /* constant */
1389
cristy150989e2010-02-01 14:59:39 +00001390 for (x=0; x < (long) image->columns; x++)
anthony602ab9b2010-01-05 08:06:50 +00001391 {
cristy150989e2010-02-01 14:59:39 +00001392 long
anthony602ab9b2010-01-05 08:06:50 +00001393 v;
1394
cristy150989e2010-02-01 14:59:39 +00001395 register long
anthony602ab9b2010-01-05 08:06:50 +00001396 u;
1397
1398 register const double
1399 *restrict k;
1400
1401 register const PixelPacket
1402 *restrict k_pixels;
1403
1404 register const IndexPacket
1405 *restrict k_indexes;
1406
1407 MagickPixelPacket
anthony5ef8e942010-05-11 06:51:12 +00001408 result,
1409 min,
1410 max;
anthony602ab9b2010-01-05 08:06:50 +00001411
anthony29188a82010-01-22 10:12:34 +00001412 /* Copy input to ouput image for unused channels
anthony83ba99b2010-01-24 08:48:15 +00001413 * This removes need for 'cloning' a new image every iteration
anthony29188a82010-01-22 10:12:34 +00001414 */
anthony602ab9b2010-01-05 08:06:50 +00001415 *q = p[r];
1416 if (image->colorspace == CMYKColorspace)
1417 q_indexes[x] = p_indexes[r];
1418
anthony5ef8e942010-05-11 06:51:12 +00001419 /* Defaults */
1420 min.red =
1421 min.green =
1422 min.blue =
1423 min.opacity =
1424 min.index = (MagickRealType) QuantumRange;
1425 max.red =
1426 max.green =
1427 max.blue =
1428 max.opacity =
1429 max.index = (MagickRealType) 0;
1430 /* original pixel value */
1431 result.red = (MagickRealType) p[r].red;
1432 result.green = (MagickRealType) p[r].green;
1433 result.blue = (MagickRealType) p[r].blue;
1434 result.opacity = QuantumRange - (MagickRealType) p[r].opacity;
cristy9b1a5442010-05-13 12:35:40 +00001435 result.index = 0;
anthony5ef8e942010-05-11 06:51:12 +00001436 if ( image->colorspace == CMYKColorspace)
1437 result.index = (MagickRealType) p_indexes[r];
1438
anthony602ab9b2010-01-05 08:06:50 +00001439 switch (method) {
1440 case ConvolveMorphology:
anthony930be612010-02-08 04:26:15 +00001441 /* Set the user defined bias of the weighted average output
1442 **
1443 ** FUTURE: provide some way for internal functions to disable
anthony5ef8e942010-05-11 06:51:12 +00001444 ** user provided bias and scaling effects.
anthony930be612010-02-08 04:26:15 +00001445 */
anthony602ab9b2010-01-05 08:06:50 +00001446 result=bias;
anthony930be612010-02-08 04:26:15 +00001447 break;
anthony4fd27e22010-02-07 08:17:18 +00001448 case DilateIntensityMorphology:
1449 case ErodeIntensityMorphology:
anthony5ef8e942010-05-11 06:51:12 +00001450 result.red = 0.0; /* flag indicating when first match found */
anthony4fd27e22010-02-07 08:17:18 +00001451 break;
anthony602ab9b2010-01-05 08:06:50 +00001452 default:
anthony602ab9b2010-01-05 08:06:50 +00001453 break;
1454 }
1455
1456 switch ( method ) {
1457 case ConvolveMorphology:
anthony930be612010-02-08 04:26:15 +00001458 /* Weighted Average of pixels using reflected kernel
1459 **
1460 ** NOTE for correct working of this operation for asymetrical
1461 ** kernels, the kernel needs to be applied in its reflected form.
1462 ** That is its values needs to be reversed.
1463 **
1464 ** Correlation is actually the same as this but without reflecting
1465 ** the kernel, and thus 'lower-level' that Convolution. However
1466 ** as Convolution is the more common method used, and it does not
1467 ** really cost us much in terms of processing to use a reflected
anthony5ef8e942010-05-11 06:51:12 +00001468 ** kernel, so it is Convolution that is implemented.
anthony930be612010-02-08 04:26:15 +00001469 **
1470 ** Correlation will have its kernel reflected before calling
1471 ** this function to do a Convolve.
1472 **
1473 ** For more details of Correlation vs Convolution see
1474 ** http://www.cs.umd.edu/~djacobs/CMSC426/Convolution.pdf
1475 */
anthony5ef8e942010-05-11 06:51:12 +00001476 if (((channel & SyncChannels) != 0 ) &&
1477 (image->matte == MagickTrue))
1478 { /* Channel has a 'Sync' Flag, and Alpha Channel enabled.
1479 ** Weight the color channels with Alpha Channel so that
1480 ** transparent pixels are not part of the results.
1481 */
anthony602ab9b2010-01-05 08:06:50 +00001482 MagickRealType
anthony5ef8e942010-05-11 06:51:12 +00001483 alpha, /* color channel weighting : kernel*alpha */
1484 gamma; /* divisor, sum of weighting values */
anthony602ab9b2010-01-05 08:06:50 +00001485
1486 gamma=0.0;
anthony29188a82010-01-22 10:12:34 +00001487 k = &kernel->values[ kernel->width*kernel->height-1 ];
anthony602ab9b2010-01-05 08:06:50 +00001488 k_pixels = p;
1489 k_indexes = p_indexes;
cristy150989e2010-02-01 14:59:39 +00001490 for (v=0; v < (long) kernel->height; v++) {
1491 for (u=0; u < (long) kernel->width; u++, k--) {
anthony602ab9b2010-01-05 08:06:50 +00001492 if ( IsNan(*k) ) continue;
1493 alpha=(*k)*(QuantumScale*(QuantumRange-
1494 k_pixels[u].opacity));
1495 gamma += alpha;
1496 result.red += alpha*k_pixels[u].red;
1497 result.green += alpha*k_pixels[u].green;
1498 result.blue += alpha*k_pixels[u].blue;
anthony83ba99b2010-01-24 08:48:15 +00001499 result.opacity += (*k)*(QuantumRange-k_pixels[u].opacity);
anthony602ab9b2010-01-05 08:06:50 +00001500 if ( image->colorspace == CMYKColorspace)
1501 result.index += alpha*k_indexes[u];
1502 }
1503 k_pixels += image->columns+kernel->width;
1504 k_indexes += image->columns+kernel->width;
1505 }
1506 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
anthony83ba99b2010-01-24 08:48:15 +00001507 result.red *= gamma;
1508 result.green *= gamma;
1509 result.blue *= gamma;
1510 result.opacity *= gamma;
1511 result.index *= gamma;
anthony602ab9b2010-01-05 08:06:50 +00001512 }
anthony5ef8e942010-05-11 06:51:12 +00001513 else
1514 {
1515 /* No 'Sync' flag, or no Alpha involved.
1516 ** Convolution is simple individual channel weigthed sum.
1517 */
1518 k = &kernel->values[ kernel->width*kernel->height-1 ];
1519 k_pixels = p;
1520 k_indexes = p_indexes;
1521 for (v=0; v < (long) kernel->height; v++) {
1522 for (u=0; u < (long) kernel->width; u++, k--) {
1523 if ( IsNan(*k) ) continue;
1524 result.red += (*k)*k_pixels[u].red;
1525 result.green += (*k)*k_pixels[u].green;
1526 result.blue += (*k)*k_pixels[u].blue;
1527 result.opacity += (*k)*(QuantumRange-k_pixels[u].opacity);
1528 if ( image->colorspace == CMYKColorspace)
1529 result.index += (*k)*k_indexes[u];
1530 }
1531 k_pixels += image->columns+kernel->width;
1532 k_indexes += image->columns+kernel->width;
1533 }
1534 }
anthony602ab9b2010-01-05 08:06:50 +00001535 break;
1536
anthony4fd27e22010-02-07 08:17:18 +00001537 case ErodeMorphology:
anthony5ef8e942010-05-11 06:51:12 +00001538 /* Minimum Value within kernel neighbourhood
anthony930be612010-02-08 04:26:15 +00001539 **
1540 ** NOTE that the kernel is not reflected for this operation!
1541 **
1542 ** NOTE: in normal Greyscale Morphology, the kernel value should
1543 ** be added to the real value, this is currently not done, due to
1544 ** the nature of the boolean kernels being used.
1545 */
anthony4fd27e22010-02-07 08:17:18 +00001546 k = kernel->values;
1547 k_pixels = p;
1548 k_indexes = p_indexes;
1549 for (v=0; v < (long) kernel->height; v++) {
1550 for (u=0; u < (long) kernel->width; u++, k++) {
1551 if ( IsNan(*k) || (*k) < 0.5 ) continue;
anthony5ef8e942010-05-11 06:51:12 +00001552 Minimize(min.red, (double) k_pixels[u].red);
1553 Minimize(min.green, (double) k_pixels[u].green);
1554 Minimize(min.blue, (double) k_pixels[u].blue);
1555 Minimize(min.opacity,
anthonyd37a5cb2010-05-07 06:37:03 +00001556 QuantumRange-(double) k_pixels[u].opacity);
anthony4fd27e22010-02-07 08:17:18 +00001557 if ( image->colorspace == CMYKColorspace)
anthony5ef8e942010-05-11 06:51:12 +00001558 Minimize(min.index, (double) k_indexes[u]);
anthony4fd27e22010-02-07 08:17:18 +00001559 }
1560 k_pixels += image->columns+kernel->width;
1561 k_indexes += image->columns+kernel->width;
1562 }
1563 break;
1564
anthony5ef8e942010-05-11 06:51:12 +00001565
anthony83ba99b2010-01-24 08:48:15 +00001566 case DilateMorphology:
anthony5ef8e942010-05-11 06:51:12 +00001567 /* Maximum Value within kernel neighbourhood
anthony930be612010-02-08 04:26:15 +00001568 **
1569 ** NOTE for correct working of this operation for asymetrical
1570 ** kernels, the kernel needs to be applied in its reflected form.
1571 ** That is its values needs to be reversed.
1572 **
1573 ** NOTE: in normal Greyscale Morphology, the kernel value should
1574 ** be added to the real value, this is currently not done, due to
1575 ** the nature of the boolean kernels being used.
1576 **
1577 */
anthony29188a82010-01-22 10:12:34 +00001578 k = &kernel->values[ kernel->width*kernel->height-1 ];
anthony602ab9b2010-01-05 08:06:50 +00001579 k_pixels = p;
1580 k_indexes = p_indexes;
cristy150989e2010-02-01 14:59:39 +00001581 for (v=0; v < (long) kernel->height; v++) {
1582 for (u=0; u < (long) kernel->width; u++, k--) {
anthony602ab9b2010-01-05 08:06:50 +00001583 if ( IsNan(*k) || (*k) < 0.5 ) continue;
anthony5ef8e942010-05-11 06:51:12 +00001584 Maximize(max.red, (double) k_pixels[u].red);
1585 Maximize(max.green, (double) k_pixels[u].green);
1586 Maximize(max.blue, (double) k_pixels[u].blue);
1587 Maximize(max.opacity,
anthonyd37a5cb2010-05-07 06:37:03 +00001588 QuantumRange-(double) k_pixels[u].opacity);
anthony602ab9b2010-01-05 08:06:50 +00001589 if ( image->colorspace == CMYKColorspace)
anthony5ef8e942010-05-11 06:51:12 +00001590 Maximize(max.index, (double) k_indexes[u]);
anthony602ab9b2010-01-05 08:06:50 +00001591 }
1592 k_pixels += image->columns+kernel->width;
1593 k_indexes += image->columns+kernel->width;
1594 }
anthony602ab9b2010-01-05 08:06:50 +00001595 break;
1596
anthony5ef8e942010-05-11 06:51:12 +00001597 case HitAndMissMorphology:
1598 case ThinningMorphology:
1599 case ThickenMorphology:
1600 /* Minimum of Foreground Pixel minus Maxumum of Background Pixels
1601 **
1602 ** NOTE that the kernel is not reflected for this operation,
1603 ** and consists of both foreground and background pixel
1604 ** neighbourhoods, 0.0 for background, and 1.0 for foreground
1605 ** with either Nan or 0.5 values for don't care.
1606 **
1607 ** Note that this can produce negative results, though really
1608 ** only a positive match has any real value.
1609 */
1610 k = kernel->values;
1611 k_pixels = p;
1612 k_indexes = p_indexes;
1613 for (v=0; v < (long) kernel->height; v++) {
1614 for (u=0; u < (long) kernel->width; u++, k++) {
1615 if ( IsNan(*k) ) continue;
1616 if ( (*k) > 0.7 )
1617 { /* minimim of foreground pixels */
1618 Minimize(min.red, (double) k_pixels[u].red);
1619 Minimize(min.green, (double) k_pixels[u].green);
1620 Minimize(min.blue, (double) k_pixels[u].blue);
1621 Minimize(min.opacity,
1622 QuantumRange-(double) k_pixels[u].opacity);
1623 if ( image->colorspace == CMYKColorspace)
1624 Minimize(min.index, (double) k_indexes[u]);
1625 }
1626 else if ( (*k) < 0.3 )
1627 { /* maximum of background pixels */
1628 Maximize(max.red, (double) k_pixels[u].red);
1629 Maximize(max.green, (double) k_pixels[u].green);
1630 Maximize(max.blue, (double) k_pixels[u].blue);
1631 Maximize(max.opacity,
1632 QuantumRange-(double) k_pixels[u].opacity);
1633 if ( image->colorspace == CMYKColorspace)
1634 Maximize(max.index, (double) k_indexes[u]);
1635 }
1636 }
1637 k_pixels += image->columns+kernel->width;
1638 k_indexes += image->columns+kernel->width;
1639 }
1640 /* Pattern Match only if min fg larger than min bg pixels */
1641 min.red -= max.red; Maximize( min.red, 0.0 );
1642 min.green -= max.green; Maximize( min.green, 0.0 );
1643 min.blue -= max.blue; Maximize( min.blue, 0.0 );
1644 min.opacity -= max.opacity; Maximize( min.opacity, 0.0 );
1645 min.index -= max.index; Maximize( min.index, 0.0 );
1646 break;
1647
anthony4fd27e22010-02-07 08:17:18 +00001648 case ErodeIntensityMorphology:
anthony930be612010-02-08 04:26:15 +00001649 /* Select Pixel with Minimum Intensity within kernel neighbourhood
1650 **
1651 ** WARNING: the intensity test fails for CMYK and does not
1652 ** take into account the moderating effect of teh alpha channel
1653 ** on the intensity.
1654 **
1655 ** NOTE that the kernel is not reflected for this operation!
1656 */
anthony602ab9b2010-01-05 08:06:50 +00001657 k = kernel->values;
1658 k_pixels = p;
1659 k_indexes = p_indexes;
cristy150989e2010-02-01 14:59:39 +00001660 for (v=0; v < (long) kernel->height; v++) {
1661 for (u=0; u < (long) kernel->width; u++, k++) {
anthony602ab9b2010-01-05 08:06:50 +00001662 if ( IsNan(*k) || (*k) < 0.5 ) continue;
anthony4fd27e22010-02-07 08:17:18 +00001663 if ( result.red == 0.0 ||
1664 PixelIntensity(&(k_pixels[u])) < PixelIntensity(q) ) {
1665 /* copy the whole pixel - no channel selection */
1666 *q = k_pixels[u];
1667 if ( result.red > 0.0 ) changed++;
1668 result.red = 1.0;
1669 }
anthony602ab9b2010-01-05 08:06:50 +00001670 }
1671 k_pixels += image->columns+kernel->width;
1672 k_indexes += image->columns+kernel->width;
1673 }
anthony602ab9b2010-01-05 08:06:50 +00001674 break;
1675
anthony83ba99b2010-01-24 08:48:15 +00001676 case DilateIntensityMorphology:
anthony930be612010-02-08 04:26:15 +00001677 /* Select Pixel with Maximum Intensity within kernel neighbourhood
1678 **
1679 ** WARNING: the intensity test fails for CMYK and does not
1680 ** take into account the moderating effect of teh alpha channel
1681 ** on the intensity.
1682 **
1683 ** NOTE for correct working of this operation for asymetrical
1684 ** kernels, the kernel needs to be applied in its reflected form.
1685 ** That is its values needs to be reversed.
1686 */
anthony29188a82010-01-22 10:12:34 +00001687 k = &kernel->values[ kernel->width*kernel->height-1 ];
anthony602ab9b2010-01-05 08:06:50 +00001688 k_pixels = p;
1689 k_indexes = p_indexes;
cristy150989e2010-02-01 14:59:39 +00001690 for (v=0; v < (long) kernel->height; v++) {
1691 for (u=0; u < (long) kernel->width; u++, k--) {
anthony29188a82010-01-22 10:12:34 +00001692 if ( IsNan(*k) || (*k) < 0.5 ) continue; /* boolean kernel */
1693 if ( result.red == 0.0 ||
1694 PixelIntensity(&(k_pixels[u])) > PixelIntensity(q) ) {
1695 /* copy the whole pixel - no channel selection */
1696 *q = k_pixels[u];
1697 if ( result.red > 0.0 ) changed++;
1698 result.red = 1.0;
1699 }
anthony602ab9b2010-01-05 08:06:50 +00001700 }
1701 k_pixels += image->columns+kernel->width;
1702 k_indexes += image->columns+kernel->width;
1703 }
anthony602ab9b2010-01-05 08:06:50 +00001704 break;
1705
anthony5ef8e942010-05-11 06:51:12 +00001706
anthony602ab9b2010-01-05 08:06:50 +00001707 case DistanceMorphology:
anthony930be612010-02-08 04:26:15 +00001708 /* Add kernel Value and select the minimum value found.
1709 ** The result is a iterative distance from edge of image shape.
1710 **
1711 ** All Distance Kernels are symetrical, but that may not always
1712 ** be the case. For example how about a distance from left edges?
1713 ** To work correctly with asymetrical kernels the reflected kernel
1714 ** needs to be applied.
anthony5ef8e942010-05-11 06:51:12 +00001715 **
1716 ** Actually this is really a GreyErode with a negative kernel!
1717 **
anthony930be612010-02-08 04:26:15 +00001718 */
anthony29188a82010-01-22 10:12:34 +00001719 k = &kernel->values[ kernel->width*kernel->height-1 ];
anthony602ab9b2010-01-05 08:06:50 +00001720 k_pixels = p;
1721 k_indexes = p_indexes;
cristy150989e2010-02-01 14:59:39 +00001722 for (v=0; v < (long) kernel->height; v++) {
1723 for (u=0; u < (long) kernel->width; u++, k--) {
anthony602ab9b2010-01-05 08:06:50 +00001724 if ( IsNan(*k) ) continue;
1725 Minimize(result.red, (*k)+k_pixels[u].red);
1726 Minimize(result.green, (*k)+k_pixels[u].green);
1727 Minimize(result.blue, (*k)+k_pixels[u].blue);
1728 Minimize(result.opacity, (*k)+QuantumRange-k_pixels[u].opacity);
1729 if ( image->colorspace == CMYKColorspace)
1730 Minimize(result.index, (*k)+k_indexes[u]);
1731 }
1732 k_pixels += image->columns+kernel->width;
1733 k_indexes += image->columns+kernel->width;
1734 }
anthony602ab9b2010-01-05 08:06:50 +00001735 break;
1736
1737 case UndefinedMorphology:
1738 default:
1739 break; /* Do nothing */
anthony83ba99b2010-01-24 08:48:15 +00001740 }
anthony5ef8e942010-05-11 06:51:12 +00001741 /* Final mathematics of results (combine with original image?)
1742 **
1743 ** NOTE: Difference Morphology operators Edge* and *Hat could also
1744 ** be done here but works better with iteration as a image difference
1745 ** in the controling function (below). Thicken and Thinning however
1746 ** should be done here so thay can be iterated correctly.
1747 */
1748 switch ( method ) {
1749 case HitAndMissMorphology:
1750 case ErodeMorphology:
1751 result = min; /* minimum of neighbourhood */
1752 break;
1753 case DilateMorphology:
1754 result = max; /* maximum of neighbourhood */
1755 break;
1756 case ThinningMorphology:
1757 /* subtract pattern match from original */
1758 result.red -= min.red;
1759 result.green -= min.green;
1760 result.blue -= min.blue;
1761 result.opacity -= min.opacity;
1762 result.index -= min.index;
1763 break;
1764 case ThickenMorphology:
1765 /* Union with original image (maximize) - or should this be + */
1766 Maximize( result.red, min.red );
1767 Maximize( result.green, min.green );
1768 Maximize( result.blue, min.blue );
1769 Maximize( result.opacity, min.opacity );
1770 Maximize( result.index, min.index );
1771 break;
1772 default:
1773 /* result directly calculated or assigned */
1774 break;
1775 }
1776 /* Assign the resulting pixel values - Clamping Result */
anthony83ba99b2010-01-24 08:48:15 +00001777 switch ( method ) {
1778 case UndefinedMorphology:
1779 case DilateIntensityMorphology:
1780 case ErodeIntensityMorphology:
anthony930be612010-02-08 04:26:15 +00001781 break; /* full pixel was directly assigned - not a channel method */
anthony83ba99b2010-01-24 08:48:15 +00001782 default:
anthony83ba99b2010-01-24 08:48:15 +00001783 if ((channel & RedChannel) != 0)
1784 q->red = ClampToQuantum(result.red);
1785 if ((channel & GreenChannel) != 0)
1786 q->green = ClampToQuantum(result.green);
1787 if ((channel & BlueChannel) != 0)
1788 q->blue = ClampToQuantum(result.blue);
1789 if ((channel & OpacityChannel) != 0
1790 && image->matte == MagickTrue )
1791 q->opacity = ClampToQuantum(QuantumRange-result.opacity);
1792 if ((channel & IndexChannel) != 0
1793 && image->colorspace == CMYKColorspace)
1794 q_indexes[x] = ClampToQuantum(result.index);
1795 break;
1796 }
anthony5ef8e942010-05-11 06:51:12 +00001797 /* Count up changed pixels */
anthony83ba99b2010-01-24 08:48:15 +00001798 if ( ( p[r].red != q->red )
1799 || ( p[r].green != q->green )
1800 || ( p[r].blue != q->blue )
1801 || ( p[r].opacity != q->opacity )
1802 || ( image->colorspace == CMYKColorspace &&
1803 p_indexes[r] != q_indexes[x] ) )
1804 changed++; /* The pixel had some value changed! */
anthony602ab9b2010-01-05 08:06:50 +00001805 p++;
1806 q++;
anthony83ba99b2010-01-24 08:48:15 +00001807 } /* x */
anthony602ab9b2010-01-05 08:06:50 +00001808 sync=SyncCacheViewAuthenticPixels(q_view,exception);
1809 if (sync == MagickFalse)
1810 status=MagickFalse;
1811 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1812 {
1813 MagickBooleanType
1814 proceed;
1815
1816#if defined(MAGICKCORE_OPENMP_SUPPORT)
1817 #pragma omp critical (MagickCore_MorphologyImage)
1818#endif
1819 proceed=SetImageProgress(image,MorphologyTag,progress++,image->rows);
1820 if (proceed == MagickFalse)
1821 status=MagickFalse;
1822 }
anthony83ba99b2010-01-24 08:48:15 +00001823 } /* y */
anthony602ab9b2010-01-05 08:06:50 +00001824 result_image->type=image->type;
1825 q_view=DestroyCacheView(q_view);
1826 p_view=DestroyCacheView(p_view);
cristy150989e2010-02-01 14:59:39 +00001827 return(status ? (unsigned long) changed : 0);
anthony602ab9b2010-01-05 08:06:50 +00001828}
1829
anthony4fd27e22010-02-07 08:17:18 +00001830
1831MagickExport Image *MorphologyImage(const Image *image, const MorphologyMethod
anthony930be612010-02-08 04:26:15 +00001832 method, const long iterations,const KernelInfo *kernel, ExceptionInfo
1833 *exception)
cristy2be15382010-01-21 02:38:03 +00001834{
1835 Image
1836 *morphology_image;
1837
1838 morphology_image=MorphologyImageChannel(image,DefaultChannels,method,
1839 iterations,kernel,exception);
1840 return(morphology_image);
1841}
1842
anthony4fd27e22010-02-07 08:17:18 +00001843
cristyef656912010-03-05 19:54:59 +00001844MagickExport Image *MorphologyImageChannel(const Image *image,
1845 const ChannelType channel,const MorphologyMethod method,
1846 const long iterations,const KernelInfo *kernel,ExceptionInfo *exception)
anthony602ab9b2010-01-05 08:06:50 +00001847{
anthony602ab9b2010-01-05 08:06:50 +00001848 Image
1849 *new_image,
anthony1b2bc0a2010-05-12 05:25:22 +00001850 *old_image,
anthony4fd27e22010-02-07 08:17:18 +00001851 *grad_image;
anthony602ab9b2010-01-05 08:06:50 +00001852
anthony4fd27e22010-02-07 08:17:18 +00001853 KernelInfo
anthony1b2bc0a2010-05-12 05:25:22 +00001854 *curr_kernel,
1855 *this_kernel;
anthony4fd27e22010-02-07 08:17:18 +00001856
1857 MorphologyMethod
1858 curr_method;
1859
anthony1b2bc0a2010-05-12 05:25:22 +00001860 unsigned long
1861 count,
1862 limit,
1863 changed,
1864 total_changed,
1865 kernel_number;
1866
anthony602ab9b2010-01-05 08:06:50 +00001867 assert(image != (Image *) NULL);
1868 assert(image->signature == MagickSignature);
anthony4fd27e22010-02-07 08:17:18 +00001869 assert(kernel != (KernelInfo *) NULL);
1870 assert(kernel->signature == MagickSignature);
anthony602ab9b2010-01-05 08:06:50 +00001871 assert(exception != (ExceptionInfo *) NULL);
1872 assert(exception->signature == MagickSignature);
1873
anthony602ab9b2010-01-05 08:06:50 +00001874 if ( iterations == 0 )
1875 return((Image *)NULL); /* null operation - nothing to do! */
1876
1877 /* kernel must be valid at this point
1878 * (except maybe for posible future morphology methods like "Prune"
1879 */
cristy2be15382010-01-21 02:38:03 +00001880 assert(kernel != (KernelInfo *)NULL);
anthony602ab9b2010-01-05 08:06:50 +00001881
anthony1b2bc0a2010-05-12 05:25:22 +00001882 new_image = (Image *) NULL; /* for cleanup */
1883 old_image = (Image *) NULL;
1884 grad_image = (Image *) NULL;
1885 curr_kernel = (KernelInfo *) NULL;
1886 count = 0; /* interation count */
1887 changed = 1; /* was last run succesfull */
anthony930be612010-02-08 04:26:15 +00001888 curr_kernel = (KernelInfo *)kernel; /* allow kernel and method */
1889 curr_method = method; /* to be changed as nessary */
anthony4fd27e22010-02-07 08:17:18 +00001890
cristy150989e2010-02-01 14:59:39 +00001891 limit = (unsigned long) iterations;
anthony602ab9b2010-01-05 08:06:50 +00001892 if ( iterations < 0 )
1893 limit = image->columns > image->rows ? image->columns : image->rows;
1894
anthony4fd27e22010-02-07 08:17:18 +00001895 /* Third-level morphology methods */
1896 switch( curr_method ) {
1897 case EdgeMorphology:
1898 grad_image = MorphologyImageChannel(image, channel,
1899 DilateMorphology, iterations, curr_kernel, exception);
anthony1b2bc0a2010-05-12 05:25:22 +00001900 if ( grad_image == (Image *) NULL )
1901 goto error_cleanup;
anthony4fd27e22010-02-07 08:17:18 +00001902 /* FALL-THRU */
1903 case EdgeInMorphology:
1904 curr_method = ErodeMorphology;
anthony602ab9b2010-01-05 08:06:50 +00001905 break;
anthony4fd27e22010-02-07 08:17:18 +00001906 case EdgeOutMorphology:
1907 curr_method = DilateMorphology;
anthony602ab9b2010-01-05 08:06:50 +00001908 break;
anthony4fd27e22010-02-07 08:17:18 +00001909 case TopHatMorphology:
1910 curr_method = OpenMorphology;
1911 break;
1912 case BottomHatMorphology:
1913 curr_method = CloseMorphology;
1914 break;
1915 default:
anthony930be612010-02-08 04:26:15 +00001916 break; /* not a third-level method */
anthony4fd27e22010-02-07 08:17:18 +00001917 }
1918
1919 /* Second-level morphology methods */
1920 switch( curr_method ) {
anthony930be612010-02-08 04:26:15 +00001921 case OpenMorphology:
1922 /* Open is a Erode then a Dilate without reflection */
anthony4fd27e22010-02-07 08:17:18 +00001923 new_image = MorphologyImageChannel(image, channel,
1924 ErodeMorphology, iterations, curr_kernel, exception);
anthony602ab9b2010-01-05 08:06:50 +00001925 if (new_image == (Image *) NULL)
anthony1b2bc0a2010-05-12 05:25:22 +00001926 goto error_cleanup;
anthony4fd27e22010-02-07 08:17:18 +00001927 curr_method = DilateMorphology;
1928 break;
anthony602ab9b2010-01-05 08:06:50 +00001929 case OpenIntensityMorphology:
anthony4fd27e22010-02-07 08:17:18 +00001930 new_image = MorphologyImageChannel(image, channel,
1931 ErodeIntensityMorphology, iterations, curr_kernel, exception);
anthony602ab9b2010-01-05 08:06:50 +00001932 if (new_image == (Image *) NULL)
anthony1b2bc0a2010-05-12 05:25:22 +00001933 goto error_cleanup;
anthony4fd27e22010-02-07 08:17:18 +00001934 curr_method = DilateIntensityMorphology;
1935 break;
anthony930be612010-02-08 04:26:15 +00001936
1937 case CloseMorphology:
1938 /* Close is a Dilate then Erode using reflected kernel */
1939 /* A reflected kernel is needed for a Close */
1940 if ( curr_kernel == kernel )
1941 curr_kernel = CloneKernelInfo(kernel);
anthony1b2bc0a2010-05-12 05:25:22 +00001942 if (curr_kernel == (KernelInfo *) NULL)
1943 goto error_cleanup;
anthony930be612010-02-08 04:26:15 +00001944 RotateKernelInfo(curr_kernel,180);
1945 new_image = MorphologyImageChannel(image, channel,
1946 DilateMorphology, iterations, curr_kernel, exception);
1947 if (new_image == (Image *) NULL)
anthony1b2bc0a2010-05-12 05:25:22 +00001948 goto error_cleanup;
anthony930be612010-02-08 04:26:15 +00001949 curr_method = ErodeMorphology;
1950 break;
anthony4fd27e22010-02-07 08:17:18 +00001951 case CloseIntensityMorphology:
anthony930be612010-02-08 04:26:15 +00001952 /* A reflected kernel is needed for a Close */
1953 if ( curr_kernel == kernel )
1954 curr_kernel = CloneKernelInfo(kernel);
anthony1b2bc0a2010-05-12 05:25:22 +00001955 if (curr_kernel == (KernelInfo *) NULL)
1956 goto error_cleanup;
anthony4fd27e22010-02-07 08:17:18 +00001957 RotateKernelInfo(curr_kernel,180);
1958 new_image = MorphologyImageChannel(image, channel,
1959 DilateIntensityMorphology, iterations, curr_kernel, exception);
1960 if (new_image == (Image *) NULL)
anthony1b2bc0a2010-05-12 05:25:22 +00001961 goto error_cleanup;
anthony4fd27e22010-02-07 08:17:18 +00001962 curr_method = ErodeIntensityMorphology;
anthony602ab9b2010-01-05 08:06:50 +00001963 break;
1964
anthony930be612010-02-08 04:26:15 +00001965 case CorrelateMorphology:
1966 /* A Correlation is actually a Convolution with a reflected kernel.
1967 ** However a Convolution is a weighted sum with a reflected kernel.
1968 ** It may seem stange to convert a Correlation into a Convolution
1969 ** as the Correleation is the simplier method, but Convolution is
1970 ** much more commonly used, and it makes sense to implement it directly
1971 ** so as to avoid the need to duplicate the kernel when it is not
1972 ** required (which is typically the default).
1973 */
1974 if ( curr_kernel == kernel )
1975 curr_kernel = CloneKernelInfo(kernel);
anthony1b2bc0a2010-05-12 05:25:22 +00001976 if (curr_kernel == (KernelInfo *) NULL)
1977 goto error_cleanup;
anthony930be612010-02-08 04:26:15 +00001978 RotateKernelInfo(curr_kernel,180);
1979 curr_method = ConvolveMorphology;
1980 /* FALL-THRU into Correlate (weigthed sum without reflection) */
1981
anthonyc94cdb02010-01-06 08:15:29 +00001982 case ConvolveMorphology:
anthony1b2bc0a2010-05-12 05:25:22 +00001983 { /* Scale or Normalize kernel, according to user wishes
1984 ** before using it for the Convolve/Correlate method.
1985 **
1986 ** FUTURE: provide some way for internal functions to disable
1987 ** user bias and scaling effects.
1988 */
1989 const char
1990 *artifact = GetImageArtifact(image,"convolve:scale");
1991 if ( artifact != (char *)NULL ) {
1992 GeometryFlags
1993 flags;
1994 GeometryInfo
1995 args;
anthony999bb2c2010-02-18 12:38:01 +00001996
anthony1b2bc0a2010-05-12 05:25:22 +00001997 if ( curr_kernel == kernel )
1998 curr_kernel = CloneKernelInfo(kernel);
1999 if (curr_kernel == (KernelInfo *) NULL)
2000 goto error_cleanup;
2001 args.rho = 1.0;
2002 flags = (GeometryFlags) ParseGeometry(artifact, &args);
2003 ScaleKernelInfo(curr_kernel, args.rho, flags);
2004 }
anthony4fd27e22010-02-07 08:17:18 +00002005 }
anthony930be612010-02-08 04:26:15 +00002006 /* FALL-THRU to do the first, and typically the only iteration */
anthony4fd27e22010-02-07 08:17:18 +00002007
anthony602ab9b2010-01-05 08:06:50 +00002008 default:
anthony930be612010-02-08 04:26:15 +00002009 /* Do a single iteration using the Low-Level Morphology method!
2010 ** This ensures a "new_image" has been generated, but allows us to skip
2011 ** the creation of 'old_image' if no more iterations are needed.
2012 **
2013 ** The "curr_method" should also be set to a low-level method that is
anthony7a01dcf2010-05-11 12:25:52 +00002014 ** understood by the MorphologyPrimative() internal function.
anthony602ab9b2010-01-05 08:06:50 +00002015 */
2016 new_image=CloneImage(image,0,0,MagickTrue,exception);
2017 if (new_image == (Image *) NULL)
anthony1b2bc0a2010-05-12 05:25:22 +00002018 goto error_cleanup;
anthony602ab9b2010-01-05 08:06:50 +00002019 if (SetImageStorageClass(new_image,DirectClass) == MagickFalse)
2020 {
2021 InheritException(exception,&new_image->exception);
anthony1b2bc0a2010-05-12 05:25:22 +00002022 goto exit_cleanup;
anthony602ab9b2010-01-05 08:06:50 +00002023 }
anthony1b2bc0a2010-05-12 05:25:22 +00002024 count++; /* iteration count */
anthony7a01dcf2010-05-11 12:25:52 +00002025 changed = MorphologyPrimative(image,new_image,curr_method,channel,
2026 curr_kernel, exception);
anthony602ab9b2010-01-05 08:06:50 +00002027 if ( GetImageArtifact(image,"verbose") != (const char *) NULL )
anthony1b2bc0a2010-05-12 05:25:22 +00002028 fprintf(stderr, "Morphology %s:%lu.%lu => Changed %lu\n",
anthony4fd27e22010-02-07 08:17:18 +00002029 MagickOptionToMnemonic(MagickMorphologyOptions, curr_method),
anthony1b2bc0a2010-05-12 05:25:22 +00002030 count, 0L, changed);
anthony930be612010-02-08 04:26:15 +00002031 break;
anthony602ab9b2010-01-05 08:06:50 +00002032 }
anthony1b2bc0a2010-05-12 05:25:22 +00002033 /* At this point
2034 * + "curr_method" should be set to a low-level morphology method
2035 * + "count=1" if the first iteration of the first kernel has been done.
2036 * + "new_image" is defined and contains the current resulting image
2037 */
anthony602ab9b2010-01-05 08:06:50 +00002038
anthony1b2bc0a2010-05-12 05:25:22 +00002039 /* The Hit-And-Miss Morphology is not 'iterated' against the resulting
2040 ** image from the previous morphology step. It must always be applied
2041 ** to the original image, with the results collected into a union (maximimum
2042 ** or lighten) of all the results. Multiple kernels may be applied but
2043 ** an iteration of the morphology does nothing, so is ignored.
anthony7a01dcf2010-05-11 12:25:52 +00002044 **
anthony1b2bc0a2010-05-12 05:25:22 +00002045 ** The first kernel is guranteed to have been done, so lets continue the
2046 ** process, with the other kernels in the kernel list.
anthony7a01dcf2010-05-11 12:25:52 +00002047 */
anthony1b2bc0a2010-05-12 05:25:22 +00002048 if ( method == HitAndMissMorphology )
anthony7a01dcf2010-05-11 12:25:52 +00002049 {
anthony1b2bc0a2010-05-12 05:25:22 +00002050 if ( curr_kernel->next != (KernelInfo *) NULL ) {
2051 /* create a second working image */
2052 old_image = CloneImage(image,0,0,MagickTrue,exception);
2053 if (old_image == (Image *) NULL)
2054 goto error_cleanup;
2055 if (SetImageStorageClass(old_image,DirectClass) == MagickFalse)
2056 {
2057 InheritException(exception,&old_image->exception);
2058 goto exit_cleanup;
2059 }
anthony7a01dcf2010-05-11 12:25:52 +00002060
anthony1b2bc0a2010-05-12 05:25:22 +00002061 /* loop through rest of the kernels */
2062 this_kernel=curr_kernel->next;
2063 kernel_number=1;
2064 while( this_kernel != (KernelInfo *) NULL )
2065 {
2066 changed = MorphologyPrimative(image,old_image,curr_method,channel,
2067 this_kernel,exception);
2068 (void) CompositeImageChannel(new_image,
2069 (ChannelType) (channel & ~SyncChannels), LightenCompositeOp,
2070 old_image, 0, 0);
2071 if ( GetImageArtifact(image,"verbose") != (const char *) NULL )
2072 fprintf(stderr, "Morphology %s:%lu.%lu => Changed %lu\n",
2073 MagickOptionToMnemonic(MagickMorphologyOptions, curr_method),
2074 count, kernel_number, changed);
2075 this_kernel = this_kernel->next;
2076 kernel_number++;
2077 }
2078 old_image=DestroyImage(old_image);
2079 }
2080 goto exit_cleanup;
anthony7a01dcf2010-05-11 12:25:52 +00002081 }
2082
anthony1b2bc0a2010-05-12 05:25:22 +00002083 /* Repeat the low-level morphology over all kernels
2084 until iteration count limit or no change from any kernel is found */
2085 if ( ( count < limit && changed > 0 ) ||
2086 curr_kernel->next != (KernelInfo *) NULL ) {
anthony930be612010-02-08 04:26:15 +00002087
anthony1b2bc0a2010-05-12 05:25:22 +00002088 /* create a second working image */
2089 old_image = CloneImage(image,0,0,MagickTrue,exception);
anthony602ab9b2010-01-05 08:06:50 +00002090 if (old_image == (Image *) NULL)
anthony1b2bc0a2010-05-12 05:25:22 +00002091 goto error_cleanup;
anthony602ab9b2010-01-05 08:06:50 +00002092 if (SetImageStorageClass(old_image,DirectClass) == MagickFalse)
2093 {
2094 InheritException(exception,&old_image->exception);
anthony1b2bc0a2010-05-12 05:25:22 +00002095 goto exit_cleanup;
anthony602ab9b2010-01-05 08:06:50 +00002096 }
anthony1b2bc0a2010-05-12 05:25:22 +00002097
2098 /* reset variables for the first/next iteration, or next kernel) */
2099 kernel_number = 0;
2100 this_kernel = curr_kernel;
2101 total_changed = count != 0 ? changed : 0;
2102 if ( count != 0 && this_kernel != (KernelInfo *) NULL ) {
2103 count = 0; /* first iteration is not yet finished! */
2104 this_kernel = curr_kernel->next;
2105 kernel_number = 1;
2106 total_changed = changed;
2107 }
2108
2109 while ( count < limit ) {
2110 count++;
2111 while ( this_kernel != (KernelInfo *) NULL ) {
anthony602ab9b2010-01-05 08:06:50 +00002112 Image *tmp = old_image;
2113 old_image = new_image;
2114 new_image = tmp;
anthony7a01dcf2010-05-11 12:25:52 +00002115 changed = MorphologyPrimative(old_image,new_image,curr_method,channel,
anthony1b2bc0a2010-05-12 05:25:22 +00002116 this_kernel,exception);
anthony602ab9b2010-01-05 08:06:50 +00002117 if ( GetImageArtifact(image,"verbose") != (const char *) NULL )
anthony1b2bc0a2010-05-12 05:25:22 +00002118 fprintf(stderr, "Morphology %s:%lu.%lu => Changed %lu\n",
anthony4fd27e22010-02-07 08:17:18 +00002119 MagickOptionToMnemonic(MagickMorphologyOptions, curr_method),
anthony1b2bc0a2010-05-12 05:25:22 +00002120 count, kernel_number, changed);
2121 total_changed += changed;
2122 this_kernel = this_kernel->next;
2123 kernel_number++;
anthony602ab9b2010-01-05 08:06:50 +00002124 }
anthony1b2bc0a2010-05-12 05:25:22 +00002125 if ( total_changed == 0 )
2126 break; /* no changes after processing all kernels - ABORT */
2127 /* prepare for next loop */
2128 total_changed = 0;
2129 kernel_number = 0;
2130 this_kernel = curr_kernel;
2131 }
cristy150989e2010-02-01 14:59:39 +00002132 old_image=DestroyImage(old_image);
anthony602ab9b2010-01-05 08:06:50 +00002133 }
anthony930be612010-02-08 04:26:15 +00002134
anthony1b2bc0a2010-05-12 05:25:22 +00002135 /* finished with kernel - destary any copy that was made */
anthony4fd27e22010-02-07 08:17:18 +00002136 if ( curr_kernel != kernel )
2137 curr_kernel=DestroyKernelInfo(curr_kernel);
2138
anthony7d10d742010-05-06 07:05:29 +00002139 /* Third-level Subtractive methods post-processing
2140 **
2141 ** The removal of any 'Sync' channel flag in the Image Compositon below
2142 ** ensures the compose method is applied in a purely mathematical way, only
2143 ** the selected channels, without any normal 'alpha blending' normally
2144 ** associated with the compose method.
2145 **
2146 ** Note "method" here is the 'original' morphological method, and not the
2147 ** 'current' morphological method used above to generate "new_image".
2148 */
anthony4fd27e22010-02-07 08:17:18 +00002149 switch( method ) {
2150 case EdgeOutMorphology:
2151 case EdgeInMorphology:
2152 case TopHatMorphology:
2153 case BottomHatMorphology:
anthony930be612010-02-08 04:26:15 +00002154 /* Get Difference relative to the original image */
cristyaeb2cbc2010-05-07 13:28:58 +00002155 (void) CompositeImageChannel(new_image, (ChannelType) (channel & ~SyncChannels),
anthony7d10d742010-05-06 07:05:29 +00002156 DifferenceCompositeOp, image, 0, 0);
anthony4fd27e22010-02-07 08:17:18 +00002157 break;
anthony7d10d742010-05-06 07:05:29 +00002158 case EdgeMorphology:
2159 /* Difference the Eroded image from the saved Dilated image */
cristyaeb2cbc2010-05-07 13:28:58 +00002160 (void) CompositeImageChannel(new_image, (ChannelType) (channel & ~SyncChannels),
anthony7d10d742010-05-06 07:05:29 +00002161 DifferenceCompositeOp, grad_image, 0, 0);
anthony4fd27e22010-02-07 08:17:18 +00002162 grad_image=DestroyImage(grad_image);
2163 break;
2164 default:
2165 break;
2166 }
anthony602ab9b2010-01-05 08:06:50 +00002167
2168 return(new_image);
anthony1b2bc0a2010-05-12 05:25:22 +00002169
2170 /* Yes goto's are bad, but in this case it makes cleanup lot more efficient */
2171error_cleanup:
2172 if ( new_image != (Image *) NULL )
2173 DestroyImage(new_image);
2174exit_cleanup:
2175 if ( old_image != (Image *) NULL )
2176 DestroyImage(old_image);
2177 if ( curr_kernel != (KernelInfo *) NULL && curr_kernel != kernel )
2178 curr_kernel=DestroyKernelInfo(curr_kernel);
2179 return(new_image);
anthony602ab9b2010-01-05 08:06:50 +00002180}
anthony83ba99b2010-01-24 08:48:15 +00002181
2182/*
2183%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2184% %
2185% %
2186% %
anthony4fd27e22010-02-07 08:17:18 +00002187+ R o t a t e K e r n e l I n f o %
anthony83ba99b2010-01-24 08:48:15 +00002188% %
2189% %
2190% %
2191%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2192%
anthony4fd27e22010-02-07 08:17:18 +00002193% RotateKernelInfo() rotates the kernel by the angle given. Currently it is
anthony83ba99b2010-01-24 08:48:15 +00002194% restricted to 90 degree angles, but this may be improved in the future.
2195%
anthony4fd27e22010-02-07 08:17:18 +00002196% The format of the RotateKernelInfo method is:
anthony83ba99b2010-01-24 08:48:15 +00002197%
anthony4fd27e22010-02-07 08:17:18 +00002198% void RotateKernelInfo(KernelInfo *kernel, double angle)
anthony83ba99b2010-01-24 08:48:15 +00002199%
2200% A description of each parameter follows:
2201%
2202% o kernel: the Morphology/Convolution kernel
2203%
2204% o angle: angle to rotate in degrees
2205%
anthonyc4c86e02010-01-27 09:30:32 +00002206% This function is only internel to this module, as it is not finalized,
2207% especially with regard to non-orthogonal angles, and rotation of larger
2208% 2D kernels.
anthony83ba99b2010-01-24 08:48:15 +00002209*/
anthony4fd27e22010-02-07 08:17:18 +00002210static void RotateKernelInfo(KernelInfo *kernel, double angle)
anthony83ba99b2010-01-24 08:48:15 +00002211{
anthony1b2bc0a2010-05-12 05:25:22 +00002212 /* angle the lower kernels first */
2213 if ( kernel->next != (KernelInfo *) NULL)
2214 RotateKernelInfo(kernel->next, angle);
2215
anthony83ba99b2010-01-24 08:48:15 +00002216 /* WARNING: Currently assumes the kernel (rightly) is horizontally symetrical
2217 **
2218 ** TODO: expand beyond simple 90 degree rotates, flips and flops
2219 */
2220
2221 /* Modulus the angle */
2222 angle = fmod(angle, 360.0);
2223 if ( angle < 0 )
2224 angle += 360.0;
2225
anthony3c10fc82010-05-13 02:40:51 +00002226 if ( 337.5 < angle || angle <= 22.5 )
anthony83ba99b2010-01-24 08:48:15 +00002227 return; /* no change! - At least at this time */
2228
2229 switch (kernel->type) {
2230 /* These built-in kernels are cylindrical kernels, rotating is useless */
2231 case GaussianKernel:
2232 case LaplacianKernel:
2233 case LOGKernel:
2234 case DOGKernel:
2235 case DiskKernel:
2236 case ChebyshevKernel:
2237 case ManhattenKernel:
2238 case EuclideanKernel:
2239 return;
2240
2241 /* These may be rotatable at non-90 angles in the future */
2242 /* but simply rotating them in multiples of 90 degrees is useless */
2243 case SquareKernel:
2244 case DiamondKernel:
2245 case PlusKernel:
2246 return;
2247
2248 /* These only allows a +/-90 degree rotation (by transpose) */
2249 /* A 180 degree rotation is useless */
2250 case BlurKernel:
2251 case RectangleKernel:
2252 if ( 135.0 < angle && angle <= 225.0 )
2253 return;
2254 if ( 225.0 < angle && angle <= 315.0 )
2255 angle -= 180;
2256 break;
2257
2258 /* these are freely rotatable in 90 degree units */
anthony3c10fc82010-05-13 02:40:51 +00002259 case SobelKernel:
anthony83ba99b2010-01-24 08:48:15 +00002260 case CometKernel:
2261 case UndefinedKernel:
2262 case UserDefinedKernel:
2263 break;
2264 }
anthony3c10fc82010-05-13 02:40:51 +00002265 /* Attempt rotations by 45 degrees */
2266 if ( 22.5 < fmod(angle,90.0) && fmod(angle,90.0) <= 67.5 )
2267 {
2268 if ( kernel->width == 3 && kernel->height == 3 )
2269 { /* Rotate a 3x3 square by 45 degree angle */
2270 MagickRealType t = kernel->values[0];
2271 kernel->values[0] = kernel->values[1];
2272 kernel->values[1] = kernel->values[2];
2273 kernel->values[2] = kernel->values[5];
2274 kernel->values[5] = kernel->values[8];
2275 kernel->values[8] = kernel->values[7];
2276 kernel->values[7] = kernel->values[6];
2277 kernel->values[6] = kernel->values[3];
2278 kernel->values[3] = t;
2279 angle = fmod(angle+45.0, 360.0);
2280 }
2281 else
2282 perror("Unable to rotate non-3x3 kernel by 45 degrees");
2283 }
2284 if ( 45.0 < fmod(angle, 180.0) && fmod(angle,180.0) <= 135.0 )
2285 {
2286 if ( kernel->width == 1 || kernel->height == 1 )
2287 { /* Do a transpose of the image, which results in a 90
2288 ** degree rotation of a 1 dimentional kernel
2289 */
2290 long
2291 t;
2292 t = (long) kernel->width;
2293 kernel->width = kernel->height;
2294 kernel->height = (unsigned long) t;
2295 t = kernel->x;
2296 kernel->x = kernel->y;
2297 kernel->y = t;
2298 if ( kernel->width == 1 )
2299 angle = fmod(angle+270.0, 360.0); /* angle -90 degrees */
2300 else
2301 angle = fmod(angle+90.0, 360.0); /* angle +90 degrees */
2302 }
2303 else if ( kernel->width == kernel->height )
2304 { /* Rotate a square array of values by 90 degrees */
2305 register unsigned long
2306 i,j,x,y;
2307 register MagickRealType
2308 *k,t;
2309 k=kernel->values;
2310 for( i=0, x=kernel->width-1; i<=x; i++, x--)
2311 for( j=0, y=kernel->height-1; j<y; j++, y--)
2312 { t = k[i+j*kernel->width];
2313 k[i+j*kernel->width] = k[y+i*kernel->width];
2314 k[y+i*kernel->width] = k[x+y*kernel->width];
2315 k[x+y*kernel->width] = k[j+x*kernel->width];
2316 k[j+x*kernel->width] = t;
2317 }
2318 angle = fmod(angle+90.0, 360.0); /* angle +90 degrees */
2319 }
2320 else
2321 perror("Unable to rotate a non-square, non-linear kernel 90 degrees");
2322 }
anthony83ba99b2010-01-24 08:48:15 +00002323 if ( 135.0 < angle && angle <= 225.0 )
2324 {
2325 /* For a 180 degree rotation - also know as a reflection */
2326 /* This is actually a very very common operation! */
2327 /* Basically all that is needed is a reversal of the kernel data! */
2328 unsigned long
2329 i,j;
2330 register double
2331 *k,t;
2332
2333 k=kernel->values;
2334 for ( i=0, j=kernel->width*kernel->height-1; i<j; i++, j--)
2335 t=k[i], k[i]=k[j], k[j]=t;
2336
anthony930be612010-02-08 04:26:15 +00002337 kernel->x = (long) kernel->width - kernel->x - 1;
2338 kernel->y = (long) kernel->height - kernel->y - 1;
anthony3c10fc82010-05-13 02:40:51 +00002339 angle = fmod(angle+180.0, 360.0); /* angle+180 degrees */
anthony83ba99b2010-01-24 08:48:15 +00002340 }
anthony3c10fc82010-05-13 02:40:51 +00002341 /* At this point angle should at least between -45 (315) and +45 degrees
anthony83ba99b2010-01-24 08:48:15 +00002342 * In the future some form of non-orthogonal angled rotates could be
2343 * performed here, posibily with a linear kernel restriction.
2344 */
2345
2346#if 0
anthony3c10fc82010-05-13 02:40:51 +00002347 { /* Do a Flop by reversing each row.
anthony83ba99b2010-01-24 08:48:15 +00002348 */
2349 unsigned long
2350 y;
cristy150989e2010-02-01 14:59:39 +00002351 register long
anthony83ba99b2010-01-24 08:48:15 +00002352 x,r;
2353 register double
2354 *k,t;
2355
2356 for ( y=0, k=kernel->values; y < kernel->height; y++, k+=kernel->width)
2357 for ( x=0, r=kernel->width-1; x<kernel->width/2; x++, r--)
2358 t=k[x], k[x]=k[r], k[r]=t;
2359
cristyc99304f2010-02-01 15:26:27 +00002360 kernel->x = kernel->width - kernel->x - 1;
anthony83ba99b2010-01-24 08:48:15 +00002361 angle = fmod(angle+180.0, 360.0);
2362 }
2363#endif
2364 return;
2365}
2366
2367/*
2368%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2369% %
2370% %
2371% %
cristy6771f1e2010-03-05 19:43:39 +00002372% S c a l e K e r n e l I n f o %
anthonycc6c8362010-01-25 04:14:01 +00002373% %
2374% %
2375% %
2376%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2377%
anthony1b2bc0a2010-05-12 05:25:22 +00002378% ScaleKernelInfo() scales the given kernel list by the given amount, with or
2379% without normalization of the sum of the kernel values (as per given flags).
anthonycc6c8362010-01-25 04:14:01 +00002380%
anthony999bb2c2010-02-18 12:38:01 +00002381% By default (no flags given) the values within the kernel is scaled
anthony1b2bc0a2010-05-12 05:25:22 +00002382% directly using given scaling factor without change.
anthonycc6c8362010-01-25 04:14:01 +00002383%
anthony1b2bc0a2010-05-12 05:25:22 +00002384% If any 'normalize_flags' are given the kernel will first be normalized and
2385% then further scaled by the scaling factor value given. A 'PercentValue'
2386% flag will cause the given scaling factor to be divided by one hundred
2387% percent.
anthony999bb2c2010-02-18 12:38:01 +00002388%
2389% Kernel normalization ('normalize_flags' given) is designed to ensure that
2390% any use of the kernel scaling factor with 'Convolve' or 'Correlate'
anthony1b2bc0a2010-05-12 05:25:22 +00002391% morphology methods will fall into -1.0 to +1.0 range. Note that for
2392% non-HDRI versions of IM this may cause images to have any negative results
2393% clipped, unless some 'bias' is used.
anthony999bb2c2010-02-18 12:38:01 +00002394%
2395% More specifically. Kernels which only contain positive values (such as a
2396% 'Gaussian' kernel) will be scaled so that those values sum to +1.0,
anthony1b2bc0a2010-05-12 05:25:22 +00002397% ensuring a 0.0 to +1.0 output range for non-HDRI images.
anthony999bb2c2010-02-18 12:38:01 +00002398%
2399% For Kernels that contain some negative values, (such as 'Sharpen' kernels)
2400% the kernel will be scaled by the absolute of the sum of kernel values, so
2401% that it will generally fall within the +/- 1.0 range.
2402%
2403% For kernels whose values sum to zero, (such as 'Laplician' kernels) kernel
2404% will be scaled by just the sum of the postive values, so that its output
2405% range will again fall into the +/- 1.0 range.
2406%
2407% For special kernels designed for locating shapes using 'Correlate', (often
2408% only containing +1 and -1 values, representing foreground/brackground
2409% matching) a special normalization method is provided to scale the positive
2410% values seperatally to those of the negative values, so the kernel will be
2411% forced to become a zero-sum kernel better suited to such searches.
2412%
anthony1b2bc0a2010-05-12 05:25:22 +00002413% WARNING: Correct normalization of the kernel assumes that the '*_range'
anthony999bb2c2010-02-18 12:38:01 +00002414% attributes within the kernel structure have been correctly set during the
2415% kernels creation.
2416%
2417% NOTE: The values used for 'normalize_flags' have been selected specifically
anthony1b2bc0a2010-05-12 05:25:22 +00002418% to match the use of geometry options, so that '!' means NormalizeValue,
2419% '^' means CorrelateNormalizeValue, and '%' means PercentValue. All other
anthony999bb2c2010-02-18 12:38:01 +00002420% GeometryFlags values are ignored.
anthonycc6c8362010-01-25 04:14:01 +00002421%
anthony4fd27e22010-02-07 08:17:18 +00002422% The format of the ScaleKernelInfo method is:
anthonycc6c8362010-01-25 04:14:01 +00002423%
anthony999bb2c2010-02-18 12:38:01 +00002424% void ScaleKernelInfo(KernelInfo *kernel, const double scaling_factor,
2425% const MagickStatusType normalize_flags )
anthonycc6c8362010-01-25 04:14:01 +00002426%
2427% A description of each parameter follows:
2428%
2429% o kernel: the Morphology/Convolution kernel
2430%
anthony999bb2c2010-02-18 12:38:01 +00002431% o scaling_factor:
2432% multiply all values (after normalization) by this factor if not
2433% zero. If the kernel is normalized regardless of any flags.
2434%
2435% o normalize_flags:
2436% GeometryFlags defining normalization method to use.
2437% specifically: NormalizeValue, CorrelateNormalizeValue,
2438% and/or PercentValue
anthonycc6c8362010-01-25 04:14:01 +00002439%
anthonyc4c86e02010-01-27 09:30:32 +00002440% This function is internal to this module only at this time, but can be
2441% exported to other modules if needed.
anthonycc6c8362010-01-25 04:14:01 +00002442*/
cristy6771f1e2010-03-05 19:43:39 +00002443MagickExport void ScaleKernelInfo(KernelInfo *kernel,
2444 const double scaling_factor,const GeometryFlags normalize_flags)
anthonycc6c8362010-01-25 04:14:01 +00002445{
cristy150989e2010-02-01 14:59:39 +00002446 register long
anthonycc6c8362010-01-25 04:14:01 +00002447 i;
2448
anthony999bb2c2010-02-18 12:38:01 +00002449 register double
2450 pos_scale,
2451 neg_scale;
2452
anthony1b2bc0a2010-05-12 05:25:22 +00002453 /* scale the lower kernels first */
2454 if ( kernel->next != (KernelInfo *) NULL)
2455 ScaleKernelInfo(kernel->next, scaling_factor, normalize_flags);
2456
anthony999bb2c2010-02-18 12:38:01 +00002457 pos_scale = 1.0;
2458 if ( (normalize_flags&NormalizeValue) != 0 ) {
2459 /* normalize kernel appropriately */
2460 if ( fabs(kernel->positive_range + kernel->negative_range) > MagickEpsilon )
2461 pos_scale = fabs(kernel->positive_range + kernel->negative_range);
anthonycc6c8362010-01-25 04:14:01 +00002462 else
anthony999bb2c2010-02-18 12:38:01 +00002463 pos_scale = kernel->positive_range; /* special zero-summing kernel */
2464 }
2465 /* force kernel into being a normalized zero-summing kernel */
2466 if ( (normalize_flags&CorrelateNormalizeValue) != 0 ) {
2467 pos_scale = ( fabs(kernel->positive_range) > MagickEpsilon )
2468 ? kernel->positive_range : 1.0;
2469 neg_scale = ( fabs(kernel->negative_range) > MagickEpsilon )
2470 ? -kernel->negative_range : 1.0;
2471 }
2472 else
2473 neg_scale = pos_scale;
2474
2475 /* finialize scaling_factor for positive and negative components */
2476 pos_scale = scaling_factor/pos_scale;
2477 neg_scale = scaling_factor/neg_scale;
2478 if ( (normalize_flags&PercentValue) != 0 ) {
2479 pos_scale /= 100.0;
2480 neg_scale /= 100.0;
anthonycc6c8362010-01-25 04:14:01 +00002481 }
2482
cristy150989e2010-02-01 14:59:39 +00002483 for (i=0; i < (long) (kernel->width*kernel->height); i++)
anthonycc6c8362010-01-25 04:14:01 +00002484 if ( ! IsNan(kernel->values[i]) )
anthony999bb2c2010-02-18 12:38:01 +00002485 kernel->values[i] *= (kernel->values[i] >= 0) ? pos_scale : neg_scale;
anthonycc6c8362010-01-25 04:14:01 +00002486
anthony999bb2c2010-02-18 12:38:01 +00002487 /* convolution output range */
2488 kernel->positive_range *= pos_scale;
2489 kernel->negative_range *= neg_scale;
2490 /* maximum and minimum values in kernel */
2491 kernel->maximum *= (kernel->maximum >= 0.0) ? pos_scale : neg_scale;
2492 kernel->minimum *= (kernel->minimum >= 0.0) ? pos_scale : neg_scale;
2493
2494 /* swap kernel settings if user scaling factor is negative */
2495 if ( scaling_factor < MagickEpsilon ) {
2496 double t;
2497 t = kernel->positive_range;
2498 kernel->positive_range = kernel->negative_range;
2499 kernel->negative_range = t;
2500 t = kernel->maximum;
2501 kernel->maximum = kernel->minimum;
2502 kernel->minimum = 1;
2503 }
anthonycc6c8362010-01-25 04:14:01 +00002504
2505 return;
2506}
2507
2508/*
2509%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2510% %
2511% %
2512% %
anthony4fd27e22010-02-07 08:17:18 +00002513+ S h o w K e r n e l I n f o %
anthony83ba99b2010-01-24 08:48:15 +00002514% %
2515% %
2516% %
2517%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2518%
anthony4fd27e22010-02-07 08:17:18 +00002519% ShowKernelInfo() outputs the details of the given kernel defination to
2520% standard error, generally due to a users 'showkernel' option request.
anthony83ba99b2010-01-24 08:48:15 +00002521%
2522% The format of the ShowKernel method is:
2523%
anthony4fd27e22010-02-07 08:17:18 +00002524% void ShowKernelInfo(KernelInfo *kernel)
anthony83ba99b2010-01-24 08:48:15 +00002525%
2526% A description of each parameter follows:
2527%
2528% o kernel: the Morphology/Convolution kernel
2529%
anthonyc4c86e02010-01-27 09:30:32 +00002530% This function is internal to this module only at this time. That may change
2531% in the future.
anthony83ba99b2010-01-24 08:48:15 +00002532*/
anthony4fd27e22010-02-07 08:17:18 +00002533MagickExport void ShowKernelInfo(KernelInfo *kernel)
anthony83ba99b2010-01-24 08:48:15 +00002534{
anthony7a01dcf2010-05-11 12:25:52 +00002535 KernelInfo
2536 *k;
anthony83ba99b2010-01-24 08:48:15 +00002537
anthony7a01dcf2010-05-11 12:25:52 +00002538 long
2539 c, i, u, v;
2540
2541 for (c=0, k=kernel; k != (KernelInfo *) NULL; c++, k=k->next ) {
2542
2543 fprintf(stderr, "Kernel ");
2544 if ( kernel->next != (KernelInfo *) NULL )
2545 fprintf(stderr, " #%ld", c );
2546 fprintf(stderr, " \"%s\" of size %lux%lu%+ld%+ld ",
2547 MagickOptionToMnemonic(MagickKernelOptions, k->type),
2548 kernel->width, k->height,
2549 kernel->x, kernel->y );
2550 fprintf(stderr,
2551 " with values from %.*lg to %.*lg\n",
2552 GetMagickPrecision(), k->minimum,
2553 GetMagickPrecision(), k->maximum);
2554 fprintf(stderr, "Forming convolution output range from %.*lg to %.*lg%s\n",
2555 GetMagickPrecision(), k->negative_range,
2556 GetMagickPrecision(), k->positive_range,
2557 /*kernel->normalized == MagickTrue ? " (normalized)" : */ "" );
2558 for (i=v=0; v < (long) k->height; v++) {
2559 fprintf(stderr,"%2ld:",v);
2560 for (u=0; u < (long) k->width; u++, i++)
2561 if ( IsNan(k->values[i]) )
2562 fprintf(stderr," %*s", GetMagickPrecision()+2, "nan");
2563 else
2564 fprintf(stderr," %*.*lg", GetMagickPrecision()+2,
2565 GetMagickPrecision(), k->values[i]);
2566 fprintf(stderr,"\n");
2567 }
anthony83ba99b2010-01-24 08:48:15 +00002568 }
2569}
anthonycc6c8362010-01-25 04:14:01 +00002570
2571/*
2572%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2573% %
2574% %
2575% %
anthony4fd27e22010-02-07 08:17:18 +00002576+ Z e r o K e r n e l N a n s %
anthonycc6c8362010-01-25 04:14:01 +00002577% %
2578% %
2579% %
2580%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2581%
2582% ZeroKernelNans() replaces any special 'nan' value that may be present in
2583% the kernel with a zero value. This is typically done when the kernel will
2584% be used in special hardware (GPU) convolution processors, to simply
2585% matters.
2586%
2587% The format of the ZeroKernelNans method is:
2588%
2589% voidZeroKernelNans (KernelInfo *kernel)
2590%
2591% A description of each parameter follows:
2592%
2593% o kernel: the Morphology/Convolution kernel
2594%
2595% FUTURE: return the information in a string for API usage.
2596*/
anthonyc4c86e02010-01-27 09:30:32 +00002597MagickExport void ZeroKernelNans(KernelInfo *kernel)
anthonycc6c8362010-01-25 04:14:01 +00002598{
cristy150989e2010-02-01 14:59:39 +00002599 register long
anthonycc6c8362010-01-25 04:14:01 +00002600 i;
2601
anthony1b2bc0a2010-05-12 05:25:22 +00002602 /* scale the lower kernels first */
2603 if ( kernel->next != (KernelInfo *) NULL)
2604 ZeroKernelNans(kernel->next);
2605
cristy150989e2010-02-01 14:59:39 +00002606 for (i=0; i < (long) (kernel->width*kernel->height); i++)
anthonycc6c8362010-01-25 04:14:01 +00002607 if ( IsNan(kernel->values[i]) )
2608 kernel->values[i] = 0.0;
2609
2610 return;
2611}