blob: 97273e244464e0244d624fc8258b154ff5c098b4 [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;
417 case ChebyshevKernel:
418 case ManhattenKernel:
419 case EuclideanKernel:
420 if ( (flags & HeightValue) == 0 )
421 args.sigma = 100.0; /* default distance scaling */
422 else if ( (flags & AspectValue ) != 0 ) /* '!' flag */
423 args.sigma = QuantumRange/args.sigma; /* maximum pixel distance */
424 else if ( (flags & PercentValue ) != 0 ) /* '%' flag */
425 args.sigma *= QuantumRange/100.0; /* percentage of color range */
426 break;
427 default:
428 break;
anthonyc84dce52010-05-07 05:42:23 +0000429 }
430
431 return(AcquireKernelBuiltIn((KernelInfoType)type, &args));
432}
433
anthony5ef8e942010-05-11 06:51:12 +0000434MagickExport KernelInfo *AcquireKernelInfo(const char *kernel_string)
435{
anthony7a01dcf2010-05-11 12:25:52 +0000436
437 KernelInfo
anthonydbc89892010-05-12 07:05:27 +0000438 *kernel,
439 *new_kernel,
440 *last_kernel;
anthony7a01dcf2010-05-11 12:25:52 +0000441
anthony5ef8e942010-05-11 06:51:12 +0000442 char
443 token[MaxTextExtent];
444
anthony7a01dcf2010-05-11 12:25:52 +0000445 const char
anthonydbc89892010-05-12 07:05:27 +0000446 *p;
anthony7a01dcf2010-05-11 12:25:52 +0000447
anthonye108a3f2010-05-12 07:24:03 +0000448 unsigned long
449 kernel_number;
450
anthonydbc89892010-05-12 07:05:27 +0000451 p = kernel_string;
452 kernel = last_kernel = NULL;
anthonye108a3f2010-05-12 07:24:03 +0000453 kernel_number = 0;
anthony5ef8e942010-05-11 06:51:12 +0000454
anthonydbc89892010-05-12 07:05:27 +0000455 while ( GetMagickToken(p,NULL,token), *token != '\0' ) {
anthony7a01dcf2010-05-11 12:25:52 +0000456
anthonydbc89892010-05-12 07:05:27 +0000457 /* ignore multiple ';' following each other */
458 if ( *token != ';' ) {
anthony7a01dcf2010-05-11 12:25:52 +0000459
anthonydbc89892010-05-12 07:05:27 +0000460 /* tokens starting with alpha is a Named kernel */
461 if (isalpha((int) *token) == 0)
462 new_kernel = ParseKernelArray(p);
463 else /* otherwise a user defined kernel array */
464 new_kernel = ParseNamedKernel(p);
anthony7a01dcf2010-05-11 12:25:52 +0000465
anthonye108a3f2010-05-12 07:24:03 +0000466 /* Error handling -- this is not proper error handling! */
467 if ( new_kernel == (KernelInfo *) NULL ) {
468 fprintf(stderr, "Failed to parse kernel number #%lu\n", kernel_number);
469 if ( kernel != (KernelInfo *) NULL )
470 kernel=DestroyKernelInfo(kernel);
471 return((KernelInfo *) NULL);
anthonydbc89892010-05-12 07:05:27 +0000472 }
anthonye108a3f2010-05-12 07:24:03 +0000473
474 /* initialise or append the kernel list */
anthony3dd0f622010-05-13 12:57:32 +0000475 if ( kernel == (KernelInfo *) NULL )
476 kernel = new_kernel;
477 else
anthonydbc89892010-05-12 07:05:27 +0000478 last_kernel->next = new_kernel;
anthony3dd0f622010-05-13 12:57:32 +0000479 last_kernel = LastKernelInfo(new_kernel);
anthonydbc89892010-05-12 07:05:27 +0000480 }
481
482 /* look for the next kernel in list */
483 p = strchr(p, ';');
484 if ( p == (char *) NULL )
485 break;
486 p++;
487
488 }
anthony7a01dcf2010-05-11 12:25:52 +0000489 return(kernel);
anthony5ef8e942010-05-11 06:51:12 +0000490}
491
anthony602ab9b2010-01-05 08:06:50 +0000492
493/*
494%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
495% %
496% %
497% %
498% A c q u i r e K e r n e l B u i l t I n %
499% %
500% %
501% %
502%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
503%
504% AcquireKernelBuiltIn() returned one of the 'named' built-in types of
505% kernels used for special purposes such as gaussian blurring, skeleton
506% pruning, and edge distance determination.
507%
508% They take a KernelType, and a set of geometry style arguments, which were
509% typically decoded from a user supplied string, or from a more complex
510% Morphology Method that was requested.
511%
512% The format of the AcquireKernalBuiltIn method is:
513%
cristy2be15382010-01-21 02:38:03 +0000514% KernelInfo *AcquireKernelBuiltIn(const KernelInfoType type,
anthony602ab9b2010-01-05 08:06:50 +0000515% const GeometryInfo args)
516%
517% A description of each parameter follows:
518%
519% o type: the pre-defined type of kernel wanted
520%
521% o args: arguments defining or modifying the kernel
522%
523% Convolution Kernels
524%
anthony3c10fc82010-05-13 02:40:51 +0000525% Gaussian:{radius},{sigma}
526% Generate a two-dimentional gaussian kernel, as used by -gaussian.
527% A sigma is required.
anthony602ab9b2010-01-05 08:06:50 +0000528%
529% NOTE: that the 'radius' is optional, but if provided can limit (clip)
530% the final size of the resulting kernel to a square 2*radius+1 in size.
531% The radius should be at least 2 times that of the sigma value, or
532% sever clipping and aliasing may result. If not given or set to 0 the
533% radius will be determined so as to produce the best minimal error
534% result, which is usally much larger than is normally needed.
535%
anthony3c10fc82010-05-13 02:40:51 +0000536% Blur:{radius},{sigma},{angle}
anthony602ab9b2010-01-05 08:06:50 +0000537% As per Gaussian, but generates a 1 dimensional or linear gaussian
538% blur, at the angle given (current restricted to orthogonal angles).
539% If a 'radius' is given the kernel is clipped to a width of 2*radius+1.
anthony3c10fc82010-05-13 02:40:51 +0000540% Angle can be rotated in multiples of 90 degrees.
anthony602ab9b2010-01-05 08:06:50 +0000541%
anthony3c10fc82010-05-13 02:40:51 +0000542% Note that two such blurs perpendicular to each other is equivelent to
543% the far large "Gaussian" kernel, but much faster to apply. This is how
544% the -blur operator works.
anthony602ab9b2010-01-05 08:06:50 +0000545%
anthony3c10fc82010-05-13 02:40:51 +0000546% Comet:{width},{sigma},{angle}
547% Blur in one direction only, much like how a bright object leaves
anthony602ab9b2010-01-05 08:06:50 +0000548% a comet like trail. The Kernel is actually half a gaussian curve,
anthony3c10fc82010-05-13 02:40:51 +0000549% Adding two such blurs in opposite directions produces a Blur Kernel.
550% Angle can be rotated in multiples of 90 degrees.
anthony602ab9b2010-01-05 08:06:50 +0000551%
anthony3c10fc82010-05-13 02:40:51 +0000552% Note that the first argument is the width of the kernel and not the
anthony602ab9b2010-01-05 08:06:50 +0000553% radius of the kernel.
554%
555% # Still to be implemented...
556% #
anthony3c10fc82010-05-13 02:40:51 +0000557% # DOG:{radius},{sigma1},{sigma2}
anthony4fd27e22010-02-07 08:17:18 +0000558% # Difference of two Gaussians
559% #
560% # Filter2D
561% # Filter1D
562% # Set kernel values using a resize filter, and given scale (sigma)
563% # Cylindrical or Linear. Is this posible with an image?
564% #
anthony602ab9b2010-01-05 08:06:50 +0000565%
anthony3c10fc82010-05-13 02:40:51 +0000566% Named Constant Convolution Kernels
567%
568% Sobel:[angle]
569% The 3x3 sobel convolution kernel. Angle may be given in multiples
570% of 45 degrees. Kernel is unscaled by default so some normalization
571% may be required to ensure results are not clipped.
572% Default kernel is -1,0,1
573% -2,0,2
574% -1,0,1
575%
576% Laplacian:{type}
577% Generate Lapacian kernel of the type specified. (1 is the default)
578% Type 0 default square laplacian 3x3: all -1's with central 8
579% Type 1 3x3: central 4 edge -1 corner 0
580% Type 2 3x3: central 4 edge 1 corner -2
581% Type 3 a 5x5 laplacian
anthony3dd0f622010-05-13 12:57:32 +0000582% Type 4 a 7x7 laplacian
anthony3c10fc82010-05-13 02:40:51 +0000583%
anthony602ab9b2010-01-05 08:06:50 +0000584% Boolean Kernels
585%
anthony3c10fc82010-05-13 02:40:51 +0000586% Rectangle:{geometry}
anthony602ab9b2010-01-05 08:06:50 +0000587% Simply generate a rectangle of 1's with the size given. You can also
588% specify the location of the 'control point', otherwise the closest
589% pixel to the center of the rectangle is selected.
590%
591% Properly centered and odd sized rectangles work the best.
592%
anthony3c10fc82010-05-13 02:40:51 +0000593% Diamond:[{radius}[,{scale}]]
anthony1b2bc0a2010-05-12 05:25:22 +0000594% Generate a diamond shaped kernel with given radius to the points.
anthony602ab9b2010-01-05 08:06:50 +0000595% Kernel size will again be radius*2+1 square and defaults to radius 1,
596% generating a 3x3 kernel that is slightly larger than a square.
597%
anthony3c10fc82010-05-13 02:40:51 +0000598% Square:[{radius}[,{scale}]]
anthony602ab9b2010-01-05 08:06:50 +0000599% Generate a square shaped kernel of size radius*2+1, and defaulting
600% to a 3x3 (radius 1).
601%
602% Note that using a larger radius for the "Square" or the "Diamond"
603% is also equivelent to iterating the basic morphological method
604% that many times. However However iterating with the smaller radius 1
605% default is actually faster than using a larger kernel radius.
606%
anthony3c10fc82010-05-13 02:40:51 +0000607% Disk:[{radius}[,{scale}]]
anthony602ab9b2010-01-05 08:06:50 +0000608% Generate a binary disk of the radius given, radius may be a float.
609% Kernel size will be ceil(radius)*2+1 square.
610% NOTE: Here are some disk shapes of specific interest
611% "disk:1" => "diamond" or "cross:1"
612% "disk:1.5" => "square"
613% "disk:2" => "diamond:2"
anthony83ba99b2010-01-24 08:48:15 +0000614% "disk:2.5" => a general disk shape of radius 2
anthony602ab9b2010-01-05 08:06:50 +0000615% "disk:2.9" => "square:2"
anthony83ba99b2010-01-24 08:48:15 +0000616% "disk:3.5" => default - octagonal/disk shape of radius 3
anthony602ab9b2010-01-05 08:06:50 +0000617% "disk:4.2" => roughly octagonal shape of radius 4
anthony83ba99b2010-01-24 08:48:15 +0000618% "disk:4.3" => a general disk shape of radius 4
anthony602ab9b2010-01-05 08:06:50 +0000619% After this all the kernel shape becomes more and more circular.
620%
621% Because a "disk" is more circular when using a larger radius, using a
622% larger radius is preferred over iterating the morphological operation.
623%
anthony3c10fc82010-05-13 02:40:51 +0000624% Plus:[{radius}[,{scale}]]
anthony3dd0f622010-05-13 12:57:32 +0000625% Cross:[{radius}[,{scale}]]
626% Generate a kernel in the shape of a 'plus' or a cross. The length of
627% each arm is also the radius, which defaults to 2.
628%
629% NOTE: "plus:1" is equivelent to a "Diamond" kernel.
anthony602ab9b2010-01-05 08:06:50 +0000630%
631% This kernel is not a good general morphological kernel, but is used
632% more for highlighting and marking any single pixels in an image using,
633% a "Dilate" or "Erode" method as appropriate.
anthonyc94cdb02010-01-06 08:15:29 +0000634%
anthony3dd0f622010-05-13 12:57:32 +0000635% For the same reasons iterating these kernels does not produce the
636% same result as using a larger radius for the symbol.
anthony602ab9b2010-01-05 08:06:50 +0000637%
anthony3dd0f622010-05-13 12:57:32 +0000638% Hit and Miss Kernels
639%
640% Peak:radius1,radius2
641% Find a foreground inside a background ring of the given radii.
642% Corners
643% Find corners of a binary shape
644% LineEnds
645% Find end points of lines (for pruning a skeletion)
646% LineJunctions
647% Find three line junctions (in a skeletion)
648% ConvexHull
649% Octagonal thicken kernel, to generate convex hulls of 45 degrees
650% Skeleton
651% Thinning kernel, which leaves behind a skeletion of a shape
anthony602ab9b2010-01-05 08:06:50 +0000652%
653% Distance Measuring Kernels
654%
anthony3dd0f622010-05-13 12:57:32 +0000655% Chebyshev:[{radius}][x{scale}[%!]]
656% Manhatten:[{radius}][x{scale}[%!]]
657% Euclidean:[{radius}][x{scale}[%!]]
anthony602ab9b2010-01-05 08:06:50 +0000658%
659% Different types of distance measuring methods, which are used with the
660% a 'Distance' morphology method for generating a gradient based on
661% distance from an edge of a binary shape, though there is a technique
662% for handling a anti-aliased shape.
663%
anthonyc94cdb02010-01-06 08:15:29 +0000664% Chebyshev Distance (also known as Tchebychev Distance) is a value of
665% one to any neighbour, orthogonal or diagonal. One why of thinking of
666% it is the number of squares a 'King' or 'Queen' in chess needs to
667% traverse reach any other position on a chess board. It results in a
668% 'square' like distance function, but one where diagonals are closer
669% than expected.
anthony602ab9b2010-01-05 08:06:50 +0000670%
anthonyc94cdb02010-01-06 08:15:29 +0000671% Manhatten Distance (also known as Rectilinear Distance, or the Taxi
672% Cab metric), is the distance needed when you can only travel in
673% orthogonal (horizontal or vertical) only. It is the distance a 'Rook'
674% in chess would travel. It results in a diamond like distances, where
675% diagonals are further than expected.
anthony602ab9b2010-01-05 08:06:50 +0000676%
anthonyc94cdb02010-01-06 08:15:29 +0000677% Euclidean Distance is the 'direct' or 'as the crow flys distance.
678% However by default the kernel size only has a radius of 1, which
679% limits the distance to 'Knight' like moves, with only orthogonal and
680% diagonal measurements being correct. As such for the default kernel
681% you will get octagonal like distance function, which is reasonally
682% accurate.
683%
684% However if you use a larger radius such as "Euclidean:4" you will
685% get a much smoother distance gradient from the edge of the shape.
686% Of course a larger kernel is slower to use, and generally not needed.
687%
688% To allow the use of fractional distances that you get with diagonals
689% the actual distance is scaled by a fixed value which the user can
690% provide. This is not actually nessary for either ""Chebyshev" or
691% "Manhatten" distance kernels, but is done for all three distance
692% kernels. If no scale is provided it is set to a value of 100,
693% allowing for a maximum distance measurement of 655 pixels using a Q16
694% version of IM, from any edge. However for small images this can
695% result in quite a dark gradient.
696%
697% See the 'Distance' Morphological Method, for information of how it is
698% applied.
anthony602ab9b2010-01-05 08:06:50 +0000699%
700*/
701
cristy2be15382010-01-21 02:38:03 +0000702MagickExport KernelInfo *AcquireKernelBuiltIn(const KernelInfoType type,
anthony602ab9b2010-01-05 08:06:50 +0000703 const GeometryInfo *args)
704{
cristy2be15382010-01-21 02:38:03 +0000705 KernelInfo
anthony602ab9b2010-01-05 08:06:50 +0000706 *kernel;
707
cristy150989e2010-02-01 14:59:39 +0000708 register long
anthony602ab9b2010-01-05 08:06:50 +0000709 i;
710
711 register long
712 u,
713 v;
714
715 double
716 nan = sqrt((double)-1.0); /* Special Value : Not A Number */
717
cristy2be15382010-01-21 02:38:03 +0000718 kernel=(KernelInfo *) AcquireMagickMemory(sizeof(*kernel));
719 if (kernel == (KernelInfo *) NULL)
anthony602ab9b2010-01-05 08:06:50 +0000720 return(kernel);
721 (void) ResetMagickMemory(kernel,0,sizeof(*kernel));
cristyc99304f2010-02-01 15:26:27 +0000722 kernel->minimum = kernel->maximum = 0.0;
723 kernel->negative_range = kernel->positive_range = 0.0;
anthony602ab9b2010-01-05 08:06:50 +0000724 kernel->type = type;
anthony7a01dcf2010-05-11 12:25:52 +0000725 kernel->next = (KernelInfo *) NULL;
cristyd43a46b2010-01-21 02:13:41 +0000726 kernel->signature = MagickSignature;
anthony602ab9b2010-01-05 08:06:50 +0000727
728 switch(type) {
729 /* Convolution Kernels */
730 case GaussianKernel:
731 { double
732 sigma = fabs(args->sigma);
733
734 sigma = (sigma <= MagickEpsilon) ? 1.0 : sigma;
735
736 kernel->width = kernel->height =
737 GetOptimalKernelWidth2D(args->rho,sigma);
cristyc99304f2010-02-01 15:26:27 +0000738 kernel->x = kernel->y = (long) (kernel->width-1)/2;
739 kernel->negative_range = kernel->positive_range = 0.0;
anthony602ab9b2010-01-05 08:06:50 +0000740 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
741 kernel->height*sizeof(double));
742 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +0000743 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000744
745 sigma = 2.0*sigma*sigma; /* simplify the expression */
cristyc99304f2010-02-01 15:26:27 +0000746 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
747 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
748 kernel->positive_range += (
anthony602ab9b2010-01-05 08:06:50 +0000749 kernel->values[i] =
750 exp(-((double)(u*u+v*v))/sigma)
751 /* / (MagickPI*sigma) */ );
cristyc99304f2010-02-01 15:26:27 +0000752 kernel->minimum = 0;
753 kernel->maximum = kernel->values[
754 kernel->y*kernel->width+kernel->x ];
anthony602ab9b2010-01-05 08:06:50 +0000755
anthony3dd0f622010-05-13 12:57:32 +0000756 /* Normalize the 2D Gaussian Kernel
757 **
758 ** As it is normalized the divisor in the above kernel generator is
759 ** not needed, so is not done above.
760 */
anthony999bb2c2010-02-18 12:38:01 +0000761 ScaleKernelInfo(kernel, 1.0, NormalizeValue); /* Normalize */
anthony602ab9b2010-01-05 08:06:50 +0000762
763 break;
764 }
765 case BlurKernel:
766 { double
767 sigma = fabs(args->sigma);
768
769 sigma = (sigma <= MagickEpsilon) ? 1.0 : sigma;
770
771 kernel->width = GetOptimalKernelWidth1D(args->rho,sigma);
cristyc99304f2010-02-01 15:26:27 +0000772 kernel->x = (long) (kernel->width-1)/2;
anthony602ab9b2010-01-05 08:06:50 +0000773 kernel->height = 1;
cristyc99304f2010-02-01 15:26:27 +0000774 kernel->y = 0;
775 kernel->negative_range = kernel->positive_range = 0.0;
anthony602ab9b2010-01-05 08:06:50 +0000776 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
777 kernel->height*sizeof(double));
778 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +0000779 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000780
781#if 1
782#define KernelRank 3
783 /* Formula derived from GetBlurKernel() in "effect.c" (plus bug fix).
784 ** It generates a gaussian 3 times the width, and compresses it into
785 ** the expected range. This produces a closer normalization of the
786 ** resulting kernel, especially for very low sigma values.
787 ** As such while wierd it is prefered.
788 **
789 ** I am told this method originally came from Photoshop.
790 */
791 sigma *= KernelRank; /* simplify expanded curve */
cristy150989e2010-02-01 14:59:39 +0000792 v = (long) (kernel->width*KernelRank-1)/2; /* start/end points to fit range */
anthony602ab9b2010-01-05 08:06:50 +0000793 (void) ResetMagickMemory(kernel->values,0, (size_t)
794 kernel->width*sizeof(double));
795 for ( u=-v; u <= v; u++) {
796 kernel->values[(u+v)/KernelRank] +=
797 exp(-((double)(u*u))/(2.0*sigma*sigma))
798 /* / (MagickSQ2PI*sigma/KernelRank) */ ;
799 }
cristy150989e2010-02-01 14:59:39 +0000800 for (i=0; i < (long) kernel->width; i++)
cristyc99304f2010-02-01 15:26:27 +0000801 kernel->positive_range += kernel->values[i];
anthony602ab9b2010-01-05 08:06:50 +0000802#else
cristyc99304f2010-02-01 15:26:27 +0000803 for ( i=0, u=-kernel->x; i < kernel->width; i++, u++)
804 kernel->positive_range += (
anthony602ab9b2010-01-05 08:06:50 +0000805 kernel->values[i] =
806 exp(-((double)(u*u))/(2.0*sigma*sigma))
807 /* / (MagickSQ2PI*sigma) */ );
808#endif
cristyc99304f2010-02-01 15:26:27 +0000809 kernel->minimum = 0;
810 kernel->maximum = kernel->values[ kernel->x ];
anthonycc6c8362010-01-25 04:14:01 +0000811 /* Note that neither methods above generate a normalized kernel,
812 ** though it gets close. The kernel may be 'clipped' by a user defined
813 ** radius, producing a smaller (darker) kernel. Also for very small
814 ** sigma's (> 0.1) the central value becomes larger than one, and thus
815 ** producing a very bright kernel.
anthony602ab9b2010-01-05 08:06:50 +0000816 */
anthonycc6c8362010-01-25 04:14:01 +0000817
anthony602ab9b2010-01-05 08:06:50 +0000818 /* Normalize the 1D Gaussian Kernel
819 **
anthony3dd0f622010-05-13 12:57:32 +0000820 ** As it is normalized the divisor in the above kernel generator is
anthonyc94cdb02010-01-06 08:15:29 +0000821 ** not needed, so is not done above.
anthony602ab9b2010-01-05 08:06:50 +0000822 */
anthony999bb2c2010-02-18 12:38:01 +0000823 ScaleKernelInfo(kernel, 1.0, NormalizeValue); /* Normalize */
anthonycc6c8362010-01-25 04:14:01 +0000824
anthony602ab9b2010-01-05 08:06:50 +0000825 /* rotate the kernel by given angle */
anthony4fd27e22010-02-07 08:17:18 +0000826 RotateKernelInfo(kernel, args->xi);
anthony602ab9b2010-01-05 08:06:50 +0000827 break;
828 }
829 case CometKernel:
830 { double
831 sigma = fabs(args->sigma);
832
833 sigma = (sigma <= MagickEpsilon) ? 1.0 : sigma;
834
835 if ( args->rho < 1.0 )
836 kernel->width = GetOptimalKernelWidth1D(args->rho,sigma);
837 else
838 kernel->width = (unsigned long)args->rho;
cristyc99304f2010-02-01 15:26:27 +0000839 kernel->x = kernel->y = 0;
anthony602ab9b2010-01-05 08:06:50 +0000840 kernel->height = 1;
cristyc99304f2010-02-01 15:26:27 +0000841 kernel->negative_range = kernel->positive_range = 0.0;
anthony602ab9b2010-01-05 08:06:50 +0000842 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
843 kernel->height*sizeof(double));
844 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +0000845 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000846
847 /* A comet blur is half a gaussian curve, so that the object is
848 ** blurred in one direction only. This may not be quite the right
anthony3dd0f622010-05-13 12:57:32 +0000849 ** curve to use so may change in the future. The function must be
850 ** normalised after generation, which also resolves any clipping.
anthony602ab9b2010-01-05 08:06:50 +0000851 */
852#if 1
853#define KernelRank 3
854 sigma *= KernelRank; /* simplify expanded curve */
cristy150989e2010-02-01 14:59:39 +0000855 v = (long) kernel->width*KernelRank; /* start/end points to fit range */
anthony602ab9b2010-01-05 08:06:50 +0000856 (void) ResetMagickMemory(kernel->values,0, (size_t)
857 kernel->width*sizeof(double));
858 for ( u=0; u < v; u++) {
859 kernel->values[u/KernelRank] +=
860 exp(-((double)(u*u))/(2.0*sigma*sigma))
861 /* / (MagickSQ2PI*sigma/KernelRank) */ ;
862 }
cristy150989e2010-02-01 14:59:39 +0000863 for (i=0; i < (long) kernel->width; i++)
cristyc99304f2010-02-01 15:26:27 +0000864 kernel->positive_range += kernel->values[i];
anthony602ab9b2010-01-05 08:06:50 +0000865#else
cristy150989e2010-02-01 14:59:39 +0000866 for ( i=0; i < (long) kernel->width; i++)
cristyc99304f2010-02-01 15:26:27 +0000867 kernel->positive_range += (
anthony602ab9b2010-01-05 08:06:50 +0000868 kernel->values[i] =
869 exp(-((double)(i*i))/(2.0*sigma*sigma))
870 /* / (MagickSQ2PI*sigma) */ );
871#endif
cristyc99304f2010-02-01 15:26:27 +0000872 kernel->minimum = 0;
873 kernel->maximum = kernel->values[0];
anthony602ab9b2010-01-05 08:06:50 +0000874
anthony999bb2c2010-02-18 12:38:01 +0000875 ScaleKernelInfo(kernel, 1.0, NormalizeValue); /* Normalize */
876 RotateKernelInfo(kernel, args->xi); /* Rotate by angle */
anthony602ab9b2010-01-05 08:06:50 +0000877 break;
878 }
anthony3c10fc82010-05-13 02:40:51 +0000879 /* Convolution Kernels - Well Known Constants */
880 case SobelKernel:
881 { kernel=DestroyKernelInfo(kernel); /* default kernel is not needed */
882 kernel=ParseKernelArray("3x3:-1,0,1 -2,0,2 -1,0,1");
883 if (kernel == (KernelInfo *) NULL)
884 return(kernel);
885 kernel->type = type;
886 RotateKernelInfo(kernel, args->rho); /* Rotate by angle */
887 break;
888 }
889 case LaplacianKernel:
890 { kernel=DestroyKernelInfo(kernel); /* default kernel is not needed */
891 switch ( (int) args->rho ) {
anthony3dd0f622010-05-13 12:57:32 +0000892 case 0:
893 default: /* default laplacian 'edge' filter */
894 kernel=ParseKernelArray("3x3: -1,-1,-1 -1,8,-1 -1,-1,-1");
895 break;
anthony3c10fc82010-05-13 02:40:51 +0000896 case 1:
897 kernel=ParseKernelArray("3x3: 0,-1,0 -1,4,-1 0,-1,0");
898 break;
899 case 2:
900 kernel=ParseKernelArray("3x3: -2,1,-2 1,4,1 -2,1,-2");
901 break;
anthony3dd0f622010-05-13 12:57:32 +0000902 case 3: /* a 5x5 laplacian */
anthony3c10fc82010-05-13 02:40:51 +0000903 kernel=ParseKernelArray(
904 "5x5: -4,-1,0,-1,-4 -1,2,3,2,-1 0,3,4,3,0 -1,2,3,2,-1 -4,-1,0,-1,-4");
905 break;
anthony3dd0f622010-05-13 12:57:32 +0000906 case 4: /* a 7x7 laplacian */
anthony3c10fc82010-05-13 02:40:51 +0000907 kernel=ParseKernelArray(
908 "7x7:-10,-5,-2,-1,-2,-5,-10 -5,0,3,4,3,0,-5 -2,3,6,7,6,3,-2 -1,4,7,8,7,4,-1 -2,3,6,7,6,3,-2 -5,0,3,4,3,0,-5 -10,-5,-2,-1,-2,-5,-10" );
909 break;
910 }
911 if (kernel == (KernelInfo *) NULL)
912 return(kernel);
913 kernel->type = type;
914 break;
915 }
anthony602ab9b2010-01-05 08:06:50 +0000916 /* Boolean Kernels */
917 case RectangleKernel:
918 case SquareKernel:
919 {
anthony4fd27e22010-02-07 08:17:18 +0000920 double scale;
anthony602ab9b2010-01-05 08:06:50 +0000921 if ( type == SquareKernel )
922 {
923 if (args->rho < 1.0)
anthonyc94cdb02010-01-06 08:15:29 +0000924 kernel->width = kernel->height = 3; /* default radius = 1 */
anthony602ab9b2010-01-05 08:06:50 +0000925 else
cristy150989e2010-02-01 14:59:39 +0000926 kernel->width = kernel->height = (unsigned long) (2*args->rho+1);
cristyc99304f2010-02-01 15:26:27 +0000927 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony4fd27e22010-02-07 08:17:18 +0000928 scale = args->sigma;
anthony602ab9b2010-01-05 08:06:50 +0000929 }
930 else {
cristy2be15382010-01-21 02:38:03 +0000931 /* NOTE: user defaults set in "AcquireKernelInfo()" */
anthony602ab9b2010-01-05 08:06:50 +0000932 if ( args->rho < 1.0 || args->sigma < 1.0 )
anthony83ba99b2010-01-24 08:48:15 +0000933 return(DestroyKernelInfo(kernel)); /* invalid args given */
anthony602ab9b2010-01-05 08:06:50 +0000934 kernel->width = (unsigned long)args->rho;
935 kernel->height = (unsigned long)args->sigma;
936 if ( args->xi < 0.0 || args->xi > (double)kernel->width ||
937 args->psi < 0.0 || args->psi > (double)kernel->height )
anthony83ba99b2010-01-24 08:48:15 +0000938 return(DestroyKernelInfo(kernel)); /* invalid args given */
cristyc99304f2010-02-01 15:26:27 +0000939 kernel->x = (long) args->xi;
940 kernel->y = (long) args->psi;
anthony4fd27e22010-02-07 08:17:18 +0000941 scale = 1.0;
anthony602ab9b2010-01-05 08:06:50 +0000942 }
943 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
944 kernel->height*sizeof(double));
945 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +0000946 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000947
anthony3dd0f622010-05-13 12:57:32 +0000948 /* set all kernel values to scale given */
cristy150989e2010-02-01 14:59:39 +0000949 u=(long) kernel->width*kernel->height;
950 for ( i=0; i < u; i++)
anthony4fd27e22010-02-07 08:17:18 +0000951 kernel->values[i] = scale;
952 kernel->minimum = kernel->maximum = scale; /* a flat shape */
953 kernel->positive_range = scale*u;
anthonycc6c8362010-01-25 04:14:01 +0000954 break;
anthony602ab9b2010-01-05 08:06:50 +0000955 }
956 case DiamondKernel:
957 {
958 if (args->rho < 1.0)
anthonyc94cdb02010-01-06 08:15:29 +0000959 kernel->width = kernel->height = 3; /* default radius = 1 */
anthony602ab9b2010-01-05 08:06:50 +0000960 else
961 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
cristyc99304f2010-02-01 15:26:27 +0000962 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony602ab9b2010-01-05 08:06:50 +0000963
964 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
965 kernel->height*sizeof(double));
966 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +0000967 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000968
anthony4fd27e22010-02-07 08:17:18 +0000969 /* set all kernel values within diamond area to scale given */
cristyc99304f2010-02-01 15:26:27 +0000970 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
971 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
972 if ((labs(u)+labs(v)) <= (long)kernel->x)
anthony4fd27e22010-02-07 08:17:18 +0000973 kernel->positive_range += kernel->values[i] = args->sigma;
anthony602ab9b2010-01-05 08:06:50 +0000974 else
975 kernel->values[i] = nan;
anthony4fd27e22010-02-07 08:17:18 +0000976 kernel->minimum = kernel->maximum = args->sigma; /* a flat shape */
anthony602ab9b2010-01-05 08:06:50 +0000977 break;
978 }
979 case DiskKernel:
980 {
981 long
982 limit;
983
984 limit = (long)(args->rho*args->rho);
anthony83ba99b2010-01-24 08:48:15 +0000985 if (args->rho < 0.1) /* default radius approx 3.5 */
986 kernel->width = kernel->height = 7L, limit = 10L;
anthony602ab9b2010-01-05 08:06:50 +0000987 else
988 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
cristyc99304f2010-02-01 15:26:27 +0000989 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony602ab9b2010-01-05 08:06:50 +0000990
991 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
992 kernel->height*sizeof(double));
993 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +0000994 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000995
anthony3dd0f622010-05-13 12:57:32 +0000996 /* set all kernel values within disk area to scale given */
997 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
cristyc99304f2010-02-01 15:26:27 +0000998 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
anthony602ab9b2010-01-05 08:06:50 +0000999 if ((u*u+v*v) <= limit)
anthony4fd27e22010-02-07 08:17:18 +00001000 kernel->positive_range += kernel->values[i] = args->sigma;
anthony602ab9b2010-01-05 08:06:50 +00001001 else
1002 kernel->values[i] = nan;
anthony4fd27e22010-02-07 08:17:18 +00001003 kernel->minimum = kernel->maximum = args->sigma; /* a flat shape */
anthony602ab9b2010-01-05 08:06:50 +00001004 break;
1005 }
1006 case PlusKernel:
1007 {
1008 if (args->rho < 1.0)
anthonyc94cdb02010-01-06 08:15:29 +00001009 kernel->width = kernel->height = 5; /* default radius 2 */
anthony602ab9b2010-01-05 08:06:50 +00001010 else
1011 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
cristyc99304f2010-02-01 15:26:27 +00001012 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony602ab9b2010-01-05 08:06:50 +00001013
1014 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
1015 kernel->height*sizeof(double));
1016 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +00001017 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +00001018
anthony3dd0f622010-05-13 12:57:32 +00001019 /* set all kernel values along axises to given scale */
cristyc99304f2010-02-01 15:26:27 +00001020 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
1021 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
anthony4fd27e22010-02-07 08:17:18 +00001022 kernel->values[i] = (u == 0 || v == 0) ? args->sigma : nan;
1023 kernel->minimum = kernel->maximum = args->sigma; /* a flat shape */
1024 kernel->positive_range = args->sigma*(kernel->width*2.0 - 1.0);
anthony602ab9b2010-01-05 08:06:50 +00001025 break;
1026 }
anthony3dd0f622010-05-13 12:57:32 +00001027 case CrossKernel:
1028 {
1029 if (args->rho < 1.0)
1030 kernel->width = kernel->height = 5; /* default radius 2 */
1031 else
1032 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
1033 kernel->x = kernel->y = (long) (kernel->width-1)/2;
1034
1035 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
1036 kernel->height*sizeof(double));
1037 if (kernel->values == (double *) NULL)
1038 return(DestroyKernelInfo(kernel));
1039
1040 /* set all kernel values along axises to given scale */
1041 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
1042 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
1043 kernel->values[i] = (u == v || u == -v) ? args->sigma : nan;
1044 kernel->minimum = kernel->maximum = args->sigma; /* a flat shape */
1045 kernel->positive_range = args->sigma*(kernel->width*2.0 - 1.0);
1046 break;
1047 }
1048 /* HitAndMiss Kernels */
1049 case PeaksKernel:
1050 {
1051 long
1052 limit1,
1053 limit2;
1054
1055 if (args->rho < args->sigma)
1056 {
1057 kernel->width = ((unsigned long)args->sigma)*2+1;
1058 limit1 = (long)args->rho*args->rho;
1059 limit2 = (long)args->sigma*args->sigma;
1060 }
1061 else
1062 {
1063 kernel->width = ((unsigned long)args->rho)*2+1;
1064 limit1 = (long)args->sigma*args->sigma;
1065 limit2 = (long)args->rho*args->rho;
1066 }
1067 if ( limit2 <= 0 ) /* default outer radius approx 3.5 */
1068 kernel->width = 7L, limit2 = 11L;
1069 kernel->height = kernel->width;
1070 kernel->x = kernel->y = (long) (kernel->width-1)/2;
1071 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
1072 kernel->height*sizeof(double));
1073 if (kernel->values == (double *) NULL)
1074 return(DestroyKernelInfo(kernel));
1075
1076 /* set a ring of background points */
1077 for ( i=0, v= -kernel->y; v <= (long)kernel->y; v++)
1078 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
1079 { long radius=u*u+v*v;
1080 if ( limit1 <= radius && radius <= limit2)
1081 kernel->values[i] = 0.0;
1082 else
1083 kernel->values[i] = nan;
1084 }
1085 /* central point is always foreground */
1086 kernel->values[kernel->x+kernel->y*kernel->width] = 1.0;
1087 kernel->positive_range = 1.0;
1088 kernel->maximum = 1.0;
1089 break;
1090 }
1091 case CornersKernel:
1092 {
1093 kernel=DestroyKernelInfo(kernel); /* default kernel is not needed */
1094 kernel=ParseKernelArray("3x3: 0,0,- 0,1,1 -,1,-");
1095 if (kernel == (KernelInfo *) NULL)
1096 return(kernel);
1097 kernel->type = type;
1098 ExpandKernelInfo(kernel, 90.0); /* Create a list of 4 rotated kernels */
1099 break;
1100 }
1101 case LineEndsKernel:
1102 {
1103 kernel=DestroyKernelInfo(kernel); /* default kernel is not needed */
1104 kernel=ParseKernelArray("3x3: 0,-,- 0,1,0 0,0,0");
1105 if (kernel == (KernelInfo *) NULL)
1106 return(kernel);
1107 kernel->type = type;
1108 ExpandKernelInfo(kernel, 45.0); /* Create a list of 8 rotated kernels */
1109 break;
1110 }
1111 case LineJunctionsKernel:
1112 {
1113 KernelInfo
1114 *new_kernel;
1115 kernel=DestroyKernelInfo(kernel); /* default kernel is not needed */
1116 /* first set of 4 kernels */
1117 kernel=ParseKernelArray("3x3: -,1,- -,1,- 1,-,1");
1118 if (kernel == (KernelInfo *) NULL)
1119 return(kernel);
1120 kernel->type = type;
1121 ExpandKernelInfo(kernel, 90.0);
1122 /* append second set of 4 kernels */
1123 new_kernel=ParseKernelArray("3x3: 1,-,- -,1,- 1,-,1");
1124 if (new_kernel == (KernelInfo *) NULL)
1125 return(DestroyKernelInfo(kernel));
1126 kernel->type = type;
1127 ExpandKernelInfo(new_kernel, 90.0);
1128 LastKernelInfo(kernel)->next = new_kernel;
1129 /* append Thrid set of 4 kernels */
1130 new_kernel=ParseKernelArray("3x3: -,1,- -,1,1 1,-,-");
1131 if (new_kernel == (KernelInfo *) NULL)
1132 return(DestroyKernelInfo(kernel));
1133 kernel->type = type;
1134 ExpandKernelInfo(new_kernel, 90.0);
1135 LastKernelInfo(kernel)->next = new_kernel;
1136 break;
1137 }
1138 case ConvexHullKernel:
1139 {
1140 KernelInfo
1141 *new_kernel;
1142 kernel=DestroyKernelInfo(kernel); /* default kernel is not needed */
1143 /* first set of 4 kernels */
1144 kernel=ParseKernelArray("3x3: 1,1,- 1,0,- 1,-,0");
1145 if (kernel == (KernelInfo *) NULL)
1146 return(kernel);
1147 kernel->type = type;
1148 ExpandKernelInfo(kernel, 90.0);
1149 /* append second set of 4 kernels */
1150 new_kernel=ParseKernelArray("3x3: -,1,1 -,0,1 0,-,1");
1151 if (new_kernel == (KernelInfo *) NULL)
1152 return(DestroyKernelInfo(kernel));
1153 kernel->type = type;
1154 ExpandKernelInfo(new_kernel, 90.0);
1155 LastKernelInfo(kernel)->next = new_kernel;
1156 break;
1157 }
1158 case SkeletonKernel:
1159 {
1160 KernelInfo
1161 *new_kernel;
1162 kernel=DestroyKernelInfo(kernel); /* default kernel is not needed */
1163 /* first set of 4 kernels - corners */
1164 kernel=ParseKernelArray("3x3: 0,0,- 0,1,1 -,1,-");
1165 if (kernel == (KernelInfo *) NULL)
1166 return(kernel);
1167 kernel->type = type;
1168 ExpandKernelInfo(kernel, 90);
1169 /* append second set of 4 kernels - edge middles */
1170 new_kernel=ParseKernelArray("3x3: 0,0,0 -,1,- 1,1,1");
1171 if (new_kernel == (KernelInfo *) NULL)
1172 return(DestroyKernelInfo(kernel));
1173 kernel->type = type;
1174 ExpandKernelInfo(new_kernel, 90);
1175 LastKernelInfo(kernel)->next = new_kernel;
1176 break;
1177 }
anthony602ab9b2010-01-05 08:06:50 +00001178 /* Distance Measuring Kernels */
1179 case ChebyshevKernel:
1180 {
anthony602ab9b2010-01-05 08:06:50 +00001181 if (args->rho < 1.0)
anthonyc94cdb02010-01-06 08:15:29 +00001182 kernel->width = kernel->height = 3; /* default radius = 1 */
anthony602ab9b2010-01-05 08:06:50 +00001183 else
1184 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
cristyc99304f2010-02-01 15:26:27 +00001185 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony602ab9b2010-01-05 08:06:50 +00001186
1187 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
1188 kernel->height*sizeof(double));
1189 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +00001190 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +00001191
cristyc99304f2010-02-01 15:26:27 +00001192 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
1193 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
1194 kernel->positive_range += ( kernel->values[i] =
anthonyc84dce52010-05-07 05:42:23 +00001195 args->sigma*((labs(u)>labs(v)) ? labs(u) : labs(v)) );
cristyc99304f2010-02-01 15:26:27 +00001196 kernel->maximum = kernel->values[0];
anthony602ab9b2010-01-05 08:06:50 +00001197 break;
1198 }
1199 case ManhattenKernel:
1200 {
anthony602ab9b2010-01-05 08:06:50 +00001201 if (args->rho < 1.0)
anthonyc94cdb02010-01-06 08:15:29 +00001202 kernel->width = kernel->height = 3; /* default radius = 1 */
anthony602ab9b2010-01-05 08:06:50 +00001203 else
1204 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
cristyc99304f2010-02-01 15:26:27 +00001205 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony602ab9b2010-01-05 08:06:50 +00001206
1207 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
1208 kernel->height*sizeof(double));
1209 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +00001210 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +00001211
cristyc99304f2010-02-01 15:26:27 +00001212 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
1213 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
1214 kernel->positive_range += ( kernel->values[i] =
anthonyc84dce52010-05-07 05:42:23 +00001215 args->sigma*(labs(u)+labs(v)) );
cristyc99304f2010-02-01 15:26:27 +00001216 kernel->maximum = kernel->values[0];
anthony602ab9b2010-01-05 08:06:50 +00001217 break;
1218 }
1219 case EuclideanKernel:
1220 {
anthony602ab9b2010-01-05 08:06:50 +00001221 if (args->rho < 1.0)
anthonyc94cdb02010-01-06 08:15:29 +00001222 kernel->width = kernel->height = 3; /* default radius = 1 */
anthony602ab9b2010-01-05 08:06:50 +00001223 else
1224 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
cristyc99304f2010-02-01 15:26:27 +00001225 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony602ab9b2010-01-05 08:06:50 +00001226
1227 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
1228 kernel->height*sizeof(double));
1229 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +00001230 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +00001231
cristyc99304f2010-02-01 15:26:27 +00001232 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
1233 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
1234 kernel->positive_range += ( kernel->values[i] =
anthonyc84dce52010-05-07 05:42:23 +00001235 args->sigma*sqrt((double)(u*u+v*v)) );
cristyc99304f2010-02-01 15:26:27 +00001236 kernel->maximum = kernel->values[0];
anthony602ab9b2010-01-05 08:06:50 +00001237 break;
1238 }
1239 /* Undefined Kernels */
anthony602ab9b2010-01-05 08:06:50 +00001240 case DOGKernel:
anthony3c10fc82010-05-13 02:40:51 +00001241 perror("Kernel Type has not been defined yet\n");
anthony602ab9b2010-01-05 08:06:50 +00001242 /* FALL THRU */
1243 default:
1244 /* Generate a No-Op minimal kernel - 1x1 pixel */
1245 kernel->values=(double *)AcquireQuantumMemory((size_t)1,sizeof(double));
1246 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +00001247 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +00001248 kernel->width = kernel->height = 1;
cristyc99304f2010-02-01 15:26:27 +00001249 kernel->x = kernel->x = 0;
anthony602ab9b2010-01-05 08:06:50 +00001250 kernel->type = UndefinedKernel;
cristyc99304f2010-02-01 15:26:27 +00001251 kernel->maximum =
1252 kernel->positive_range =
anthonyc94cdb02010-01-06 08:15:29 +00001253 kernel->values[0] = 1.0; /* a flat single-point no-op kernel! */
anthony602ab9b2010-01-05 08:06:50 +00001254 break;
1255 }
1256
1257 return(kernel);
1258}
anthonyc94cdb02010-01-06 08:15:29 +00001259
anthony602ab9b2010-01-05 08:06:50 +00001260/*
1261%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1262% %
1263% %
1264% %
cristy6771f1e2010-03-05 19:43:39 +00001265% C l o n e K e r n e l I n f o %
anthony4fd27e22010-02-07 08:17:18 +00001266% %
1267% %
1268% %
1269%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1270%
anthony1b2bc0a2010-05-12 05:25:22 +00001271% CloneKernelInfo() creates a new clone of the given Kernel List so that its
1272% can be modified without effecting the original. The cloned kernel should
1273% be destroyed using DestoryKernelInfo() when no longer needed.
anthony7a01dcf2010-05-11 12:25:52 +00001274%
cristye6365592010-04-02 17:31:23 +00001275% The format of the CloneKernelInfo method is:
anthony4fd27e22010-02-07 08:17:18 +00001276%
anthony930be612010-02-08 04:26:15 +00001277% KernelInfo *CloneKernelInfo(const KernelInfo *kernel)
anthony4fd27e22010-02-07 08:17:18 +00001278%
1279% A description of each parameter follows:
1280%
1281% o kernel: the Morphology/Convolution kernel to be cloned
1282%
1283*/
cristyef656912010-03-05 19:54:59 +00001284MagickExport KernelInfo *CloneKernelInfo(const KernelInfo *kernel)
anthony4fd27e22010-02-07 08:17:18 +00001285{
1286 register long
1287 i;
1288
cristy19eb6412010-04-23 14:42:29 +00001289 KernelInfo
anthony7a01dcf2010-05-11 12:25:52 +00001290 *new_kernel;
anthony4fd27e22010-02-07 08:17:18 +00001291
1292 assert(kernel != (KernelInfo *) NULL);
anthony7a01dcf2010-05-11 12:25:52 +00001293 new_kernel=(KernelInfo *) AcquireMagickMemory(sizeof(*kernel));
1294 if (new_kernel == (KernelInfo *) NULL)
1295 return(new_kernel);
1296 *new_kernel=(*kernel); /* copy values in structure */
anthony7a01dcf2010-05-11 12:25:52 +00001297
1298 /* replace the values with a copy of the values */
1299 new_kernel->values=(double *) AcquireQuantumMemory(kernel->width,
cristy19eb6412010-04-23 14:42:29 +00001300 kernel->height*sizeof(double));
anthony7a01dcf2010-05-11 12:25:52 +00001301 if (new_kernel->values == (double *) NULL)
1302 return(DestroyKernelInfo(new_kernel));
anthony4fd27e22010-02-07 08:17:18 +00001303 for (i=0; i < (long) (kernel->width*kernel->height); i++)
anthony7a01dcf2010-05-11 12:25:52 +00001304 new_kernel->values[i]=kernel->values[i];
anthony1b2bc0a2010-05-12 05:25:22 +00001305
1306 /* Also clone the next kernel in the kernel list */
1307 if ( kernel->next != (KernelInfo *) NULL ) {
1308 new_kernel->next = CloneKernelInfo(kernel->next);
1309 if ( new_kernel->next == (KernelInfo *) NULL )
1310 return(DestroyKernelInfo(new_kernel));
1311 }
1312
anthony7a01dcf2010-05-11 12:25:52 +00001313 return(new_kernel);
anthony4fd27e22010-02-07 08:17:18 +00001314}
1315
1316/*
1317%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1318% %
1319% %
1320% %
anthony83ba99b2010-01-24 08:48:15 +00001321% D e s t r o y K e r n e l I n f o %
anthony602ab9b2010-01-05 08:06:50 +00001322% %
1323% %
1324% %
1325%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1326%
anthony83ba99b2010-01-24 08:48:15 +00001327% DestroyKernelInfo() frees the memory used by a Convolution/Morphology
1328% kernel.
anthony602ab9b2010-01-05 08:06:50 +00001329%
anthony83ba99b2010-01-24 08:48:15 +00001330% The format of the DestroyKernelInfo method is:
anthony602ab9b2010-01-05 08:06:50 +00001331%
anthony83ba99b2010-01-24 08:48:15 +00001332% KernelInfo *DestroyKernelInfo(KernelInfo *kernel)
anthony602ab9b2010-01-05 08:06:50 +00001333%
1334% A description of each parameter follows:
1335%
1336% o kernel: the Morphology/Convolution kernel to be destroyed
1337%
1338*/
1339
anthony83ba99b2010-01-24 08:48:15 +00001340MagickExport KernelInfo *DestroyKernelInfo(KernelInfo *kernel)
anthony602ab9b2010-01-05 08:06:50 +00001341{
cristy2be15382010-01-21 02:38:03 +00001342 assert(kernel != (KernelInfo *) NULL);
anthony4fd27e22010-02-07 08:17:18 +00001343
anthony7a01dcf2010-05-11 12:25:52 +00001344 if ( kernel->next != (KernelInfo *) NULL )
1345 kernel->next = DestroyKernelInfo(kernel->next);
1346
1347 kernel->values = (double *)RelinquishMagickMemory(kernel->values);
1348 kernel = (KernelInfo *) RelinquishMagickMemory(kernel);
anthony602ab9b2010-01-05 08:06:50 +00001349 return(kernel);
1350}
anthonyc94cdb02010-01-06 08:15:29 +00001351
1352/*
1353%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1354% %
1355% %
1356% %
anthony3c10fc82010-05-13 02:40:51 +00001357% E x p a n d K e r n e l I n f o %
1358% %
1359% %
1360% %
1361%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1362%
1363% ExpandKernelInfo() takes a single kernel, and expands it into a list
1364% of kernels each incrementally rotated the angle given.
1365%
1366% WARNING: 45 degree rotations only works for 3x3 kernels.
1367% While 90 degree roatations only works for linear and square kernels
1368%
1369% The format of the RotateKernelInfo method is:
1370%
1371% void ExpandKernelInfo(KernelInfo *kernel, double angle)
1372%
1373% A description of each parameter follows:
1374%
1375% o kernel: the Morphology/Convolution kernel
1376%
1377% o angle: angle to rotate in degrees
1378%
1379% This function is only internel to this module, as it is not finalized,
1380% especially with regard to non-orthogonal angles, and rotation of larger
1381% 2D kernels.
1382*/
1383static void ExpandKernelInfo(KernelInfo *kernel, double angle)
1384{
1385 KernelInfo
cristya9a61ad2010-05-13 12:47:41 +00001386 *clone,
anthony3c10fc82010-05-13 02:40:51 +00001387 *last;
cristya9a61ad2010-05-13 12:47:41 +00001388
anthony3c10fc82010-05-13 02:40:51 +00001389 double
1390 a;
1391
1392 last = kernel;
1393 for (a=angle; a<355.0; a+=angle) {
cristya9a61ad2010-05-13 12:47:41 +00001394 clone = CloneKernelInfo(last);
1395 RotateKernelInfo(clone, angle);
1396 last->next = clone;
1397 last = clone;
anthony3c10fc82010-05-13 02:40:51 +00001398 }
1399}
1400
1401/*
1402%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1403% %
1404% %
1405% %
anthony29188a82010-01-22 10:12:34 +00001406% M o r p h o l o g y I m a g e C h a n n e l %
anthony602ab9b2010-01-05 08:06:50 +00001407% %
1408% %
1409% %
1410%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1411%
anthony29188a82010-01-22 10:12:34 +00001412% MorphologyImageChannel() applies a user supplied kernel to the image
1413% according to the given mophology method.
anthony602ab9b2010-01-05 08:06:50 +00001414%
1415% The given kernel is assumed to have been pre-scaled appropriatally, usally
1416% by the kernel generator.
1417%
1418% The format of the MorphologyImage method is:
1419%
cristyef656912010-03-05 19:54:59 +00001420% Image *MorphologyImage(const Image *image,MorphologyMethod method,
1421% const long iterations,KernelInfo *kernel,ExceptionInfo *exception)
anthony29188a82010-01-22 10:12:34 +00001422% Image *MorphologyImageChannel(const Image *image, const ChannelType
cristyef656912010-03-05 19:54:59 +00001423% channel,MorphologyMethod method,const long iterations,
1424% KernelInfo *kernel,ExceptionInfo *exception)
anthony602ab9b2010-01-05 08:06:50 +00001425%
1426% A description of each parameter follows:
1427%
1428% o image: the image.
1429%
1430% o method: the morphology method to be applied.
1431%
1432% o iterations: apply the operation this many times (or no change).
1433% A value of -1 means loop until no change found.
1434% How this is applied may depend on the morphology method.
1435% Typically this is a value of 1.
1436%
1437% o channel: the channel type.
1438%
1439% o kernel: An array of double representing the morphology kernel.
anthony29188a82010-01-22 10:12:34 +00001440% Warning: kernel may be normalized for the Convolve method.
anthony602ab9b2010-01-05 08:06:50 +00001441%
1442% o exception: return any errors or warnings in this structure.
1443%
1444%
1445% TODO: bias and auto-scale handling of the kernel for convolution
1446% The given kernel is assumed to have been pre-scaled appropriatally, usally
1447% by the kernel generator.
1448%
1449*/
1450
anthony930be612010-02-08 04:26:15 +00001451
anthony602ab9b2010-01-05 08:06:50 +00001452/* Internal function
anthony7a01dcf2010-05-11 12:25:52 +00001453 * Apply the low-level Morphology Method Primatives using the given Kernel
anthony930be612010-02-08 04:26:15 +00001454 * Returning the number of pixels that changed.
1455 * Two pre-created images must be provided, no image is created.
anthony602ab9b2010-01-05 08:06:50 +00001456 */
anthony7a01dcf2010-05-11 12:25:52 +00001457static unsigned long MorphologyPrimative(const Image *image, Image
anthony602ab9b2010-01-05 08:06:50 +00001458 *result_image, const MorphologyMethod method, const ChannelType channel,
cristy2be15382010-01-21 02:38:03 +00001459 const KernelInfo *kernel, ExceptionInfo *exception)
anthony602ab9b2010-01-05 08:06:50 +00001460{
cristy2be15382010-01-21 02:38:03 +00001461#define MorphologyTag "Morphology/Image"
anthony602ab9b2010-01-05 08:06:50 +00001462
1463 long
cristy150989e2010-02-01 14:59:39 +00001464 progress,
anthony29188a82010-01-22 10:12:34 +00001465 y, offx, offy,
anthony602ab9b2010-01-05 08:06:50 +00001466 changed;
1467
1468 MagickBooleanType
1469 status;
1470
1471 MagickPixelPacket
1472 bias;
1473
1474 CacheView
1475 *p_view,
1476 *q_view;
1477
anthony4fd27e22010-02-07 08:17:18 +00001478 /* Only the most basic morphology is actually performed by this routine */
anthony4fd27e22010-02-07 08:17:18 +00001479
anthony602ab9b2010-01-05 08:06:50 +00001480 /*
anthony4fd27e22010-02-07 08:17:18 +00001481 Apply Basic Morphology to Image.
anthony602ab9b2010-01-05 08:06:50 +00001482 */
1483 status=MagickTrue;
1484 changed=0;
1485 progress=0;
1486
1487 GetMagickPixelPacket(image,&bias);
1488 SetMagickPixelPacketBias(image,&bias);
anthonycc6c8362010-01-25 04:14:01 +00001489 /* Future: handle auto-bias from user, based on kernel input */
anthony602ab9b2010-01-05 08:06:50 +00001490
1491 p_view=AcquireCacheView(image);
1492 q_view=AcquireCacheView(result_image);
anthony29188a82010-01-22 10:12:34 +00001493
anthonycc6c8362010-01-25 04:14:01 +00001494 /* Some methods (including convolve) needs use a reflected kernel.
1495 * Adjust 'origin' offsets for this reflected kernel.
anthony29188a82010-01-22 10:12:34 +00001496 */
cristyc99304f2010-02-01 15:26:27 +00001497 offx = kernel->x;
1498 offy = kernel->y;
anthony29188a82010-01-22 10:12:34 +00001499 switch(method) {
anthony930be612010-02-08 04:26:15 +00001500 case ConvolveMorphology:
1501 case DilateMorphology:
1502 case DilateIntensityMorphology:
1503 case DistanceMorphology:
anthony5ef8e942010-05-11 06:51:12 +00001504 /* kernel needs to used with reflection about origin */
cristy150989e2010-02-01 14:59:39 +00001505 offx = (long) kernel->width-offx-1;
1506 offy = (long) kernel->height-offy-1;
anthony29188a82010-01-22 10:12:34 +00001507 break;
anthony5ef8e942010-05-11 06:51:12 +00001508 case ErodeMorphology:
1509 case ErodeIntensityMorphology:
1510 case HitAndMissMorphology:
1511 case ThinningMorphology:
1512 case ThickenMorphology:
1513 /* kernel is user as is, without reflection */
1514 break;
anthony930be612010-02-08 04:26:15 +00001515 default:
anthony5ef8e942010-05-11 06:51:12 +00001516 perror("Not a low level Morphology Method");
anthony930be612010-02-08 04:26:15 +00001517 break;
anthony29188a82010-01-22 10:12:34 +00001518 }
1519
anthony602ab9b2010-01-05 08:06:50 +00001520#if defined(MAGICKCORE_OPENMP_SUPPORT)
1521 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
1522#endif
cristy150989e2010-02-01 14:59:39 +00001523 for (y=0; y < (long) image->rows; y++)
anthony602ab9b2010-01-05 08:06:50 +00001524 {
1525 MagickBooleanType
1526 sync;
1527
1528 register const PixelPacket
1529 *restrict p;
1530
1531 register const IndexPacket
1532 *restrict p_indexes;
1533
1534 register PixelPacket
1535 *restrict q;
1536
1537 register IndexPacket
1538 *restrict q_indexes;
1539
cristy150989e2010-02-01 14:59:39 +00001540 register long
anthony602ab9b2010-01-05 08:06:50 +00001541 x;
1542
anthony29188a82010-01-22 10:12:34 +00001543 unsigned long
anthony602ab9b2010-01-05 08:06:50 +00001544 r;
1545
1546 if (status == MagickFalse)
1547 continue;
anthony29188a82010-01-22 10:12:34 +00001548 p=GetCacheViewVirtualPixels(p_view, -offx, y-offy,
1549 image->columns+kernel->width, kernel->height, exception);
anthony602ab9b2010-01-05 08:06:50 +00001550 q=GetCacheViewAuthenticPixels(q_view,0,y,result_image->columns,1,
1551 exception);
1552 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
1553 {
1554 status=MagickFalse;
1555 continue;
1556 }
1557 p_indexes=GetCacheViewVirtualIndexQueue(p_view);
1558 q_indexes=GetCacheViewAuthenticIndexQueue(q_view);
anthony29188a82010-01-22 10:12:34 +00001559 r = (image->columns+kernel->width)*offy+offx; /* constant */
1560
cristy150989e2010-02-01 14:59:39 +00001561 for (x=0; x < (long) image->columns; x++)
anthony602ab9b2010-01-05 08:06:50 +00001562 {
cristy150989e2010-02-01 14:59:39 +00001563 long
anthony602ab9b2010-01-05 08:06:50 +00001564 v;
1565
cristy150989e2010-02-01 14:59:39 +00001566 register long
anthony602ab9b2010-01-05 08:06:50 +00001567 u;
1568
1569 register const double
1570 *restrict k;
1571
1572 register const PixelPacket
1573 *restrict k_pixels;
1574
1575 register const IndexPacket
1576 *restrict k_indexes;
1577
1578 MagickPixelPacket
anthony5ef8e942010-05-11 06:51:12 +00001579 result,
1580 min,
1581 max;
anthony602ab9b2010-01-05 08:06:50 +00001582
anthony29188a82010-01-22 10:12:34 +00001583 /* Copy input to ouput image for unused channels
anthony83ba99b2010-01-24 08:48:15 +00001584 * This removes need for 'cloning' a new image every iteration
anthony29188a82010-01-22 10:12:34 +00001585 */
anthony602ab9b2010-01-05 08:06:50 +00001586 *q = p[r];
1587 if (image->colorspace == CMYKColorspace)
1588 q_indexes[x] = p_indexes[r];
1589
anthony5ef8e942010-05-11 06:51:12 +00001590 /* Defaults */
1591 min.red =
1592 min.green =
1593 min.blue =
1594 min.opacity =
1595 min.index = (MagickRealType) QuantumRange;
1596 max.red =
1597 max.green =
1598 max.blue =
1599 max.opacity =
1600 max.index = (MagickRealType) 0;
1601 /* original pixel value */
1602 result.red = (MagickRealType) p[r].red;
1603 result.green = (MagickRealType) p[r].green;
1604 result.blue = (MagickRealType) p[r].blue;
1605 result.opacity = QuantumRange - (MagickRealType) p[r].opacity;
cristy9b1a5442010-05-13 12:35:40 +00001606 result.index = 0;
anthony5ef8e942010-05-11 06:51:12 +00001607 if ( image->colorspace == CMYKColorspace)
1608 result.index = (MagickRealType) p_indexes[r];
1609
anthony602ab9b2010-01-05 08:06:50 +00001610 switch (method) {
1611 case ConvolveMorphology:
anthony930be612010-02-08 04:26:15 +00001612 /* Set the user defined bias of the weighted average output
1613 **
1614 ** FUTURE: provide some way for internal functions to disable
anthony5ef8e942010-05-11 06:51:12 +00001615 ** user provided bias and scaling effects.
anthony930be612010-02-08 04:26:15 +00001616 */
anthony602ab9b2010-01-05 08:06:50 +00001617 result=bias;
anthony930be612010-02-08 04:26:15 +00001618 break;
anthony4fd27e22010-02-07 08:17:18 +00001619 case DilateIntensityMorphology:
1620 case ErodeIntensityMorphology:
anthony5ef8e942010-05-11 06:51:12 +00001621 result.red = 0.0; /* flag indicating when first match found */
anthony4fd27e22010-02-07 08:17:18 +00001622 break;
anthony602ab9b2010-01-05 08:06:50 +00001623 default:
anthony602ab9b2010-01-05 08:06:50 +00001624 break;
1625 }
1626
1627 switch ( method ) {
1628 case ConvolveMorphology:
anthony930be612010-02-08 04:26:15 +00001629 /* Weighted Average of pixels using reflected kernel
1630 **
1631 ** NOTE for correct working of this operation for asymetrical
1632 ** kernels, the kernel needs to be applied in its reflected form.
1633 ** That is its values needs to be reversed.
1634 **
1635 ** Correlation is actually the same as this but without reflecting
1636 ** the kernel, and thus 'lower-level' that Convolution. However
1637 ** as Convolution is the more common method used, and it does not
1638 ** really cost us much in terms of processing to use a reflected
anthony5ef8e942010-05-11 06:51:12 +00001639 ** kernel, so it is Convolution that is implemented.
anthony930be612010-02-08 04:26:15 +00001640 **
1641 ** Correlation will have its kernel reflected before calling
1642 ** this function to do a Convolve.
1643 **
1644 ** For more details of Correlation vs Convolution see
1645 ** http://www.cs.umd.edu/~djacobs/CMSC426/Convolution.pdf
1646 */
anthony5ef8e942010-05-11 06:51:12 +00001647 if (((channel & SyncChannels) != 0 ) &&
1648 (image->matte == MagickTrue))
1649 { /* Channel has a 'Sync' Flag, and Alpha Channel enabled.
1650 ** Weight the color channels with Alpha Channel so that
1651 ** transparent pixels are not part of the results.
1652 */
anthony602ab9b2010-01-05 08:06:50 +00001653 MagickRealType
anthony5ef8e942010-05-11 06:51:12 +00001654 alpha, /* color channel weighting : kernel*alpha */
1655 gamma; /* divisor, sum of weighting values */
anthony602ab9b2010-01-05 08:06:50 +00001656
1657 gamma=0.0;
anthony29188a82010-01-22 10:12:34 +00001658 k = &kernel->values[ kernel->width*kernel->height-1 ];
anthony602ab9b2010-01-05 08:06:50 +00001659 k_pixels = p;
1660 k_indexes = p_indexes;
cristy150989e2010-02-01 14:59:39 +00001661 for (v=0; v < (long) kernel->height; v++) {
1662 for (u=0; u < (long) kernel->width; u++, k--) {
anthony602ab9b2010-01-05 08:06:50 +00001663 if ( IsNan(*k) ) continue;
1664 alpha=(*k)*(QuantumScale*(QuantumRange-
1665 k_pixels[u].opacity));
1666 gamma += alpha;
1667 result.red += alpha*k_pixels[u].red;
1668 result.green += alpha*k_pixels[u].green;
1669 result.blue += alpha*k_pixels[u].blue;
anthony83ba99b2010-01-24 08:48:15 +00001670 result.opacity += (*k)*(QuantumRange-k_pixels[u].opacity);
anthony602ab9b2010-01-05 08:06:50 +00001671 if ( image->colorspace == CMYKColorspace)
1672 result.index += alpha*k_indexes[u];
1673 }
1674 k_pixels += image->columns+kernel->width;
1675 k_indexes += image->columns+kernel->width;
1676 }
1677 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
anthony83ba99b2010-01-24 08:48:15 +00001678 result.red *= gamma;
1679 result.green *= gamma;
1680 result.blue *= gamma;
1681 result.opacity *= gamma;
1682 result.index *= gamma;
anthony602ab9b2010-01-05 08:06:50 +00001683 }
anthony5ef8e942010-05-11 06:51:12 +00001684 else
1685 {
1686 /* No 'Sync' flag, or no Alpha involved.
1687 ** Convolution is simple individual channel weigthed sum.
1688 */
1689 k = &kernel->values[ kernel->width*kernel->height-1 ];
1690 k_pixels = p;
1691 k_indexes = p_indexes;
1692 for (v=0; v < (long) kernel->height; v++) {
1693 for (u=0; u < (long) kernel->width; u++, k--) {
1694 if ( IsNan(*k) ) continue;
1695 result.red += (*k)*k_pixels[u].red;
1696 result.green += (*k)*k_pixels[u].green;
1697 result.blue += (*k)*k_pixels[u].blue;
1698 result.opacity += (*k)*(QuantumRange-k_pixels[u].opacity);
1699 if ( image->colorspace == CMYKColorspace)
1700 result.index += (*k)*k_indexes[u];
1701 }
1702 k_pixels += image->columns+kernel->width;
1703 k_indexes += image->columns+kernel->width;
1704 }
1705 }
anthony602ab9b2010-01-05 08:06:50 +00001706 break;
1707
anthony4fd27e22010-02-07 08:17:18 +00001708 case ErodeMorphology:
anthony5ef8e942010-05-11 06:51:12 +00001709 /* Minimum Value within kernel neighbourhood
anthony930be612010-02-08 04:26:15 +00001710 **
1711 ** NOTE that the kernel is not reflected for this operation!
1712 **
1713 ** NOTE: in normal Greyscale Morphology, the kernel value should
1714 ** be added to the real value, this is currently not done, due to
1715 ** the nature of the boolean kernels being used.
1716 */
anthony4fd27e22010-02-07 08:17:18 +00001717 k = kernel->values;
1718 k_pixels = p;
1719 k_indexes = p_indexes;
1720 for (v=0; v < (long) kernel->height; v++) {
1721 for (u=0; u < (long) kernel->width; u++, k++) {
1722 if ( IsNan(*k) || (*k) < 0.5 ) continue;
anthony5ef8e942010-05-11 06:51:12 +00001723 Minimize(min.red, (double) k_pixels[u].red);
1724 Minimize(min.green, (double) k_pixels[u].green);
1725 Minimize(min.blue, (double) k_pixels[u].blue);
1726 Minimize(min.opacity,
anthonyd37a5cb2010-05-07 06:37:03 +00001727 QuantumRange-(double) k_pixels[u].opacity);
anthony4fd27e22010-02-07 08:17:18 +00001728 if ( image->colorspace == CMYKColorspace)
anthony5ef8e942010-05-11 06:51:12 +00001729 Minimize(min.index, (double) k_indexes[u]);
anthony4fd27e22010-02-07 08:17:18 +00001730 }
1731 k_pixels += image->columns+kernel->width;
1732 k_indexes += image->columns+kernel->width;
1733 }
1734 break;
1735
anthony5ef8e942010-05-11 06:51:12 +00001736
anthony83ba99b2010-01-24 08:48:15 +00001737 case DilateMorphology:
anthony5ef8e942010-05-11 06:51:12 +00001738 /* Maximum Value within kernel neighbourhood
anthony930be612010-02-08 04:26:15 +00001739 **
1740 ** NOTE for correct working of this operation for asymetrical
1741 ** kernels, the kernel needs to be applied in its reflected form.
1742 ** That is its values needs to be reversed.
1743 **
1744 ** NOTE: in normal Greyscale Morphology, the kernel value should
1745 ** be added to the real value, this is currently not done, due to
1746 ** the nature of the boolean kernels being used.
1747 **
1748 */
anthony29188a82010-01-22 10:12:34 +00001749 k = &kernel->values[ kernel->width*kernel->height-1 ];
anthony602ab9b2010-01-05 08:06:50 +00001750 k_pixels = p;
1751 k_indexes = p_indexes;
cristy150989e2010-02-01 14:59:39 +00001752 for (v=0; v < (long) kernel->height; v++) {
1753 for (u=0; u < (long) kernel->width; u++, k--) {
anthony602ab9b2010-01-05 08:06:50 +00001754 if ( IsNan(*k) || (*k) < 0.5 ) continue;
anthony5ef8e942010-05-11 06:51:12 +00001755 Maximize(max.red, (double) k_pixels[u].red);
1756 Maximize(max.green, (double) k_pixels[u].green);
1757 Maximize(max.blue, (double) k_pixels[u].blue);
1758 Maximize(max.opacity,
anthonyd37a5cb2010-05-07 06:37:03 +00001759 QuantumRange-(double) k_pixels[u].opacity);
anthony602ab9b2010-01-05 08:06:50 +00001760 if ( image->colorspace == CMYKColorspace)
anthony5ef8e942010-05-11 06:51:12 +00001761 Maximize(max.index, (double) k_indexes[u]);
anthony602ab9b2010-01-05 08:06:50 +00001762 }
1763 k_pixels += image->columns+kernel->width;
1764 k_indexes += image->columns+kernel->width;
1765 }
anthony602ab9b2010-01-05 08:06:50 +00001766 break;
1767
anthony5ef8e942010-05-11 06:51:12 +00001768 case HitAndMissMorphology:
1769 case ThinningMorphology:
1770 case ThickenMorphology:
1771 /* Minimum of Foreground Pixel minus Maxumum of Background Pixels
1772 **
1773 ** NOTE that the kernel is not reflected for this operation,
1774 ** and consists of both foreground and background pixel
1775 ** neighbourhoods, 0.0 for background, and 1.0 for foreground
1776 ** with either Nan or 0.5 values for don't care.
1777 **
1778 ** Note that this can produce negative results, though really
1779 ** only a positive match has any real value.
1780 */
1781 k = kernel->values;
1782 k_pixels = p;
1783 k_indexes = p_indexes;
1784 for (v=0; v < (long) kernel->height; v++) {
1785 for (u=0; u < (long) kernel->width; u++, k++) {
1786 if ( IsNan(*k) ) continue;
1787 if ( (*k) > 0.7 )
1788 { /* minimim of foreground pixels */
1789 Minimize(min.red, (double) k_pixels[u].red);
1790 Minimize(min.green, (double) k_pixels[u].green);
1791 Minimize(min.blue, (double) k_pixels[u].blue);
1792 Minimize(min.opacity,
1793 QuantumRange-(double) k_pixels[u].opacity);
1794 if ( image->colorspace == CMYKColorspace)
1795 Minimize(min.index, (double) k_indexes[u]);
1796 }
1797 else if ( (*k) < 0.3 )
1798 { /* maximum of background pixels */
1799 Maximize(max.red, (double) k_pixels[u].red);
1800 Maximize(max.green, (double) k_pixels[u].green);
1801 Maximize(max.blue, (double) k_pixels[u].blue);
1802 Maximize(max.opacity,
1803 QuantumRange-(double) k_pixels[u].opacity);
1804 if ( image->colorspace == CMYKColorspace)
1805 Maximize(max.index, (double) k_indexes[u]);
1806 }
1807 }
1808 k_pixels += image->columns+kernel->width;
1809 k_indexes += image->columns+kernel->width;
1810 }
1811 /* Pattern Match only if min fg larger than min bg pixels */
1812 min.red -= max.red; Maximize( min.red, 0.0 );
1813 min.green -= max.green; Maximize( min.green, 0.0 );
1814 min.blue -= max.blue; Maximize( min.blue, 0.0 );
1815 min.opacity -= max.opacity; Maximize( min.opacity, 0.0 );
1816 min.index -= max.index; Maximize( min.index, 0.0 );
1817 break;
1818
anthony4fd27e22010-02-07 08:17:18 +00001819 case ErodeIntensityMorphology:
anthony930be612010-02-08 04:26:15 +00001820 /* Select Pixel with Minimum Intensity within kernel neighbourhood
1821 **
1822 ** WARNING: the intensity test fails for CMYK and does not
1823 ** take into account the moderating effect of teh alpha channel
1824 ** on the intensity.
1825 **
1826 ** NOTE that the kernel is not reflected for this operation!
1827 */
anthony602ab9b2010-01-05 08:06:50 +00001828 k = kernel->values;
1829 k_pixels = p;
1830 k_indexes = p_indexes;
cristy150989e2010-02-01 14:59:39 +00001831 for (v=0; v < (long) kernel->height; v++) {
1832 for (u=0; u < (long) kernel->width; u++, k++) {
anthony602ab9b2010-01-05 08:06:50 +00001833 if ( IsNan(*k) || (*k) < 0.5 ) continue;
anthony4fd27e22010-02-07 08:17:18 +00001834 if ( result.red == 0.0 ||
1835 PixelIntensity(&(k_pixels[u])) < PixelIntensity(q) ) {
1836 /* copy the whole pixel - no channel selection */
1837 *q = k_pixels[u];
1838 if ( result.red > 0.0 ) changed++;
1839 result.red = 1.0;
1840 }
anthony602ab9b2010-01-05 08:06:50 +00001841 }
1842 k_pixels += image->columns+kernel->width;
1843 k_indexes += image->columns+kernel->width;
1844 }
anthony602ab9b2010-01-05 08:06:50 +00001845 break;
1846
anthony83ba99b2010-01-24 08:48:15 +00001847 case DilateIntensityMorphology:
anthony930be612010-02-08 04:26:15 +00001848 /* Select Pixel with Maximum Intensity within kernel neighbourhood
1849 **
1850 ** WARNING: the intensity test fails for CMYK and does not
1851 ** take into account the moderating effect of teh alpha channel
1852 ** on the intensity.
1853 **
1854 ** NOTE for correct working of this operation for asymetrical
1855 ** kernels, the kernel needs to be applied in its reflected form.
1856 ** That is its values needs to be reversed.
1857 */
anthony29188a82010-01-22 10:12:34 +00001858 k = &kernel->values[ kernel->width*kernel->height-1 ];
anthony602ab9b2010-01-05 08:06:50 +00001859 k_pixels = p;
1860 k_indexes = p_indexes;
cristy150989e2010-02-01 14:59:39 +00001861 for (v=0; v < (long) kernel->height; v++) {
1862 for (u=0; u < (long) kernel->width; u++, k--) {
anthony29188a82010-01-22 10:12:34 +00001863 if ( IsNan(*k) || (*k) < 0.5 ) continue; /* boolean kernel */
1864 if ( result.red == 0.0 ||
1865 PixelIntensity(&(k_pixels[u])) > PixelIntensity(q) ) {
1866 /* copy the whole pixel - no channel selection */
1867 *q = k_pixels[u];
1868 if ( result.red > 0.0 ) changed++;
1869 result.red = 1.0;
1870 }
anthony602ab9b2010-01-05 08:06:50 +00001871 }
1872 k_pixels += image->columns+kernel->width;
1873 k_indexes += image->columns+kernel->width;
1874 }
anthony602ab9b2010-01-05 08:06:50 +00001875 break;
1876
anthony5ef8e942010-05-11 06:51:12 +00001877
anthony602ab9b2010-01-05 08:06:50 +00001878 case DistanceMorphology:
anthony930be612010-02-08 04:26:15 +00001879 /* Add kernel Value and select the minimum value found.
1880 ** The result is a iterative distance from edge of image shape.
1881 **
1882 ** All Distance Kernels are symetrical, but that may not always
1883 ** be the case. For example how about a distance from left edges?
1884 ** To work correctly with asymetrical kernels the reflected kernel
1885 ** needs to be applied.
anthony5ef8e942010-05-11 06:51:12 +00001886 **
1887 ** Actually this is really a GreyErode with a negative kernel!
1888 **
anthony930be612010-02-08 04:26:15 +00001889 */
anthony29188a82010-01-22 10:12:34 +00001890 k = &kernel->values[ kernel->width*kernel->height-1 ];
anthony602ab9b2010-01-05 08:06:50 +00001891 k_pixels = p;
1892 k_indexes = p_indexes;
cristy150989e2010-02-01 14:59:39 +00001893 for (v=0; v < (long) kernel->height; v++) {
1894 for (u=0; u < (long) kernel->width; u++, k--) {
anthony602ab9b2010-01-05 08:06:50 +00001895 if ( IsNan(*k) ) continue;
1896 Minimize(result.red, (*k)+k_pixels[u].red);
1897 Minimize(result.green, (*k)+k_pixels[u].green);
1898 Minimize(result.blue, (*k)+k_pixels[u].blue);
1899 Minimize(result.opacity, (*k)+QuantumRange-k_pixels[u].opacity);
1900 if ( image->colorspace == CMYKColorspace)
1901 Minimize(result.index, (*k)+k_indexes[u]);
1902 }
1903 k_pixels += image->columns+kernel->width;
1904 k_indexes += image->columns+kernel->width;
1905 }
anthony602ab9b2010-01-05 08:06:50 +00001906 break;
1907
1908 case UndefinedMorphology:
1909 default:
1910 break; /* Do nothing */
anthony83ba99b2010-01-24 08:48:15 +00001911 }
anthony5ef8e942010-05-11 06:51:12 +00001912 /* Final mathematics of results (combine with original image?)
1913 **
1914 ** NOTE: Difference Morphology operators Edge* and *Hat could also
1915 ** be done here but works better with iteration as a image difference
1916 ** in the controling function (below). Thicken and Thinning however
1917 ** should be done here so thay can be iterated correctly.
1918 */
1919 switch ( method ) {
1920 case HitAndMissMorphology:
1921 case ErodeMorphology:
1922 result = min; /* minimum of neighbourhood */
1923 break;
1924 case DilateMorphology:
1925 result = max; /* maximum of neighbourhood */
1926 break;
1927 case ThinningMorphology:
1928 /* subtract pattern match from original */
1929 result.red -= min.red;
1930 result.green -= min.green;
1931 result.blue -= min.blue;
1932 result.opacity -= min.opacity;
1933 result.index -= min.index;
1934 break;
1935 case ThickenMorphology:
1936 /* Union with original image (maximize) - or should this be + */
1937 Maximize( result.red, min.red );
1938 Maximize( result.green, min.green );
1939 Maximize( result.blue, min.blue );
1940 Maximize( result.opacity, min.opacity );
1941 Maximize( result.index, min.index );
1942 break;
1943 default:
1944 /* result directly calculated or assigned */
1945 break;
1946 }
1947 /* Assign the resulting pixel values - Clamping Result */
anthony83ba99b2010-01-24 08:48:15 +00001948 switch ( method ) {
1949 case UndefinedMorphology:
1950 case DilateIntensityMorphology:
1951 case ErodeIntensityMorphology:
anthony930be612010-02-08 04:26:15 +00001952 break; /* full pixel was directly assigned - not a channel method */
anthony83ba99b2010-01-24 08:48:15 +00001953 default:
anthony83ba99b2010-01-24 08:48:15 +00001954 if ((channel & RedChannel) != 0)
1955 q->red = ClampToQuantum(result.red);
1956 if ((channel & GreenChannel) != 0)
1957 q->green = ClampToQuantum(result.green);
1958 if ((channel & BlueChannel) != 0)
1959 q->blue = ClampToQuantum(result.blue);
1960 if ((channel & OpacityChannel) != 0
1961 && image->matte == MagickTrue )
1962 q->opacity = ClampToQuantum(QuantumRange-result.opacity);
1963 if ((channel & IndexChannel) != 0
1964 && image->colorspace == CMYKColorspace)
1965 q_indexes[x] = ClampToQuantum(result.index);
1966 break;
1967 }
anthony5ef8e942010-05-11 06:51:12 +00001968 /* Count up changed pixels */
anthony83ba99b2010-01-24 08:48:15 +00001969 if ( ( p[r].red != q->red )
1970 || ( p[r].green != q->green )
1971 || ( p[r].blue != q->blue )
1972 || ( p[r].opacity != q->opacity )
1973 || ( image->colorspace == CMYKColorspace &&
1974 p_indexes[r] != q_indexes[x] ) )
1975 changed++; /* The pixel had some value changed! */
anthony602ab9b2010-01-05 08:06:50 +00001976 p++;
1977 q++;
anthony83ba99b2010-01-24 08:48:15 +00001978 } /* x */
anthony602ab9b2010-01-05 08:06:50 +00001979 sync=SyncCacheViewAuthenticPixels(q_view,exception);
1980 if (sync == MagickFalse)
1981 status=MagickFalse;
1982 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1983 {
1984 MagickBooleanType
1985 proceed;
1986
1987#if defined(MAGICKCORE_OPENMP_SUPPORT)
1988 #pragma omp critical (MagickCore_MorphologyImage)
1989#endif
1990 proceed=SetImageProgress(image,MorphologyTag,progress++,image->rows);
1991 if (proceed == MagickFalse)
1992 status=MagickFalse;
1993 }
anthony83ba99b2010-01-24 08:48:15 +00001994 } /* y */
anthony602ab9b2010-01-05 08:06:50 +00001995 result_image->type=image->type;
1996 q_view=DestroyCacheView(q_view);
1997 p_view=DestroyCacheView(p_view);
cristy150989e2010-02-01 14:59:39 +00001998 return(status ? (unsigned long) changed : 0);
anthony602ab9b2010-01-05 08:06:50 +00001999}
2000
anthony4fd27e22010-02-07 08:17:18 +00002001
2002MagickExport Image *MorphologyImage(const Image *image, const MorphologyMethod
anthony930be612010-02-08 04:26:15 +00002003 method, const long iterations,const KernelInfo *kernel, ExceptionInfo
2004 *exception)
cristy2be15382010-01-21 02:38:03 +00002005{
2006 Image
2007 *morphology_image;
2008
2009 morphology_image=MorphologyImageChannel(image,DefaultChannels,method,
2010 iterations,kernel,exception);
2011 return(morphology_image);
2012}
2013
anthony4fd27e22010-02-07 08:17:18 +00002014
cristyef656912010-03-05 19:54:59 +00002015MagickExport Image *MorphologyImageChannel(const Image *image,
2016 const ChannelType channel,const MorphologyMethod method,
2017 const long iterations,const KernelInfo *kernel,ExceptionInfo *exception)
anthony602ab9b2010-01-05 08:06:50 +00002018{
anthony602ab9b2010-01-05 08:06:50 +00002019 Image
2020 *new_image,
anthony1b2bc0a2010-05-12 05:25:22 +00002021 *old_image,
anthony4fd27e22010-02-07 08:17:18 +00002022 *grad_image;
anthony602ab9b2010-01-05 08:06:50 +00002023
anthony4fd27e22010-02-07 08:17:18 +00002024 KernelInfo
anthony1b2bc0a2010-05-12 05:25:22 +00002025 *curr_kernel,
2026 *this_kernel;
anthony4fd27e22010-02-07 08:17:18 +00002027
2028 MorphologyMethod
2029 curr_method;
2030
anthony1b2bc0a2010-05-12 05:25:22 +00002031 unsigned long
2032 count,
2033 limit,
2034 changed,
2035 total_changed,
2036 kernel_number;
2037
anthony602ab9b2010-01-05 08:06:50 +00002038 assert(image != (Image *) NULL);
2039 assert(image->signature == MagickSignature);
anthony4fd27e22010-02-07 08:17:18 +00002040 assert(kernel != (KernelInfo *) NULL);
2041 assert(kernel->signature == MagickSignature);
anthony602ab9b2010-01-05 08:06:50 +00002042 assert(exception != (ExceptionInfo *) NULL);
2043 assert(exception->signature == MagickSignature);
2044
anthony602ab9b2010-01-05 08:06:50 +00002045 if ( iterations == 0 )
2046 return((Image *)NULL); /* null operation - nothing to do! */
2047
2048 /* kernel must be valid at this point
2049 * (except maybe for posible future morphology methods like "Prune"
2050 */
cristy2be15382010-01-21 02:38:03 +00002051 assert(kernel != (KernelInfo *)NULL);
anthony602ab9b2010-01-05 08:06:50 +00002052
anthony1b2bc0a2010-05-12 05:25:22 +00002053 new_image = (Image *) NULL; /* for cleanup */
2054 old_image = (Image *) NULL;
2055 grad_image = (Image *) NULL;
2056 curr_kernel = (KernelInfo *) NULL;
2057 count = 0; /* interation count */
2058 changed = 1; /* was last run succesfull */
anthony930be612010-02-08 04:26:15 +00002059 curr_kernel = (KernelInfo *)kernel; /* allow kernel and method */
2060 curr_method = method; /* to be changed as nessary */
anthony4fd27e22010-02-07 08:17:18 +00002061
cristy150989e2010-02-01 14:59:39 +00002062 limit = (unsigned long) iterations;
anthony602ab9b2010-01-05 08:06:50 +00002063 if ( iterations < 0 )
2064 limit = image->columns > image->rows ? image->columns : image->rows;
2065
anthony4fd27e22010-02-07 08:17:18 +00002066 /* Third-level morphology methods */
2067 switch( curr_method ) {
2068 case EdgeMorphology:
2069 grad_image = MorphologyImageChannel(image, channel,
2070 DilateMorphology, iterations, curr_kernel, exception);
anthony1b2bc0a2010-05-12 05:25:22 +00002071 if ( grad_image == (Image *) NULL )
2072 goto error_cleanup;
anthony4fd27e22010-02-07 08:17:18 +00002073 /* FALL-THRU */
2074 case EdgeInMorphology:
2075 curr_method = ErodeMorphology;
anthony602ab9b2010-01-05 08:06:50 +00002076 break;
anthony4fd27e22010-02-07 08:17:18 +00002077 case EdgeOutMorphology:
2078 curr_method = DilateMorphology;
anthony602ab9b2010-01-05 08:06:50 +00002079 break;
anthony4fd27e22010-02-07 08:17:18 +00002080 case TopHatMorphology:
2081 curr_method = OpenMorphology;
2082 break;
2083 case BottomHatMorphology:
2084 curr_method = CloseMorphology;
2085 break;
2086 default:
anthony930be612010-02-08 04:26:15 +00002087 break; /* not a third-level method */
anthony4fd27e22010-02-07 08:17:18 +00002088 }
2089
2090 /* Second-level morphology methods */
2091 switch( curr_method ) {
anthony930be612010-02-08 04:26:15 +00002092 case OpenMorphology:
2093 /* Open is a Erode then a Dilate without reflection */
anthony4fd27e22010-02-07 08:17:18 +00002094 new_image = MorphologyImageChannel(image, channel,
2095 ErodeMorphology, iterations, curr_kernel, exception);
anthony602ab9b2010-01-05 08:06:50 +00002096 if (new_image == (Image *) NULL)
anthony1b2bc0a2010-05-12 05:25:22 +00002097 goto error_cleanup;
anthony4fd27e22010-02-07 08:17:18 +00002098 curr_method = DilateMorphology;
2099 break;
anthony602ab9b2010-01-05 08:06:50 +00002100 case OpenIntensityMorphology:
anthony4fd27e22010-02-07 08:17:18 +00002101 new_image = MorphologyImageChannel(image, channel,
2102 ErodeIntensityMorphology, iterations, curr_kernel, exception);
anthony602ab9b2010-01-05 08:06:50 +00002103 if (new_image == (Image *) NULL)
anthony1b2bc0a2010-05-12 05:25:22 +00002104 goto error_cleanup;
anthony4fd27e22010-02-07 08:17:18 +00002105 curr_method = DilateIntensityMorphology;
2106 break;
anthony930be612010-02-08 04:26:15 +00002107
2108 case CloseMorphology:
2109 /* Close is a Dilate then Erode using reflected kernel */
2110 /* A reflected kernel is needed for a Close */
2111 if ( curr_kernel == kernel )
2112 curr_kernel = CloneKernelInfo(kernel);
anthony1b2bc0a2010-05-12 05:25:22 +00002113 if (curr_kernel == (KernelInfo *) NULL)
2114 goto error_cleanup;
anthony930be612010-02-08 04:26:15 +00002115 RotateKernelInfo(curr_kernel,180);
2116 new_image = MorphologyImageChannel(image, channel,
2117 DilateMorphology, iterations, curr_kernel, exception);
2118 if (new_image == (Image *) NULL)
anthony1b2bc0a2010-05-12 05:25:22 +00002119 goto error_cleanup;
anthony930be612010-02-08 04:26:15 +00002120 curr_method = ErodeMorphology;
2121 break;
anthony4fd27e22010-02-07 08:17:18 +00002122 case CloseIntensityMorphology:
anthony930be612010-02-08 04:26:15 +00002123 /* A reflected kernel is needed for a Close */
2124 if ( curr_kernel == kernel )
2125 curr_kernel = CloneKernelInfo(kernel);
anthony1b2bc0a2010-05-12 05:25:22 +00002126 if (curr_kernel == (KernelInfo *) NULL)
2127 goto error_cleanup;
anthony4fd27e22010-02-07 08:17:18 +00002128 RotateKernelInfo(curr_kernel,180);
2129 new_image = MorphologyImageChannel(image, channel,
2130 DilateIntensityMorphology, iterations, curr_kernel, exception);
2131 if (new_image == (Image *) NULL)
anthony1b2bc0a2010-05-12 05:25:22 +00002132 goto error_cleanup;
anthony4fd27e22010-02-07 08:17:18 +00002133 curr_method = ErodeIntensityMorphology;
anthony602ab9b2010-01-05 08:06:50 +00002134 break;
2135
anthony930be612010-02-08 04:26:15 +00002136 case CorrelateMorphology:
2137 /* A Correlation is actually a Convolution with a reflected kernel.
2138 ** However a Convolution is a weighted sum with a reflected kernel.
2139 ** It may seem stange to convert a Correlation into a Convolution
2140 ** as the Correleation is the simplier method, but Convolution is
2141 ** much more commonly used, and it makes sense to implement it directly
2142 ** so as to avoid the need to duplicate the kernel when it is not
2143 ** required (which is typically the default).
2144 */
2145 if ( curr_kernel == kernel )
2146 curr_kernel = CloneKernelInfo(kernel);
anthony1b2bc0a2010-05-12 05:25:22 +00002147 if (curr_kernel == (KernelInfo *) NULL)
2148 goto error_cleanup;
anthony930be612010-02-08 04:26:15 +00002149 RotateKernelInfo(curr_kernel,180);
2150 curr_method = ConvolveMorphology;
2151 /* FALL-THRU into Correlate (weigthed sum without reflection) */
2152
anthonyc94cdb02010-01-06 08:15:29 +00002153 case ConvolveMorphology:
anthony1b2bc0a2010-05-12 05:25:22 +00002154 { /* Scale or Normalize kernel, according to user wishes
2155 ** before using it for the Convolve/Correlate method.
2156 **
2157 ** FUTURE: provide some way for internal functions to disable
2158 ** user bias and scaling effects.
2159 */
2160 const char
2161 *artifact = GetImageArtifact(image,"convolve:scale");
2162 if ( artifact != (char *)NULL ) {
2163 GeometryFlags
2164 flags;
2165 GeometryInfo
2166 args;
anthony999bb2c2010-02-18 12:38:01 +00002167
anthony1b2bc0a2010-05-12 05:25:22 +00002168 if ( curr_kernel == kernel )
2169 curr_kernel = CloneKernelInfo(kernel);
2170 if (curr_kernel == (KernelInfo *) NULL)
2171 goto error_cleanup;
2172 args.rho = 1.0;
2173 flags = (GeometryFlags) ParseGeometry(artifact, &args);
2174 ScaleKernelInfo(curr_kernel, args.rho, flags);
2175 }
anthony4fd27e22010-02-07 08:17:18 +00002176 }
anthony930be612010-02-08 04:26:15 +00002177 /* FALL-THRU to do the first, and typically the only iteration */
anthony4fd27e22010-02-07 08:17:18 +00002178
anthony602ab9b2010-01-05 08:06:50 +00002179 default:
anthony930be612010-02-08 04:26:15 +00002180 /* Do a single iteration using the Low-Level Morphology method!
2181 ** This ensures a "new_image" has been generated, but allows us to skip
2182 ** the creation of 'old_image' if no more iterations are needed.
2183 **
2184 ** The "curr_method" should also be set to a low-level method that is
anthony7a01dcf2010-05-11 12:25:52 +00002185 ** understood by the MorphologyPrimative() internal function.
anthony602ab9b2010-01-05 08:06:50 +00002186 */
2187 new_image=CloneImage(image,0,0,MagickTrue,exception);
2188 if (new_image == (Image *) NULL)
anthony1b2bc0a2010-05-12 05:25:22 +00002189 goto error_cleanup;
anthony602ab9b2010-01-05 08:06:50 +00002190 if (SetImageStorageClass(new_image,DirectClass) == MagickFalse)
2191 {
2192 InheritException(exception,&new_image->exception);
anthony1b2bc0a2010-05-12 05:25:22 +00002193 goto exit_cleanup;
anthony602ab9b2010-01-05 08:06:50 +00002194 }
anthony1b2bc0a2010-05-12 05:25:22 +00002195 count++; /* iteration count */
anthony7a01dcf2010-05-11 12:25:52 +00002196 changed = MorphologyPrimative(image,new_image,curr_method,channel,
2197 curr_kernel, exception);
anthony602ab9b2010-01-05 08:06:50 +00002198 if ( GetImageArtifact(image,"verbose") != (const char *) NULL )
anthony1b2bc0a2010-05-12 05:25:22 +00002199 fprintf(stderr, "Morphology %s:%lu.%lu => Changed %lu\n",
anthony4fd27e22010-02-07 08:17:18 +00002200 MagickOptionToMnemonic(MagickMorphologyOptions, curr_method),
anthony1b2bc0a2010-05-12 05:25:22 +00002201 count, 0L, changed);
anthony930be612010-02-08 04:26:15 +00002202 break;
anthony602ab9b2010-01-05 08:06:50 +00002203 }
anthony1b2bc0a2010-05-12 05:25:22 +00002204 /* At this point
2205 * + "curr_method" should be set to a low-level morphology method
2206 * + "count=1" if the first iteration of the first kernel has been done.
2207 * + "new_image" is defined and contains the current resulting image
2208 */
anthony602ab9b2010-01-05 08:06:50 +00002209
anthony1b2bc0a2010-05-12 05:25:22 +00002210 /* The Hit-And-Miss Morphology is not 'iterated' against the resulting
2211 ** image from the previous morphology step. It must always be applied
2212 ** to the original image, with the results collected into a union (maximimum
2213 ** or lighten) of all the results. Multiple kernels may be applied but
2214 ** an iteration of the morphology does nothing, so is ignored.
anthony7a01dcf2010-05-11 12:25:52 +00002215 **
anthony1b2bc0a2010-05-12 05:25:22 +00002216 ** The first kernel is guranteed to have been done, so lets continue the
2217 ** process, with the other kernels in the kernel list.
anthony7a01dcf2010-05-11 12:25:52 +00002218 */
anthony1b2bc0a2010-05-12 05:25:22 +00002219 if ( method == HitAndMissMorphology )
anthony7a01dcf2010-05-11 12:25:52 +00002220 {
anthony1b2bc0a2010-05-12 05:25:22 +00002221 if ( curr_kernel->next != (KernelInfo *) NULL ) {
2222 /* create a second working image */
2223 old_image = CloneImage(image,0,0,MagickTrue,exception);
2224 if (old_image == (Image *) NULL)
2225 goto error_cleanup;
2226 if (SetImageStorageClass(old_image,DirectClass) == MagickFalse)
2227 {
2228 InheritException(exception,&old_image->exception);
2229 goto exit_cleanup;
2230 }
anthony7a01dcf2010-05-11 12:25:52 +00002231
anthony1b2bc0a2010-05-12 05:25:22 +00002232 /* loop through rest of the kernels */
2233 this_kernel=curr_kernel->next;
2234 kernel_number=1;
2235 while( this_kernel != (KernelInfo *) NULL )
2236 {
2237 changed = MorphologyPrimative(image,old_image,curr_method,channel,
2238 this_kernel,exception);
2239 (void) CompositeImageChannel(new_image,
2240 (ChannelType) (channel & ~SyncChannels), LightenCompositeOp,
2241 old_image, 0, 0);
2242 if ( GetImageArtifact(image,"verbose") != (const char *) NULL )
2243 fprintf(stderr, "Morphology %s:%lu.%lu => Changed %lu\n",
2244 MagickOptionToMnemonic(MagickMorphologyOptions, curr_method),
2245 count, kernel_number, changed);
2246 this_kernel = this_kernel->next;
2247 kernel_number++;
2248 }
2249 old_image=DestroyImage(old_image);
2250 }
2251 goto exit_cleanup;
anthony7a01dcf2010-05-11 12:25:52 +00002252 }
2253
anthony1b2bc0a2010-05-12 05:25:22 +00002254 /* Repeat the low-level morphology over all kernels
2255 until iteration count limit or no change from any kernel is found */
2256 if ( ( count < limit && changed > 0 ) ||
2257 curr_kernel->next != (KernelInfo *) NULL ) {
anthony930be612010-02-08 04:26:15 +00002258
anthony1b2bc0a2010-05-12 05:25:22 +00002259 /* create a second working image */
2260 old_image = CloneImage(image,0,0,MagickTrue,exception);
anthony602ab9b2010-01-05 08:06:50 +00002261 if (old_image == (Image *) NULL)
anthony1b2bc0a2010-05-12 05:25:22 +00002262 goto error_cleanup;
anthony602ab9b2010-01-05 08:06:50 +00002263 if (SetImageStorageClass(old_image,DirectClass) == MagickFalse)
2264 {
2265 InheritException(exception,&old_image->exception);
anthony1b2bc0a2010-05-12 05:25:22 +00002266 goto exit_cleanup;
anthony602ab9b2010-01-05 08:06:50 +00002267 }
anthony1b2bc0a2010-05-12 05:25:22 +00002268
2269 /* reset variables for the first/next iteration, or next kernel) */
2270 kernel_number = 0;
2271 this_kernel = curr_kernel;
2272 total_changed = count != 0 ? changed : 0;
2273 if ( count != 0 && this_kernel != (KernelInfo *) NULL ) {
2274 count = 0; /* first iteration is not yet finished! */
2275 this_kernel = curr_kernel->next;
2276 kernel_number = 1;
2277 total_changed = changed;
2278 }
2279
2280 while ( count < limit ) {
2281 count++;
2282 while ( this_kernel != (KernelInfo *) NULL ) {
anthony602ab9b2010-01-05 08:06:50 +00002283 Image *tmp = old_image;
2284 old_image = new_image;
2285 new_image = tmp;
anthony7a01dcf2010-05-11 12:25:52 +00002286 changed = MorphologyPrimative(old_image,new_image,curr_method,channel,
anthony1b2bc0a2010-05-12 05:25:22 +00002287 this_kernel,exception);
anthony602ab9b2010-01-05 08:06:50 +00002288 if ( GetImageArtifact(image,"verbose") != (const char *) NULL )
anthony1b2bc0a2010-05-12 05:25:22 +00002289 fprintf(stderr, "Morphology %s:%lu.%lu => Changed %lu\n",
anthony4fd27e22010-02-07 08:17:18 +00002290 MagickOptionToMnemonic(MagickMorphologyOptions, curr_method),
anthony1b2bc0a2010-05-12 05:25:22 +00002291 count, kernel_number, changed);
2292 total_changed += changed;
2293 this_kernel = this_kernel->next;
2294 kernel_number++;
anthony602ab9b2010-01-05 08:06:50 +00002295 }
anthony3dd0f622010-05-13 12:57:32 +00002296 if ( kernel_number > 1 )
2297 if ( GetImageArtifact(image,"verbose") != (const char *) NULL )
2298 fprintf(stderr, "Morphology %s:%lu ===> Total Changed %lu\n",
2299 MagickOptionToMnemonic(MagickMorphologyOptions, curr_method),
2300 count, total_changed);
anthony1b2bc0a2010-05-12 05:25:22 +00002301 if ( total_changed == 0 )
2302 break; /* no changes after processing all kernels - ABORT */
2303 /* prepare for next loop */
2304 total_changed = 0;
2305 kernel_number = 0;
2306 this_kernel = curr_kernel;
2307 }
cristy150989e2010-02-01 14:59:39 +00002308 old_image=DestroyImage(old_image);
anthony602ab9b2010-01-05 08:06:50 +00002309 }
anthony930be612010-02-08 04:26:15 +00002310
anthony3dd0f622010-05-13 12:57:32 +00002311 /* finished with kernel - destroy any copy that was made */
anthony4fd27e22010-02-07 08:17:18 +00002312 if ( curr_kernel != kernel )
2313 curr_kernel=DestroyKernelInfo(curr_kernel);
2314
anthony7d10d742010-05-06 07:05:29 +00002315 /* Third-level Subtractive methods post-processing
2316 **
2317 ** The removal of any 'Sync' channel flag in the Image Compositon below
2318 ** ensures the compose method is applied in a purely mathematical way, only
2319 ** the selected channels, without any normal 'alpha blending' normally
2320 ** associated with the compose method.
2321 **
2322 ** Note "method" here is the 'original' morphological method, and not the
2323 ** 'current' morphological method used above to generate "new_image".
2324 */
anthony4fd27e22010-02-07 08:17:18 +00002325 switch( method ) {
2326 case EdgeOutMorphology:
2327 case EdgeInMorphology:
2328 case TopHatMorphology:
2329 case BottomHatMorphology:
anthony930be612010-02-08 04:26:15 +00002330 /* Get Difference relative to the original image */
cristyaeb2cbc2010-05-07 13:28:58 +00002331 (void) CompositeImageChannel(new_image, (ChannelType) (channel & ~SyncChannels),
anthony7d10d742010-05-06 07:05:29 +00002332 DifferenceCompositeOp, image, 0, 0);
anthony4fd27e22010-02-07 08:17:18 +00002333 break;
anthony7d10d742010-05-06 07:05:29 +00002334 case EdgeMorphology:
2335 /* Difference the Eroded image from the saved Dilated image */
cristyaeb2cbc2010-05-07 13:28:58 +00002336 (void) CompositeImageChannel(new_image, (ChannelType) (channel & ~SyncChannels),
anthony7d10d742010-05-06 07:05:29 +00002337 DifferenceCompositeOp, grad_image, 0, 0);
anthony4fd27e22010-02-07 08:17:18 +00002338 grad_image=DestroyImage(grad_image);
2339 break;
2340 default:
2341 break;
2342 }
anthony602ab9b2010-01-05 08:06:50 +00002343
2344 return(new_image);
anthony1b2bc0a2010-05-12 05:25:22 +00002345
2346 /* Yes goto's are bad, but in this case it makes cleanup lot more efficient */
2347error_cleanup:
2348 if ( new_image != (Image *) NULL )
2349 DestroyImage(new_image);
2350exit_cleanup:
2351 if ( old_image != (Image *) NULL )
2352 DestroyImage(old_image);
2353 if ( curr_kernel != (KernelInfo *) NULL && curr_kernel != kernel )
2354 curr_kernel=DestroyKernelInfo(curr_kernel);
2355 return(new_image);
anthony602ab9b2010-01-05 08:06:50 +00002356}
anthony83ba99b2010-01-24 08:48:15 +00002357
2358/*
2359%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2360% %
2361% %
2362% %
anthony4fd27e22010-02-07 08:17:18 +00002363+ R o t a t e K e r n e l I n f o %
anthony83ba99b2010-01-24 08:48:15 +00002364% %
2365% %
2366% %
2367%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2368%
anthony4fd27e22010-02-07 08:17:18 +00002369% RotateKernelInfo() rotates the kernel by the angle given. Currently it is
anthony83ba99b2010-01-24 08:48:15 +00002370% restricted to 90 degree angles, but this may be improved in the future.
2371%
anthony4fd27e22010-02-07 08:17:18 +00002372% The format of the RotateKernelInfo method is:
anthony83ba99b2010-01-24 08:48:15 +00002373%
anthony4fd27e22010-02-07 08:17:18 +00002374% void RotateKernelInfo(KernelInfo *kernel, double angle)
anthony83ba99b2010-01-24 08:48:15 +00002375%
2376% A description of each parameter follows:
2377%
2378% o kernel: the Morphology/Convolution kernel
2379%
2380% o angle: angle to rotate in degrees
2381%
anthonyc4c86e02010-01-27 09:30:32 +00002382% This function is only internel to this module, as it is not finalized,
2383% especially with regard to non-orthogonal angles, and rotation of larger
2384% 2D kernels.
anthony83ba99b2010-01-24 08:48:15 +00002385*/
anthony4fd27e22010-02-07 08:17:18 +00002386static void RotateKernelInfo(KernelInfo *kernel, double angle)
anthony83ba99b2010-01-24 08:48:15 +00002387{
anthony1b2bc0a2010-05-12 05:25:22 +00002388 /* angle the lower kernels first */
2389 if ( kernel->next != (KernelInfo *) NULL)
2390 RotateKernelInfo(kernel->next, angle);
2391
anthony83ba99b2010-01-24 08:48:15 +00002392 /* WARNING: Currently assumes the kernel (rightly) is horizontally symetrical
2393 **
2394 ** TODO: expand beyond simple 90 degree rotates, flips and flops
2395 */
2396
2397 /* Modulus the angle */
2398 angle = fmod(angle, 360.0);
2399 if ( angle < 0 )
2400 angle += 360.0;
2401
anthony3c10fc82010-05-13 02:40:51 +00002402 if ( 337.5 < angle || angle <= 22.5 )
anthony83ba99b2010-01-24 08:48:15 +00002403 return; /* no change! - At least at this time */
2404
anthony3dd0f622010-05-13 12:57:32 +00002405 /* Handle special cases */
anthony83ba99b2010-01-24 08:48:15 +00002406 switch (kernel->type) {
2407 /* These built-in kernels are cylindrical kernels, rotating is useless */
2408 case GaussianKernel:
anthony83ba99b2010-01-24 08:48:15 +00002409 case DOGKernel:
2410 case DiskKernel:
anthony3dd0f622010-05-13 12:57:32 +00002411 case PeaksKernel:
2412 case LaplacianKernel:
anthony83ba99b2010-01-24 08:48:15 +00002413 case ChebyshevKernel:
2414 case ManhattenKernel:
2415 case EuclideanKernel:
2416 return;
2417
2418 /* These may be rotatable at non-90 angles in the future */
2419 /* but simply rotating them in multiples of 90 degrees is useless */
2420 case SquareKernel:
2421 case DiamondKernel:
2422 case PlusKernel:
anthony3dd0f622010-05-13 12:57:32 +00002423 case CrossKernel:
anthony83ba99b2010-01-24 08:48:15 +00002424 return;
2425
2426 /* These only allows a +/-90 degree rotation (by transpose) */
2427 /* A 180 degree rotation is useless */
2428 case BlurKernel:
2429 case RectangleKernel:
2430 if ( 135.0 < angle && angle <= 225.0 )
2431 return;
2432 if ( 225.0 < angle && angle <= 315.0 )
2433 angle -= 180;
2434 break;
2435
anthony3dd0f622010-05-13 12:57:32 +00002436 default:
anthony83ba99b2010-01-24 08:48:15 +00002437 break;
2438 }
anthony3c10fc82010-05-13 02:40:51 +00002439 /* Attempt rotations by 45 degrees */
2440 if ( 22.5 < fmod(angle,90.0) && fmod(angle,90.0) <= 67.5 )
2441 {
2442 if ( kernel->width == 3 && kernel->height == 3 )
2443 { /* Rotate a 3x3 square by 45 degree angle */
2444 MagickRealType t = kernel->values[0];
2445 kernel->values[0] = kernel->values[1];
2446 kernel->values[1] = kernel->values[2];
2447 kernel->values[2] = kernel->values[5];
2448 kernel->values[5] = kernel->values[8];
2449 kernel->values[8] = kernel->values[7];
2450 kernel->values[7] = kernel->values[6];
2451 kernel->values[6] = kernel->values[3];
2452 kernel->values[3] = t;
2453 angle = fmod(angle+45.0, 360.0);
2454 }
2455 else
2456 perror("Unable to rotate non-3x3 kernel by 45 degrees");
2457 }
2458 if ( 45.0 < fmod(angle, 180.0) && fmod(angle,180.0) <= 135.0 )
2459 {
2460 if ( kernel->width == 1 || kernel->height == 1 )
2461 { /* Do a transpose of the image, which results in a 90
2462 ** degree rotation of a 1 dimentional kernel
2463 */
2464 long
2465 t;
2466 t = (long) kernel->width;
2467 kernel->width = kernel->height;
2468 kernel->height = (unsigned long) t;
2469 t = kernel->x;
2470 kernel->x = kernel->y;
2471 kernel->y = t;
2472 if ( kernel->width == 1 )
2473 angle = fmod(angle+270.0, 360.0); /* angle -90 degrees */
2474 else
2475 angle = fmod(angle+90.0, 360.0); /* angle +90 degrees */
2476 }
2477 else if ( kernel->width == kernel->height )
2478 { /* Rotate a square array of values by 90 degrees */
2479 register unsigned long
2480 i,j,x,y;
2481 register MagickRealType
2482 *k,t;
2483 k=kernel->values;
2484 for( i=0, x=kernel->width-1; i<=x; i++, x--)
2485 for( j=0, y=kernel->height-1; j<y; j++, y--)
2486 { t = k[i+j*kernel->width];
2487 k[i+j*kernel->width] = k[y+i*kernel->width];
2488 k[y+i*kernel->width] = k[x+y*kernel->width];
2489 k[x+y*kernel->width] = k[j+x*kernel->width];
2490 k[j+x*kernel->width] = t;
2491 }
2492 angle = fmod(angle+90.0, 360.0); /* angle +90 degrees */
2493 }
2494 else
2495 perror("Unable to rotate a non-square, non-linear kernel 90 degrees");
2496 }
anthony83ba99b2010-01-24 08:48:15 +00002497 if ( 135.0 < angle && angle <= 225.0 )
2498 {
2499 /* For a 180 degree rotation - also know as a reflection */
2500 /* This is actually a very very common operation! */
2501 /* Basically all that is needed is a reversal of the kernel data! */
2502 unsigned long
2503 i,j;
2504 register double
2505 *k,t;
2506
2507 k=kernel->values;
2508 for ( i=0, j=kernel->width*kernel->height-1; i<j; i++, j--)
2509 t=k[i], k[i]=k[j], k[j]=t;
2510
anthony930be612010-02-08 04:26:15 +00002511 kernel->x = (long) kernel->width - kernel->x - 1;
2512 kernel->y = (long) kernel->height - kernel->y - 1;
anthony3c10fc82010-05-13 02:40:51 +00002513 angle = fmod(angle+180.0, 360.0); /* angle+180 degrees */
anthony83ba99b2010-01-24 08:48:15 +00002514 }
anthony3c10fc82010-05-13 02:40:51 +00002515 /* At this point angle should at least between -45 (315) and +45 degrees
anthony83ba99b2010-01-24 08:48:15 +00002516 * In the future some form of non-orthogonal angled rotates could be
2517 * performed here, posibily with a linear kernel restriction.
2518 */
2519
2520#if 0
anthony3c10fc82010-05-13 02:40:51 +00002521 { /* Do a Flop by reversing each row.
anthony83ba99b2010-01-24 08:48:15 +00002522 */
2523 unsigned long
2524 y;
cristy150989e2010-02-01 14:59:39 +00002525 register long
anthony83ba99b2010-01-24 08:48:15 +00002526 x,r;
2527 register double
2528 *k,t;
2529
2530 for ( y=0, k=kernel->values; y < kernel->height; y++, k+=kernel->width)
2531 for ( x=0, r=kernel->width-1; x<kernel->width/2; x++, r--)
2532 t=k[x], k[x]=k[r], k[r]=t;
2533
cristyc99304f2010-02-01 15:26:27 +00002534 kernel->x = kernel->width - kernel->x - 1;
anthony83ba99b2010-01-24 08:48:15 +00002535 angle = fmod(angle+180.0, 360.0);
2536 }
2537#endif
2538 return;
2539}
2540
2541/*
2542%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2543% %
2544% %
2545% %
cristy6771f1e2010-03-05 19:43:39 +00002546% S c a l e K e r n e l I n f o %
anthonycc6c8362010-01-25 04:14:01 +00002547% %
2548% %
2549% %
2550%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2551%
anthony1b2bc0a2010-05-12 05:25:22 +00002552% ScaleKernelInfo() scales the given kernel list by the given amount, with or
2553% without normalization of the sum of the kernel values (as per given flags).
anthonycc6c8362010-01-25 04:14:01 +00002554%
anthony999bb2c2010-02-18 12:38:01 +00002555% By default (no flags given) the values within the kernel is scaled
anthony1b2bc0a2010-05-12 05:25:22 +00002556% directly using given scaling factor without change.
anthonycc6c8362010-01-25 04:14:01 +00002557%
anthony1b2bc0a2010-05-12 05:25:22 +00002558% If any 'normalize_flags' are given the kernel will first be normalized and
2559% then further scaled by the scaling factor value given. A 'PercentValue'
2560% flag will cause the given scaling factor to be divided by one hundred
2561% percent.
anthony999bb2c2010-02-18 12:38:01 +00002562%
2563% Kernel normalization ('normalize_flags' given) is designed to ensure that
2564% any use of the kernel scaling factor with 'Convolve' or 'Correlate'
anthony1b2bc0a2010-05-12 05:25:22 +00002565% morphology methods will fall into -1.0 to +1.0 range. Note that for
2566% non-HDRI versions of IM this may cause images to have any negative results
2567% clipped, unless some 'bias' is used.
anthony999bb2c2010-02-18 12:38:01 +00002568%
2569% More specifically. Kernels which only contain positive values (such as a
2570% 'Gaussian' kernel) will be scaled so that those values sum to +1.0,
anthony1b2bc0a2010-05-12 05:25:22 +00002571% ensuring a 0.0 to +1.0 output range for non-HDRI images.
anthony999bb2c2010-02-18 12:38:01 +00002572%
2573% For Kernels that contain some negative values, (such as 'Sharpen' kernels)
2574% the kernel will be scaled by the absolute of the sum of kernel values, so
2575% that it will generally fall within the +/- 1.0 range.
2576%
2577% For kernels whose values sum to zero, (such as 'Laplician' kernels) kernel
2578% will be scaled by just the sum of the postive values, so that its output
2579% range will again fall into the +/- 1.0 range.
2580%
2581% For special kernels designed for locating shapes using 'Correlate', (often
2582% only containing +1 and -1 values, representing foreground/brackground
2583% matching) a special normalization method is provided to scale the positive
2584% values seperatally to those of the negative values, so the kernel will be
2585% forced to become a zero-sum kernel better suited to such searches.
2586%
anthony1b2bc0a2010-05-12 05:25:22 +00002587% WARNING: Correct normalization of the kernel assumes that the '*_range'
anthony999bb2c2010-02-18 12:38:01 +00002588% attributes within the kernel structure have been correctly set during the
2589% kernels creation.
2590%
2591% NOTE: The values used for 'normalize_flags' have been selected specifically
anthony1b2bc0a2010-05-12 05:25:22 +00002592% to match the use of geometry options, so that '!' means NormalizeValue,
2593% '^' means CorrelateNormalizeValue, and '%' means PercentValue. All other
anthony999bb2c2010-02-18 12:38:01 +00002594% GeometryFlags values are ignored.
anthonycc6c8362010-01-25 04:14:01 +00002595%
anthony4fd27e22010-02-07 08:17:18 +00002596% The format of the ScaleKernelInfo method is:
anthonycc6c8362010-01-25 04:14:01 +00002597%
anthony999bb2c2010-02-18 12:38:01 +00002598% void ScaleKernelInfo(KernelInfo *kernel, const double scaling_factor,
2599% const MagickStatusType normalize_flags )
anthonycc6c8362010-01-25 04:14:01 +00002600%
2601% A description of each parameter follows:
2602%
2603% o kernel: the Morphology/Convolution kernel
2604%
anthony999bb2c2010-02-18 12:38:01 +00002605% o scaling_factor:
2606% multiply all values (after normalization) by this factor if not
2607% zero. If the kernel is normalized regardless of any flags.
2608%
2609% o normalize_flags:
2610% GeometryFlags defining normalization method to use.
2611% specifically: NormalizeValue, CorrelateNormalizeValue,
2612% and/or PercentValue
anthonycc6c8362010-01-25 04:14:01 +00002613%
anthonyc4c86e02010-01-27 09:30:32 +00002614% This function is internal to this module only at this time, but can be
2615% exported to other modules if needed.
anthonycc6c8362010-01-25 04:14:01 +00002616*/
cristy6771f1e2010-03-05 19:43:39 +00002617MagickExport void ScaleKernelInfo(KernelInfo *kernel,
2618 const double scaling_factor,const GeometryFlags normalize_flags)
anthonycc6c8362010-01-25 04:14:01 +00002619{
cristy150989e2010-02-01 14:59:39 +00002620 register long
anthonycc6c8362010-01-25 04:14:01 +00002621 i;
2622
anthony999bb2c2010-02-18 12:38:01 +00002623 register double
2624 pos_scale,
2625 neg_scale;
2626
anthony1b2bc0a2010-05-12 05:25:22 +00002627 /* scale the lower kernels first */
2628 if ( kernel->next != (KernelInfo *) NULL)
2629 ScaleKernelInfo(kernel->next, scaling_factor, normalize_flags);
2630
anthony999bb2c2010-02-18 12:38:01 +00002631 pos_scale = 1.0;
2632 if ( (normalize_flags&NormalizeValue) != 0 ) {
2633 /* normalize kernel appropriately */
2634 if ( fabs(kernel->positive_range + kernel->negative_range) > MagickEpsilon )
2635 pos_scale = fabs(kernel->positive_range + kernel->negative_range);
anthonycc6c8362010-01-25 04:14:01 +00002636 else
anthony999bb2c2010-02-18 12:38:01 +00002637 pos_scale = kernel->positive_range; /* special zero-summing kernel */
2638 }
2639 /* force kernel into being a normalized zero-summing kernel */
2640 if ( (normalize_flags&CorrelateNormalizeValue) != 0 ) {
2641 pos_scale = ( fabs(kernel->positive_range) > MagickEpsilon )
2642 ? kernel->positive_range : 1.0;
2643 neg_scale = ( fabs(kernel->negative_range) > MagickEpsilon )
2644 ? -kernel->negative_range : 1.0;
2645 }
2646 else
2647 neg_scale = pos_scale;
2648
2649 /* finialize scaling_factor for positive and negative components */
2650 pos_scale = scaling_factor/pos_scale;
2651 neg_scale = scaling_factor/neg_scale;
2652 if ( (normalize_flags&PercentValue) != 0 ) {
2653 pos_scale /= 100.0;
2654 neg_scale /= 100.0;
anthonycc6c8362010-01-25 04:14:01 +00002655 }
2656
cristy150989e2010-02-01 14:59:39 +00002657 for (i=0; i < (long) (kernel->width*kernel->height); i++)
anthonycc6c8362010-01-25 04:14:01 +00002658 if ( ! IsNan(kernel->values[i]) )
anthony999bb2c2010-02-18 12:38:01 +00002659 kernel->values[i] *= (kernel->values[i] >= 0) ? pos_scale : neg_scale;
anthonycc6c8362010-01-25 04:14:01 +00002660
anthony999bb2c2010-02-18 12:38:01 +00002661 /* convolution output range */
2662 kernel->positive_range *= pos_scale;
2663 kernel->negative_range *= neg_scale;
2664 /* maximum and minimum values in kernel */
2665 kernel->maximum *= (kernel->maximum >= 0.0) ? pos_scale : neg_scale;
2666 kernel->minimum *= (kernel->minimum >= 0.0) ? pos_scale : neg_scale;
2667
2668 /* swap kernel settings if user scaling factor is negative */
2669 if ( scaling_factor < MagickEpsilon ) {
2670 double t;
2671 t = kernel->positive_range;
2672 kernel->positive_range = kernel->negative_range;
2673 kernel->negative_range = t;
2674 t = kernel->maximum;
2675 kernel->maximum = kernel->minimum;
2676 kernel->minimum = 1;
2677 }
anthonycc6c8362010-01-25 04:14:01 +00002678
2679 return;
2680}
2681
2682/*
2683%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2684% %
2685% %
2686% %
anthony4fd27e22010-02-07 08:17:18 +00002687+ S h o w K e r n e l I n f o %
anthony83ba99b2010-01-24 08:48:15 +00002688% %
2689% %
2690% %
2691%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2692%
anthony4fd27e22010-02-07 08:17:18 +00002693% ShowKernelInfo() outputs the details of the given kernel defination to
2694% standard error, generally due to a users 'showkernel' option request.
anthony83ba99b2010-01-24 08:48:15 +00002695%
2696% The format of the ShowKernel method is:
2697%
anthony4fd27e22010-02-07 08:17:18 +00002698% void ShowKernelInfo(KernelInfo *kernel)
anthony83ba99b2010-01-24 08:48:15 +00002699%
2700% A description of each parameter follows:
2701%
2702% o kernel: the Morphology/Convolution kernel
2703%
anthonyc4c86e02010-01-27 09:30:32 +00002704% This function is internal to this module only at this time. That may change
2705% in the future.
anthony83ba99b2010-01-24 08:48:15 +00002706*/
anthony4fd27e22010-02-07 08:17:18 +00002707MagickExport void ShowKernelInfo(KernelInfo *kernel)
anthony83ba99b2010-01-24 08:48:15 +00002708{
anthony7a01dcf2010-05-11 12:25:52 +00002709 KernelInfo
2710 *k;
anthony83ba99b2010-01-24 08:48:15 +00002711
anthony7a01dcf2010-05-11 12:25:52 +00002712 long
2713 c, i, u, v;
2714
2715 for (c=0, k=kernel; k != (KernelInfo *) NULL; c++, k=k->next ) {
2716
2717 fprintf(stderr, "Kernel ");
2718 if ( kernel->next != (KernelInfo *) NULL )
2719 fprintf(stderr, " #%ld", c );
2720 fprintf(stderr, " \"%s\" of size %lux%lu%+ld%+ld ",
2721 MagickOptionToMnemonic(MagickKernelOptions, k->type),
2722 kernel->width, k->height,
2723 kernel->x, kernel->y );
2724 fprintf(stderr,
2725 " with values from %.*lg to %.*lg\n",
2726 GetMagickPrecision(), k->minimum,
2727 GetMagickPrecision(), k->maximum);
2728 fprintf(stderr, "Forming convolution output range from %.*lg to %.*lg%s\n",
2729 GetMagickPrecision(), k->negative_range,
2730 GetMagickPrecision(), k->positive_range,
2731 /*kernel->normalized == MagickTrue ? " (normalized)" : */ "" );
2732 for (i=v=0; v < (long) k->height; v++) {
2733 fprintf(stderr,"%2ld:",v);
2734 for (u=0; u < (long) k->width; u++, i++)
2735 if ( IsNan(k->values[i]) )
2736 fprintf(stderr," %*s", GetMagickPrecision()+2, "nan");
2737 else
2738 fprintf(stderr," %*.*lg", GetMagickPrecision()+2,
2739 GetMagickPrecision(), k->values[i]);
2740 fprintf(stderr,"\n");
2741 }
anthony83ba99b2010-01-24 08:48:15 +00002742 }
2743}
anthonycc6c8362010-01-25 04:14:01 +00002744
2745/*
2746%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2747% %
2748% %
2749% %
anthony4fd27e22010-02-07 08:17:18 +00002750+ Z e r o K e r n e l N a n s %
anthonycc6c8362010-01-25 04:14:01 +00002751% %
2752% %
2753% %
2754%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2755%
2756% ZeroKernelNans() replaces any special 'nan' value that may be present in
2757% the kernel with a zero value. This is typically done when the kernel will
2758% be used in special hardware (GPU) convolution processors, to simply
2759% matters.
2760%
2761% The format of the ZeroKernelNans method is:
2762%
2763% voidZeroKernelNans (KernelInfo *kernel)
2764%
2765% A description of each parameter follows:
2766%
2767% o kernel: the Morphology/Convolution kernel
2768%
2769% FUTURE: return the information in a string for API usage.
2770*/
anthonyc4c86e02010-01-27 09:30:32 +00002771MagickExport void ZeroKernelNans(KernelInfo *kernel)
anthonycc6c8362010-01-25 04:14:01 +00002772{
cristy150989e2010-02-01 14:59:39 +00002773 register long
anthonycc6c8362010-01-25 04:14:01 +00002774 i;
2775
anthony1b2bc0a2010-05-12 05:25:22 +00002776 /* scale the lower kernels first */
2777 if ( kernel->next != (KernelInfo *) NULL)
2778 ZeroKernelNans(kernel->next);
2779
cristy150989e2010-02-01 14:59:39 +00002780 for (i=0; i < (long) (kernel->width*kernel->height); i++)
anthonycc6c8362010-01-25 04:14:01 +00002781 if ( IsNan(kernel->values[i]) )
2782 kernel->values[i] = 0.0;
2783
2784 return;
2785}