blob: fe7cf3226d348f106d054e8e0e4d6cc0e2fc66b6 [file] [log] [blame]
cristy701db312009-11-20 03:14:08 +00001/*
2%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3% %
4% %
5% %
6% M M OOO RRRR PPPP H H OOO L OOO GGGG Y Y %
7% MM MM O O R R P P H H O O L O O G Y Y %
8% M M M O O RRRR PPPP HHHHH O O L O O G GGG Y %
9% M M O O R R P H H O O L O O G G Y %
10% M M OOO R R P H H OOO LLLLL OOO GGG Y %
11% %
12% %
13% MagickCore Morphology Methods %
14% %
15% Software Design %
16% Anthony Thyssen %
anthonyc94cdb02010-01-06 08:15:29 +000017% January 2010 %
cristy701db312009-11-20 03:14:08 +000018% %
19% %
cristy16af1cb2009-12-11 21:38:29 +000020% Copyright 1999-2010 ImageMagick Studio LLC, a non-profit organization %
cristy701db312009-11-20 03:14:08 +000021% dedicated to making software imaging solutions freely available. %
22% %
23% You may not use this file except in compliance with the License. You may %
24% obtain a copy of the License at %
25% %
26% http://www.imagemagick.org/script/license.php %
27% %
28% Unless required by applicable law or agreed to in writing, software %
29% distributed under the License is distributed on an "AS IS" BASIS, %
30% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %
31% See the License for the specific language governing permissions and %
32% limitations under the License. %
33% %
34%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
35%
anthony1b2bc0a2010-05-12 05:25:22 +000036% Morpology is the the application of various kernels, of any size and even
anthony602ab9b2010-01-05 08:06:50 +000037% shape, to a image in various ways (typically binary, but not always).
cristy701db312009-11-20 03:14:08 +000038%
anthony602ab9b2010-01-05 08:06:50 +000039% Convolution (weighted sum or average) is just one specific type of
40% morphology. Just one that is very common for image bluring and sharpening
41% effects. Not only 2D Gaussian blurring, but also 2-pass 1D Blurring.
42%
43% This module provides not only a general morphology function, and the ability
44% to apply more advanced or iterative morphologies, but also functions for the
45% generation of many different types of kernel arrays from user supplied
46% arguments. Prehaps even the generation of a kernel from a small image.
cristy701db312009-11-20 03:14:08 +000047*/
48
49/*
50 Include declarations.
51*/
52#include "magick/studio.h"
anthony602ab9b2010-01-05 08:06:50 +000053#include "magick/artifact.h"
cristy701db312009-11-20 03:14:08 +000054#include "magick/cache-view.h"
55#include "magick/color-private.h"
56#include "magick/enhance.h"
57#include "magick/exception.h"
58#include "magick/exception-private.h"
anthony602ab9b2010-01-05 08:06:50 +000059#include "magick/gem.h"
cristy701db312009-11-20 03:14:08 +000060#include "magick/hashmap.h"
61#include "magick/image.h"
cristybba804b2010-01-05 15:39:59 +000062#include "magick/image-private.h"
cristy701db312009-11-20 03:14:08 +000063#include "magick/list.h"
anthony29188a82010-01-22 10:12:34 +000064#include "magick/magick.h"
cristy701db312009-11-20 03:14:08 +000065#include "magick/memory_.h"
66#include "magick/monitor-private.h"
67#include "magick/morphology.h"
anthony602ab9b2010-01-05 08:06:50 +000068#include "magick/option.h"
cristy701db312009-11-20 03:14:08 +000069#include "magick/pixel-private.h"
70#include "magick/prepress.h"
71#include "magick/quantize.h"
72#include "magick/registry.h"
73#include "magick/semaphore.h"
74#include "magick/splay-tree.h"
75#include "magick/statistic.h"
76#include "magick/string_.h"
anthony602ab9b2010-01-05 08:06:50 +000077#include "magick/string-private.h"
78#include "magick/token.h"
cristya29d45f2010-03-05 21:14:54 +000079
anthony602ab9b2010-01-05 08:06:50 +000080/*
cristya29d45f2010-03-05 21:14:54 +000081 The following test is for special floating point numbers of value NaN (not
82 a number), that may be used within a Kernel Definition. NaN's are defined
83 as part of the IEEE standard for floating point number representation.
84
anthony1b2bc0a2010-05-12 05:25:22 +000085 These are used a Kernel value of NaN means that that kernel position is not
cristya29d45f2010-03-05 21:14:54 +000086 part of the normal convolution or morphology process, and thus allowing the
87 use of 'shaped' kernels.
88
89 Special properities two NaN's are never equal, even if they are from the
90 same variable That is the IsNaN() macro is only true if the value is NaN.
91*/
anthony602ab9b2010-01-05 08:06:50 +000092#define IsNan(a) ((a)!=(a))
93
anthony29188a82010-01-22 10:12:34 +000094/*
cristya29d45f2010-03-05 21:14:54 +000095 Other global definitions used by module.
96*/
anthony29188a82010-01-22 10:12:34 +000097static inline double MagickMin(const double x,const double y)
98{
99 return( x < y ? x : y);
100}
101static inline double MagickMax(const double x,const double y)
102{
103 return( x > y ? x : y);
104}
105#define Minimize(assign,value) assign=MagickMin(assign,value)
106#define Maximize(assign,value) assign=MagickMax(assign,value)
107
anthonyc4c86e02010-01-27 09:30:32 +0000108/* Currently these are only internal to this module */
109static void
anthony3c10fc82010-05-13 02:40:51 +0000110 ExpandKernelInfo(KernelInfo *, double),
cristyef656912010-03-05 19:54:59 +0000111 RotateKernelInfo(KernelInfo *, double);
anthony602ab9b2010-01-05 08:06:50 +0000112
anthony3dd0f622010-05-13 12:57:32 +0000113
114/* Quick function to find last kernel in a kernel list */
115static inline KernelInfo *LastKernelInfo(KernelInfo *kernel)
116{
117 while (kernel->next != (KernelInfo *) NULL)
118 kernel = kernel->next;
119 return(kernel);
120}
121
122
anthony602ab9b2010-01-05 08:06:50 +0000123/*
124%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
125% %
126% %
127% %
anthony83ba99b2010-01-24 08:48:15 +0000128% A c q u i r e K e r n e l I n f o %
anthony602ab9b2010-01-05 08:06:50 +0000129% %
130% %
131% %
132%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
133%
cristy2be15382010-01-21 02:38:03 +0000134% AcquireKernelInfo() takes the given string (generally supplied by the
anthony602ab9b2010-01-05 08:06:50 +0000135% user) and converts it into a Morphology/Convolution Kernel. This allows
136% users to specify a kernel from a number of pre-defined kernels, or to fully
137% specify their own kernel for a specific Convolution or Morphology
138% Operation.
139%
140% The kernel so generated can be any rectangular array of floating point
141% values (doubles) with the 'control point' or 'pixel being affected'
142% anywhere within that array of values.
143%
anthony83ba99b2010-01-24 08:48:15 +0000144% Previously IM was restricted to a square of odd size using the exact
145% center as origin, this is no longer the case, and any rectangular kernel
146% with any value being declared the origin. This in turn allows the use of
147% highly asymmetrical kernels.
anthony602ab9b2010-01-05 08:06:50 +0000148%
149% The floating point values in the kernel can also include a special value
anthony83ba99b2010-01-24 08:48:15 +0000150% known as 'nan' or 'not a number' to indicate that this value is not part
151% of the kernel array. This allows you to shaped the kernel within its
152% rectangular area. That is 'nan' values provide a 'mask' for the kernel
153% shape. However at least one non-nan value must be provided for correct
154% working of a kernel.
anthony602ab9b2010-01-05 08:06:50 +0000155%
anthony7a01dcf2010-05-11 12:25:52 +0000156% The returned kernel should be freed using the DestroyKernelInfo() when you
157% are finished with it. Do not free this memory yourself.
anthony602ab9b2010-01-05 08:06:50 +0000158%
159% Input kernel defintion strings can consist of any of three types.
160%
anthony29188a82010-01-22 10:12:34 +0000161% "name:args"
162% Select from one of the built in kernels, using the name and
163% geometry arguments supplied. See AcquireKernelBuiltIn()
anthony602ab9b2010-01-05 08:06:50 +0000164%
165% "WxH[+X+Y]:num, num, num ..."
anthony1b2bc0a2010-05-12 05:25:22 +0000166% a kernel of size W by H, with W*H floating point numbers following.
anthony602ab9b2010-01-05 08:06:50 +0000167% the 'center' can be optionally be defined at +X+Y (such that +0+0
anthony29188a82010-01-22 10:12:34 +0000168% is top left corner). If not defined the pixel in the center, for
169% odd sizes, or to the immediate top or left of center for even sizes
170% is automatically selected.
anthony602ab9b2010-01-05 08:06:50 +0000171%
anthony29188a82010-01-22 10:12:34 +0000172% "num, num, num, num, ..."
173% list of floating point numbers defining an 'old style' odd sized
174% square kernel. At least 9 values should be provided for a 3x3
175% square kernel, 25 for a 5x5 square kernel, 49 for 7x7, etc.
176% Values can be space or comma separated. This is not recommended.
anthony602ab9b2010-01-05 08:06:50 +0000177%
anthony83ba99b2010-01-24 08:48:15 +0000178% Note that 'name' kernels will start with an alphabetic character while the
179% new kernel specification has a ':' character in its specification string.
180% If neither is the case, it is assumed an old style of a simple list of
181% numbers generating a odd-sized square kernel has been given.
anthony602ab9b2010-01-05 08:06:50 +0000182%
anthony7a01dcf2010-05-11 12:25:52 +0000183% You can define a 'list of kernels' which can be used by some morphology
184% operators A list is defined as a semi-colon seperated list kernels.
185%
anthonydbc89892010-05-12 07:05:27 +0000186% " kernel ; kernel ; kernel ; "
anthony7a01dcf2010-05-11 12:25:52 +0000187%
anthonydbc89892010-05-12 07:05:27 +0000188% Extra ';' characters are simply ignored.
anthony7a01dcf2010-05-11 12:25:52 +0000189%
anthony602ab9b2010-01-05 08:06:50 +0000190% The format of the AcquireKernal method is:
191%
cristy2be15382010-01-21 02:38:03 +0000192% KernelInfo *AcquireKernelInfo(const char *kernel_string)
anthony602ab9b2010-01-05 08:06:50 +0000193%
194% A description of each parameter follows:
195%
196% o kernel_string: the Morphology/Convolution kernel wanted.
197%
198*/
199
anthonyc84dce52010-05-07 05:42:23 +0000200/* This was separated so that it could be used as a separate
anthony5ef8e942010-05-11 06:51:12 +0000201** array input handling function, such as for -color-matrix
anthonyc84dce52010-05-07 05:42:23 +0000202*/
anthony5ef8e942010-05-11 06:51:12 +0000203static KernelInfo *ParseKernelArray(const char *kernel_string)
anthony602ab9b2010-01-05 08:06:50 +0000204{
cristy2be15382010-01-21 02:38:03 +0000205 KernelInfo
anthony602ab9b2010-01-05 08:06:50 +0000206 *kernel;
207
208 char
209 token[MaxTextExtent];
210
anthony602ab9b2010-01-05 08:06:50 +0000211 const char
anthony5ef8e942010-05-11 06:51:12 +0000212 *p,
213 *end;
anthony602ab9b2010-01-05 08:06:50 +0000214
anthonyc84dce52010-05-07 05:42:23 +0000215 register long
216 i;
anthony602ab9b2010-01-05 08:06:50 +0000217
anthony29188a82010-01-22 10:12:34 +0000218 double
219 nan = sqrt((double)-1.0); /* Special Value : Not A Number */
220
cristy2be15382010-01-21 02:38:03 +0000221 kernel=(KernelInfo *) AcquireMagickMemory(sizeof(*kernel));
222 if (kernel == (KernelInfo *)NULL)
anthony602ab9b2010-01-05 08:06:50 +0000223 return(kernel);
224 (void) ResetMagickMemory(kernel,0,sizeof(*kernel));
anthony7a01dcf2010-05-11 12:25:52 +0000225 kernel->minimum = kernel->maximum = 0.0;
226 kernel->negative_range = kernel->positive_range = 0.0;
anthony602ab9b2010-01-05 08:06:50 +0000227 kernel->type = UserDefinedKernel;
anthony7a01dcf2010-05-11 12:25:52 +0000228 kernel->next = (KernelInfo *) NULL;
cristyd43a46b2010-01-21 02:13:41 +0000229 kernel->signature = MagickSignature;
anthony602ab9b2010-01-05 08:06:50 +0000230
anthony5ef8e942010-05-11 06:51:12 +0000231 /* find end of this specific kernel definition string */
232 end = strchr(kernel_string, ';');
233 if ( end == (char *) NULL )
234 end = strchr(kernel_string, '\0');
235
anthony602ab9b2010-01-05 08:06:50 +0000236 /* Has a ':' in argument - New user kernel specification */
237 p = strchr(kernel_string, ':');
anthony5ef8e942010-05-11 06:51:12 +0000238 if ( p != (char *) NULL && p < end)
anthony602ab9b2010-01-05 08:06:50 +0000239 {
anthonyc84dce52010-05-07 05:42:23 +0000240 MagickStatusType
241 flags;
242
243 GeometryInfo
244 args;
245
anthony602ab9b2010-01-05 08:06:50 +0000246 /* ParseGeometry() needs the geometry separated! -- Arrgghh */
cristy150989e2010-02-01 14:59:39 +0000247 memcpy(token, kernel_string, (size_t) (p-kernel_string));
anthony602ab9b2010-01-05 08:06:50 +0000248 token[p-kernel_string] = '\0';
anthonyc84dce52010-05-07 05:42:23 +0000249 SetGeometryInfo(&args);
anthony602ab9b2010-01-05 08:06:50 +0000250 flags = ParseGeometry(token, &args);
anthony602ab9b2010-01-05 08:06:50 +0000251
anthony29188a82010-01-22 10:12:34 +0000252 /* Size handling and checks of geometry settings */
anthony602ab9b2010-01-05 08:06:50 +0000253 if ( (flags & WidthValue) == 0 ) /* if no width then */
254 args.rho = args.sigma; /* then width = height */
255 if ( args.rho < 1.0 ) /* if width too small */
256 args.rho = 1.0; /* then width = 1 */
257 if ( args.sigma < 1.0 ) /* if height too small */
258 args.sigma = args.rho; /* then height = width */
259 kernel->width = (unsigned long)args.rho;
260 kernel->height = (unsigned long)args.sigma;
261
262 /* Offset Handling and Checks */
263 if ( args.xi < 0.0 || args.psi < 0.0 )
anthony83ba99b2010-01-24 08:48:15 +0000264 return(DestroyKernelInfo(kernel));
cristyc99304f2010-02-01 15:26:27 +0000265 kernel->x = ((flags & XValue)!=0) ? (long)args.xi
cristy150989e2010-02-01 14:59:39 +0000266 : (long) (kernel->width-1)/2;
cristyc99304f2010-02-01 15:26:27 +0000267 kernel->y = ((flags & YValue)!=0) ? (long)args.psi
cristy150989e2010-02-01 14:59:39 +0000268 : (long) (kernel->height-1)/2;
cristyc99304f2010-02-01 15:26:27 +0000269 if ( kernel->x >= (long) kernel->width ||
270 kernel->y >= (long) kernel->height )
anthony83ba99b2010-01-24 08:48:15 +0000271 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000272
273 p++; /* advance beyond the ':' */
274 }
275 else
anthonyc84dce52010-05-07 05:42:23 +0000276 { /* ELSE - Old old specification, forming odd-square kernel */
anthony602ab9b2010-01-05 08:06:50 +0000277 /* count up number of values given */
278 p=(const char *) kernel_string;
cristya699b172010-01-06 16:48:49 +0000279 while ((isspace((int) ((unsigned char) *p)) != 0) || (*p == '\''))
anthony29188a82010-01-22 10:12:34 +0000280 p++; /* ignore "'" chars for convolve filter usage - Cristy */
anthony5ef8e942010-05-11 06:51:12 +0000281 for (i=0; p < end; i++)
anthony602ab9b2010-01-05 08:06:50 +0000282 {
283 GetMagickToken(p,&p,token);
284 if (*token == ',')
285 GetMagickToken(p,&p,token);
286 }
287 /* set the size of the kernel - old sized square */
288 kernel->width = kernel->height= (unsigned long) sqrt((double) i+1.0);
cristyc99304f2010-02-01 15:26:27 +0000289 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony602ab9b2010-01-05 08:06:50 +0000290 p=(const char *) kernel_string;
anthony29188a82010-01-22 10:12:34 +0000291 while ((isspace((int) ((unsigned char) *p)) != 0) || (*p == '\''))
292 p++; /* ignore "'" chars for convolve filter usage - Cristy */
anthony602ab9b2010-01-05 08:06:50 +0000293 }
294
295 /* Read in the kernel values from rest of input string argument */
296 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
297 kernel->height*sizeof(double));
298 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +0000299 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000300
cristyc99304f2010-02-01 15:26:27 +0000301 kernel->minimum = +MagickHuge;
302 kernel->maximum = -MagickHuge;
303 kernel->negative_range = kernel->positive_range = 0.0;
anthonyc84dce52010-05-07 05:42:23 +0000304
anthony5ef8e942010-05-11 06:51:12 +0000305 for (i=0; (i < (long) (kernel->width*kernel->height)) && (p < end); i++)
anthony602ab9b2010-01-05 08:06:50 +0000306 {
307 GetMagickToken(p,&p,token);
308 if (*token == ',')
309 GetMagickToken(p,&p,token);
anthony29188a82010-01-22 10:12:34 +0000310 if ( LocaleCompare("nan",token) == 0
anthonyc84dce52010-05-07 05:42:23 +0000311 || LocaleCompare("-",token) == 0 ) {
anthony29188a82010-01-22 10:12:34 +0000312 kernel->values[i] = nan; /* do not include this value in kernel */
313 }
314 else {
315 kernel->values[i] = StringToDouble(token);
316 ( kernel->values[i] < 0)
cristyc99304f2010-02-01 15:26:27 +0000317 ? ( kernel->negative_range += kernel->values[i] )
318 : ( kernel->positive_range += kernel->values[i] );
319 Minimize(kernel->minimum, kernel->values[i]);
320 Maximize(kernel->maximum, kernel->values[i]);
anthony29188a82010-01-22 10:12:34 +0000321 }
anthony602ab9b2010-01-05 08:06:50 +0000322 }
anthony29188a82010-01-22 10:12:34 +0000323
anthony5ef8e942010-05-11 06:51:12 +0000324 /* sanity check -- no more values in kernel definition */
325 GetMagickToken(p,&p,token);
326 if ( *token != '\0' && *token != ';' && *token != '\'' )
327 return(DestroyKernelInfo(kernel));
328
anthonyc84dce52010-05-07 05:42:23 +0000329#if 0
330 /* this was the old method of handling a incomplete kernel */
cristy150989e2010-02-01 14:59:39 +0000331 if ( i < (long) (kernel->width*kernel->height) ) {
cristyc99304f2010-02-01 15:26:27 +0000332 Minimize(kernel->minimum, kernel->values[i]);
333 Maximize(kernel->maximum, kernel->values[i]);
cristy150989e2010-02-01 14:59:39 +0000334 for ( ; i < (long) (kernel->width*kernel->height); i++)
anthony29188a82010-01-22 10:12:34 +0000335 kernel->values[i]=0.0;
336 }
anthonyc84dce52010-05-07 05:42:23 +0000337#else
338 /* Number of values for kernel was not enough - Report Error */
339 if ( i < (long) (kernel->width*kernel->height) )
340 return(DestroyKernelInfo(kernel));
341#endif
342
343 /* check that we recieved at least one real (non-nan) value! */
344 if ( kernel->minimum == MagickHuge )
345 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000346
347 return(kernel);
348}
anthonyc84dce52010-05-07 05:42:23 +0000349
anthony5ef8e942010-05-11 06:51:12 +0000350static KernelInfo *ParseNamedKernel(const char *kernel_string)
anthonyc84dce52010-05-07 05:42:23 +0000351{
352 char
353 token[MaxTextExtent];
354
anthony5ef8e942010-05-11 06:51:12 +0000355 long
356 type;
357
anthonyc84dce52010-05-07 05:42:23 +0000358 const char
anthony7a01dcf2010-05-11 12:25:52 +0000359 *p,
360 *end;
anthonyc84dce52010-05-07 05:42:23 +0000361
362 MagickStatusType
363 flags;
364
365 GeometryInfo
366 args;
367
anthonyc84dce52010-05-07 05:42:23 +0000368 /* Parse special 'named' kernel */
anthony5ef8e942010-05-11 06:51:12 +0000369 GetMagickToken(kernel_string,&p,token);
anthonyc84dce52010-05-07 05:42:23 +0000370 type=ParseMagickOption(MagickKernelOptions,MagickFalse,token);
371 if ( type < 0 || type == UserDefinedKernel )
anthony5ef8e942010-05-11 06:51:12 +0000372 return((KernelInfo *)NULL); /* not a valid named kernel */
anthonyc84dce52010-05-07 05:42:23 +0000373
374 while (((isspace((int) ((unsigned char) *p)) != 0) ||
anthony5ef8e942010-05-11 06:51:12 +0000375 (*p == ',') || (*p == ':' )) && (*p != '\0') && (*p != ';'))
anthonyc84dce52010-05-07 05:42:23 +0000376 p++;
anthony7a01dcf2010-05-11 12:25:52 +0000377
378 end = strchr(p, ';'); /* end of this kernel defintion */
379 if ( end == (char *) NULL )
380 end = strchr(p, '\0');
381
382 /* ParseGeometry() needs the geometry separated! -- Arrgghh */
383 memcpy(token, p, (size_t) (end-p));
384 token[end-p] = '\0';
anthonyc84dce52010-05-07 05:42:23 +0000385 SetGeometryInfo(&args);
anthony7a01dcf2010-05-11 12:25:52 +0000386 flags = ParseGeometry(token, &args);
anthonyc84dce52010-05-07 05:42:23 +0000387
anthony3c10fc82010-05-13 02:40:51 +0000388#if 0
389 /* For Debugging Geometry Input */
390 fprintf(stderr, "Geometry = %04x : %lf x %lf %+lf %+lf\n",
391 flags, args.rho, args.sigma, args.xi, args.psi );
392#endif
393
anthonyc84dce52010-05-07 05:42:23 +0000394 /* special handling of missing values in input string */
395 switch( type ) {
anthony5ef8e942010-05-11 06:51:12 +0000396 case RectangleKernel:
397 if ( (flags & WidthValue) == 0 ) /* if no width then */
398 args.rho = args.sigma; /* then width = height */
399 if ( args.rho < 1.0 ) /* if width too small */
400 args.rho = 3; /* then width = 3 */
401 if ( args.sigma < 1.0 ) /* if height too small */
402 args.sigma = args.rho; /* then height = width */
403 if ( (flags & XValue) == 0 ) /* center offset if not defined */
404 args.xi = (double)(((long)args.rho-1)/2);
405 if ( (flags & YValue) == 0 )
406 args.psi = (double)(((long)args.sigma-1)/2);
407 break;
408 case SquareKernel:
409 case DiamondKernel:
410 case DiskKernel:
411 case PlusKernel:
anthony3dd0f622010-05-13 12:57:32 +0000412 case CrossKernel:
anthony5ef8e942010-05-11 06:51:12 +0000413 /* If no scale given (a 0 scale is valid! - set it to 1.0 */
414 if ( (flags & HeightValue) == 0 )
415 args.sigma = 1.0;
416 break;
anthonyc1061722010-05-14 06:23:49 +0000417 case RingKernel:
418 if ( (flags & XValue) == 0 )
419 args.xi = 1.0;
420 break;
anthony5ef8e942010-05-11 06:51:12 +0000421 case ChebyshevKernel:
422 case ManhattenKernel:
423 case EuclideanKernel:
424 if ( (flags & HeightValue) == 0 )
425 args.sigma = 100.0; /* default distance scaling */
426 else if ( (flags & AspectValue ) != 0 ) /* '!' flag */
427 args.sigma = QuantumRange/args.sigma; /* maximum pixel distance */
428 else if ( (flags & PercentValue ) != 0 ) /* '%' flag */
429 args.sigma *= QuantumRange/100.0; /* percentage of color range */
430 break;
431 default:
432 break;
anthonyc84dce52010-05-07 05:42:23 +0000433 }
434
435 return(AcquireKernelBuiltIn((KernelInfoType)type, &args));
436}
437
anthony5ef8e942010-05-11 06:51:12 +0000438MagickExport KernelInfo *AcquireKernelInfo(const char *kernel_string)
439{
anthony7a01dcf2010-05-11 12:25:52 +0000440
441 KernelInfo
anthonydbc89892010-05-12 07:05:27 +0000442 *kernel,
443 *new_kernel,
444 *last_kernel;
anthony7a01dcf2010-05-11 12:25:52 +0000445
anthony5ef8e942010-05-11 06:51:12 +0000446 char
447 token[MaxTextExtent];
448
anthony7a01dcf2010-05-11 12:25:52 +0000449 const char
anthonydbc89892010-05-12 07:05:27 +0000450 *p;
anthony7a01dcf2010-05-11 12:25:52 +0000451
anthonye108a3f2010-05-12 07:24:03 +0000452 unsigned long
453 kernel_number;
454
anthonydbc89892010-05-12 07:05:27 +0000455 p = kernel_string;
456 kernel = last_kernel = NULL;
anthonye108a3f2010-05-12 07:24:03 +0000457 kernel_number = 0;
anthony5ef8e942010-05-11 06:51:12 +0000458
anthonydbc89892010-05-12 07:05:27 +0000459 while ( GetMagickToken(p,NULL,token), *token != '\0' ) {
anthony7a01dcf2010-05-11 12:25:52 +0000460
anthonydbc89892010-05-12 07:05:27 +0000461 /* ignore multiple ';' following each other */
462 if ( *token != ';' ) {
anthony7a01dcf2010-05-11 12:25:52 +0000463
anthonydbc89892010-05-12 07:05:27 +0000464 /* tokens starting with alpha is a Named kernel */
465 if (isalpha((int) *token) == 0)
466 new_kernel = ParseKernelArray(p);
467 else /* otherwise a user defined kernel array */
468 new_kernel = ParseNamedKernel(p);
anthony7a01dcf2010-05-11 12:25:52 +0000469
anthonye108a3f2010-05-12 07:24:03 +0000470 /* Error handling -- this is not proper error handling! */
471 if ( new_kernel == (KernelInfo *) NULL ) {
472 fprintf(stderr, "Failed to parse kernel number #%lu\n", kernel_number);
473 if ( kernel != (KernelInfo *) NULL )
474 kernel=DestroyKernelInfo(kernel);
475 return((KernelInfo *) NULL);
anthonydbc89892010-05-12 07:05:27 +0000476 }
anthonye108a3f2010-05-12 07:24:03 +0000477
478 /* initialise or append the kernel list */
anthony3dd0f622010-05-13 12:57:32 +0000479 if ( kernel == (KernelInfo *) NULL )
480 kernel = new_kernel;
481 else
anthonydbc89892010-05-12 07:05:27 +0000482 last_kernel->next = new_kernel;
anthony3dd0f622010-05-13 12:57:32 +0000483 last_kernel = LastKernelInfo(new_kernel);
anthonydbc89892010-05-12 07:05:27 +0000484 }
485
486 /* look for the next kernel in list */
487 p = strchr(p, ';');
488 if ( p == (char *) NULL )
489 break;
490 p++;
491
492 }
anthony7a01dcf2010-05-11 12:25:52 +0000493 return(kernel);
anthony5ef8e942010-05-11 06:51:12 +0000494}
495
anthony602ab9b2010-01-05 08:06:50 +0000496
497/*
498%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
499% %
500% %
501% %
502% A c q u i r e K e r n e l B u i l t I n %
503% %
504% %
505% %
506%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
507%
508% AcquireKernelBuiltIn() returned one of the 'named' built-in types of
509% kernels used for special purposes such as gaussian blurring, skeleton
510% pruning, and edge distance determination.
511%
512% They take a KernelType, and a set of geometry style arguments, which were
513% typically decoded from a user supplied string, or from a more complex
514% Morphology Method that was requested.
515%
516% The format of the AcquireKernalBuiltIn method is:
517%
cristy2be15382010-01-21 02:38:03 +0000518% KernelInfo *AcquireKernelBuiltIn(const KernelInfoType type,
anthony602ab9b2010-01-05 08:06:50 +0000519% const GeometryInfo args)
520%
521% A description of each parameter follows:
522%
523% o type: the pre-defined type of kernel wanted
524%
525% o args: arguments defining or modifying the kernel
526%
527% Convolution Kernels
528%
anthony3c10fc82010-05-13 02:40:51 +0000529% Gaussian:{radius},{sigma}
530% Generate a two-dimentional gaussian kernel, as used by -gaussian.
anthonyc1061722010-05-14 06:23:49 +0000531% The sigma for the curve is required. The resulting kernel is
532% normalized,
533%
534% If 'sigma' is zero, you get a single pixel on a field of zeros.
anthony602ab9b2010-01-05 08:06:50 +0000535%
536% NOTE: that the 'radius' is optional, but if provided can limit (clip)
537% the final size of the resulting kernel to a square 2*radius+1 in size.
538% The radius should be at least 2 times that of the sigma value, or
539% sever clipping and aliasing may result. If not given or set to 0 the
540% radius will be determined so as to produce the best minimal error
541% result, which is usally much larger than is normally needed.
542%
anthonyc1061722010-05-14 06:23:49 +0000543% DOG:{radius},{sigma1},{sigma2}
544% "Difference of Gaussians" Kernel.
545% As "Gaussian" but with a gaussian produced by 'sigma2' subtracted
546% from the gaussian produced by 'sigma1'. Typically sigma2 > sigma1.
547% The result is a zero-summing kernel.
anthony602ab9b2010-01-05 08:06:50 +0000548%
anthony9eb4f742010-05-18 02:45:54 +0000549% LOG:{radius},{sigma}
550% "Laplacian of a Gaussian" or "Mexician Hat" Kernel.
551% The supposed ideal edge detection, zero-summing kernel.
552%
553% An alturnative to this kernel is to use a "DOG" with a sigma ratio of
554% approx 1.6, which can also be applied as a 2 pass "DOB" (see below).
555%
anthonyc1061722010-05-14 06:23:49 +0000556% Blur:{radius},{sigma}[,{angle}]
557% Generates a 1 dimensional or linear gaussian blur, at the angle given
558% (current restricted to orthogonal angles). If a 'radius' is given the
559% kernel is clipped to a width of 2*radius+1. Kernel can be rotated
560% by a 90 degree angle.
561%
562% If 'sigma' is zero, you get a single pixel on a field of zeros.
563%
564% Note that two convolutions with two "Blur" kernels perpendicular to
565% each other, is equivelent to a far larger "Gaussian" kernel with the
566% same sigma value, However it is much faster to apply. This is how the
567% "-blur" operator actually works.
568%
569% DOB:{radius},{sigma1},{sigma2}[,{angle}]
570% "Difference of Blurs" Kernel.
571% As "Blur" but with the 1D gaussian produced by 'sigma2' subtracted
572% from thethe 1D gaussian produced by 'sigma1'.
573% The result is a zero-summing kernel.
574%
575% This can be used to generate a faster "DOG" convolution, in the same
576% way "Blur" can.
anthony602ab9b2010-01-05 08:06:50 +0000577%
anthony3c10fc82010-05-13 02:40:51 +0000578% Comet:{width},{sigma},{angle}
579% Blur in one direction only, much like how a bright object leaves
anthony602ab9b2010-01-05 08:06:50 +0000580% a comet like trail. The Kernel is actually half a gaussian curve,
anthony3c10fc82010-05-13 02:40:51 +0000581% Adding two such blurs in opposite directions produces a Blur Kernel.
582% Angle can be rotated in multiples of 90 degrees.
anthony602ab9b2010-01-05 08:06:50 +0000583%
anthony3c10fc82010-05-13 02:40:51 +0000584% Note that the first argument is the width of the kernel and not the
anthony602ab9b2010-01-05 08:06:50 +0000585% radius of the kernel.
586%
587% # Still to be implemented...
588% #
anthony4fd27e22010-02-07 08:17:18 +0000589% # Filter2D
590% # Filter1D
591% # Set kernel values using a resize filter, and given scale (sigma)
592% # Cylindrical or Linear. Is this posible with an image?
593% #
anthony602ab9b2010-01-05 08:06:50 +0000594%
anthony3c10fc82010-05-13 02:40:51 +0000595% Named Constant Convolution Kernels
596%
anthonyc1061722010-05-14 06:23:49 +0000597% All these are unscaled, zero-summing kernels by default. As such for
598% non-HDRI version of ImageMagick some form of normalization, user scaling,
599% and biasing the results is recommended, to prevent the resulting image
600% being 'clipped'.
601%
602% The 3x3 kernels (most of these) can be circularly rotated in multiples of
603% 45 degrees to generate the 8 angled varients of each of the kernels.
anthony3c10fc82010-05-13 02:40:51 +0000604%
605% Laplacian:{type}
anthony9eb4f742010-05-18 02:45:54 +0000606% Discrete Lapacian Kernels, (unnormalized)
anthonyc1061722010-05-14 06:23:49 +0000607% Type 0 : 3x3 with center:8 surounded by -1 (8 neighbourhood)
608% Type 1 : 3x3 with center:4 edge:-1 corner:0 (4 neighbourhood)
anthony9eb4f742010-05-18 02:45:54 +0000609% Type 2 : 3x3 with center:4 edge:1 corner:-2
610% Type 3 : 3x3 with center:4 edge:-2 corner:1
611% Type 5 : 5x5 laplacian
612% Type 7 : 7x7 laplacian
613% Type 10 : 5x5 LOG (sigma approx 1.4)
anthonyc1061722010-05-14 06:23:49 +0000614%
615% Sobel:{angle}
616% Sobel 3x3 'Edge' convolution kernel (3x3)
617% -1, 0, 1
618% -2, 0,-2
619% -1, 0, 1
620% Roberts:{angle}
621% Roberts 3x3 convolution kernel (3x3)
622% 0, 0, 0
623% -1, 1, 0
624% 0, 0, 0
anthonyc1061722010-05-14 06:23:49 +0000625% Prewitt:{angle}
626% Prewitt Edge convolution kernel (3x3)
627% -1, 0, 1
628% -1, 0, 1
629% -1, 0, 1
anthony9eb4f742010-05-18 02:45:54 +0000630% Compass:{angle}
631% Prewitt's "Compass" convolution kernel (3x3)
632% -1, 1, 1
633% -1,-2, 1
634% -1, 1, 1
635% Kirsch:{angle}
636% Kirsch's "Compass" convolution kernel (3x3)
637% -3,-3, 5
638% -3, 0, 5
639% -3,-3, 5
anthony3c10fc82010-05-13 02:40:51 +0000640%
anthony602ab9b2010-01-05 08:06:50 +0000641% Boolean Kernels
642%
anthony3c10fc82010-05-13 02:40:51 +0000643% Diamond:[{radius}[,{scale}]]
anthony1b2bc0a2010-05-12 05:25:22 +0000644% Generate a diamond shaped kernel with given radius to the points.
anthony602ab9b2010-01-05 08:06:50 +0000645% Kernel size will again be radius*2+1 square and defaults to radius 1,
646% generating a 3x3 kernel that is slightly larger than a square.
647%
anthony3c10fc82010-05-13 02:40:51 +0000648% Square:[{radius}[,{scale}]]
anthony602ab9b2010-01-05 08:06:50 +0000649% Generate a square shaped kernel of size radius*2+1, and defaulting
650% to a 3x3 (radius 1).
651%
anthonyc1061722010-05-14 06:23:49 +0000652% Note that using a larger radius for the "Square" or the "Diamond" is
653% also equivelent to iterating the basic morphological method that many
654% times. However iterating with the smaller radius is actually faster
655% than using a larger kernel radius.
656%
657% Rectangle:{geometry}
658% Simply generate a rectangle of 1's with the size given. You can also
659% specify the location of the 'control point', otherwise the closest
660% pixel to the center of the rectangle is selected.
661%
662% Properly centered and odd sized rectangles work the best.
anthony602ab9b2010-01-05 08:06:50 +0000663%
anthony3c10fc82010-05-13 02:40:51 +0000664% Disk:[{radius}[,{scale}]]
anthony602ab9b2010-01-05 08:06:50 +0000665% Generate a binary disk of the radius given, radius may be a float.
666% Kernel size will be ceil(radius)*2+1 square.
667% NOTE: Here are some disk shapes of specific interest
anthonyc1061722010-05-14 06:23:49 +0000668% "Disk:1" => "diamond" or "cross:1"
669% "Disk:1.5" => "square"
670% "Disk:2" => "diamond:2"
671% "Disk:2.5" => a general disk shape of radius 2
672% "Disk:2.9" => "square:2"
673% "Disk:3.5" => default - octagonal/disk shape of radius 3
674% "Disk:4.2" => roughly octagonal shape of radius 4
675% "Disk:4.3" => a general disk shape of radius 4
anthony602ab9b2010-01-05 08:06:50 +0000676% After this all the kernel shape becomes more and more circular.
677%
678% Because a "disk" is more circular when using a larger radius, using a
679% larger radius is preferred over iterating the morphological operation.
680%
anthonyc1061722010-05-14 06:23:49 +0000681% Symbol Dilation Kernels
682%
683% These kernel is not a good general morphological kernel, but is used
684% more for highlighting and marking any single pixels in an image using,
685% a "Dilate" method as appropriate.
686%
687% For the same reasons iterating these kernels does not produce the
688% same result as using a larger radius for the symbol.
689%
anthony3c10fc82010-05-13 02:40:51 +0000690% Plus:[{radius}[,{scale}]]
anthony3dd0f622010-05-13 12:57:32 +0000691% Cross:[{radius}[,{scale}]]
anthonyc1061722010-05-14 06:23:49 +0000692% Generate a kernel in the shape of a 'plus' or a 'cross' with
693% a each arm the length of the given radius (default 2).
anthony3dd0f622010-05-13 12:57:32 +0000694%
695% NOTE: "plus:1" is equivelent to a "Diamond" kernel.
anthony602ab9b2010-01-05 08:06:50 +0000696%
anthonyc1061722010-05-14 06:23:49 +0000697% Ring:{radius1},{radius2}[,{scale}]
698% A ring of the values given that falls between the two radii.
699% Defaults to a ring of approximataly 3 radius in a 7x7 kernel.
700% This is the 'edge' pixels of the default "Disk" kernel,
701% More specifically, "Ring" -> "Ring:2.5,3.5,1.0"
anthony602ab9b2010-01-05 08:06:50 +0000702%
anthony3dd0f622010-05-13 12:57:32 +0000703% Hit and Miss Kernels
704%
705% Peak:radius1,radius2
anthonyc1061722010-05-14 06:23:49 +0000706% Find any peak larger than the pixels the fall between the two radii.
707% The default ring of pixels is as per "Ring".
anthony3dd0f622010-05-13 12:57:32 +0000708% Corners
709% Find corners of a binary shape
710% LineEnds
711% Find end points of lines (for pruning a skeletion)
712% LineJunctions
713% Find three line junctions (in a skeletion)
714% ConvexHull
715% Octagonal thicken kernel, to generate convex hulls of 45 degrees
716% Skeleton
717% Thinning kernel, which leaves behind a skeletion of a shape
anthony602ab9b2010-01-05 08:06:50 +0000718%
719% Distance Measuring Kernels
720%
anthonyc1061722010-05-14 06:23:49 +0000721% Different types of distance measuring methods, which are used with the
722% a 'Distance' morphology method for generating a gradient based on
723% distance from an edge of a binary shape, though there is a technique
724% for handling a anti-aliased shape.
725%
726% See the 'Distance' Morphological Method, for information of how it is
727% applied.
728%
anthony3dd0f622010-05-13 12:57:32 +0000729% Chebyshev:[{radius}][x{scale}[%!]]
anthonyc94cdb02010-01-06 08:15:29 +0000730% Chebyshev Distance (also known as Tchebychev Distance) is a value of
731% one to any neighbour, orthogonal or diagonal. One why of thinking of
732% it is the number of squares a 'King' or 'Queen' in chess needs to
733% traverse reach any other position on a chess board. It results in a
734% 'square' like distance function, but one where diagonals are closer
735% than expected.
anthony602ab9b2010-01-05 08:06:50 +0000736%
anthonyc1061722010-05-14 06:23:49 +0000737% Manhatten:[{radius}][x{scale}[%!]]
anthonyc94cdb02010-01-06 08:15:29 +0000738% Manhatten Distance (also known as Rectilinear Distance, or the Taxi
739% Cab metric), is the distance needed when you can only travel in
740% orthogonal (horizontal or vertical) only. It is the distance a 'Rook'
741% in chess would travel. It results in a diamond like distances, where
742% diagonals are further than expected.
anthony602ab9b2010-01-05 08:06:50 +0000743%
anthonyc1061722010-05-14 06:23:49 +0000744% Euclidean:[{radius}][x{scale}[%!]]
anthonyc94cdb02010-01-06 08:15:29 +0000745% Euclidean Distance is the 'direct' or 'as the crow flys distance.
746% However by default the kernel size only has a radius of 1, which
747% limits the distance to 'Knight' like moves, with only orthogonal and
748% diagonal measurements being correct. As such for the default kernel
749% you will get octagonal like distance function, which is reasonally
750% accurate.
751%
752% However if you use a larger radius such as "Euclidean:4" you will
753% get a much smoother distance gradient from the edge of the shape.
754% Of course a larger kernel is slower to use, and generally not needed.
755%
756% To allow the use of fractional distances that you get with diagonals
757% the actual distance is scaled by a fixed value which the user can
758% provide. This is not actually nessary for either ""Chebyshev" or
759% "Manhatten" distance kernels, but is done for all three distance
760% kernels. If no scale is provided it is set to a value of 100,
761% allowing for a maximum distance measurement of 655 pixels using a Q16
762% version of IM, from any edge. However for small images this can
763% result in quite a dark gradient.
764%
anthony602ab9b2010-01-05 08:06:50 +0000765*/
766
cristy2be15382010-01-21 02:38:03 +0000767MagickExport KernelInfo *AcquireKernelBuiltIn(const KernelInfoType type,
anthony602ab9b2010-01-05 08:06:50 +0000768 const GeometryInfo *args)
769{
cristy2be15382010-01-21 02:38:03 +0000770 KernelInfo
anthony602ab9b2010-01-05 08:06:50 +0000771 *kernel;
772
cristy150989e2010-02-01 14:59:39 +0000773 register long
anthony602ab9b2010-01-05 08:06:50 +0000774 i;
775
776 register long
777 u,
778 v;
779
780 double
781 nan = sqrt((double)-1.0); /* Special Value : Not A Number */
782
anthonyc1061722010-05-14 06:23:49 +0000783 /* Generate a new empty kernel if needed */
784 switch(type) {
anthony9eb4f742010-05-18 02:45:54 +0000785 case UndefinedKernel: /* These should not be used here */
786 case UserDefinedKernel:
787 break;
788 case LaplacianKernel: /* Named Descrete Convolution Kernels */
789 case SobelKernel:
790 case RobertsKernel:
791 case PrewittKernel:
792 case CompassKernel:
793 case KirschKernel:
794 case CornersKernel: /* Hit and Miss kernels */
795 case LineEndsKernel:
796 case LineJunctionsKernel:
797 case ThickenKernel:
798 case ThinningKernel:
799 case ConvexHullKernel:
800 case SkeletonKernel:
801 /* A pre-generated kernel is not needed */
802 break;
803#if 0
anthonyc1061722010-05-14 06:23:49 +0000804 case GaussianKernel:
805 case DOGKernel:
806 case BlurKernel:
807 case DOBKernel:
808 case CometKernel:
809 case DiamondKernel:
810 case SquareKernel:
811 case RectangleKernel:
812 case DiskKernel:
813 case PlusKernel:
814 case CrossKernel:
815 case RingKernel:
816 case PeaksKernel:
817 case ChebyshevKernel:
818 case ManhattenKernel:
819 case EuclideanKernel:
anthony9eb4f742010-05-18 02:45:54 +0000820#endif
821 default:
822 /* Generate the base Kernel Structure */
anthonyc1061722010-05-14 06:23:49 +0000823 kernel=(KernelInfo *) AcquireMagickMemory(sizeof(*kernel));
824 if (kernel == (KernelInfo *) NULL)
825 return(kernel);
826 (void) ResetMagickMemory(kernel,0,sizeof(*kernel));
827 kernel->minimum = kernel->maximum = 0.0;
828 kernel->negative_range = kernel->positive_range = 0.0;
829 kernel->type = type;
830 kernel->next = (KernelInfo *) NULL;
831 kernel->signature = MagickSignature;
anthonyc1061722010-05-14 06:23:49 +0000832 break;
833 }
anthony602ab9b2010-01-05 08:06:50 +0000834
835 switch(type) {
836 /* Convolution Kernels */
837 case GaussianKernel:
anthonyc1061722010-05-14 06:23:49 +0000838 case DOGKernel:
anthony9eb4f742010-05-18 02:45:54 +0000839 case LOGKernel:
anthony602ab9b2010-01-05 08:06:50 +0000840 { double
anthonyc1061722010-05-14 06:23:49 +0000841 sigma = fabs(args->sigma),
842 sigma2 = fabs(args->xi),
anthony9eb4f742010-05-18 02:45:54 +0000843 A, B, R;
anthony602ab9b2010-01-05 08:06:50 +0000844
anthonyc1061722010-05-14 06:23:49 +0000845 if ( args->rho >= 1.0 )
846 kernel->width = (unsigned long)args->rho*2+1;
anthony9eb4f742010-05-18 02:45:54 +0000847 else if ( (type != DOGKernel) || (sigma >= sigma2) )
anthonyc1061722010-05-14 06:23:49 +0000848 kernel->width = GetOptimalKernelWidth2D(args->rho,sigma);
849 else
850 kernel->width = GetOptimalKernelWidth2D(args->rho,sigma2);
851 kernel->height = kernel->width;
cristyc99304f2010-02-01 15:26:27 +0000852 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony602ab9b2010-01-05 08:06:50 +0000853 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
854 kernel->height*sizeof(double));
855 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +0000856 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000857
anthony9eb4f742010-05-18 02:45:54 +0000858 /* The following generates a 'sampled gaussian' kernel.
859 * What we really want is a 'discrete gaussian' kernel.
860 */
861
862 if ( type == GaussianKernel || type == DOGKernel )
863 { /* Calculate a Gaussian, OR positive half of a DOG */
864 if ( sigma > MagickEpsilon )
865 { A = 1.0/(2.0*sigma*sigma); /* simplify loop expressions */
866 B = 1.0/(Magick2PI*sigma*sigma);
867 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
868 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
869 kernel->values[i] = exp(-((double)(u*u+v*v))*A)*B;
870 }
871 else /* limiting case - a unity (normalized Dirac) kernel */
872 { (void) ResetMagickMemory(kernel->values,0, (size_t)
873 kernel->width*kernel->height*sizeof(double));
874 kernel->values[kernel->x+kernel->y*kernel->width] = 1.0;
875 }
anthonyc1061722010-05-14 06:23:49 +0000876 }
anthony9eb4f742010-05-18 02:45:54 +0000877
anthonyc1061722010-05-14 06:23:49 +0000878 if ( type == DOGKernel )
879 { /* Subtract a Negative Gaussian for "Difference of Gaussian" */
880 if ( sigma2 > MagickEpsilon )
881 { sigma = sigma2; /* simplify loop expressions */
anthony9eb4f742010-05-18 02:45:54 +0000882 A = 1.0/(2.0*sigma*sigma);
883 B = 1.0/(Magick2PI*sigma*sigma);
anthonyc1061722010-05-14 06:23:49 +0000884 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
885 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
anthony9eb4f742010-05-18 02:45:54 +0000886 kernel->values[i] -= exp(-((double)(u*u+v*v))*A)*B;
anthonyc1061722010-05-14 06:23:49 +0000887 }
anthony9eb4f742010-05-18 02:45:54 +0000888 else /* limiting case - a unity (normalized Dirac) kernel */
anthonyc1061722010-05-14 06:23:49 +0000889 kernel->values[kernel->x+kernel->y*kernel->width] -= 1.0;
890 }
anthony9eb4f742010-05-18 02:45:54 +0000891
892 if ( type == LOGKernel )
893 { /* Calculate a Laplacian of a Gaussian - Or Mexician Hat */
894 if ( sigma > MagickEpsilon )
895 { A = 1.0/(2.0*sigma*sigma); /* simplify loop expressions */
896 B = 1.0/(MagickPI*sigma*sigma*sigma*sigma);
897 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
898 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
899 { R = ((double)(u*u+v*v))*A;
900 kernel->values[i] = (1-R)*exp(-R)*B;
901 }
902 }
903 else /* special case - generate a unity kernel */
904 { (void) ResetMagickMemory(kernel->values,0, (size_t)
905 kernel->width*kernel->height*sizeof(double));
906 kernel->values[kernel->x+kernel->y*kernel->width] = 1.0;
907 }
908 }
909
910 /* Note the above kernels may have been 'clipped' by a user defined
anthonyc1061722010-05-14 06:23:49 +0000911 ** radius, producing a smaller (darker) kernel. Also for very small
912 ** sigma's (> 0.1) the central value becomes larger than one, and thus
913 ** producing a very bright kernel.
914 **
915 ** Normalization will still be needed.
916 */
anthony602ab9b2010-01-05 08:06:50 +0000917
anthonyc1061722010-05-14 06:23:49 +0000918 /* Work out the meta-data about kernel */
919 kernel->minimum = kernel->maximum = 0.0;
920 kernel->negative_range = kernel->positive_range = 0.0;
921 u=(long) kernel->width*kernel->height;
922 for ( i=0; i < u; i++)
923 {
924 if ( fabs(kernel->values[i]) < MagickEpsilon )
925 kernel->values[i] = 0.0;
926 ( kernel->values[i] < 0)
927 ? ( kernel->negative_range += kernel->values[i] )
928 : ( kernel->positive_range += kernel->values[i] );
929 Minimize(kernel->minimum, kernel->values[i]);
930 Maximize(kernel->maximum, kernel->values[i]);
931 }
anthony3dd0f622010-05-13 12:57:32 +0000932 /* Normalize the 2D Gaussian Kernel
933 **
anthonyc1061722010-05-14 06:23:49 +0000934 ** NB: a CorrelateNormalize performs a normal Normalize if
935 ** there are no negative values.
anthony3dd0f622010-05-13 12:57:32 +0000936 */
anthonyc1061722010-05-14 06:23:49 +0000937 ScaleKernelInfo(kernel, 1.0, CorrelateNormalizeValue);
anthony602ab9b2010-01-05 08:06:50 +0000938
939 break;
940 }
941 case BlurKernel:
anthonyc1061722010-05-14 06:23:49 +0000942 case DOBKernel:
anthony602ab9b2010-01-05 08:06:50 +0000943 { double
anthonyc1061722010-05-14 06:23:49 +0000944 sigma = fabs(args->sigma),
945 sigma2 = fabs(args->xi),
anthony9eb4f742010-05-18 02:45:54 +0000946 A, B;
anthony602ab9b2010-01-05 08:06:50 +0000947
anthonyc1061722010-05-14 06:23:49 +0000948 if ( args->rho >= 1.0 )
949 kernel->width = (unsigned long)args->rho*2+1;
950 else if ( (type == BlurKernel) || (sigma >= sigma2) )
951 kernel->width = GetOptimalKernelWidth1D(args->rho,sigma);
952 else
953 kernel->width = GetOptimalKernelWidth1D(args->rho,sigma2);
anthony602ab9b2010-01-05 08:06:50 +0000954 kernel->height = 1;
anthonyc1061722010-05-14 06:23:49 +0000955 kernel->x = (long) (kernel->width-1)/2;
cristyc99304f2010-02-01 15:26:27 +0000956 kernel->y = 0;
957 kernel->negative_range = kernel->positive_range = 0.0;
anthony602ab9b2010-01-05 08:06:50 +0000958 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
959 kernel->height*sizeof(double));
960 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +0000961 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000962
963#if 1
964#define KernelRank 3
965 /* Formula derived from GetBlurKernel() in "effect.c" (plus bug fix).
966 ** It generates a gaussian 3 times the width, and compresses it into
967 ** the expected range. This produces a closer normalization of the
968 ** resulting kernel, especially for very low sigma values.
969 ** As such while wierd it is prefered.
970 **
971 ** I am told this method originally came from Photoshop.
anthony9eb4f742010-05-18 02:45:54 +0000972 **
973 ** A properly normalized curve is generated (apart from edge clipping)
974 ** even though we later normalize the result (for edge clipping)
975 ** to allow the correct generation of a "Difference of Blurs".
anthony602ab9b2010-01-05 08:06:50 +0000976 */
anthonyc1061722010-05-14 06:23:49 +0000977
978 /* initialize */
cristy150989e2010-02-01 14:59:39 +0000979 v = (long) (kernel->width*KernelRank-1)/2; /* start/end points to fit range */
anthony9eb4f742010-05-18 02:45:54 +0000980 (void) ResetMagickMemory(kernel->values,0, (size_t)
981 kernel->width*kernel->height*sizeof(double));
anthonyc1061722010-05-14 06:23:49 +0000982 /* Calculate a Positive 1D Gaussian */
983 if ( sigma > MagickEpsilon )
984 { sigma *= KernelRank; /* simplify loop expressions */
anthony9eb4f742010-05-18 02:45:54 +0000985 A = 1.0/(2.0*sigma*sigma);
986 B = 1.0/(MagickSQ2PI*sigma );
anthonyc1061722010-05-14 06:23:49 +0000987 for ( u=-v; u <= v; u++) {
anthony9eb4f742010-05-18 02:45:54 +0000988 kernel->values[(u+v)/KernelRank] += exp(-((double)(u*u))*A)*B;
anthonyc1061722010-05-14 06:23:49 +0000989 }
990 }
991 else /* special case - generate a unity kernel */
992 kernel->values[kernel->x+kernel->y*kernel->width] = 1.0;
anthony9eb4f742010-05-18 02:45:54 +0000993
994 /* Subtract a Second 1D Gaussian for "Difference of Blur" */
anthonyc1061722010-05-14 06:23:49 +0000995 if ( type == DOBKernel )
anthony9eb4f742010-05-18 02:45:54 +0000996 {
anthonyc1061722010-05-14 06:23:49 +0000997 if ( sigma2 > MagickEpsilon )
998 { sigma = sigma2*KernelRank; /* simplify loop expressions */
anthony9eb4f742010-05-18 02:45:54 +0000999 A = 1.0/(2.0*sigma*sigma);
1000 B = 1.0/(MagickSQ2PI*sigma);
anthonyc1061722010-05-14 06:23:49 +00001001 for ( u=-v; u <= v; u++)
anthony9eb4f742010-05-18 02:45:54 +00001002 kernel->values[(u+v)/KernelRank] -= exp(-((double)(u*u))*A)*B;
anthonyc1061722010-05-14 06:23:49 +00001003 }
anthony9eb4f742010-05-18 02:45:54 +00001004 else /* limiting case - a unity (normalized Dirac) kernel */
anthonyc1061722010-05-14 06:23:49 +00001005 kernel->values[kernel->x+kernel->y*kernel->width] -= 1.0;
1006 }
anthony602ab9b2010-01-05 08:06:50 +00001007#else
anthonyc1061722010-05-14 06:23:49 +00001008 /* Direct calculation without curve averaging */
1009
1010 /* Calculate a Positive Gaussian */
1011 if ( sigma > MagickEpsilon )
anthony9eb4f742010-05-18 02:45:54 +00001012 { A = 1.0/(2.0*sigma*sigma); /* simplify loop expressions */
1013 B = 1.0/(MagickSQ2PI*sigma);
anthonyc1061722010-05-14 06:23:49 +00001014 for ( i=0, u=-kernel->x; u <= (long)kernel->x; u++, i++)
anthony9eb4f742010-05-18 02:45:54 +00001015 kernel->values[i] = exp(-((double)(u*u))*A)*B;
anthonyc1061722010-05-14 06:23:49 +00001016 }
1017 else /* special case - generate a unity kernel */
1018 { (void) ResetMagickMemory(kernel->values,0, (size_t)
1019 kernel->width*kernel->height*sizeof(double));
1020 kernel->values[kernel->x+kernel->y*kernel->width] = 1.0;
1021 }
anthony9eb4f742010-05-18 02:45:54 +00001022
1023 /* Subtract a Second 1D Gaussian for "Difference of Blur" */
anthonyc1061722010-05-14 06:23:49 +00001024 if ( type == DOBKernel )
anthony9eb4f742010-05-18 02:45:54 +00001025 {
anthonyc1061722010-05-14 06:23:49 +00001026 if ( sigma2 > MagickEpsilon )
1027 { sigma = sigma2; /* simplify loop expressions */
anthony9eb4f742010-05-18 02:45:54 +00001028 A = 1.0/(2.0*sigma*sigma);
1029 B = 1.0/(MagickSQ2PI*sigma);
anthonyc1061722010-05-14 06:23:49 +00001030 for ( i=0, u=-kernel->x; u <= (long)kernel->x; u++, i++)
anthony9eb4f742010-05-18 02:45:54 +00001031 kernel->values[i] -= exp(-((double)(u*u))*A)*B;
anthonyc1061722010-05-14 06:23:49 +00001032 }
anthony9eb4f742010-05-18 02:45:54 +00001033 else /* limiting case - a unity (normalized Dirac) kernel */
anthonyc1061722010-05-14 06:23:49 +00001034 kernel->values[kernel->x+kernel->y*kernel->width] -= 1.0;
1035 }
anthony602ab9b2010-01-05 08:06:50 +00001036#endif
anthonyc1061722010-05-14 06:23:49 +00001037 /* Note the above kernel may have been 'clipped' by a user defined
anthonycc6c8362010-01-25 04:14:01 +00001038 ** radius, producing a smaller (darker) kernel. Also for very small
1039 ** sigma's (> 0.1) the central value becomes larger than one, and thus
1040 ** producing a very bright kernel.
anthonyc1061722010-05-14 06:23:49 +00001041 **
1042 ** Normalization will still be needed.
anthony602ab9b2010-01-05 08:06:50 +00001043 */
anthonycc6c8362010-01-25 04:14:01 +00001044
anthonyc1061722010-05-14 06:23:49 +00001045 /* Work out the meta-data about kernel */
1046 for ( i=0; i < (long) kernel->width; i++)
1047 {
anthonyc1061722010-05-14 06:23:49 +00001048 ( kernel->values[i] < 0)
1049 ? ( kernel->negative_range += kernel->values[i] )
1050 : ( kernel->positive_range += kernel->values[i] );
1051 Minimize(kernel->minimum, kernel->values[i]);
1052 Maximize(kernel->maximum, kernel->values[i]);
1053 }
1054
anthony602ab9b2010-01-05 08:06:50 +00001055 /* Normalize the 1D Gaussian Kernel
1056 **
anthonyc1061722010-05-14 06:23:49 +00001057 ** NB: a CorrelateNormalize performs a normal Normalize if
1058 ** there are no negative values.
anthony602ab9b2010-01-05 08:06:50 +00001059 */
anthony9eb4f742010-05-18 02:45:54 +00001060 //ScaleKernelInfo(kernel, 1.0, CorrelateNormalizeValue);
anthonycc6c8362010-01-25 04:14:01 +00001061
anthonyc1061722010-05-14 06:23:49 +00001062 /* rotate the 1D kernel by given angle */
1063 RotateKernelInfo(kernel, (type == BlurKernel) ? args->xi : args->psi );
anthony602ab9b2010-01-05 08:06:50 +00001064 break;
1065 }
1066 case CometKernel:
1067 { double
anthony9eb4f742010-05-18 02:45:54 +00001068 sigma = fabs(args->sigma),
1069 A;
anthony602ab9b2010-01-05 08:06:50 +00001070
anthony602ab9b2010-01-05 08:06:50 +00001071 if ( args->rho < 1.0 )
1072 kernel->width = GetOptimalKernelWidth1D(args->rho,sigma);
1073 else
1074 kernel->width = (unsigned long)args->rho;
cristyc99304f2010-02-01 15:26:27 +00001075 kernel->x = kernel->y = 0;
anthony602ab9b2010-01-05 08:06:50 +00001076 kernel->height = 1;
cristyc99304f2010-02-01 15:26:27 +00001077 kernel->negative_range = kernel->positive_range = 0.0;
anthony602ab9b2010-01-05 08:06:50 +00001078 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
1079 kernel->height*sizeof(double));
1080 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +00001081 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +00001082
anthonyc1061722010-05-14 06:23:49 +00001083 /* A comet blur is half a 1D gaussian curve, so that the object is
anthony602ab9b2010-01-05 08:06:50 +00001084 ** blurred in one direction only. This may not be quite the right
anthony3dd0f622010-05-13 12:57:32 +00001085 ** curve to use so may change in the future. The function must be
1086 ** normalised after generation, which also resolves any clipping.
anthonyc1061722010-05-14 06:23:49 +00001087 **
1088 ** As we are normalizing and not subtracting gaussians,
1089 ** there is no need for a divisor in the gaussian formula
1090 **
anthony9eb4f742010-05-18 02:45:54 +00001091 ** It is less comples
anthony602ab9b2010-01-05 08:06:50 +00001092 */
anthony9eb4f742010-05-18 02:45:54 +00001093 if ( sigma > MagickEpsilon )
1094 {
anthony602ab9b2010-01-05 08:06:50 +00001095#if 1
1096#define KernelRank 3
anthony9eb4f742010-05-18 02:45:54 +00001097 v = (long) kernel->width*KernelRank; /* start/end points */
1098 (void) ResetMagickMemory(kernel->values,0, (size_t)
1099 kernel->width*sizeof(double));
1100 sigma *= KernelRank; /* simplify the loop expression */
1101 A = 1.0/(2.0*sigma*sigma);
1102 /* B = 1.0/(MagickSQ2PI*sigma); */
1103 for ( u=0; u < v; u++) {
1104 kernel->values[u/KernelRank] +=
1105 exp(-((double)(u*u))*A);
1106 /* exp(-((double)(i*i))/2.0*sigma*sigma)/(MagickSQ2PI*sigma); */
1107 }
1108 for (i=0; i < (long) kernel->width; i++)
1109 kernel->positive_range += kernel->values[i];
anthony602ab9b2010-01-05 08:06:50 +00001110#else
anthony9eb4f742010-05-18 02:45:54 +00001111 A = 1.0/(2.0*sigma*sigma); /* simplify the loop expression */
1112 /* B = 1.0/(MagickSQ2PI*sigma); */
1113 for ( i=0; i < (long) kernel->width; i++)
1114 kernel->positive_range +=
1115 kernel->values[i] =
1116 exp(-((double)(i*i))*A);
1117 /* exp(-((double)(i*i))/2.0*sigma*sigma)/(MagickSQ2PI*sigma); */
anthony602ab9b2010-01-05 08:06:50 +00001118#endif
anthony9eb4f742010-05-18 02:45:54 +00001119 }
1120 else /* special case - generate a unity kernel */
1121 { (void) ResetMagickMemory(kernel->values,0, (size_t)
1122 kernel->width*kernel->height*sizeof(double));
1123 kernel->values[kernel->x+kernel->y*kernel->width] = 1.0;
1124 kernel->positive_range = 1.0;
1125 }
cristyc99304f2010-02-01 15:26:27 +00001126 kernel->minimum = 0;
1127 kernel->maximum = kernel->values[0];
anthony602ab9b2010-01-05 08:06:50 +00001128
anthony999bb2c2010-02-18 12:38:01 +00001129 ScaleKernelInfo(kernel, 1.0, NormalizeValue); /* Normalize */
1130 RotateKernelInfo(kernel, args->xi); /* Rotate by angle */
anthony602ab9b2010-01-05 08:06:50 +00001131 break;
1132 }
anthonyc1061722010-05-14 06:23:49 +00001133
anthony3c10fc82010-05-13 02:40:51 +00001134 /* Convolution Kernels - Well Known Constants */
anthony3c10fc82010-05-13 02:40:51 +00001135 case LaplacianKernel:
anthonyc1061722010-05-14 06:23:49 +00001136 {
anthony3c10fc82010-05-13 02:40:51 +00001137 switch ( (int) args->rho ) {
anthony3dd0f622010-05-13 12:57:32 +00001138 case 0:
anthony9eb4f742010-05-18 02:45:54 +00001139 default: /* laplacian square filter -- default */
anthonyc1061722010-05-14 06:23:49 +00001140 kernel=ParseKernelArray("3: -1,-1,-1 -1,8,-1 -1,-1,-1");
anthony3dd0f622010-05-13 12:57:32 +00001141 break;
anthony9eb4f742010-05-18 02:45:54 +00001142 case 1: /* laplacian diamond filter */
anthonyc1061722010-05-14 06:23:49 +00001143 kernel=ParseKernelArray("3: 0,-1,0 -1,4,-1 0,-1,0");
anthony3c10fc82010-05-13 02:40:51 +00001144 break;
1145 case 2:
anthony9eb4f742010-05-18 02:45:54 +00001146 kernel=ParseKernelArray("3: -2,1,-2 1,4,1 -2,1,-2");
1147 break;
1148 case 3:
anthonyc1061722010-05-14 06:23:49 +00001149 kernel=ParseKernelArray("3: 1,-2,1 -2,4,-2 1,-2,1");
anthony3c10fc82010-05-13 02:40:51 +00001150 break;
anthony9eb4f742010-05-18 02:45:54 +00001151 case 5: /* a 5x5 laplacian */
anthony3c10fc82010-05-13 02:40:51 +00001152 kernel=ParseKernelArray(
anthony9eb4f742010-05-18 02:45:54 +00001153 "5: -4,-1,0,-1,-4 -1,2,3,2,-1 0,3,4,3,0 -1,2,3,2,-1 -4,-1,0,-1,-4");
anthony3c10fc82010-05-13 02:40:51 +00001154 break;
anthony9eb4f742010-05-18 02:45:54 +00001155 case 7: /* a 7x7 laplacian */
anthony3c10fc82010-05-13 02:40:51 +00001156 kernel=ParseKernelArray(
anthonyc1061722010-05-14 06:23:49 +00001157 "7:-10,-5,-2,-1,-2,-5,-10 -5,0,3,4,3,0,-5 -2,3,6,7,6,3,-2 -1,4,7,8,7,4,-1 -2,3,6,7,6,3,-2 -5,0,3,4,3,0,-5 -10,-5,-2,-1,-2,-5,-10" );
anthony3c10fc82010-05-13 02:40:51 +00001158 break;
anthony9eb4f742010-05-18 02:45:54 +00001159 case 10: /* a 5x5 LOG (sigma approx 1.4) */
1160 kernel=ParseKernelArray(
1161 "5: 0,0,-1,0,0 0,-1,-2,-1,0 -1,-2,16,-2,-1 0,-1,-2,-1,0 0,0,-1,0,0");
1162 break;
anthony3c10fc82010-05-13 02:40:51 +00001163 }
1164 if (kernel == (KernelInfo *) NULL)
1165 return(kernel);
1166 kernel->type = type;
1167 break;
1168 }
anthonyc1061722010-05-14 06:23:49 +00001169 case SobelKernel:
anthony602ab9b2010-01-05 08:06:50 +00001170 {
anthonyc1061722010-05-14 06:23:49 +00001171 kernel=ParseKernelArray("3: -1,0,1 -2,0,2 -1,0,1");
1172 if (kernel == (KernelInfo *) NULL)
1173 return(kernel);
1174 kernel->type = type;
1175 RotateKernelInfo(kernel, args->rho); /* Rotate by angle */
1176 break;
1177 }
1178 case RobertsKernel:
1179 {
1180 kernel=ParseKernelArray("3: 0,0,0 -1,1,0 0,0,0");
1181 if (kernel == (KernelInfo *) NULL)
1182 return(kernel);
1183 kernel->type = type;
1184 RotateKernelInfo(kernel, args->rho); /* Rotate by angle */
1185 break;
1186 }
1187 case PrewittKernel:
1188 {
1189 kernel=ParseKernelArray("3: -1,1,1 0,0,0 -1,1,1");
1190 if (kernel == (KernelInfo *) NULL)
1191 return(kernel);
1192 kernel->type = type;
1193 RotateKernelInfo(kernel, args->rho); /* Rotate by angle */
1194 break;
1195 }
1196 case CompassKernel:
1197 {
1198 kernel=ParseKernelArray("3: -1,1,1 -1,-2,1 -1,1,1");
1199 if (kernel == (KernelInfo *) NULL)
1200 return(kernel);
1201 kernel->type = type;
1202 RotateKernelInfo(kernel, args->rho); /* Rotate by angle */
1203 break;
1204 }
anthony9eb4f742010-05-18 02:45:54 +00001205 case KirschKernel:
1206 {
1207 kernel=ParseKernelArray("3: -3,-3,5 -3,0,5 -3,-3,5");
1208 if (kernel == (KernelInfo *) NULL)
1209 return(kernel);
1210 kernel->type = type;
1211 RotateKernelInfo(kernel, args->rho); /* Rotate by angle */
1212 break;
1213 }
anthonyc1061722010-05-14 06:23:49 +00001214 /* Boolean Kernels */
1215 case DiamondKernel:
1216 {
1217 if (args->rho < 1.0)
1218 kernel->width = kernel->height = 3; /* default radius = 1 */
1219 else
1220 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
1221 kernel->x = kernel->y = (long) (kernel->width-1)/2;
1222
1223 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
1224 kernel->height*sizeof(double));
1225 if (kernel->values == (double *) NULL)
1226 return(DestroyKernelInfo(kernel));
1227
1228 /* set all kernel values within diamond area to scale given */
1229 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
1230 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
1231 if ((labs(u)+labs(v)) <= (long)kernel->x)
1232 kernel->positive_range += kernel->values[i] = args->sigma;
1233 else
1234 kernel->values[i] = nan;
1235 kernel->minimum = kernel->maximum = args->sigma; /* a flat shape */
1236 break;
1237 }
1238 case SquareKernel:
1239 case RectangleKernel:
1240 { double
1241 scale;
anthony602ab9b2010-01-05 08:06:50 +00001242 if ( type == SquareKernel )
1243 {
1244 if (args->rho < 1.0)
anthonyc94cdb02010-01-06 08:15:29 +00001245 kernel->width = kernel->height = 3; /* default radius = 1 */
anthony602ab9b2010-01-05 08:06:50 +00001246 else
cristy150989e2010-02-01 14:59:39 +00001247 kernel->width = kernel->height = (unsigned long) (2*args->rho+1);
cristyc99304f2010-02-01 15:26:27 +00001248 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony4fd27e22010-02-07 08:17:18 +00001249 scale = args->sigma;
anthony602ab9b2010-01-05 08:06:50 +00001250 }
1251 else {
cristy2be15382010-01-21 02:38:03 +00001252 /* NOTE: user defaults set in "AcquireKernelInfo()" */
anthony602ab9b2010-01-05 08:06:50 +00001253 if ( args->rho < 1.0 || args->sigma < 1.0 )
anthony83ba99b2010-01-24 08:48:15 +00001254 return(DestroyKernelInfo(kernel)); /* invalid args given */
anthony602ab9b2010-01-05 08:06:50 +00001255 kernel->width = (unsigned long)args->rho;
1256 kernel->height = (unsigned long)args->sigma;
1257 if ( args->xi < 0.0 || args->xi > (double)kernel->width ||
1258 args->psi < 0.0 || args->psi > (double)kernel->height )
anthony83ba99b2010-01-24 08:48:15 +00001259 return(DestroyKernelInfo(kernel)); /* invalid args given */
cristyc99304f2010-02-01 15:26:27 +00001260 kernel->x = (long) args->xi;
1261 kernel->y = (long) args->psi;
anthony4fd27e22010-02-07 08:17:18 +00001262 scale = 1.0;
anthony602ab9b2010-01-05 08:06:50 +00001263 }
1264 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
1265 kernel->height*sizeof(double));
1266 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +00001267 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +00001268
anthony3dd0f622010-05-13 12:57:32 +00001269 /* set all kernel values to scale given */
cristy150989e2010-02-01 14:59:39 +00001270 u=(long) kernel->width*kernel->height;
1271 for ( i=0; i < u; i++)
anthony4fd27e22010-02-07 08:17:18 +00001272 kernel->values[i] = scale;
1273 kernel->minimum = kernel->maximum = scale; /* a flat shape */
1274 kernel->positive_range = scale*u;
anthonycc6c8362010-01-25 04:14:01 +00001275 break;
anthony602ab9b2010-01-05 08:06:50 +00001276 }
anthony602ab9b2010-01-05 08:06:50 +00001277 case DiskKernel:
1278 {
anthonyc1061722010-05-14 06:23:49 +00001279 long limit = (long)(args->rho*args->rho);
anthony83ba99b2010-01-24 08:48:15 +00001280 if (args->rho < 0.1) /* default radius approx 3.5 */
1281 kernel->width = kernel->height = 7L, limit = 10L;
anthony602ab9b2010-01-05 08:06:50 +00001282 else
1283 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
cristyc99304f2010-02-01 15:26:27 +00001284 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony602ab9b2010-01-05 08:06:50 +00001285
1286 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
1287 kernel->height*sizeof(double));
1288 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +00001289 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +00001290
anthony3dd0f622010-05-13 12:57:32 +00001291 /* set all kernel values within disk area to scale given */
1292 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
cristyc99304f2010-02-01 15:26:27 +00001293 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
anthony602ab9b2010-01-05 08:06:50 +00001294 if ((u*u+v*v) <= limit)
anthony4fd27e22010-02-07 08:17:18 +00001295 kernel->positive_range += kernel->values[i] = args->sigma;
anthony602ab9b2010-01-05 08:06:50 +00001296 else
1297 kernel->values[i] = nan;
anthony4fd27e22010-02-07 08:17:18 +00001298 kernel->minimum = kernel->maximum = args->sigma; /* a flat shape */
anthony602ab9b2010-01-05 08:06:50 +00001299 break;
1300 }
1301 case PlusKernel:
1302 {
1303 if (args->rho < 1.0)
anthonyc94cdb02010-01-06 08:15:29 +00001304 kernel->width = kernel->height = 5; /* default radius 2 */
anthony602ab9b2010-01-05 08:06:50 +00001305 else
1306 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
cristyc99304f2010-02-01 15:26:27 +00001307 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony602ab9b2010-01-05 08:06:50 +00001308
1309 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
1310 kernel->height*sizeof(double));
1311 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +00001312 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +00001313
anthony3dd0f622010-05-13 12:57:32 +00001314 /* set all kernel values along axises to given scale */
cristyc99304f2010-02-01 15:26:27 +00001315 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
1316 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
anthony4fd27e22010-02-07 08:17:18 +00001317 kernel->values[i] = (u == 0 || v == 0) ? args->sigma : nan;
1318 kernel->minimum = kernel->maximum = args->sigma; /* a flat shape */
1319 kernel->positive_range = args->sigma*(kernel->width*2.0 - 1.0);
anthony602ab9b2010-01-05 08:06:50 +00001320 break;
1321 }
anthony3dd0f622010-05-13 12:57:32 +00001322 case CrossKernel:
1323 {
1324 if (args->rho < 1.0)
1325 kernel->width = kernel->height = 5; /* default radius 2 */
1326 else
1327 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
1328 kernel->x = kernel->y = (long) (kernel->width-1)/2;
1329
1330 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
1331 kernel->height*sizeof(double));
1332 if (kernel->values == (double *) NULL)
1333 return(DestroyKernelInfo(kernel));
1334
1335 /* set all kernel values along axises to given scale */
1336 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
1337 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
1338 kernel->values[i] = (u == v || u == -v) ? args->sigma : nan;
1339 kernel->minimum = kernel->maximum = args->sigma; /* a flat shape */
1340 kernel->positive_range = args->sigma*(kernel->width*2.0 - 1.0);
1341 break;
1342 }
1343 /* HitAndMiss Kernels */
anthonyc1061722010-05-14 06:23:49 +00001344 case RingKernel:
anthony3dd0f622010-05-13 12:57:32 +00001345 case PeaksKernel:
1346 {
1347 long
1348 limit1,
anthonyc1061722010-05-14 06:23:49 +00001349 limit2,
1350 scale;
anthony3dd0f622010-05-13 12:57:32 +00001351
1352 if (args->rho < args->sigma)
1353 {
1354 kernel->width = ((unsigned long)args->sigma)*2+1;
1355 limit1 = (long)args->rho*args->rho;
1356 limit2 = (long)args->sigma*args->sigma;
1357 }
1358 else
1359 {
1360 kernel->width = ((unsigned long)args->rho)*2+1;
1361 limit1 = (long)args->sigma*args->sigma;
1362 limit2 = (long)args->rho*args->rho;
1363 }
anthonyc1061722010-05-14 06:23:49 +00001364 if ( limit2 <= 0 )
1365 kernel->width = 7L, limit1 = 7L, limit2 = 11L;
1366
anthony3dd0f622010-05-13 12:57:32 +00001367 kernel->height = kernel->width;
1368 kernel->x = kernel->y = (long) (kernel->width-1)/2;
1369 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
1370 kernel->height*sizeof(double));
1371 if (kernel->values == (double *) NULL)
1372 return(DestroyKernelInfo(kernel));
1373
anthonyc1061722010-05-14 06:23:49 +00001374 /* set a ring of points of 'scale' ( 0.0 for PeaksKernel ) */
1375 scale = ( type == PeaksKernel) ? 0.0 : args->xi;
anthony3dd0f622010-05-13 12:57:32 +00001376 for ( i=0, v= -kernel->y; v <= (long)kernel->y; v++)
1377 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
1378 { long radius=u*u+v*v;
anthonyc1061722010-05-14 06:23:49 +00001379 if (limit1 < radius && radius <= limit2)
1380 kernel->positive_range += kernel->values[i] = scale;
anthony3dd0f622010-05-13 12:57:32 +00001381 else
1382 kernel->values[i] = nan;
1383 }
anthonyc1061722010-05-14 06:23:49 +00001384 kernel->minimum = kernel->minimum = scale;
1385 if ( type == PeaksKernel ) {
1386 /* set the central point in the middle */
1387 kernel->values[kernel->x+kernel->y*kernel->width] = 1.0;
1388 kernel->positive_range = 1.0;
1389 kernel->maximum = 1.0;
1390 }
anthony3dd0f622010-05-13 12:57:32 +00001391 break;
1392 }
1393 case CornersKernel:
1394 {
anthony4f1dcb72010-05-14 08:43:10 +00001395 kernel=ParseKernelArray("3: 0,0,- 0,1,1 -,1,-");
anthony3dd0f622010-05-13 12:57:32 +00001396 if (kernel == (KernelInfo *) NULL)
1397 return(kernel);
1398 kernel->type = type;
1399 ExpandKernelInfo(kernel, 90.0); /* Create a list of 4 rotated kernels */
1400 break;
1401 }
1402 case LineEndsKernel:
1403 {
anthony4f1dcb72010-05-14 08:43:10 +00001404 kernel=ParseKernelArray("3: 0,-,- 0,1,0 0,0,0");
anthony3dd0f622010-05-13 12:57:32 +00001405 if (kernel == (KernelInfo *) NULL)
1406 return(kernel);
1407 kernel->type = type;
1408 ExpandKernelInfo(kernel, 45.0); /* Create a list of 8 rotated kernels */
1409 break;
1410 }
1411 case LineJunctionsKernel:
1412 {
1413 KernelInfo
1414 *new_kernel;
anthony3dd0f622010-05-13 12:57:32 +00001415 /* first set of 4 kernels */
anthony4f1dcb72010-05-14 08:43:10 +00001416 kernel=ParseKernelArray("3: -,1,- -,1,- 1,-,1");
anthony3dd0f622010-05-13 12:57:32 +00001417 if (kernel == (KernelInfo *) NULL)
1418 return(kernel);
1419 kernel->type = type;
1420 ExpandKernelInfo(kernel, 90.0);
1421 /* append second set of 4 kernels */
anthony4f1dcb72010-05-14 08:43:10 +00001422 new_kernel=ParseKernelArray("3: 1,-,- -,1,- 1,-,1");
anthony3dd0f622010-05-13 12:57:32 +00001423 if (new_kernel == (KernelInfo *) NULL)
1424 return(DestroyKernelInfo(kernel));
1425 kernel->type = type;
1426 ExpandKernelInfo(new_kernel, 90.0);
1427 LastKernelInfo(kernel)->next = new_kernel;
1428 /* append Thrid set of 4 kernels */
anthony4f1dcb72010-05-14 08:43:10 +00001429 new_kernel=ParseKernelArray("3: -,1,- -,1,1 1,-,-");
anthony3dd0f622010-05-13 12:57:32 +00001430 if (new_kernel == (KernelInfo *) NULL)
1431 return(DestroyKernelInfo(kernel));
1432 kernel->type = type;
1433 ExpandKernelInfo(new_kernel, 90.0);
1434 LastKernelInfo(kernel)->next = new_kernel;
1435 break;
1436 }
anthony4f1dcb72010-05-14 08:43:10 +00001437 case ThickenKernel:
1438 { /* Thicken Kernel ?? -- Under trial */
1439 kernel=ParseKernelArray("3: 1,1,- 1,0,0 -,0,0");
1440 if (kernel == (KernelInfo *) NULL)
1441 return(kernel);
1442 kernel->type = type;
1443 ExpandKernelInfo(kernel, 45);
1444 break;
1445 }
1446 case ThinningKernel:
1447 { /* Thinning Kernel ?? -- Under trial */
1448 kernel=ParseKernelArray("3: 0,0,- 0,1,1 -,1,1");
1449 if (kernel == (KernelInfo *) NULL)
1450 return(kernel);
1451 kernel->type = type;
1452 ExpandKernelInfo(kernel, 45);
1453 break;
1454 }
anthony3dd0f622010-05-13 12:57:32 +00001455 case ConvexHullKernel:
1456 {
1457 KernelInfo
1458 *new_kernel;
anthony3dd0f622010-05-13 12:57:32 +00001459 /* first set of 4 kernels */
anthony4f1dcb72010-05-14 08:43:10 +00001460 kernel=ParseKernelArray("3: 1,1,- 1,0,- 1,-,0");
anthony3dd0f622010-05-13 12:57:32 +00001461 if (kernel == (KernelInfo *) NULL)
1462 return(kernel);
1463 kernel->type = type;
1464 ExpandKernelInfo(kernel, 90.0);
1465 /* append second set of 4 kernels */
anthony4f1dcb72010-05-14 08:43:10 +00001466 new_kernel=ParseKernelArray("3: -,1,1 -,0,1 0,-,1");
anthony3dd0f622010-05-13 12:57:32 +00001467 if (new_kernel == (KernelInfo *) NULL)
1468 return(DestroyKernelInfo(kernel));
1469 kernel->type = type;
1470 ExpandKernelInfo(new_kernel, 90.0);
1471 LastKernelInfo(kernel)->next = new_kernel;
1472 break;
1473 }
1474 case SkeletonKernel:
1475 {
1476 KernelInfo
1477 *new_kernel;
anthony3dd0f622010-05-13 12:57:32 +00001478 /* first set of 4 kernels - corners */
anthony4f1dcb72010-05-14 08:43:10 +00001479 kernel=ParseKernelArray("3: 0,0,- 0,1,1 -,1,-");
anthony3dd0f622010-05-13 12:57:32 +00001480 if (kernel == (KernelInfo *) NULL)
1481 return(kernel);
1482 kernel->type = type;
1483 ExpandKernelInfo(kernel, 90);
1484 /* append second set of 4 kernels - edge middles */
anthony4f1dcb72010-05-14 08:43:10 +00001485 new_kernel=ParseKernelArray("3: 0,0,0 -,1,- 1,1,1");
anthony3dd0f622010-05-13 12:57:32 +00001486 if (new_kernel == (KernelInfo *) NULL)
1487 return(DestroyKernelInfo(kernel));
1488 kernel->type = type;
1489 ExpandKernelInfo(new_kernel, 90);
1490 LastKernelInfo(kernel)->next = new_kernel;
1491 break;
1492 }
anthony602ab9b2010-01-05 08:06:50 +00001493 /* Distance Measuring Kernels */
1494 case ChebyshevKernel:
1495 {
anthony602ab9b2010-01-05 08:06:50 +00001496 if (args->rho < 1.0)
anthonyc94cdb02010-01-06 08:15:29 +00001497 kernel->width = kernel->height = 3; /* default radius = 1 */
anthony602ab9b2010-01-05 08:06:50 +00001498 else
1499 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
cristyc99304f2010-02-01 15:26:27 +00001500 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony602ab9b2010-01-05 08:06:50 +00001501
1502 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
1503 kernel->height*sizeof(double));
1504 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +00001505 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +00001506
cristyc99304f2010-02-01 15:26:27 +00001507 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
1508 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
1509 kernel->positive_range += ( kernel->values[i] =
anthonyc84dce52010-05-07 05:42:23 +00001510 args->sigma*((labs(u)>labs(v)) ? labs(u) : labs(v)) );
cristyc99304f2010-02-01 15:26:27 +00001511 kernel->maximum = kernel->values[0];
anthony602ab9b2010-01-05 08:06:50 +00001512 break;
1513 }
1514 case ManhattenKernel:
1515 {
anthony602ab9b2010-01-05 08:06:50 +00001516 if (args->rho < 1.0)
anthonyc94cdb02010-01-06 08:15:29 +00001517 kernel->width = kernel->height = 3; /* default radius = 1 */
anthony602ab9b2010-01-05 08:06:50 +00001518 else
1519 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
cristyc99304f2010-02-01 15:26:27 +00001520 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony602ab9b2010-01-05 08:06:50 +00001521
1522 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
1523 kernel->height*sizeof(double));
1524 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +00001525 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +00001526
cristyc99304f2010-02-01 15:26:27 +00001527 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
1528 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
1529 kernel->positive_range += ( kernel->values[i] =
anthonyc84dce52010-05-07 05:42:23 +00001530 args->sigma*(labs(u)+labs(v)) );
cristyc99304f2010-02-01 15:26:27 +00001531 kernel->maximum = kernel->values[0];
anthony602ab9b2010-01-05 08:06:50 +00001532 break;
1533 }
1534 case EuclideanKernel:
1535 {
anthony602ab9b2010-01-05 08:06:50 +00001536 if (args->rho < 1.0)
anthonyc94cdb02010-01-06 08:15:29 +00001537 kernel->width = kernel->height = 3; /* default radius = 1 */
anthony602ab9b2010-01-05 08:06:50 +00001538 else
1539 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
cristyc99304f2010-02-01 15:26:27 +00001540 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony602ab9b2010-01-05 08:06:50 +00001541
1542 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
1543 kernel->height*sizeof(double));
1544 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +00001545 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +00001546
cristyc99304f2010-02-01 15:26:27 +00001547 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
1548 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
1549 kernel->positive_range += ( kernel->values[i] =
anthonyc84dce52010-05-07 05:42:23 +00001550 args->sigma*sqrt((double)(u*u+v*v)) );
cristyc99304f2010-02-01 15:26:27 +00001551 kernel->maximum = kernel->values[0];
anthony602ab9b2010-01-05 08:06:50 +00001552 break;
1553 }
anthony602ab9b2010-01-05 08:06:50 +00001554 default:
anthonyc1061722010-05-14 06:23:49 +00001555 {
1556 /* Generate a No-Op minimal kernel - 1x1 pixel */
1557 kernel=ParseKernelArray("1");
1558 if (kernel == (KernelInfo *) NULL)
1559 return(kernel);
1560 kernel->type = UndefinedKernel;
1561 break;
1562 }
anthony602ab9b2010-01-05 08:06:50 +00001563 break;
1564 }
1565
1566 return(kernel);
1567}
anthonyc94cdb02010-01-06 08:15:29 +00001568
anthony602ab9b2010-01-05 08:06:50 +00001569/*
1570%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1571% %
1572% %
1573% %
cristy6771f1e2010-03-05 19:43:39 +00001574% C l o n e K e r n e l I n f o %
anthony4fd27e22010-02-07 08:17:18 +00001575% %
1576% %
1577% %
1578%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1579%
anthony1b2bc0a2010-05-12 05:25:22 +00001580% CloneKernelInfo() creates a new clone of the given Kernel List so that its
1581% can be modified without effecting the original. The cloned kernel should
1582% be destroyed using DestoryKernelInfo() when no longer needed.
anthony7a01dcf2010-05-11 12:25:52 +00001583%
cristye6365592010-04-02 17:31:23 +00001584% The format of the CloneKernelInfo method is:
anthony4fd27e22010-02-07 08:17:18 +00001585%
anthony930be612010-02-08 04:26:15 +00001586% KernelInfo *CloneKernelInfo(const KernelInfo *kernel)
anthony4fd27e22010-02-07 08:17:18 +00001587%
1588% A description of each parameter follows:
1589%
1590% o kernel: the Morphology/Convolution kernel to be cloned
1591%
1592*/
cristyef656912010-03-05 19:54:59 +00001593MagickExport KernelInfo *CloneKernelInfo(const KernelInfo *kernel)
anthony4fd27e22010-02-07 08:17:18 +00001594{
1595 register long
1596 i;
1597
cristy19eb6412010-04-23 14:42:29 +00001598 KernelInfo
anthony7a01dcf2010-05-11 12:25:52 +00001599 *new_kernel;
anthony4fd27e22010-02-07 08:17:18 +00001600
1601 assert(kernel != (KernelInfo *) NULL);
anthony7a01dcf2010-05-11 12:25:52 +00001602 new_kernel=(KernelInfo *) AcquireMagickMemory(sizeof(*kernel));
1603 if (new_kernel == (KernelInfo *) NULL)
1604 return(new_kernel);
1605 *new_kernel=(*kernel); /* copy values in structure */
anthony7a01dcf2010-05-11 12:25:52 +00001606
1607 /* replace the values with a copy of the values */
1608 new_kernel->values=(double *) AcquireQuantumMemory(kernel->width,
cristy19eb6412010-04-23 14:42:29 +00001609 kernel->height*sizeof(double));
anthony7a01dcf2010-05-11 12:25:52 +00001610 if (new_kernel->values == (double *) NULL)
1611 return(DestroyKernelInfo(new_kernel));
anthony4fd27e22010-02-07 08:17:18 +00001612 for (i=0; i < (long) (kernel->width*kernel->height); i++)
anthony7a01dcf2010-05-11 12:25:52 +00001613 new_kernel->values[i]=kernel->values[i];
anthony1b2bc0a2010-05-12 05:25:22 +00001614
1615 /* Also clone the next kernel in the kernel list */
1616 if ( kernel->next != (KernelInfo *) NULL ) {
1617 new_kernel->next = CloneKernelInfo(kernel->next);
1618 if ( new_kernel->next == (KernelInfo *) NULL )
1619 return(DestroyKernelInfo(new_kernel));
1620 }
1621
anthony7a01dcf2010-05-11 12:25:52 +00001622 return(new_kernel);
anthony4fd27e22010-02-07 08:17:18 +00001623}
1624
1625/*
1626%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1627% %
1628% %
1629% %
anthony83ba99b2010-01-24 08:48:15 +00001630% D e s t r o y K e r n e l I n f o %
anthony602ab9b2010-01-05 08:06:50 +00001631% %
1632% %
1633% %
1634%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1635%
anthony83ba99b2010-01-24 08:48:15 +00001636% DestroyKernelInfo() frees the memory used by a Convolution/Morphology
1637% kernel.
anthony602ab9b2010-01-05 08:06:50 +00001638%
anthony83ba99b2010-01-24 08:48:15 +00001639% The format of the DestroyKernelInfo method is:
anthony602ab9b2010-01-05 08:06:50 +00001640%
anthony83ba99b2010-01-24 08:48:15 +00001641% KernelInfo *DestroyKernelInfo(KernelInfo *kernel)
anthony602ab9b2010-01-05 08:06:50 +00001642%
1643% A description of each parameter follows:
1644%
1645% o kernel: the Morphology/Convolution kernel to be destroyed
1646%
1647*/
1648
anthony83ba99b2010-01-24 08:48:15 +00001649MagickExport KernelInfo *DestroyKernelInfo(KernelInfo *kernel)
anthony602ab9b2010-01-05 08:06:50 +00001650{
cristy2be15382010-01-21 02:38:03 +00001651 assert(kernel != (KernelInfo *) NULL);
anthony4fd27e22010-02-07 08:17:18 +00001652
anthony7a01dcf2010-05-11 12:25:52 +00001653 if ( kernel->next != (KernelInfo *) NULL )
1654 kernel->next = DestroyKernelInfo(kernel->next);
1655
1656 kernel->values = (double *)RelinquishMagickMemory(kernel->values);
1657 kernel = (KernelInfo *) RelinquishMagickMemory(kernel);
anthony602ab9b2010-01-05 08:06:50 +00001658 return(kernel);
1659}
anthonyc94cdb02010-01-06 08:15:29 +00001660
1661/*
1662%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1663% %
1664% %
1665% %
anthony3c10fc82010-05-13 02:40:51 +00001666% E x p a n d K e r n e l I n f o %
1667% %
1668% %
1669% %
1670%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1671%
1672% ExpandKernelInfo() takes a single kernel, and expands it into a list
1673% of kernels each incrementally rotated the angle given.
1674%
1675% WARNING: 45 degree rotations only works for 3x3 kernels.
1676% While 90 degree roatations only works for linear and square kernels
1677%
1678% The format of the RotateKernelInfo method is:
1679%
1680% void ExpandKernelInfo(KernelInfo *kernel, double angle)
1681%
1682% A description of each parameter follows:
1683%
1684% o kernel: the Morphology/Convolution kernel
1685%
1686% o angle: angle to rotate in degrees
1687%
1688% This function is only internel to this module, as it is not finalized,
1689% especially with regard to non-orthogonal angles, and rotation of larger
1690% 2D kernels.
1691*/
1692static void ExpandKernelInfo(KernelInfo *kernel, double angle)
1693{
1694 KernelInfo
cristya9a61ad2010-05-13 12:47:41 +00001695 *clone,
anthony3c10fc82010-05-13 02:40:51 +00001696 *last;
cristya9a61ad2010-05-13 12:47:41 +00001697
anthony3c10fc82010-05-13 02:40:51 +00001698 double
1699 a;
1700
1701 last = kernel;
1702 for (a=angle; a<355.0; a+=angle) {
cristya9a61ad2010-05-13 12:47:41 +00001703 clone = CloneKernelInfo(last);
1704 RotateKernelInfo(clone, angle);
1705 last->next = clone;
1706 last = clone;
anthony3c10fc82010-05-13 02:40:51 +00001707 }
1708}
1709
1710/*
1711%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1712% %
1713% %
1714% %
anthony9eb4f742010-05-18 02:45:54 +00001715% M o r p h o l o g y A p p l y %
anthony602ab9b2010-01-05 08:06:50 +00001716% %
1717% %
1718% %
1719%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1720%
anthony9eb4f742010-05-18 02:45:54 +00001721% MorphologyApply() applies a morphological method, multiple times using
1722% a list of multiple kernels.
anthony602ab9b2010-01-05 08:06:50 +00001723%
anthony9eb4f742010-05-18 02:45:54 +00001724% It is basically equivelent to as MorphologyImageChannel() (see below) but
1725% without user controls, that that function extracts and applies to kernels
1726% and morphology methods.
1727%
1728% More specifically kernels are not normalized/scaled/blended by the
1729% 'convolve:scale' Image Artifact (-set setting), and the convolve bias
1730% (-bias setting or image->bias) is passed directly to this function,
1731% and not extracted from an image.
anthony602ab9b2010-01-05 08:06:50 +00001732%
1733% The format of the MorphologyImage method is:
1734%
anthony9eb4f742010-05-18 02:45:54 +00001735% Image *MorphologyApply(const Image *image,MorphologyMethod method,
1736% const long iterations,const KernelInfo *kernel,const double bias,
1737% ExceptionInfo *exception)
anthony602ab9b2010-01-05 08:06:50 +00001738%
1739% A description of each parameter follows:
1740%
1741% o image: the image.
1742%
1743% o method: the morphology method to be applied.
1744%
1745% o iterations: apply the operation this many times (or no change).
1746% A value of -1 means loop until no change found.
1747% How this is applied may depend on the morphology method.
1748% Typically this is a value of 1.
1749%
1750% o channel: the channel type.
1751%
1752% o kernel: An array of double representing the morphology kernel.
anthony29188a82010-01-22 10:12:34 +00001753% Warning: kernel may be normalized for the Convolve method.
anthony602ab9b2010-01-05 08:06:50 +00001754%
anthony9eb4f742010-05-18 02:45:54 +00001755% o bias: Convolution Bias to use.
1756%
anthony602ab9b2010-01-05 08:06:50 +00001757% o exception: return any errors or warnings in this structure.
1758%
anthony602ab9b2010-01-05 08:06:50 +00001759*/
1760
anthony930be612010-02-08 04:26:15 +00001761
anthony9eb4f742010-05-18 02:45:54 +00001762/* Apply a Morphology Primative to an image using the given kernel.
1763** Two pre-created images must be provided, no image is created.
1764** Returning the number of pixels that changed.
1765*/
anthony7a01dcf2010-05-11 12:25:52 +00001766static unsigned long MorphologyPrimative(const Image *image, Image
anthony602ab9b2010-01-05 08:06:50 +00001767 *result_image, const MorphologyMethod method, const ChannelType channel,
anthony9eb4f742010-05-18 02:45:54 +00001768 const KernelInfo *kernel,const double bias,ExceptionInfo *exception)
anthony602ab9b2010-01-05 08:06:50 +00001769{
cristy2be15382010-01-21 02:38:03 +00001770#define MorphologyTag "Morphology/Image"
anthony602ab9b2010-01-05 08:06:50 +00001771
1772 long
cristy150989e2010-02-01 14:59:39 +00001773 progress,
anthony29188a82010-01-22 10:12:34 +00001774 y, offx, offy,
anthony602ab9b2010-01-05 08:06:50 +00001775 changed;
1776
1777 MagickBooleanType
1778 status;
1779
anthony602ab9b2010-01-05 08:06:50 +00001780 CacheView
1781 *p_view,
1782 *q_view;
1783
anthony602ab9b2010-01-05 08:06:50 +00001784 status=MagickTrue;
1785 changed=0;
1786 progress=0;
1787
anthony602ab9b2010-01-05 08:06:50 +00001788 p_view=AcquireCacheView(image);
1789 q_view=AcquireCacheView(result_image);
anthony29188a82010-01-22 10:12:34 +00001790
anthonycc6c8362010-01-25 04:14:01 +00001791 /* Some methods (including convolve) needs use a reflected kernel.
anthony9eb4f742010-05-18 02:45:54 +00001792 * Adjust 'origin' offsets to loop though kernel as a reflection.
anthony29188a82010-01-22 10:12:34 +00001793 */
cristyc99304f2010-02-01 15:26:27 +00001794 offx = kernel->x;
1795 offy = kernel->y;
anthony29188a82010-01-22 10:12:34 +00001796 switch(method) {
anthony930be612010-02-08 04:26:15 +00001797 case ConvolveMorphology:
1798 case DilateMorphology:
1799 case DilateIntensityMorphology:
1800 case DistanceMorphology:
anthony5ef8e942010-05-11 06:51:12 +00001801 /* kernel needs to used with reflection about origin */
cristy150989e2010-02-01 14:59:39 +00001802 offx = (long) kernel->width-offx-1;
1803 offy = (long) kernel->height-offy-1;
anthony29188a82010-01-22 10:12:34 +00001804 break;
anthony5ef8e942010-05-11 06:51:12 +00001805 case ErodeMorphology:
1806 case ErodeIntensityMorphology:
1807 case HitAndMissMorphology:
1808 case ThinningMorphology:
1809 case ThickenMorphology:
1810 /* kernel is user as is, without reflection */
1811 break;
anthony930be612010-02-08 04:26:15 +00001812 default:
anthony9eb4f742010-05-18 02:45:54 +00001813 assert("Not a Primitive Morphology Method" != (char *) NULL);
anthony930be612010-02-08 04:26:15 +00001814 break;
anthony29188a82010-01-22 10:12:34 +00001815 }
1816
anthony602ab9b2010-01-05 08:06:50 +00001817#if defined(MAGICKCORE_OPENMP_SUPPORT)
1818 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
1819#endif
cristy150989e2010-02-01 14:59:39 +00001820 for (y=0; y < (long) image->rows; y++)
anthony602ab9b2010-01-05 08:06:50 +00001821 {
1822 MagickBooleanType
1823 sync;
1824
1825 register const PixelPacket
1826 *restrict p;
1827
1828 register const IndexPacket
1829 *restrict p_indexes;
1830
1831 register PixelPacket
1832 *restrict q;
1833
1834 register IndexPacket
1835 *restrict q_indexes;
1836
cristy150989e2010-02-01 14:59:39 +00001837 register long
anthony602ab9b2010-01-05 08:06:50 +00001838 x;
1839
anthony29188a82010-01-22 10:12:34 +00001840 unsigned long
anthony602ab9b2010-01-05 08:06:50 +00001841 r;
1842
1843 if (status == MagickFalse)
1844 continue;
anthony29188a82010-01-22 10:12:34 +00001845 p=GetCacheViewVirtualPixels(p_view, -offx, y-offy,
1846 image->columns+kernel->width, kernel->height, exception);
anthony602ab9b2010-01-05 08:06:50 +00001847 q=GetCacheViewAuthenticPixels(q_view,0,y,result_image->columns,1,
1848 exception);
1849 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
1850 {
1851 status=MagickFalse;
1852 continue;
1853 }
1854 p_indexes=GetCacheViewVirtualIndexQueue(p_view);
1855 q_indexes=GetCacheViewAuthenticIndexQueue(q_view);
anthony29188a82010-01-22 10:12:34 +00001856 r = (image->columns+kernel->width)*offy+offx; /* constant */
1857
cristy150989e2010-02-01 14:59:39 +00001858 for (x=0; x < (long) image->columns; x++)
anthony602ab9b2010-01-05 08:06:50 +00001859 {
cristy150989e2010-02-01 14:59:39 +00001860 long
anthony602ab9b2010-01-05 08:06:50 +00001861 v;
1862
cristy150989e2010-02-01 14:59:39 +00001863 register long
anthony602ab9b2010-01-05 08:06:50 +00001864 u;
1865
1866 register const double
1867 *restrict k;
1868
1869 register const PixelPacket
1870 *restrict k_pixels;
1871
1872 register const IndexPacket
1873 *restrict k_indexes;
1874
1875 MagickPixelPacket
anthony5ef8e942010-05-11 06:51:12 +00001876 result,
1877 min,
1878 max;
anthony602ab9b2010-01-05 08:06:50 +00001879
anthony29188a82010-01-22 10:12:34 +00001880 /* Copy input to ouput image for unused channels
anthony83ba99b2010-01-24 08:48:15 +00001881 * This removes need for 'cloning' a new image every iteration
anthony29188a82010-01-22 10:12:34 +00001882 */
anthony602ab9b2010-01-05 08:06:50 +00001883 *q = p[r];
1884 if (image->colorspace == CMYKColorspace)
1885 q_indexes[x] = p_indexes[r];
1886
anthony5ef8e942010-05-11 06:51:12 +00001887 /* Defaults */
1888 min.red =
1889 min.green =
1890 min.blue =
1891 min.opacity =
1892 min.index = (MagickRealType) QuantumRange;
1893 max.red =
1894 max.green =
1895 max.blue =
1896 max.opacity =
1897 max.index = (MagickRealType) 0;
anthony9eb4f742010-05-18 02:45:54 +00001898 /* default result is the original pixel value */
anthony5ef8e942010-05-11 06:51:12 +00001899 result.red = (MagickRealType) p[r].red;
1900 result.green = (MagickRealType) p[r].green;
1901 result.blue = (MagickRealType) p[r].blue;
1902 result.opacity = QuantumRange - (MagickRealType) p[r].opacity;
cristy9b1a5442010-05-13 12:35:40 +00001903 result.index = 0;
anthony5ef8e942010-05-11 06:51:12 +00001904 if ( image->colorspace == CMYKColorspace)
1905 result.index = (MagickRealType) p_indexes[r];
1906
anthony602ab9b2010-01-05 08:06:50 +00001907 switch (method) {
1908 case ConvolveMorphology:
anthony9eb4f742010-05-18 02:45:54 +00001909 /* Set the user defined bias of the weighted average output */
1910 result.red =
1911 result.green =
1912 result.blue =
1913 result.opacity =
1914 result.index = bias;
anthony930be612010-02-08 04:26:15 +00001915 break;
anthony4fd27e22010-02-07 08:17:18 +00001916 case DilateIntensityMorphology:
1917 case ErodeIntensityMorphology:
anthony9eb4f742010-05-18 02:45:54 +00001918 /* use a boolean flag indicating when first match found */
1919 result.red = 0.0; /* result is not used otherwise */
anthony4fd27e22010-02-07 08:17:18 +00001920 break;
anthony602ab9b2010-01-05 08:06:50 +00001921 default:
anthony602ab9b2010-01-05 08:06:50 +00001922 break;
1923 }
1924
1925 switch ( method ) {
1926 case ConvolveMorphology:
anthony930be612010-02-08 04:26:15 +00001927 /* Weighted Average of pixels using reflected kernel
1928 **
1929 ** NOTE for correct working of this operation for asymetrical
1930 ** kernels, the kernel needs to be applied in its reflected form.
1931 ** That is its values needs to be reversed.
1932 **
1933 ** Correlation is actually the same as this but without reflecting
1934 ** the kernel, and thus 'lower-level' that Convolution. However
1935 ** as Convolution is the more common method used, and it does not
1936 ** really cost us much in terms of processing to use a reflected
anthony5ef8e942010-05-11 06:51:12 +00001937 ** kernel, so it is Convolution that is implemented.
anthony930be612010-02-08 04:26:15 +00001938 **
1939 ** Correlation will have its kernel reflected before calling
1940 ** this function to do a Convolve.
1941 **
1942 ** For more details of Correlation vs Convolution see
1943 ** http://www.cs.umd.edu/~djacobs/CMSC426/Convolution.pdf
1944 */
anthony5ef8e942010-05-11 06:51:12 +00001945 if (((channel & SyncChannels) != 0 ) &&
1946 (image->matte == MagickTrue))
1947 { /* Channel has a 'Sync' Flag, and Alpha Channel enabled.
1948 ** Weight the color channels with Alpha Channel so that
1949 ** transparent pixels are not part of the results.
1950 */
anthony602ab9b2010-01-05 08:06:50 +00001951 MagickRealType
anthony5ef8e942010-05-11 06:51:12 +00001952 alpha, /* color channel weighting : kernel*alpha */
1953 gamma; /* divisor, sum of weighting values */
anthony602ab9b2010-01-05 08:06:50 +00001954
1955 gamma=0.0;
anthony29188a82010-01-22 10:12:34 +00001956 k = &kernel->values[ kernel->width*kernel->height-1 ];
anthony602ab9b2010-01-05 08:06:50 +00001957 k_pixels = p;
1958 k_indexes = p_indexes;
cristy150989e2010-02-01 14:59:39 +00001959 for (v=0; v < (long) kernel->height; v++) {
1960 for (u=0; u < (long) kernel->width; u++, k--) {
anthony602ab9b2010-01-05 08:06:50 +00001961 if ( IsNan(*k) ) continue;
1962 alpha=(*k)*(QuantumScale*(QuantumRange-
1963 k_pixels[u].opacity));
1964 gamma += alpha;
1965 result.red += alpha*k_pixels[u].red;
1966 result.green += alpha*k_pixels[u].green;
1967 result.blue += alpha*k_pixels[u].blue;
anthony83ba99b2010-01-24 08:48:15 +00001968 result.opacity += (*k)*(QuantumRange-k_pixels[u].opacity);
anthony602ab9b2010-01-05 08:06:50 +00001969 if ( image->colorspace == CMYKColorspace)
1970 result.index += alpha*k_indexes[u];
1971 }
1972 k_pixels += image->columns+kernel->width;
1973 k_indexes += image->columns+kernel->width;
1974 }
1975 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
anthony83ba99b2010-01-24 08:48:15 +00001976 result.red *= gamma;
1977 result.green *= gamma;
1978 result.blue *= gamma;
1979 result.opacity *= gamma;
1980 result.index *= gamma;
anthony602ab9b2010-01-05 08:06:50 +00001981 }
anthony5ef8e942010-05-11 06:51:12 +00001982 else
1983 {
1984 /* No 'Sync' flag, or no Alpha involved.
1985 ** Convolution is simple individual channel weigthed sum.
1986 */
1987 k = &kernel->values[ kernel->width*kernel->height-1 ];
1988 k_pixels = p;
1989 k_indexes = p_indexes;
1990 for (v=0; v < (long) kernel->height; v++) {
1991 for (u=0; u < (long) kernel->width; u++, k--) {
1992 if ( IsNan(*k) ) continue;
1993 result.red += (*k)*k_pixels[u].red;
1994 result.green += (*k)*k_pixels[u].green;
1995 result.blue += (*k)*k_pixels[u].blue;
1996 result.opacity += (*k)*(QuantumRange-k_pixels[u].opacity);
1997 if ( image->colorspace == CMYKColorspace)
1998 result.index += (*k)*k_indexes[u];
1999 }
2000 k_pixels += image->columns+kernel->width;
2001 k_indexes += image->columns+kernel->width;
2002 }
2003 }
anthony602ab9b2010-01-05 08:06:50 +00002004 break;
2005
anthony4fd27e22010-02-07 08:17:18 +00002006 case ErodeMorphology:
anthony5ef8e942010-05-11 06:51:12 +00002007 /* Minimum Value within kernel neighbourhood
anthony930be612010-02-08 04:26:15 +00002008 **
2009 ** NOTE that the kernel is not reflected for this operation!
2010 **
2011 ** NOTE: in normal Greyscale Morphology, the kernel value should
2012 ** be added to the real value, this is currently not done, due to
2013 ** the nature of the boolean kernels being used.
2014 */
anthony4fd27e22010-02-07 08:17:18 +00002015 k = kernel->values;
2016 k_pixels = p;
2017 k_indexes = p_indexes;
2018 for (v=0; v < (long) kernel->height; v++) {
2019 for (u=0; u < (long) kernel->width; u++, k++) {
2020 if ( IsNan(*k) || (*k) < 0.5 ) continue;
anthony5ef8e942010-05-11 06:51:12 +00002021 Minimize(min.red, (double) k_pixels[u].red);
2022 Minimize(min.green, (double) k_pixels[u].green);
2023 Minimize(min.blue, (double) k_pixels[u].blue);
2024 Minimize(min.opacity,
anthonyd37a5cb2010-05-07 06:37:03 +00002025 QuantumRange-(double) k_pixels[u].opacity);
anthony4fd27e22010-02-07 08:17:18 +00002026 if ( image->colorspace == CMYKColorspace)
anthony5ef8e942010-05-11 06:51:12 +00002027 Minimize(min.index, (double) k_indexes[u]);
anthony4fd27e22010-02-07 08:17:18 +00002028 }
2029 k_pixels += image->columns+kernel->width;
2030 k_indexes += image->columns+kernel->width;
2031 }
2032 break;
2033
anthony5ef8e942010-05-11 06:51:12 +00002034
anthony83ba99b2010-01-24 08:48:15 +00002035 case DilateMorphology:
anthony5ef8e942010-05-11 06:51:12 +00002036 /* Maximum Value within kernel neighbourhood
anthony930be612010-02-08 04:26:15 +00002037 **
2038 ** NOTE for correct working of this operation for asymetrical
2039 ** kernels, the kernel needs to be applied in its reflected form.
2040 ** That is its values needs to be reversed.
2041 **
2042 ** NOTE: in normal Greyscale Morphology, the kernel value should
2043 ** be added to the real value, this is currently not done, due to
2044 ** the nature of the boolean kernels being used.
2045 **
2046 */
anthony29188a82010-01-22 10:12:34 +00002047 k = &kernel->values[ kernel->width*kernel->height-1 ];
anthony602ab9b2010-01-05 08:06:50 +00002048 k_pixels = p;
2049 k_indexes = p_indexes;
cristy150989e2010-02-01 14:59:39 +00002050 for (v=0; v < (long) kernel->height; v++) {
2051 for (u=0; u < (long) kernel->width; u++, k--) {
anthony602ab9b2010-01-05 08:06:50 +00002052 if ( IsNan(*k) || (*k) < 0.5 ) continue;
anthony5ef8e942010-05-11 06:51:12 +00002053 Maximize(max.red, (double) k_pixels[u].red);
2054 Maximize(max.green, (double) k_pixels[u].green);
2055 Maximize(max.blue, (double) k_pixels[u].blue);
2056 Maximize(max.opacity,
anthonyd37a5cb2010-05-07 06:37:03 +00002057 QuantumRange-(double) k_pixels[u].opacity);
anthony602ab9b2010-01-05 08:06:50 +00002058 if ( image->colorspace == CMYKColorspace)
anthony5ef8e942010-05-11 06:51:12 +00002059 Maximize(max.index, (double) k_indexes[u]);
anthony602ab9b2010-01-05 08:06:50 +00002060 }
2061 k_pixels += image->columns+kernel->width;
2062 k_indexes += image->columns+kernel->width;
2063 }
anthony602ab9b2010-01-05 08:06:50 +00002064 break;
2065
anthony5ef8e942010-05-11 06:51:12 +00002066 case HitAndMissMorphology:
2067 case ThinningMorphology:
2068 case ThickenMorphology:
2069 /* Minimum of Foreground Pixel minus Maxumum of Background Pixels
2070 **
2071 ** NOTE that the kernel is not reflected for this operation,
2072 ** and consists of both foreground and background pixel
2073 ** neighbourhoods, 0.0 for background, and 1.0 for foreground
2074 ** with either Nan or 0.5 values for don't care.
2075 **
2076 ** Note that this can produce negative results, though really
2077 ** only a positive match has any real value.
2078 */
2079 k = kernel->values;
2080 k_pixels = p;
2081 k_indexes = p_indexes;
2082 for (v=0; v < (long) kernel->height; v++) {
2083 for (u=0; u < (long) kernel->width; u++, k++) {
2084 if ( IsNan(*k) ) continue;
2085 if ( (*k) > 0.7 )
2086 { /* minimim of foreground pixels */
2087 Minimize(min.red, (double) k_pixels[u].red);
2088 Minimize(min.green, (double) k_pixels[u].green);
2089 Minimize(min.blue, (double) k_pixels[u].blue);
2090 Minimize(min.opacity,
2091 QuantumRange-(double) k_pixels[u].opacity);
2092 if ( image->colorspace == CMYKColorspace)
2093 Minimize(min.index, (double) k_indexes[u]);
2094 }
2095 else if ( (*k) < 0.3 )
2096 { /* maximum of background pixels */
2097 Maximize(max.red, (double) k_pixels[u].red);
2098 Maximize(max.green, (double) k_pixels[u].green);
2099 Maximize(max.blue, (double) k_pixels[u].blue);
2100 Maximize(max.opacity,
2101 QuantumRange-(double) k_pixels[u].opacity);
2102 if ( image->colorspace == CMYKColorspace)
2103 Maximize(max.index, (double) k_indexes[u]);
2104 }
2105 }
2106 k_pixels += image->columns+kernel->width;
2107 k_indexes += image->columns+kernel->width;
2108 }
2109 /* Pattern Match only if min fg larger than min bg pixels */
2110 min.red -= max.red; Maximize( min.red, 0.0 );
2111 min.green -= max.green; Maximize( min.green, 0.0 );
2112 min.blue -= max.blue; Maximize( min.blue, 0.0 );
2113 min.opacity -= max.opacity; Maximize( min.opacity, 0.0 );
2114 min.index -= max.index; Maximize( min.index, 0.0 );
2115 break;
2116
anthony4fd27e22010-02-07 08:17:18 +00002117 case ErodeIntensityMorphology:
anthony930be612010-02-08 04:26:15 +00002118 /* Select Pixel with Minimum Intensity within kernel neighbourhood
2119 **
2120 ** WARNING: the intensity test fails for CMYK and does not
2121 ** take into account the moderating effect of teh alpha channel
2122 ** on the intensity.
2123 **
2124 ** NOTE that the kernel is not reflected for this operation!
2125 */
anthony602ab9b2010-01-05 08:06:50 +00002126 k = kernel->values;
2127 k_pixels = p;
2128 k_indexes = p_indexes;
cristy150989e2010-02-01 14:59:39 +00002129 for (v=0; v < (long) kernel->height; v++) {
2130 for (u=0; u < (long) kernel->width; u++, k++) {
anthony602ab9b2010-01-05 08:06:50 +00002131 if ( IsNan(*k) || (*k) < 0.5 ) continue;
anthony4fd27e22010-02-07 08:17:18 +00002132 if ( result.red == 0.0 ||
2133 PixelIntensity(&(k_pixels[u])) < PixelIntensity(q) ) {
2134 /* copy the whole pixel - no channel selection */
2135 *q = k_pixels[u];
2136 if ( result.red > 0.0 ) changed++;
2137 result.red = 1.0;
2138 }
anthony602ab9b2010-01-05 08:06:50 +00002139 }
2140 k_pixels += image->columns+kernel->width;
2141 k_indexes += image->columns+kernel->width;
2142 }
anthony602ab9b2010-01-05 08:06:50 +00002143 break;
2144
anthony83ba99b2010-01-24 08:48:15 +00002145 case DilateIntensityMorphology:
anthony930be612010-02-08 04:26:15 +00002146 /* Select Pixel with Maximum Intensity within kernel neighbourhood
2147 **
2148 ** WARNING: the intensity test fails for CMYK and does not
anthony9eb4f742010-05-18 02:45:54 +00002149 ** take into account the moderating effect of the alpha channel
2150 ** on the intensity (yet).
anthony930be612010-02-08 04:26:15 +00002151 **
2152 ** NOTE for correct working of this operation for asymetrical
2153 ** kernels, the kernel needs to be applied in its reflected form.
2154 ** That is its values needs to be reversed.
2155 */
anthony29188a82010-01-22 10:12:34 +00002156 k = &kernel->values[ kernel->width*kernel->height-1 ];
anthony602ab9b2010-01-05 08:06:50 +00002157 k_pixels = p;
2158 k_indexes = p_indexes;
cristy150989e2010-02-01 14:59:39 +00002159 for (v=0; v < (long) kernel->height; v++) {
2160 for (u=0; u < (long) kernel->width; u++, k--) {
anthony29188a82010-01-22 10:12:34 +00002161 if ( IsNan(*k) || (*k) < 0.5 ) continue; /* boolean kernel */
2162 if ( result.red == 0.0 ||
2163 PixelIntensity(&(k_pixels[u])) > PixelIntensity(q) ) {
2164 /* copy the whole pixel - no channel selection */
2165 *q = k_pixels[u];
2166 if ( result.red > 0.0 ) changed++;
2167 result.red = 1.0;
2168 }
anthony602ab9b2010-01-05 08:06:50 +00002169 }
2170 k_pixels += image->columns+kernel->width;
2171 k_indexes += image->columns+kernel->width;
2172 }
anthony602ab9b2010-01-05 08:06:50 +00002173 break;
2174
anthony5ef8e942010-05-11 06:51:12 +00002175
anthony602ab9b2010-01-05 08:06:50 +00002176 case DistanceMorphology:
anthony930be612010-02-08 04:26:15 +00002177 /* Add kernel Value and select the minimum value found.
2178 ** The result is a iterative distance from edge of image shape.
2179 **
2180 ** All Distance Kernels are symetrical, but that may not always
2181 ** be the case. For example how about a distance from left edges?
2182 ** To work correctly with asymetrical kernels the reflected kernel
2183 ** needs to be applied.
anthony5ef8e942010-05-11 06:51:12 +00002184 **
2185 ** Actually this is really a GreyErode with a negative kernel!
2186 **
anthony930be612010-02-08 04:26:15 +00002187 */
anthony29188a82010-01-22 10:12:34 +00002188 k = &kernel->values[ kernel->width*kernel->height-1 ];
anthony602ab9b2010-01-05 08:06:50 +00002189 k_pixels = p;
2190 k_indexes = p_indexes;
cristy150989e2010-02-01 14:59:39 +00002191 for (v=0; v < (long) kernel->height; v++) {
2192 for (u=0; u < (long) kernel->width; u++, k--) {
anthony602ab9b2010-01-05 08:06:50 +00002193 if ( IsNan(*k) ) continue;
2194 Minimize(result.red, (*k)+k_pixels[u].red);
2195 Minimize(result.green, (*k)+k_pixels[u].green);
2196 Minimize(result.blue, (*k)+k_pixels[u].blue);
2197 Minimize(result.opacity, (*k)+QuantumRange-k_pixels[u].opacity);
2198 if ( image->colorspace == CMYKColorspace)
2199 Minimize(result.index, (*k)+k_indexes[u]);
2200 }
2201 k_pixels += image->columns+kernel->width;
2202 k_indexes += image->columns+kernel->width;
2203 }
anthony602ab9b2010-01-05 08:06:50 +00002204 break;
2205
2206 case UndefinedMorphology:
2207 default:
2208 break; /* Do nothing */
anthony83ba99b2010-01-24 08:48:15 +00002209 }
anthony5ef8e942010-05-11 06:51:12 +00002210 /* Final mathematics of results (combine with original image?)
2211 **
2212 ** NOTE: Difference Morphology operators Edge* and *Hat could also
2213 ** be done here but works better with iteration as a image difference
2214 ** in the controling function (below). Thicken and Thinning however
2215 ** should be done here so thay can be iterated correctly.
2216 */
2217 switch ( method ) {
2218 case HitAndMissMorphology:
2219 case ErodeMorphology:
2220 result = min; /* minimum of neighbourhood */
2221 break;
2222 case DilateMorphology:
2223 result = max; /* maximum of neighbourhood */
2224 break;
2225 case ThinningMorphology:
2226 /* subtract pattern match from original */
2227 result.red -= min.red;
2228 result.green -= min.green;
2229 result.blue -= min.blue;
2230 result.opacity -= min.opacity;
2231 result.index -= min.index;
2232 break;
2233 case ThickenMorphology:
2234 /* Union with original image (maximize) - or should this be + */
2235 Maximize( result.red, min.red );
2236 Maximize( result.green, min.green );
2237 Maximize( result.blue, min.blue );
2238 Maximize( result.opacity, min.opacity );
2239 Maximize( result.index, min.index );
2240 break;
2241 default:
2242 /* result directly calculated or assigned */
2243 break;
2244 }
2245 /* Assign the resulting pixel values - Clamping Result */
anthony83ba99b2010-01-24 08:48:15 +00002246 switch ( method ) {
2247 case UndefinedMorphology:
2248 case DilateIntensityMorphology:
2249 case ErodeIntensityMorphology:
anthony930be612010-02-08 04:26:15 +00002250 break; /* full pixel was directly assigned - not a channel method */
anthony83ba99b2010-01-24 08:48:15 +00002251 default:
anthony83ba99b2010-01-24 08:48:15 +00002252 if ((channel & RedChannel) != 0)
2253 q->red = ClampToQuantum(result.red);
2254 if ((channel & GreenChannel) != 0)
2255 q->green = ClampToQuantum(result.green);
2256 if ((channel & BlueChannel) != 0)
2257 q->blue = ClampToQuantum(result.blue);
2258 if ((channel & OpacityChannel) != 0
2259 && image->matte == MagickTrue )
2260 q->opacity = ClampToQuantum(QuantumRange-result.opacity);
2261 if ((channel & IndexChannel) != 0
2262 && image->colorspace == CMYKColorspace)
2263 q_indexes[x] = ClampToQuantum(result.index);
2264 break;
2265 }
anthony5ef8e942010-05-11 06:51:12 +00002266 /* Count up changed pixels */
anthony83ba99b2010-01-24 08:48:15 +00002267 if ( ( p[r].red != q->red )
2268 || ( p[r].green != q->green )
2269 || ( p[r].blue != q->blue )
2270 || ( p[r].opacity != q->opacity )
2271 || ( image->colorspace == CMYKColorspace &&
2272 p_indexes[r] != q_indexes[x] ) )
2273 changed++; /* The pixel had some value changed! */
anthony602ab9b2010-01-05 08:06:50 +00002274 p++;
2275 q++;
anthony83ba99b2010-01-24 08:48:15 +00002276 } /* x */
anthony602ab9b2010-01-05 08:06:50 +00002277 sync=SyncCacheViewAuthenticPixels(q_view,exception);
2278 if (sync == MagickFalse)
2279 status=MagickFalse;
2280 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2281 {
2282 MagickBooleanType
2283 proceed;
2284
2285#if defined(MAGICKCORE_OPENMP_SUPPORT)
2286 #pragma omp critical (MagickCore_MorphologyImage)
2287#endif
2288 proceed=SetImageProgress(image,MorphologyTag,progress++,image->rows);
2289 if (proceed == MagickFalse)
2290 status=MagickFalse;
2291 }
anthony83ba99b2010-01-24 08:48:15 +00002292 } /* y */
anthony602ab9b2010-01-05 08:06:50 +00002293 result_image->type=image->type;
2294 q_view=DestroyCacheView(q_view);
2295 p_view=DestroyCacheView(p_view);
cristy150989e2010-02-01 14:59:39 +00002296 return(status ? (unsigned long) changed : 0);
anthony602ab9b2010-01-05 08:06:50 +00002297}
2298
anthony4fd27e22010-02-07 08:17:18 +00002299
anthony9eb4f742010-05-18 02:45:54 +00002300MagickExport Image *MorphologyApply(const Image *image, const ChannelType
2301 channel,const MorphologyMethod method, const long iterations,
2302 const KernelInfo *kernel,const double bias,ExceptionInfo *exception)
cristy2be15382010-01-21 02:38:03 +00002303{
2304 Image
anthony9eb4f742010-05-18 02:45:54 +00002305 *curr_image, /* Image we are working with */
2306 *work_image, /* secondary working image */
2307 *save_image; /* save image for later use */
anthony602ab9b2010-01-05 08:06:50 +00002308
anthony4fd27e22010-02-07 08:17:18 +00002309 KernelInfo
anthony9eb4f742010-05-18 02:45:54 +00002310 *curr_kernel, /* current kernel list to apply */
2311 *this_kernel; /* current individual kernel to apply */
anthony4fd27e22010-02-07 08:17:18 +00002312
2313 MorphologyMethod
anthony9eb4f742010-05-18 02:45:54 +00002314 primative; /* the current morphology primative being applied */
2315
2316 MagickBooleanType
2317 verbose; /* verbose output of results */
2318
2319 CompositeOperator
2320 kernel_compose; /* Handling the result of multiple kernels*/
anthony4fd27e22010-02-07 08:17:18 +00002321
anthony1b2bc0a2010-05-12 05:25:22 +00002322 unsigned long
anthony9eb4f742010-05-18 02:45:54 +00002323 count, /* count of primative steps applied */
2324 loop, /* number of times though kernel list (iterations) */
2325 loop_limit, /* finish looping after this many times */
2326 stage, /* stage number for compound morphology */
2327 changed, /* number pixels changed by one primative operation */
2328 loop_changed, /* changes made over loop though of kernels */
2329 total_changed, /* total count of all changes to image */
2330 kernel_number; /* kernel number being applied */
anthony1b2bc0a2010-05-12 05:25:22 +00002331
anthony602ab9b2010-01-05 08:06:50 +00002332 assert(image != (Image *) NULL);
2333 assert(image->signature == MagickSignature);
anthony4fd27e22010-02-07 08:17:18 +00002334 assert(kernel != (KernelInfo *) NULL);
2335 assert(kernel->signature == MagickSignature);
anthony602ab9b2010-01-05 08:06:50 +00002336 assert(exception != (ExceptionInfo *) NULL);
2337 assert(exception->signature == MagickSignature);
2338
anthony9eb4f742010-05-18 02:45:54 +00002339 loop_limit = iterations;
2340 if ( iterations < 0 )
2341 loop_limit = image->columns > image->rows ? image->columns : image->rows;
anthony602ab9b2010-01-05 08:06:50 +00002342 if ( iterations == 0 )
2343 return((Image *)NULL); /* null operation - nothing to do! */
2344
2345 /* kernel must be valid at this point
2346 * (except maybe for posible future morphology methods like "Prune"
2347 */
cristy2be15382010-01-21 02:38:03 +00002348 assert(kernel != (KernelInfo *)NULL);
anthony602ab9b2010-01-05 08:06:50 +00002349
anthony9eb4f742010-05-18 02:45:54 +00002350 verbose = ( GetImageArtifact(image,"verbose") != (const char *) NULL );
anthony4f1dcb72010-05-14 08:43:10 +00002351
anthony9eb4f742010-05-18 02:45:54 +00002352 /* initialise for cleanup */
2353 curr_image = (Image *) image; /* result of morpholgy primative */
2354 work_image = (Image *) NULL; /* secondary working image */
2355 save_image = (Image *) NULL; /* save image for some compound methods */
2356 curr_kernel = (KernelInfo *)kernel; /* allow kernel list to be modified */
anthony4fd27e22010-02-07 08:17:18 +00002357
anthony9eb4f742010-05-18 02:45:54 +00002358 kernel_compose = NoCompositeOp; /* iterated over all kernels */
anthony602ab9b2010-01-05 08:06:50 +00002359
anthony9eb4f742010-05-18 02:45:54 +00002360 /* Select initial primative morphology to apply */
2361 switch( method ) {
anthony930be612010-02-08 04:26:15 +00002362 case CorrelateMorphology:
2363 /* A Correlation is actually a Convolution with a reflected kernel.
anthony9eb4f742010-05-18 02:45:54 +00002364 ** However a Convolution is a weighted sum using a reflected kernel.
anthony930be612010-02-08 04:26:15 +00002365 ** It may seem stange to convert a Correlation into a Convolution
2366 ** as the Correleation is the simplier method, but Convolution is
2367 ** much more commonly used, and it makes sense to implement it directly
2368 ** so as to avoid the need to duplicate the kernel when it is not
2369 ** required (which is typically the default).
2370 */
anthony9eb4f742010-05-18 02:45:54 +00002371 curr_kernel = CloneKernelInfo(kernel);
anthony1b2bc0a2010-05-12 05:25:22 +00002372 if (curr_kernel == (KernelInfo *) NULL)
2373 goto error_cleanup;
anthony930be612010-02-08 04:26:15 +00002374 RotateKernelInfo(curr_kernel,180);
anthony9eb4f742010-05-18 02:45:54 +00002375 /* FALL THRU to Convolve */
anthonyc94cdb02010-01-06 08:15:29 +00002376 case ConvolveMorphology:
anthony9eb4f742010-05-18 02:45:54 +00002377 primative = ConvolveMorphology;
2378 kernel_compose = NoCompositeOp;
2379 break;
2380 case ErodeMorphology: /* just erode */
2381 case OpenMorphology: /* erode then dialate */
2382 case EdgeInMorphology: /* erode and image difference */
2383 case TopHatMorphology: /* erode, dilate and image difference */
2384 case SmoothMorphology: /* erode, dilate, dilate, erode */
2385 primative = ErodeMorphology;
2386 break;
2387 case ErodeIntensityMorphology:
2388 case OpenIntensityMorphology:
2389 primative = ErodeIntensityMorphology;
2390 break;
2391 case DilateMorphology: /* just dilate */
2392 case EdgeOutMorphology: /* dilate and image difference */
2393 case EdgeMorphology: /* dilate and erode difference */
2394 primative = DilateMorphology;
2395 break;
2396 case CloseMorphology: /* dilate, then erode */
2397 case BottomHatMorphology: /* dilate and image difference */
2398 curr_kernel = CloneKernelInfo(kernel);
2399 if (curr_kernel == (KernelInfo *) NULL)
anthony1b2bc0a2010-05-12 05:25:22 +00002400 goto error_cleanup;
anthony9eb4f742010-05-18 02:45:54 +00002401 RotateKernelInfo(curr_kernel,180);
2402 primative = DilateMorphology;
2403 break;
2404 case DilateIntensityMorphology:
2405 case CloseIntensityMorphology:
2406 curr_kernel = CloneKernelInfo(kernel);
2407 if (curr_kernel == (KernelInfo *) NULL)
2408 goto error_cleanup;
2409 RotateKernelInfo(curr_kernel,180);
2410 primative = DilateIntensityMorphology;
2411 break;
2412 case HitAndMissMorphology:
2413 primative = HitAndMissMorphology;
2414 loop_limit = 1; /* iterate only once */
2415 kernel_compose = LightenCompositeOp; /* Union of Hit-And-Miss */
2416 break;
2417 case ThinningMorphology: /* iterative morphology */
2418 case ThickenMorphology:
2419 case DistanceMorphology: /* Distance should never use multple kernels */
2420 case UndefinedMorphology:
2421 kernel_compose = NoCompositeOp;
anthony930be612010-02-08 04:26:15 +00002422 break;
anthony602ab9b2010-01-05 08:06:50 +00002423 }
2424
anthony9eb4f742010-05-18 02:45:54 +00002425 { /* User override of results handling */
2426 const char
2427 *artifact = GetImageArtifact(image,"morphology:style");
2428 if ( artifact != (const char *) NULL ) {
2429 if (LocaleCompare("union",artifact) == 0)
2430 kernel_compose = LightenCompositeOp;
2431 if (LocaleCompare("iterate",artifact) == 0)
2432 kernel_compose = NoCompositeOp;
2433 else
2434 kernel_compose = (CompositeOperator) ParseMagickOption(
2435 MagickComposeOptions,MagickFalse,artifact);
2436 if ( kernel_compose == UndefinedCompositeOp )
2437 perror("Invalid \"morphology:compose\" setting\n");
2438 }
2439 }
anthony7a01dcf2010-05-11 12:25:52 +00002440
anthony9eb4f742010-05-18 02:45:54 +00002441 /* Initialize compound morphology stages */
2442 count = 0; /* number of low-level morphology primatives performed */
2443 total_changed = 0; /* total number of pixels changed thoughout */
2444 stage = 1; /* the compound morphology stage number */
2445
2446 /* compount morphology staging loop */
2447 while ( 1 ) {
2448
2449#if 1
2450 /* Extra information for debugging compound operations */
2451 if ( verbose == MagickTrue && primative != method )
2452 fprintf(stderr, "Morphology %s: Stage %lu %s%s (%s)\n",
2453 MagickOptionToMnemonic(MagickMorphologyOptions, method), stage,
2454 MagickOptionToMnemonic(MagickMorphologyOptions, primative),
2455 ( curr_kernel == kernel) ? "" : "*",
2456 ( kernel_compose == NoCompositeOp ) ? "iterate"
2457 : MagickOptionToMnemonic(MagickComposeOptions, kernel_compose) );
2458#endif
2459
2460 if ( kernel_compose == NoCompositeOp ) {
2461 /******************************
2462 ** Iterate over all Kernels **
2463 ******************************/
2464 loop = 0;
2465 loop_changed = 1;
2466 while ( loop < loop_limit && loop_changed > 0 ) {
2467 loop++; /* the iteration of this kernel */
2468
2469 loop_changed = 0;
2470 this_kernel = curr_kernel;
2471 kernel_number = 0;
2472 while ( this_kernel != NULL ) {
2473
2474 /* Create a destination image, if not yet defined */
2475 if ( work_image == (Image *) NULL )
2476 {
2477 work_image=CloneImage(image,0,0,MagickTrue,exception);
2478 if (work_image == (Image *) NULL)
2479 goto error_cleanup;
2480 if (SetImageStorageClass(work_image,DirectClass) == MagickFalse)
2481 {
2482 InheritException(exception,&work_image->exception);
2483 goto error_cleanup;
2484 }
2485 }
2486
2487 /* morphological primative curr -> work */
2488 count++;
2489 changed = MorphologyPrimative(curr_image, work_image, primative,
2490 channel, this_kernel, bias, exception);
2491 loop_changed += changed;
2492 total_changed += changed;
2493
2494 if ( verbose == MagickTrue )
2495 fprintf(stderr, "Morphology %s:%lu.%lu #%lu => Changed %lu\n",
2496 MagickOptionToMnemonic(MagickMorphologyOptions, primative),
2497 loop, kernel_number, count, changed);
2498
2499 /* prepare next loop */
2500 { Image *tmp = work_image; /* swap images for iteration */
2501 work_image = curr_image;
2502 curr_image = tmp;
2503 }
2504 if ( work_image == image )
2505 work_image = (Image *) NULL; /* never assign image to 'work' */
2506 this_kernel = this_kernel->next; /* prepare next kernel (if any) */
anthony1b2bc0a2010-05-12 05:25:22 +00002507 kernel_number++;
2508 }
anthony7a01dcf2010-05-11 12:25:52 +00002509
anthony9eb4f742010-05-18 02:45:54 +00002510 if ( verbose == MagickTrue && kernel->next != NULL )
2511 fprintf(stderr, "Morphology %s:%lu #%lu ===> Changed %lu Total %lu\n",
2512 MagickOptionToMnemonic(MagickMorphologyOptions, primative),
2513 loop, count, loop_changed, total_changed );
anthony602ab9b2010-01-05 08:06:50 +00002514 }
anthony1b2bc0a2010-05-12 05:25:22 +00002515 }
2516
anthony9eb4f742010-05-18 02:45:54 +00002517 else {
2518 /*************************************
2519 ** Composition of Iterated Kernels **
2520 *************************************/
2521 Image
2522 *input_image, /* starting point for kernels */
2523 *union_image;
2524 input_image = curr_image;
2525 union_image = (Image *) NULL;
2526
2527 this_kernel = curr_kernel;
2528 kernel_number = 0;
2529 while ( this_kernel != NULL ) {
2530
2531 if( curr_image != (Image *) NULL && curr_image != input_image )
2532 curr_image=DestroyImage(curr_image);
2533 curr_image = input_image; /* always start with the original image */
2534
2535 loop = 0;
2536 changed = 1;
2537 loop_changed = 0;
2538 while ( loop < loop_limit && changed > 0 ) {
2539 loop++; /* the iteration of this kernel */
2540
2541 /* Create a destination image, if not defined */
2542 if ( work_image == (Image *) NULL )
2543 {
2544 work_image=CloneImage(image,0,0,MagickTrue,exception);
2545 if (work_image == (Image *) NULL)
2546 goto error_cleanup;
2547 if (SetImageStorageClass(work_image,DirectClass) == MagickFalse)
2548 {
2549 InheritException(exception,&curr_image->exception);
2550 if( union_image != (Image *) NULL )
2551 union_image=DestroyImage(union_image);
2552 if( curr_image != input_image )
2553 curr_image = DestroyImage(curr_image);
2554 curr_image = (Image *) input_image;
2555 goto error_cleanup;
2556 }
2557 }
2558
2559 /* morphological primative curr -> work */
2560 count++;
2561 changed = MorphologyPrimative(curr_image,work_image,primative,
2562 channel, this_kernel, bias, exception);
2563 loop_changed += changed;
2564 total_changed += changed;
2565
2566 if ( verbose == MagickTrue )
anthony4f1dcb72010-05-14 08:43:10 +00002567 fprintf(stderr, "Morphology %s:%lu.%lu #%lu => Changed %lu\n",
anthony9eb4f742010-05-18 02:45:54 +00002568 MagickOptionToMnemonic(MagickMorphologyOptions, primative),
2569 loop, kernel_number, count, changed);
2570
2571 /* prepare next loop */
2572 { Image *tmp = work_image; /* swap images for iteration */
2573 work_image = curr_image; /* curr_image is now the results */
2574 curr_image = tmp;
2575 }
2576 if ( work_image == input_image )
2577 work_image = (Image *) NULL; /* clear work of the input_image */
2578
2579 } /* end kernel iteration */
2580
2581 /* make a union of the iterated kernel */
2582 if ( union_image == (Image *) NULL) /* start the union? */
2583 union_image = curr_image, curr_image = (Image *)NULL;
2584 else
2585 (void) CompositeImageChannel(union_image,
2586 (ChannelType) (channel & ~SyncChannels), kernel_compose,
2587 curr_image, 0, 0);
2588
2589 this_kernel = this_kernel->next; /* next kernel (if any) */
anthony1b2bc0a2010-05-12 05:25:22 +00002590 kernel_number++;
anthony602ab9b2010-01-05 08:06:50 +00002591 }
anthony9eb4f742010-05-18 02:45:54 +00002592
2593 if ( verbose == MagickTrue && kernel->next != NULL && loop_limit > 1 )
2594 fprintf(stderr, "Morphology %s:%lu #%lu ===> Changed %lu Total %lu\n",
2595 MagickOptionToMnemonic(MagickMorphologyOptions, primative),
2596 loop, count, loop_changed, total_changed );
2597
2598#if 0
2599fprintf(stderr, "--E-- image=0x%lx\n", (unsigned long)image);
2600fprintf(stderr, " input=0x%lx\n", (unsigned long)input_image);
2601fprintf(stderr, " union=0x%lx\n", (unsigned long)union_image);
2602fprintf(stderr, " curr =0x%lx\n", (unsigned long)curr_image);
2603fprintf(stderr, " work =0x%lx\n", (unsigned long)work_image);
2604fprintf(stderr, " save =0x%lx\n", (unsigned long)save_image);
2605#endif
2606
2607 /* Finish up - return the union of results */
2608 if( curr_image != (Image *) NULL && curr_image != input_image )
2609 curr_image=DestroyImage(curr_image);
2610 if( input_image != input_image )
2611 input_image = DestroyImage(input_image);
2612 curr_image = union_image;
anthony1b2bc0a2010-05-12 05:25:22 +00002613 }
anthony9eb4f742010-05-18 02:45:54 +00002614
2615 /* Compound Morphology Operations
2616 * set next 'primative' iteration, and continue
2617 * or break when all operations are complete.
2618 */
2619 stage++; /* what is the next stage number to do */
2620 switch( method ) {
2621 case SmoothMorphology: /* open, close */
2622 switch ( stage ) {
2623 /* case 1: initialized above */
2624 case 2: /* open part 2 */
2625 primative = DilateMorphology;
2626 continue;
2627 case 3: /* close part 1 */
2628 curr_kernel = CloneKernelInfo(kernel);
2629 if (curr_kernel == (KernelInfo *) NULL)
2630 goto error_cleanup;
2631 RotateKernelInfo(curr_kernel,180);
2632 continue;
2633 case 4: /* close part 2 */
2634 primative = ErodeMorphology;
2635 continue;
2636 }
2637 break;
2638 case OpenMorphology: /* erode, dilate */
2639 case TopHatMorphology:
2640 primative = DilateMorphology;
2641 if ( stage <= 2 ) continue;
2642 break;
2643 case OpenIntensityMorphology:
2644 primative = DilateIntensityMorphology;
2645 if ( stage <= 2 ) continue;
2646 break;
2647 case CloseMorphology: /* dilate, erode */
2648 case BottomHatMorphology:
2649 primative = ErodeMorphology;
2650 if ( stage <= 2 ) continue;
2651 break;
2652 case CloseIntensityMorphology:
2653 primative = ErodeIntensityMorphology;
2654 if ( stage <= 2 ) continue;
2655 break;
2656 case EdgeMorphology: /* dilate and erode difference */
2657 if (stage <= 2) {
2658 save_image = curr_image;
2659 curr_image = (Image *) image;
2660 primative = ErodeMorphology;
2661 continue;
2662 }
2663 break;
2664 default: /* Primitive Morphology is just finished! */
2665 break;
2666 }
2667
2668 if ( verbose == MagickTrue && count > 1 )
2669 fprintf(stderr, "Morphology %s: ======> Total %lu\n",
2670 MagickOptionToMnemonic(MagickMorphologyOptions, method),
2671 total_changed );
2672
2673 /* If we reach this point we are finished! - Break the Loop */
2674 break;
anthony602ab9b2010-01-05 08:06:50 +00002675 }
anthony930be612010-02-08 04:26:15 +00002676
anthony9eb4f742010-05-18 02:45:54 +00002677 /* Final Post-processing for some Compound Methods
anthony7d10d742010-05-06 07:05:29 +00002678 **
2679 ** The removal of any 'Sync' channel flag in the Image Compositon below
2680 ** ensures the compose method is applied in a purely mathematical way, only
2681 ** the selected channels, without any normal 'alpha blending' normally
2682 ** associated with the compose method.
2683 **
2684 ** Note "method" here is the 'original' morphological method, and not the
2685 ** 'current' morphological method used above to generate "new_image".
2686 */
anthony4fd27e22010-02-07 08:17:18 +00002687 switch( method ) {
2688 case EdgeOutMorphology:
2689 case EdgeInMorphology:
2690 case TopHatMorphology:
2691 case BottomHatMorphology:
anthony9eb4f742010-05-18 02:45:54 +00002692 (void) CompositeImageChannel(curr_image,
2693 (ChannelType) (channel & ~SyncChannels), DifferenceCompositeOp,
2694 image, 0, 0);
anthony4fd27e22010-02-07 08:17:18 +00002695 break;
anthony7d10d742010-05-06 07:05:29 +00002696 case EdgeMorphology:
anthony9eb4f742010-05-18 02:45:54 +00002697 /* Difference the Eroded Image with a Dilate image */
2698 (void) CompositeImageChannel(curr_image,
2699 (ChannelType) (channel & ~SyncChannels), DifferenceCompositeOp,
2700 save_image, 0, 0);
anthony4fd27e22010-02-07 08:17:18 +00002701 break;
2702 default:
2703 break;
2704 }
anthony602ab9b2010-01-05 08:06:50 +00002705
anthony9eb4f742010-05-18 02:45:54 +00002706 goto exit_cleanup;
anthony1b2bc0a2010-05-12 05:25:22 +00002707
2708 /* Yes goto's are bad, but in this case it makes cleanup lot more efficient */
2709error_cleanup:
anthony9eb4f742010-05-18 02:45:54 +00002710 if ( curr_image != (Image *) NULL && curr_image != image )
2711 DestroyImage(curr_image);
anthony1b2bc0a2010-05-12 05:25:22 +00002712exit_cleanup:
anthony9eb4f742010-05-18 02:45:54 +00002713 if ( work_image != (Image *) NULL )
2714 DestroyImage(work_image);
2715 if ( save_image != (Image *) NULL )
2716 DestroyImage(save_image);
2717 return(curr_image);
2718}
2719
2720/*
2721%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2722% %
2723% %
2724% %
2725% M o r p h o l o g y I m a g e C h a n n e l %
2726% %
2727% %
2728% %
2729%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2730%
2731% MorphologyImageChannel() applies a user supplied kernel to the image
2732% according to the given mophology method.
2733%
2734% This function applies any and all user defined settings before calling
2735% the above internal function MorphologyApply().
2736%
2737% User defined settings include...
2738% * convolution/correlation output bias (as per "-bias")
2739% * kernel normalization/scaling settings ("-set 'option:convolve:scale'")
2740% * kernel printing (after modification) ("-set option:showkernel 1")
2741%
2742%
2743% The format of the MorphologyImage method is:
2744%
2745% Image *MorphologyImage(const Image *image,MorphologyMethod method,
2746% const long iterations,KernelInfo *kernel,ExceptionInfo *exception)
2747%
2748% Image *MorphologyImageChannel(const Image *image, const ChannelType
2749% channel,MorphologyMethod method,const long iterations,
2750% KernelInfo *kernel,ExceptionInfo *exception)
2751%
2752% A description of each parameter follows:
2753%
2754% o image: the image.
2755%
2756% o method: the morphology method to be applied.
2757%
2758% o iterations: apply the operation this many times (or no change).
2759% A value of -1 means loop until no change found.
2760% How this is applied may depend on the morphology method.
2761% Typically this is a value of 1.
2762%
2763% o channel: the channel type.
2764%
2765% o kernel: An array of double representing the morphology kernel.
2766% Warning: kernel may be normalized for the Convolve method.
2767%
2768% o exception: return any errors or warnings in this structure.
2769%
2770*/
2771
2772MagickExport Image *MorphologyImageChannel(const Image *image,
2773 const ChannelType channel,const MorphologyMethod method,
2774 const long iterations,const KernelInfo *kernel,ExceptionInfo *exception)
2775{
2776 const char
2777 *artifact;
2778
2779 KernelInfo
2780 *curr_kernel;
2781
2782 Image
2783 *morphology_image;
2784
2785
2786 /* Apply Convolve/Correlate Normalization and Scaling Factors
2787 this is done BEFORE the ShowKernelInfo() function is called
2788 so that users can see the results of the 'convolve:scale' option.
2789 */
2790 curr_kernel = (KernelInfo *) kernel;
2791 if ( method == ConvolveMorphology )
2792 {
2793 artifact = GetImageArtifact(image,"convolve:scale");
2794 if ( artifact != (char *)NULL ) {
2795 GeometryFlags
2796 flags;
2797 GeometryInfo
2798 args;
2799
2800 if ( curr_kernel == kernel )
2801 curr_kernel = CloneKernelInfo(kernel);
2802 if (curr_kernel == (KernelInfo *) NULL) {
2803 curr_kernel=DestroyKernelInfo(curr_kernel);
2804 return((Image *) NULL);
2805 }
2806 args.rho = 1.0;
2807 flags = (GeometryFlags) ParseGeometry(artifact, &args);
2808 ScaleKernelInfo(curr_kernel, args.rho, flags);
2809 }
2810 }
2811
2812 /* display the (normalized) kernel via stderr */
2813 artifact = GetImageArtifact(image,"showkernel");
2814 if ( artifact != (const char *) NULL)
2815 ShowKernelInfo(curr_kernel);
2816
2817 /* Apply the Morphology */
2818 morphology_image = MorphologyApply(image, channel, method, iterations,
2819 curr_kernel, image->bias, exception);
2820
2821 /* Cleanup and Exit */
2822 if ( curr_kernel != kernel )
anthony1b2bc0a2010-05-12 05:25:22 +00002823 curr_kernel=DestroyKernelInfo(curr_kernel);
anthony9eb4f742010-05-18 02:45:54 +00002824 return(morphology_image);
2825}
2826
2827MagickExport Image *MorphologyImage(const Image *image, const MorphologyMethod
2828 method, const long iterations,const KernelInfo *kernel, ExceptionInfo
2829 *exception)
2830{
2831 Image
2832 *morphology_image;
2833
2834 morphology_image=MorphologyImageChannel(image,DefaultChannels,method,
2835 iterations,kernel,exception);
2836 return(morphology_image);
anthony602ab9b2010-01-05 08:06:50 +00002837}
anthony83ba99b2010-01-24 08:48:15 +00002838
2839/*
2840%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2841% %
2842% %
2843% %
anthony4fd27e22010-02-07 08:17:18 +00002844+ R o t a t e K e r n e l I n f o %
anthony83ba99b2010-01-24 08:48:15 +00002845% %
2846% %
2847% %
2848%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2849%
anthony4fd27e22010-02-07 08:17:18 +00002850% RotateKernelInfo() rotates the kernel by the angle given. Currently it is
anthony83ba99b2010-01-24 08:48:15 +00002851% restricted to 90 degree angles, but this may be improved in the future.
2852%
anthony4fd27e22010-02-07 08:17:18 +00002853% The format of the RotateKernelInfo method is:
anthony83ba99b2010-01-24 08:48:15 +00002854%
anthony4fd27e22010-02-07 08:17:18 +00002855% void RotateKernelInfo(KernelInfo *kernel, double angle)
anthony83ba99b2010-01-24 08:48:15 +00002856%
2857% A description of each parameter follows:
2858%
2859% o kernel: the Morphology/Convolution kernel
2860%
2861% o angle: angle to rotate in degrees
2862%
anthonyc4c86e02010-01-27 09:30:32 +00002863% This function is only internel to this module, as it is not finalized,
2864% especially with regard to non-orthogonal angles, and rotation of larger
2865% 2D kernels.
anthony83ba99b2010-01-24 08:48:15 +00002866*/
anthony4fd27e22010-02-07 08:17:18 +00002867static void RotateKernelInfo(KernelInfo *kernel, double angle)
anthony83ba99b2010-01-24 08:48:15 +00002868{
anthony1b2bc0a2010-05-12 05:25:22 +00002869 /* angle the lower kernels first */
2870 if ( kernel->next != (KernelInfo *) NULL)
2871 RotateKernelInfo(kernel->next, angle);
2872
anthony83ba99b2010-01-24 08:48:15 +00002873 /* WARNING: Currently assumes the kernel (rightly) is horizontally symetrical
2874 **
2875 ** TODO: expand beyond simple 90 degree rotates, flips and flops
2876 */
2877
2878 /* Modulus the angle */
2879 angle = fmod(angle, 360.0);
2880 if ( angle < 0 )
2881 angle += 360.0;
2882
anthony3c10fc82010-05-13 02:40:51 +00002883 if ( 337.5 < angle || angle <= 22.5 )
anthony83ba99b2010-01-24 08:48:15 +00002884 return; /* no change! - At least at this time */
2885
anthony3dd0f622010-05-13 12:57:32 +00002886 /* Handle special cases */
anthony83ba99b2010-01-24 08:48:15 +00002887 switch (kernel->type) {
2888 /* These built-in kernels are cylindrical kernels, rotating is useless */
2889 case GaussianKernel:
anthony83ba99b2010-01-24 08:48:15 +00002890 case DOGKernel:
2891 case DiskKernel:
anthony3dd0f622010-05-13 12:57:32 +00002892 case PeaksKernel:
2893 case LaplacianKernel:
anthony83ba99b2010-01-24 08:48:15 +00002894 case ChebyshevKernel:
2895 case ManhattenKernel:
2896 case EuclideanKernel:
2897 return;
2898
2899 /* These may be rotatable at non-90 angles in the future */
2900 /* but simply rotating them in multiples of 90 degrees is useless */
2901 case SquareKernel:
2902 case DiamondKernel:
2903 case PlusKernel:
anthony3dd0f622010-05-13 12:57:32 +00002904 case CrossKernel:
anthony83ba99b2010-01-24 08:48:15 +00002905 return;
2906
2907 /* These only allows a +/-90 degree rotation (by transpose) */
2908 /* A 180 degree rotation is useless */
2909 case BlurKernel:
2910 case RectangleKernel:
2911 if ( 135.0 < angle && angle <= 225.0 )
2912 return;
2913 if ( 225.0 < angle && angle <= 315.0 )
2914 angle -= 180;
2915 break;
2916
anthony3dd0f622010-05-13 12:57:32 +00002917 default:
anthony83ba99b2010-01-24 08:48:15 +00002918 break;
2919 }
anthony3c10fc82010-05-13 02:40:51 +00002920 /* Attempt rotations by 45 degrees */
2921 if ( 22.5 < fmod(angle,90.0) && fmod(angle,90.0) <= 67.5 )
2922 {
2923 if ( kernel->width == 3 && kernel->height == 3 )
2924 { /* Rotate a 3x3 square by 45 degree angle */
2925 MagickRealType t = kernel->values[0];
2926 kernel->values[0] = kernel->values[1];
2927 kernel->values[1] = kernel->values[2];
2928 kernel->values[2] = kernel->values[5];
2929 kernel->values[5] = kernel->values[8];
2930 kernel->values[8] = kernel->values[7];
2931 kernel->values[7] = kernel->values[6];
2932 kernel->values[6] = kernel->values[3];
2933 kernel->values[3] = t;
2934 angle = fmod(angle+45.0, 360.0);
2935 }
2936 else
2937 perror("Unable to rotate non-3x3 kernel by 45 degrees");
2938 }
2939 if ( 45.0 < fmod(angle, 180.0) && fmod(angle,180.0) <= 135.0 )
2940 {
2941 if ( kernel->width == 1 || kernel->height == 1 )
2942 { /* Do a transpose of the image, which results in a 90
2943 ** degree rotation of a 1 dimentional kernel
2944 */
2945 long
2946 t;
2947 t = (long) kernel->width;
2948 kernel->width = kernel->height;
2949 kernel->height = (unsigned long) t;
2950 t = kernel->x;
2951 kernel->x = kernel->y;
2952 kernel->y = t;
2953 if ( kernel->width == 1 )
2954 angle = fmod(angle+270.0, 360.0); /* angle -90 degrees */
2955 else
2956 angle = fmod(angle+90.0, 360.0); /* angle +90 degrees */
2957 }
2958 else if ( kernel->width == kernel->height )
2959 { /* Rotate a square array of values by 90 degrees */
2960 register unsigned long
2961 i,j,x,y;
2962 register MagickRealType
2963 *k,t;
2964 k=kernel->values;
2965 for( i=0, x=kernel->width-1; i<=x; i++, x--)
2966 for( j=0, y=kernel->height-1; j<y; j++, y--)
2967 { t = k[i+j*kernel->width];
2968 k[i+j*kernel->width] = k[y+i*kernel->width];
2969 k[y+i*kernel->width] = k[x+y*kernel->width];
2970 k[x+y*kernel->width] = k[j+x*kernel->width];
2971 k[j+x*kernel->width] = t;
2972 }
2973 angle = fmod(angle+90.0, 360.0); /* angle +90 degrees */
2974 }
2975 else
2976 perror("Unable to rotate a non-square, non-linear kernel 90 degrees");
2977 }
anthony83ba99b2010-01-24 08:48:15 +00002978 if ( 135.0 < angle && angle <= 225.0 )
2979 {
2980 /* For a 180 degree rotation - also know as a reflection */
2981 /* This is actually a very very common operation! */
2982 /* Basically all that is needed is a reversal of the kernel data! */
2983 unsigned long
2984 i,j;
2985 register double
2986 *k,t;
2987
2988 k=kernel->values;
2989 for ( i=0, j=kernel->width*kernel->height-1; i<j; i++, j--)
2990 t=k[i], k[i]=k[j], k[j]=t;
2991
anthony930be612010-02-08 04:26:15 +00002992 kernel->x = (long) kernel->width - kernel->x - 1;
2993 kernel->y = (long) kernel->height - kernel->y - 1;
anthony3c10fc82010-05-13 02:40:51 +00002994 angle = fmod(angle+180.0, 360.0); /* angle+180 degrees */
anthony83ba99b2010-01-24 08:48:15 +00002995 }
anthony3c10fc82010-05-13 02:40:51 +00002996 /* At this point angle should at least between -45 (315) and +45 degrees
anthony83ba99b2010-01-24 08:48:15 +00002997 * In the future some form of non-orthogonal angled rotates could be
2998 * performed here, posibily with a linear kernel restriction.
2999 */
3000
3001#if 0
anthony3c10fc82010-05-13 02:40:51 +00003002 { /* Do a Flop by reversing each row.
anthony83ba99b2010-01-24 08:48:15 +00003003 */
3004 unsigned long
3005 y;
cristy150989e2010-02-01 14:59:39 +00003006 register long
anthony83ba99b2010-01-24 08:48:15 +00003007 x,r;
3008 register double
3009 *k,t;
3010
3011 for ( y=0, k=kernel->values; y < kernel->height; y++, k+=kernel->width)
3012 for ( x=0, r=kernel->width-1; x<kernel->width/2; x++, r--)
3013 t=k[x], k[x]=k[r], k[r]=t;
3014
cristyc99304f2010-02-01 15:26:27 +00003015 kernel->x = kernel->width - kernel->x - 1;
anthony83ba99b2010-01-24 08:48:15 +00003016 angle = fmod(angle+180.0, 360.0);
3017 }
3018#endif
3019 return;
3020}
3021
3022/*
3023%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3024% %
3025% %
3026% %
cristy6771f1e2010-03-05 19:43:39 +00003027% S c a l e K e r n e l I n f o %
anthonycc6c8362010-01-25 04:14:01 +00003028% %
3029% %
3030% %
3031%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3032%
anthony1b2bc0a2010-05-12 05:25:22 +00003033% ScaleKernelInfo() scales the given kernel list by the given amount, with or
3034% without normalization of the sum of the kernel values (as per given flags).
anthonycc6c8362010-01-25 04:14:01 +00003035%
anthony999bb2c2010-02-18 12:38:01 +00003036% By default (no flags given) the values within the kernel is scaled
anthony1b2bc0a2010-05-12 05:25:22 +00003037% directly using given scaling factor without change.
anthonycc6c8362010-01-25 04:14:01 +00003038%
anthony1b2bc0a2010-05-12 05:25:22 +00003039% If any 'normalize_flags' are given the kernel will first be normalized and
3040% then further scaled by the scaling factor value given. A 'PercentValue'
3041% flag will cause the given scaling factor to be divided by one hundred
3042% percent.
anthony999bb2c2010-02-18 12:38:01 +00003043%
3044% Kernel normalization ('normalize_flags' given) is designed to ensure that
3045% any use of the kernel scaling factor with 'Convolve' or 'Correlate'
anthony1b2bc0a2010-05-12 05:25:22 +00003046% morphology methods will fall into -1.0 to +1.0 range. Note that for
3047% non-HDRI versions of IM this may cause images to have any negative results
3048% clipped, unless some 'bias' is used.
anthony999bb2c2010-02-18 12:38:01 +00003049%
3050% More specifically. Kernels which only contain positive values (such as a
3051% 'Gaussian' kernel) will be scaled so that those values sum to +1.0,
anthony1b2bc0a2010-05-12 05:25:22 +00003052% ensuring a 0.0 to +1.0 output range for non-HDRI images.
anthony999bb2c2010-02-18 12:38:01 +00003053%
3054% For Kernels that contain some negative values, (such as 'Sharpen' kernels)
3055% the kernel will be scaled by the absolute of the sum of kernel values, so
3056% that it will generally fall within the +/- 1.0 range.
3057%
3058% For kernels whose values sum to zero, (such as 'Laplician' kernels) kernel
3059% will be scaled by just the sum of the postive values, so that its output
3060% range will again fall into the +/- 1.0 range.
3061%
3062% For special kernels designed for locating shapes using 'Correlate', (often
3063% only containing +1 and -1 values, representing foreground/brackground
3064% matching) a special normalization method is provided to scale the positive
3065% values seperatally to those of the negative values, so the kernel will be
3066% forced to become a zero-sum kernel better suited to such searches.
3067%
anthony1b2bc0a2010-05-12 05:25:22 +00003068% WARNING: Correct normalization of the kernel assumes that the '*_range'
anthony999bb2c2010-02-18 12:38:01 +00003069% attributes within the kernel structure have been correctly set during the
3070% kernels creation.
3071%
3072% NOTE: The values used for 'normalize_flags' have been selected specifically
anthony1b2bc0a2010-05-12 05:25:22 +00003073% to match the use of geometry options, so that '!' means NormalizeValue,
3074% '^' means CorrelateNormalizeValue, and '%' means PercentValue. All other
anthony999bb2c2010-02-18 12:38:01 +00003075% GeometryFlags values are ignored.
anthonycc6c8362010-01-25 04:14:01 +00003076%
anthony4fd27e22010-02-07 08:17:18 +00003077% The format of the ScaleKernelInfo method is:
anthonycc6c8362010-01-25 04:14:01 +00003078%
anthony999bb2c2010-02-18 12:38:01 +00003079% void ScaleKernelInfo(KernelInfo *kernel, const double scaling_factor,
3080% const MagickStatusType normalize_flags )
anthonycc6c8362010-01-25 04:14:01 +00003081%
3082% A description of each parameter follows:
3083%
3084% o kernel: the Morphology/Convolution kernel
3085%
anthony999bb2c2010-02-18 12:38:01 +00003086% o scaling_factor:
3087% multiply all values (after normalization) by this factor if not
3088% zero. If the kernel is normalized regardless of any flags.
3089%
3090% o normalize_flags:
3091% GeometryFlags defining normalization method to use.
3092% specifically: NormalizeValue, CorrelateNormalizeValue,
3093% and/or PercentValue
anthonycc6c8362010-01-25 04:14:01 +00003094%
anthonyc4c86e02010-01-27 09:30:32 +00003095% This function is internal to this module only at this time, but can be
3096% exported to other modules if needed.
anthonycc6c8362010-01-25 04:14:01 +00003097*/
cristy6771f1e2010-03-05 19:43:39 +00003098MagickExport void ScaleKernelInfo(KernelInfo *kernel,
3099 const double scaling_factor,const GeometryFlags normalize_flags)
anthonycc6c8362010-01-25 04:14:01 +00003100{
cristy150989e2010-02-01 14:59:39 +00003101 register long
anthonycc6c8362010-01-25 04:14:01 +00003102 i;
3103
anthony999bb2c2010-02-18 12:38:01 +00003104 register double
3105 pos_scale,
3106 neg_scale;
3107
anthony1b2bc0a2010-05-12 05:25:22 +00003108 /* scale the lower kernels first */
3109 if ( kernel->next != (KernelInfo *) NULL)
3110 ScaleKernelInfo(kernel->next, scaling_factor, normalize_flags);
3111
anthony999bb2c2010-02-18 12:38:01 +00003112 pos_scale = 1.0;
3113 if ( (normalize_flags&NormalizeValue) != 0 ) {
3114 /* normalize kernel appropriately */
3115 if ( fabs(kernel->positive_range + kernel->negative_range) > MagickEpsilon )
3116 pos_scale = fabs(kernel->positive_range + kernel->negative_range);
anthonycc6c8362010-01-25 04:14:01 +00003117 else
anthony999bb2c2010-02-18 12:38:01 +00003118 pos_scale = kernel->positive_range; /* special zero-summing kernel */
3119 }
3120 /* force kernel into being a normalized zero-summing kernel */
3121 if ( (normalize_flags&CorrelateNormalizeValue) != 0 ) {
3122 pos_scale = ( fabs(kernel->positive_range) > MagickEpsilon )
3123 ? kernel->positive_range : 1.0;
3124 neg_scale = ( fabs(kernel->negative_range) > MagickEpsilon )
3125 ? -kernel->negative_range : 1.0;
3126 }
3127 else
3128 neg_scale = pos_scale;
3129
3130 /* finialize scaling_factor for positive and negative components */
3131 pos_scale = scaling_factor/pos_scale;
3132 neg_scale = scaling_factor/neg_scale;
3133 if ( (normalize_flags&PercentValue) != 0 ) {
3134 pos_scale /= 100.0;
3135 neg_scale /= 100.0;
anthonycc6c8362010-01-25 04:14:01 +00003136 }
3137
cristy150989e2010-02-01 14:59:39 +00003138 for (i=0; i < (long) (kernel->width*kernel->height); i++)
anthonycc6c8362010-01-25 04:14:01 +00003139 if ( ! IsNan(kernel->values[i]) )
anthony999bb2c2010-02-18 12:38:01 +00003140 kernel->values[i] *= (kernel->values[i] >= 0) ? pos_scale : neg_scale;
anthonycc6c8362010-01-25 04:14:01 +00003141
anthony999bb2c2010-02-18 12:38:01 +00003142 /* convolution output range */
3143 kernel->positive_range *= pos_scale;
3144 kernel->negative_range *= neg_scale;
3145 /* maximum and minimum values in kernel */
3146 kernel->maximum *= (kernel->maximum >= 0.0) ? pos_scale : neg_scale;
3147 kernel->minimum *= (kernel->minimum >= 0.0) ? pos_scale : neg_scale;
3148
3149 /* swap kernel settings if user scaling factor is negative */
3150 if ( scaling_factor < MagickEpsilon ) {
3151 double t;
3152 t = kernel->positive_range;
3153 kernel->positive_range = kernel->negative_range;
3154 kernel->negative_range = t;
3155 t = kernel->maximum;
3156 kernel->maximum = kernel->minimum;
3157 kernel->minimum = 1;
3158 }
anthonycc6c8362010-01-25 04:14:01 +00003159
3160 return;
3161}
3162
3163/*
3164%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3165% %
3166% %
3167% %
anthony4fd27e22010-02-07 08:17:18 +00003168+ S h o w K e r n e l I n f o %
anthony83ba99b2010-01-24 08:48:15 +00003169% %
3170% %
3171% %
3172%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3173%
anthony4fd27e22010-02-07 08:17:18 +00003174% ShowKernelInfo() outputs the details of the given kernel defination to
3175% standard error, generally due to a users 'showkernel' option request.
anthony83ba99b2010-01-24 08:48:15 +00003176%
3177% The format of the ShowKernel method is:
3178%
anthony4fd27e22010-02-07 08:17:18 +00003179% void ShowKernelInfo(KernelInfo *kernel)
anthony83ba99b2010-01-24 08:48:15 +00003180%
3181% A description of each parameter follows:
3182%
3183% o kernel: the Morphology/Convolution kernel
3184%
anthonyc4c86e02010-01-27 09:30:32 +00003185% This function is internal to this module only at this time. That may change
3186% in the future.
anthony83ba99b2010-01-24 08:48:15 +00003187*/
anthony4fd27e22010-02-07 08:17:18 +00003188MagickExport void ShowKernelInfo(KernelInfo *kernel)
anthony83ba99b2010-01-24 08:48:15 +00003189{
anthony7a01dcf2010-05-11 12:25:52 +00003190 KernelInfo
3191 *k;
anthony83ba99b2010-01-24 08:48:15 +00003192
anthony7a01dcf2010-05-11 12:25:52 +00003193 long
3194 c, i, u, v;
3195
3196 for (c=0, k=kernel; k != (KernelInfo *) NULL; c++, k=k->next ) {
3197
3198 fprintf(stderr, "Kernel ");
3199 if ( kernel->next != (KernelInfo *) NULL )
3200 fprintf(stderr, " #%ld", c );
3201 fprintf(stderr, " \"%s\" of size %lux%lu%+ld%+ld ",
3202 MagickOptionToMnemonic(MagickKernelOptions, k->type),
3203 kernel->width, k->height,
3204 kernel->x, kernel->y );
3205 fprintf(stderr,
3206 " with values from %.*lg to %.*lg\n",
3207 GetMagickPrecision(), k->minimum,
3208 GetMagickPrecision(), k->maximum);
3209 fprintf(stderr, "Forming convolution output range from %.*lg to %.*lg%s\n",
3210 GetMagickPrecision(), k->negative_range,
3211 GetMagickPrecision(), k->positive_range,
3212 /*kernel->normalized == MagickTrue ? " (normalized)" : */ "" );
3213 for (i=v=0; v < (long) k->height; v++) {
3214 fprintf(stderr,"%2ld:",v);
3215 for (u=0; u < (long) k->width; u++, i++)
3216 if ( IsNan(k->values[i]) )
3217 fprintf(stderr," %*s", GetMagickPrecision()+2, "nan");
3218 else
3219 fprintf(stderr," %*.*lg", GetMagickPrecision()+2,
3220 GetMagickPrecision(), k->values[i]);
3221 fprintf(stderr,"\n");
3222 }
anthony83ba99b2010-01-24 08:48:15 +00003223 }
3224}
anthonycc6c8362010-01-25 04:14:01 +00003225
3226/*
3227%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3228% %
3229% %
3230% %
anthony4fd27e22010-02-07 08:17:18 +00003231+ Z e r o K e r n e l N a n s %
anthonycc6c8362010-01-25 04:14:01 +00003232% %
3233% %
3234% %
3235%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3236%
3237% ZeroKernelNans() replaces any special 'nan' value that may be present in
3238% the kernel with a zero value. This is typically done when the kernel will
3239% be used in special hardware (GPU) convolution processors, to simply
3240% matters.
3241%
3242% The format of the ZeroKernelNans method is:
3243%
3244% voidZeroKernelNans (KernelInfo *kernel)
3245%
3246% A description of each parameter follows:
3247%
3248% o kernel: the Morphology/Convolution kernel
3249%
3250% FUTURE: return the information in a string for API usage.
3251*/
anthonyc4c86e02010-01-27 09:30:32 +00003252MagickExport void ZeroKernelNans(KernelInfo *kernel)
anthonycc6c8362010-01-25 04:14:01 +00003253{
cristy150989e2010-02-01 14:59:39 +00003254 register long
anthonycc6c8362010-01-25 04:14:01 +00003255 i;
3256
anthony1b2bc0a2010-05-12 05:25:22 +00003257 /* scale the lower kernels first */
3258 if ( kernel->next != (KernelInfo *) NULL)
3259 ZeroKernelNans(kernel->next);
3260
cristy150989e2010-02-01 14:59:39 +00003261 for (i=0; i < (long) (kernel->width*kernel->height); i++)
anthonycc6c8362010-01-25 04:14:01 +00003262 if ( IsNan(kernel->values[i]) )
3263 kernel->values[i] = 0.0;
3264
3265 return;
3266}