blob: d45c2c8b0b4ca3ef3a17d2c0426f8b7c6e6f5e97 [file] [log] [blame]
cristy701db312009-11-20 03:14:08 +00001/*
2%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3% %
4% %
5% %
6% M M OOO RRRR PPPP H H OOO L OOO GGGG Y Y %
7% MM MM O O R R P P H H O O L O O G Y Y %
8% M M M O O RRRR PPPP HHHHH O O L O O G GGG Y %
9% M M O O R R P H H O O L O O G G Y %
10% M M OOO R R P H H OOO LLLLL OOO GGG Y %
11% %
12% %
13% MagickCore Morphology Methods %
14% %
15% Software Design %
16% Anthony Thyssen %
anthonyc94cdb02010-01-06 08:15:29 +000017% January 2010 %
cristy701db312009-11-20 03:14:08 +000018% %
19% %
cristy16af1cb2009-12-11 21:38:29 +000020% Copyright 1999-2010 ImageMagick Studio LLC, a non-profit organization %
cristy701db312009-11-20 03:14:08 +000021% dedicated to making software imaging solutions freely available. %
22% %
23% You may not use this file except in compliance with the License. You may %
24% obtain a copy of the License at %
25% %
26% http://www.imagemagick.org/script/license.php %
27% %
28% Unless required by applicable law or agreed to in writing, software %
29% distributed under the License is distributed on an "AS IS" BASIS, %
30% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %
31% See the License for the specific language governing permissions and %
32% limitations under the License. %
33% %
34%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
35%
anthony1b2bc0a2010-05-12 05:25:22 +000036% Morpology is the the application of various kernels, of any size and even
anthony602ab9b2010-01-05 08:06:50 +000037% shape, to a image in various ways (typically binary, but not always).
cristy701db312009-11-20 03:14:08 +000038%
anthony602ab9b2010-01-05 08:06:50 +000039% Convolution (weighted sum or average) is just one specific type of
40% morphology. Just one that is very common for image bluring and sharpening
41% effects. Not only 2D Gaussian blurring, but also 2-pass 1D Blurring.
42%
43% This module provides not only a general morphology function, and the ability
44% to apply more advanced or iterative morphologies, but also functions for the
45% generation of many different types of kernel arrays from user supplied
46% arguments. Prehaps even the generation of a kernel from a small image.
cristy701db312009-11-20 03:14:08 +000047*/
48
49/*
50 Include declarations.
51*/
52#include "magick/studio.h"
anthony602ab9b2010-01-05 08:06:50 +000053#include "magick/artifact.h"
cristy701db312009-11-20 03:14:08 +000054#include "magick/cache-view.h"
55#include "magick/color-private.h"
56#include "magick/enhance.h"
57#include "magick/exception.h"
58#include "magick/exception-private.h"
anthony602ab9b2010-01-05 08:06:50 +000059#include "magick/gem.h"
cristy701db312009-11-20 03:14:08 +000060#include "magick/hashmap.h"
61#include "magick/image.h"
cristybba804b2010-01-05 15:39:59 +000062#include "magick/image-private.h"
cristy701db312009-11-20 03:14:08 +000063#include "magick/list.h"
anthony29188a82010-01-22 10:12:34 +000064#include "magick/magick.h"
cristy701db312009-11-20 03:14:08 +000065#include "magick/memory_.h"
66#include "magick/monitor-private.h"
67#include "magick/morphology.h"
anthony602ab9b2010-01-05 08:06:50 +000068#include "magick/option.h"
cristy701db312009-11-20 03:14:08 +000069#include "magick/pixel-private.h"
70#include "magick/prepress.h"
71#include "magick/quantize.h"
72#include "magick/registry.h"
73#include "magick/semaphore.h"
74#include "magick/splay-tree.h"
75#include "magick/statistic.h"
76#include "magick/string_.h"
anthony602ab9b2010-01-05 08:06:50 +000077#include "magick/string-private.h"
78#include "magick/token.h"
cristya29d45f2010-03-05 21:14:54 +000079
anthony602ab9b2010-01-05 08:06:50 +000080/*
cristya29d45f2010-03-05 21:14:54 +000081 The following test is for special floating point numbers of value NaN (not
82 a number), that may be used within a Kernel Definition. NaN's are defined
83 as part of the IEEE standard for floating point number representation.
84
anthony1b2bc0a2010-05-12 05:25:22 +000085 These are used a Kernel value of NaN means that that kernel position is not
cristya29d45f2010-03-05 21:14:54 +000086 part of the normal convolution or morphology process, and thus allowing the
87 use of 'shaped' kernels.
88
89 Special properities two NaN's are never equal, even if they are from the
90 same variable That is the IsNaN() macro is only true if the value is NaN.
91*/
anthony602ab9b2010-01-05 08:06:50 +000092#define IsNan(a) ((a)!=(a))
93
anthony29188a82010-01-22 10:12:34 +000094/*
cristya29d45f2010-03-05 21:14:54 +000095 Other global definitions used by module.
96*/
anthony29188a82010-01-22 10:12:34 +000097static inline double MagickMin(const double x,const double y)
98{
99 return( x < y ? x : y);
100}
101static inline double MagickMax(const double x,const double y)
102{
103 return( x > y ? x : y);
104}
105#define Minimize(assign,value) assign=MagickMin(assign,value)
106#define Maximize(assign,value) assign=MagickMax(assign,value)
107
anthonyc4c86e02010-01-27 09:30:32 +0000108/* Currently these are only internal to this module */
109static void
anthony3c10fc82010-05-13 02:40:51 +0000110 ExpandKernelInfo(KernelInfo *, double),
cristyef656912010-03-05 19:54:59 +0000111 RotateKernelInfo(KernelInfo *, double);
anthony602ab9b2010-01-05 08:06:50 +0000112
anthony3dd0f622010-05-13 12:57:32 +0000113
114/* Quick function to find last kernel in a kernel list */
115static inline KernelInfo *LastKernelInfo(KernelInfo *kernel)
116{
117 while (kernel->next != (KernelInfo *) NULL)
118 kernel = kernel->next;
119 return(kernel);
120}
121
122
anthony602ab9b2010-01-05 08:06:50 +0000123/*
124%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
125% %
126% %
127% %
anthony83ba99b2010-01-24 08:48:15 +0000128% A c q u i r e K e r n e l I n f o %
anthony602ab9b2010-01-05 08:06:50 +0000129% %
130% %
131% %
132%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
133%
cristy2be15382010-01-21 02:38:03 +0000134% AcquireKernelInfo() takes the given string (generally supplied by the
anthony602ab9b2010-01-05 08:06:50 +0000135% user) and converts it into a Morphology/Convolution Kernel. This allows
136% users to specify a kernel from a number of pre-defined kernels, or to fully
137% specify their own kernel for a specific Convolution or Morphology
138% Operation.
139%
140% The kernel so generated can be any rectangular array of floating point
141% values (doubles) with the 'control point' or 'pixel being affected'
142% anywhere within that array of values.
143%
anthony83ba99b2010-01-24 08:48:15 +0000144% Previously IM was restricted to a square of odd size using the exact
145% center as origin, this is no longer the case, and any rectangular kernel
146% with any value being declared the origin. This in turn allows the use of
147% highly asymmetrical kernels.
anthony602ab9b2010-01-05 08:06:50 +0000148%
149% The floating point values in the kernel can also include a special value
anthony83ba99b2010-01-24 08:48:15 +0000150% known as 'nan' or 'not a number' to indicate that this value is not part
151% of the kernel array. This allows you to shaped the kernel within its
152% rectangular area. That is 'nan' values provide a 'mask' for the kernel
153% shape. However at least one non-nan value must be provided for correct
154% working of a kernel.
anthony602ab9b2010-01-05 08:06:50 +0000155%
anthony7a01dcf2010-05-11 12:25:52 +0000156% The returned kernel should be freed using the DestroyKernelInfo() when you
157% are finished with it. Do not free this memory yourself.
anthony602ab9b2010-01-05 08:06:50 +0000158%
159% Input kernel defintion strings can consist of any of three types.
160%
anthony29188a82010-01-22 10:12:34 +0000161% "name:args"
162% Select from one of the built in kernels, using the name and
163% geometry arguments supplied. See AcquireKernelBuiltIn()
anthony602ab9b2010-01-05 08:06:50 +0000164%
165% "WxH[+X+Y]:num, num, num ..."
anthony1b2bc0a2010-05-12 05:25:22 +0000166% a kernel of size W by H, with W*H floating point numbers following.
anthony602ab9b2010-01-05 08:06:50 +0000167% the 'center' can be optionally be defined at +X+Y (such that +0+0
anthony29188a82010-01-22 10:12:34 +0000168% is top left corner). If not defined the pixel in the center, for
169% odd sizes, or to the immediate top or left of center for even sizes
170% is automatically selected.
anthony602ab9b2010-01-05 08:06:50 +0000171%
anthony29188a82010-01-22 10:12:34 +0000172% "num, num, num, num, ..."
173% list of floating point numbers defining an 'old style' odd sized
174% square kernel. At least 9 values should be provided for a 3x3
175% square kernel, 25 for a 5x5 square kernel, 49 for 7x7, etc.
176% Values can be space or comma separated. This is not recommended.
anthony602ab9b2010-01-05 08:06:50 +0000177%
anthony83ba99b2010-01-24 08:48:15 +0000178% Note that 'name' kernels will start with an alphabetic character while the
179% new kernel specification has a ':' character in its specification string.
180% If neither is the case, it is assumed an old style of a simple list of
181% numbers generating a odd-sized square kernel has been given.
anthony602ab9b2010-01-05 08:06:50 +0000182%
anthony7a01dcf2010-05-11 12:25:52 +0000183% You can define a 'list of kernels' which can be used by some morphology
184% operators A list is defined as a semi-colon seperated list kernels.
185%
anthonydbc89892010-05-12 07:05:27 +0000186% " kernel ; kernel ; kernel ; "
anthony7a01dcf2010-05-11 12:25:52 +0000187%
anthonydbc89892010-05-12 07:05:27 +0000188% Extra ';' characters are simply ignored.
anthony7a01dcf2010-05-11 12:25:52 +0000189%
anthony602ab9b2010-01-05 08:06:50 +0000190% The format of the AcquireKernal method is:
191%
cristy2be15382010-01-21 02:38:03 +0000192% KernelInfo *AcquireKernelInfo(const char *kernel_string)
anthony602ab9b2010-01-05 08:06:50 +0000193%
194% A description of each parameter follows:
195%
196% o kernel_string: the Morphology/Convolution kernel wanted.
197%
198*/
199
anthonyc84dce52010-05-07 05:42:23 +0000200/* This was separated so that it could be used as a separate
anthony5ef8e942010-05-11 06:51:12 +0000201** array input handling function, such as for -color-matrix
anthonyc84dce52010-05-07 05:42:23 +0000202*/
anthony5ef8e942010-05-11 06:51:12 +0000203static KernelInfo *ParseKernelArray(const char *kernel_string)
anthony602ab9b2010-01-05 08:06:50 +0000204{
cristy2be15382010-01-21 02:38:03 +0000205 KernelInfo
anthony602ab9b2010-01-05 08:06:50 +0000206 *kernel;
207
208 char
209 token[MaxTextExtent];
210
anthony602ab9b2010-01-05 08:06:50 +0000211 const char
anthony5ef8e942010-05-11 06:51:12 +0000212 *p,
213 *end;
anthony602ab9b2010-01-05 08:06:50 +0000214
anthonyc84dce52010-05-07 05:42:23 +0000215 register long
216 i;
anthony602ab9b2010-01-05 08:06:50 +0000217
anthony29188a82010-01-22 10:12:34 +0000218 double
219 nan = sqrt((double)-1.0); /* Special Value : Not A Number */
220
cristy2be15382010-01-21 02:38:03 +0000221 kernel=(KernelInfo *) AcquireMagickMemory(sizeof(*kernel));
222 if (kernel == (KernelInfo *)NULL)
anthony602ab9b2010-01-05 08:06:50 +0000223 return(kernel);
224 (void) ResetMagickMemory(kernel,0,sizeof(*kernel));
anthony7a01dcf2010-05-11 12:25:52 +0000225 kernel->minimum = kernel->maximum = 0.0;
226 kernel->negative_range = kernel->positive_range = 0.0;
anthony602ab9b2010-01-05 08:06:50 +0000227 kernel->type = UserDefinedKernel;
anthony7a01dcf2010-05-11 12:25:52 +0000228 kernel->next = (KernelInfo *) NULL;
cristyd43a46b2010-01-21 02:13:41 +0000229 kernel->signature = MagickSignature;
anthony602ab9b2010-01-05 08:06:50 +0000230
anthony5ef8e942010-05-11 06:51:12 +0000231 /* find end of this specific kernel definition string */
232 end = strchr(kernel_string, ';');
233 if ( end == (char *) NULL )
234 end = strchr(kernel_string, '\0');
235
anthony602ab9b2010-01-05 08:06:50 +0000236 /* Has a ':' in argument - New user kernel specification */
237 p = strchr(kernel_string, ':');
anthony5ef8e942010-05-11 06:51:12 +0000238 if ( p != (char *) NULL && p < end)
anthony602ab9b2010-01-05 08:06:50 +0000239 {
anthonyc84dce52010-05-07 05:42:23 +0000240 MagickStatusType
241 flags;
242
243 GeometryInfo
244 args;
245
anthony602ab9b2010-01-05 08:06:50 +0000246 /* ParseGeometry() needs the geometry separated! -- Arrgghh */
cristy150989e2010-02-01 14:59:39 +0000247 memcpy(token, kernel_string, (size_t) (p-kernel_string));
anthony602ab9b2010-01-05 08:06:50 +0000248 token[p-kernel_string] = '\0';
anthonyc84dce52010-05-07 05:42:23 +0000249 SetGeometryInfo(&args);
anthony602ab9b2010-01-05 08:06:50 +0000250 flags = ParseGeometry(token, &args);
anthony602ab9b2010-01-05 08:06:50 +0000251
anthony29188a82010-01-22 10:12:34 +0000252 /* Size handling and checks of geometry settings */
anthony602ab9b2010-01-05 08:06:50 +0000253 if ( (flags & WidthValue) == 0 ) /* if no width then */
254 args.rho = args.sigma; /* then width = height */
255 if ( args.rho < 1.0 ) /* if width too small */
256 args.rho = 1.0; /* then width = 1 */
257 if ( args.sigma < 1.0 ) /* if height too small */
258 args.sigma = args.rho; /* then height = width */
259 kernel->width = (unsigned long)args.rho;
260 kernel->height = (unsigned long)args.sigma;
261
262 /* Offset Handling and Checks */
263 if ( args.xi < 0.0 || args.psi < 0.0 )
anthony83ba99b2010-01-24 08:48:15 +0000264 return(DestroyKernelInfo(kernel));
cristyc99304f2010-02-01 15:26:27 +0000265 kernel->x = ((flags & XValue)!=0) ? (long)args.xi
cristy150989e2010-02-01 14:59:39 +0000266 : (long) (kernel->width-1)/2;
cristyc99304f2010-02-01 15:26:27 +0000267 kernel->y = ((flags & YValue)!=0) ? (long)args.psi
cristy150989e2010-02-01 14:59:39 +0000268 : (long) (kernel->height-1)/2;
cristyc99304f2010-02-01 15:26:27 +0000269 if ( kernel->x >= (long) kernel->width ||
270 kernel->y >= (long) kernel->height )
anthony83ba99b2010-01-24 08:48:15 +0000271 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000272
273 p++; /* advance beyond the ':' */
274 }
275 else
anthonyc84dce52010-05-07 05:42:23 +0000276 { /* ELSE - Old old specification, forming odd-square kernel */
anthony602ab9b2010-01-05 08:06:50 +0000277 /* count up number of values given */
278 p=(const char *) kernel_string;
cristya699b172010-01-06 16:48:49 +0000279 while ((isspace((int) ((unsigned char) *p)) != 0) || (*p == '\''))
anthony29188a82010-01-22 10:12:34 +0000280 p++; /* ignore "'" chars for convolve filter usage - Cristy */
anthony5ef8e942010-05-11 06:51:12 +0000281 for (i=0; p < end; i++)
anthony602ab9b2010-01-05 08:06:50 +0000282 {
283 GetMagickToken(p,&p,token);
284 if (*token == ',')
285 GetMagickToken(p,&p,token);
286 }
287 /* set the size of the kernel - old sized square */
288 kernel->width = kernel->height= (unsigned long) sqrt((double) i+1.0);
cristyc99304f2010-02-01 15:26:27 +0000289 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony602ab9b2010-01-05 08:06:50 +0000290 p=(const char *) kernel_string;
anthony29188a82010-01-22 10:12:34 +0000291 while ((isspace((int) ((unsigned char) *p)) != 0) || (*p == '\''))
292 p++; /* ignore "'" chars for convolve filter usage - Cristy */
anthony602ab9b2010-01-05 08:06:50 +0000293 }
294
295 /* Read in the kernel values from rest of input string argument */
296 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
297 kernel->height*sizeof(double));
298 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +0000299 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000300
cristyc99304f2010-02-01 15:26:27 +0000301 kernel->minimum = +MagickHuge;
302 kernel->maximum = -MagickHuge;
303 kernel->negative_range = kernel->positive_range = 0.0;
anthonyc84dce52010-05-07 05:42:23 +0000304
anthony5ef8e942010-05-11 06:51:12 +0000305 for (i=0; (i < (long) (kernel->width*kernel->height)) && (p < end); i++)
anthony602ab9b2010-01-05 08:06:50 +0000306 {
307 GetMagickToken(p,&p,token);
308 if (*token == ',')
309 GetMagickToken(p,&p,token);
anthony29188a82010-01-22 10:12:34 +0000310 if ( LocaleCompare("nan",token) == 0
anthonyc84dce52010-05-07 05:42:23 +0000311 || LocaleCompare("-",token) == 0 ) {
anthony29188a82010-01-22 10:12:34 +0000312 kernel->values[i] = nan; /* do not include this value in kernel */
313 }
314 else {
315 kernel->values[i] = StringToDouble(token);
316 ( kernel->values[i] < 0)
cristyc99304f2010-02-01 15:26:27 +0000317 ? ( kernel->negative_range += kernel->values[i] )
318 : ( kernel->positive_range += kernel->values[i] );
319 Minimize(kernel->minimum, kernel->values[i]);
320 Maximize(kernel->maximum, kernel->values[i]);
anthony29188a82010-01-22 10:12:34 +0000321 }
anthony602ab9b2010-01-05 08:06:50 +0000322 }
anthony29188a82010-01-22 10:12:34 +0000323
anthony5ef8e942010-05-11 06:51:12 +0000324 /* sanity check -- no more values in kernel definition */
325 GetMagickToken(p,&p,token);
326 if ( *token != '\0' && *token != ';' && *token != '\'' )
327 return(DestroyKernelInfo(kernel));
328
anthonyc84dce52010-05-07 05:42:23 +0000329#if 0
330 /* this was the old method of handling a incomplete kernel */
cristy150989e2010-02-01 14:59:39 +0000331 if ( i < (long) (kernel->width*kernel->height) ) {
cristyc99304f2010-02-01 15:26:27 +0000332 Minimize(kernel->minimum, kernel->values[i]);
333 Maximize(kernel->maximum, kernel->values[i]);
cristy150989e2010-02-01 14:59:39 +0000334 for ( ; i < (long) (kernel->width*kernel->height); i++)
anthony29188a82010-01-22 10:12:34 +0000335 kernel->values[i]=0.0;
336 }
anthonyc84dce52010-05-07 05:42:23 +0000337#else
338 /* Number of values for kernel was not enough - Report Error */
339 if ( i < (long) (kernel->width*kernel->height) )
340 return(DestroyKernelInfo(kernel));
341#endif
342
343 /* check that we recieved at least one real (non-nan) value! */
344 if ( kernel->minimum == MagickHuge )
345 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000346
347 return(kernel);
348}
anthonyc84dce52010-05-07 05:42:23 +0000349
anthony5ef8e942010-05-11 06:51:12 +0000350static KernelInfo *ParseNamedKernel(const char *kernel_string)
anthonyc84dce52010-05-07 05:42:23 +0000351{
352 char
353 token[MaxTextExtent];
354
anthony5ef8e942010-05-11 06:51:12 +0000355 long
356 type;
357
anthonyc84dce52010-05-07 05:42:23 +0000358 const char
anthony7a01dcf2010-05-11 12:25:52 +0000359 *p,
360 *end;
anthonyc84dce52010-05-07 05:42:23 +0000361
362 MagickStatusType
363 flags;
364
365 GeometryInfo
366 args;
367
anthonyc84dce52010-05-07 05:42:23 +0000368 /* Parse special 'named' kernel */
anthony5ef8e942010-05-11 06:51:12 +0000369 GetMagickToken(kernel_string,&p,token);
anthonyc84dce52010-05-07 05:42:23 +0000370 type=ParseMagickOption(MagickKernelOptions,MagickFalse,token);
371 if ( type < 0 || type == UserDefinedKernel )
anthony5ef8e942010-05-11 06:51:12 +0000372 return((KernelInfo *)NULL); /* not a valid named kernel */
anthonyc84dce52010-05-07 05:42:23 +0000373
374 while (((isspace((int) ((unsigned char) *p)) != 0) ||
anthony5ef8e942010-05-11 06:51:12 +0000375 (*p == ',') || (*p == ':' )) && (*p != '\0') && (*p != ';'))
anthonyc84dce52010-05-07 05:42:23 +0000376 p++;
anthony7a01dcf2010-05-11 12:25:52 +0000377
378 end = strchr(p, ';'); /* end of this kernel defintion */
379 if ( end == (char *) NULL )
380 end = strchr(p, '\0');
381
382 /* ParseGeometry() needs the geometry separated! -- Arrgghh */
383 memcpy(token, p, (size_t) (end-p));
384 token[end-p] = '\0';
anthonyc84dce52010-05-07 05:42:23 +0000385 SetGeometryInfo(&args);
anthony7a01dcf2010-05-11 12:25:52 +0000386 flags = ParseGeometry(token, &args);
anthonyc84dce52010-05-07 05:42:23 +0000387
anthony3c10fc82010-05-13 02:40:51 +0000388#if 0
389 /* For Debugging Geometry Input */
390 fprintf(stderr, "Geometry = %04x : %lf x %lf %+lf %+lf\n",
391 flags, args.rho, args.sigma, args.xi, args.psi );
392#endif
393
anthonyc84dce52010-05-07 05:42:23 +0000394 /* special handling of missing values in input string */
395 switch( type ) {
anthony5ef8e942010-05-11 06:51:12 +0000396 case RectangleKernel:
397 if ( (flags & WidthValue) == 0 ) /* if no width then */
398 args.rho = args.sigma; /* then width = height */
399 if ( args.rho < 1.0 ) /* if width too small */
400 args.rho = 3; /* then width = 3 */
401 if ( args.sigma < 1.0 ) /* if height too small */
402 args.sigma = args.rho; /* then height = width */
403 if ( (flags & XValue) == 0 ) /* center offset if not defined */
404 args.xi = (double)(((long)args.rho-1)/2);
405 if ( (flags & YValue) == 0 )
406 args.psi = (double)(((long)args.sigma-1)/2);
407 break;
408 case SquareKernel:
409 case DiamondKernel:
410 case DiskKernel:
411 case PlusKernel:
anthony3dd0f622010-05-13 12:57:32 +0000412 case CrossKernel:
anthony5ef8e942010-05-11 06:51:12 +0000413 /* If no scale given (a 0 scale is valid! - set it to 1.0 */
414 if ( (flags & HeightValue) == 0 )
415 args.sigma = 1.0;
416 break;
anthonyc1061722010-05-14 06:23:49 +0000417 case RingKernel:
418 if ( (flags & XValue) == 0 )
419 args.xi = 1.0;
420 break;
anthony5ef8e942010-05-11 06:51:12 +0000421 case ChebyshevKernel:
422 case ManhattenKernel:
423 case EuclideanKernel:
424 if ( (flags & HeightValue) == 0 )
425 args.sigma = 100.0; /* default distance scaling */
426 else if ( (flags & AspectValue ) != 0 ) /* '!' flag */
427 args.sigma = QuantumRange/args.sigma; /* maximum pixel distance */
428 else if ( (flags & PercentValue ) != 0 ) /* '%' flag */
429 args.sigma *= QuantumRange/100.0; /* percentage of color range */
430 break;
431 default:
432 break;
anthonyc84dce52010-05-07 05:42:23 +0000433 }
434
435 return(AcquireKernelBuiltIn((KernelInfoType)type, &args));
436}
437
anthony5ef8e942010-05-11 06:51:12 +0000438MagickExport KernelInfo *AcquireKernelInfo(const char *kernel_string)
439{
anthony7a01dcf2010-05-11 12:25:52 +0000440
441 KernelInfo
anthonydbc89892010-05-12 07:05:27 +0000442 *kernel,
443 *new_kernel,
444 *last_kernel;
anthony7a01dcf2010-05-11 12:25:52 +0000445
anthony5ef8e942010-05-11 06:51:12 +0000446 char
447 token[MaxTextExtent];
448
anthony7a01dcf2010-05-11 12:25:52 +0000449 const char
anthonydbc89892010-05-12 07:05:27 +0000450 *p;
anthony7a01dcf2010-05-11 12:25:52 +0000451
anthonye108a3f2010-05-12 07:24:03 +0000452 unsigned long
453 kernel_number;
454
anthonydbc89892010-05-12 07:05:27 +0000455 p = kernel_string;
456 kernel = last_kernel = NULL;
anthonye108a3f2010-05-12 07:24:03 +0000457 kernel_number = 0;
anthony5ef8e942010-05-11 06:51:12 +0000458
anthonydbc89892010-05-12 07:05:27 +0000459 while ( GetMagickToken(p,NULL,token), *token != '\0' ) {
anthony7a01dcf2010-05-11 12:25:52 +0000460
anthonydbc89892010-05-12 07:05:27 +0000461 /* ignore multiple ';' following each other */
462 if ( *token != ';' ) {
anthony7a01dcf2010-05-11 12:25:52 +0000463
anthonydbc89892010-05-12 07:05:27 +0000464 /* tokens starting with alpha is a Named kernel */
465 if (isalpha((int) *token) == 0)
466 new_kernel = ParseKernelArray(p);
467 else /* otherwise a user defined kernel array */
468 new_kernel = ParseNamedKernel(p);
anthony7a01dcf2010-05-11 12:25:52 +0000469
anthonye108a3f2010-05-12 07:24:03 +0000470 /* Error handling -- this is not proper error handling! */
471 if ( new_kernel == (KernelInfo *) NULL ) {
472 fprintf(stderr, "Failed to parse kernel number #%lu\n", kernel_number);
473 if ( kernel != (KernelInfo *) NULL )
474 kernel=DestroyKernelInfo(kernel);
475 return((KernelInfo *) NULL);
anthonydbc89892010-05-12 07:05:27 +0000476 }
anthonye108a3f2010-05-12 07:24:03 +0000477
478 /* initialise or append the kernel list */
anthony3dd0f622010-05-13 12:57:32 +0000479 if ( kernel == (KernelInfo *) NULL )
480 kernel = new_kernel;
481 else
anthonydbc89892010-05-12 07:05:27 +0000482 last_kernel->next = new_kernel;
anthony3dd0f622010-05-13 12:57:32 +0000483 last_kernel = LastKernelInfo(new_kernel);
anthonydbc89892010-05-12 07:05:27 +0000484 }
485
486 /* look for the next kernel in list */
487 p = strchr(p, ';');
488 if ( p == (char *) NULL )
489 break;
490 p++;
491
492 }
anthony7a01dcf2010-05-11 12:25:52 +0000493 return(kernel);
anthony5ef8e942010-05-11 06:51:12 +0000494}
495
anthony602ab9b2010-01-05 08:06:50 +0000496
497/*
498%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
499% %
500% %
501% %
502% A c q u i r e K e r n e l B u i l t I n %
503% %
504% %
505% %
506%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
507%
508% AcquireKernelBuiltIn() returned one of the 'named' built-in types of
509% kernels used for special purposes such as gaussian blurring, skeleton
510% pruning, and edge distance determination.
511%
512% They take a KernelType, and a set of geometry style arguments, which were
513% typically decoded from a user supplied string, or from a more complex
514% Morphology Method that was requested.
515%
516% The format of the AcquireKernalBuiltIn method is:
517%
cristy2be15382010-01-21 02:38:03 +0000518% KernelInfo *AcquireKernelBuiltIn(const KernelInfoType type,
anthony602ab9b2010-01-05 08:06:50 +0000519% const GeometryInfo args)
520%
521% A description of each parameter follows:
522%
523% o type: the pre-defined type of kernel wanted
524%
525% o args: arguments defining or modifying the kernel
526%
527% Convolution Kernels
528%
anthony3c10fc82010-05-13 02:40:51 +0000529% Gaussian:{radius},{sigma}
530% Generate a two-dimentional gaussian kernel, as used by -gaussian.
anthonyc1061722010-05-14 06:23:49 +0000531% The sigma for the curve is required. The resulting kernel is
532% normalized,
533%
534% If 'sigma' is zero, you get a single pixel on a field of zeros.
anthony602ab9b2010-01-05 08:06:50 +0000535%
536% NOTE: that the 'radius' is optional, but if provided can limit (clip)
537% the final size of the resulting kernel to a square 2*radius+1 in size.
538% The radius should be at least 2 times that of the sigma value, or
539% sever clipping and aliasing may result. If not given or set to 0 the
540% radius will be determined so as to produce the best minimal error
541% result, which is usally much larger than is normally needed.
542%
anthonyc1061722010-05-14 06:23:49 +0000543% DOG:{radius},{sigma1},{sigma2}
544% "Difference of Gaussians" Kernel.
545% As "Gaussian" but with a gaussian produced by 'sigma2' subtracted
546% from the gaussian produced by 'sigma1'. Typically sigma2 > sigma1.
547% The result is a zero-summing kernel.
anthony602ab9b2010-01-05 08:06:50 +0000548%
anthonyc1061722010-05-14 06:23:49 +0000549% Blur:{radius},{sigma}[,{angle}]
550% Generates a 1 dimensional or linear gaussian blur, at the angle given
551% (current restricted to orthogonal angles). If a 'radius' is given the
552% kernel is clipped to a width of 2*radius+1. Kernel can be rotated
553% by a 90 degree angle.
554%
555% If 'sigma' is zero, you get a single pixel on a field of zeros.
556%
557% Note that two convolutions with two "Blur" kernels perpendicular to
558% each other, is equivelent to a far larger "Gaussian" kernel with the
559% same sigma value, However it is much faster to apply. This is how the
560% "-blur" operator actually works.
561%
562% DOB:{radius},{sigma1},{sigma2}[,{angle}]
563% "Difference of Blurs" Kernel.
564% As "Blur" but with the 1D gaussian produced by 'sigma2' subtracted
565% from thethe 1D gaussian produced by 'sigma1'.
566% The result is a zero-summing kernel.
567%
568% This can be used to generate a faster "DOG" convolution, in the same
569% way "Blur" can.
anthony602ab9b2010-01-05 08:06:50 +0000570%
anthony3c10fc82010-05-13 02:40:51 +0000571% Comet:{width},{sigma},{angle}
572% Blur in one direction only, much like how a bright object leaves
anthony602ab9b2010-01-05 08:06:50 +0000573% a comet like trail. The Kernel is actually half a gaussian curve,
anthony3c10fc82010-05-13 02:40:51 +0000574% Adding two such blurs in opposite directions produces a Blur Kernel.
575% Angle can be rotated in multiples of 90 degrees.
anthony602ab9b2010-01-05 08:06:50 +0000576%
anthony3c10fc82010-05-13 02:40:51 +0000577% Note that the first argument is the width of the kernel and not the
anthony602ab9b2010-01-05 08:06:50 +0000578% radius of the kernel.
579%
580% # Still to be implemented...
581% #
anthony4fd27e22010-02-07 08:17:18 +0000582% # Filter2D
583% # Filter1D
584% # Set kernel values using a resize filter, and given scale (sigma)
585% # Cylindrical or Linear. Is this posible with an image?
586% #
anthony602ab9b2010-01-05 08:06:50 +0000587%
anthony3c10fc82010-05-13 02:40:51 +0000588% Named Constant Convolution Kernels
589%
anthonyc1061722010-05-14 06:23:49 +0000590% All these are unscaled, zero-summing kernels by default. As such for
591% non-HDRI version of ImageMagick some form of normalization, user scaling,
592% and biasing the results is recommended, to prevent the resulting image
593% being 'clipped'.
594%
595% The 3x3 kernels (most of these) can be circularly rotated in multiples of
596% 45 degrees to generate the 8 angled varients of each of the kernels.
anthony3c10fc82010-05-13 02:40:51 +0000597%
598% Laplacian:{type}
599% Generate Lapacian kernel of the type specified. (1 is the default)
anthonyc1061722010-05-14 06:23:49 +0000600% Type 0 : 3x3 with center:8 surounded by -1 (8 neighbourhood)
601% Type 1 : 3x3 with center:4 edge:-1 corner:0 (4 neighbourhood)
602% Type 2 : 3x3 with center:4 edge:-2 corner:1
603% Type 3 : 3x3 with center:4 edge:1 corner:-2
604% Type 4 : 5x5 laplacian
605% Type 5 : 7x7 laplacian
606%
607% Sobel:{angle}
608% Sobel 3x3 'Edge' convolution kernel (3x3)
609% -1, 0, 1
610% -2, 0,-2
611% -1, 0, 1
612% Roberts:{angle}
613% Roberts 3x3 convolution kernel (3x3)
614% 0, 0, 0
615% -1, 1, 0
616% 0, 0, 0
617% Compass:{angle}
618% Prewitt's "Compass" convolution kernel (3x3)
619% -1, 1, 1
620% -1,-2, 1
621% -1, 1, 1
622% Prewitt:{angle}
623% Prewitt Edge convolution kernel (3x3)
624% -1, 0, 1
625% -1, 0, 1
626% -1, 0, 1
anthony3c10fc82010-05-13 02:40:51 +0000627%
anthony602ab9b2010-01-05 08:06:50 +0000628% Boolean Kernels
629%
anthony3c10fc82010-05-13 02:40:51 +0000630% Diamond:[{radius}[,{scale}]]
anthony1b2bc0a2010-05-12 05:25:22 +0000631% Generate a diamond shaped kernel with given radius to the points.
anthony602ab9b2010-01-05 08:06:50 +0000632% Kernel size will again be radius*2+1 square and defaults to radius 1,
633% generating a 3x3 kernel that is slightly larger than a square.
634%
anthony3c10fc82010-05-13 02:40:51 +0000635% Square:[{radius}[,{scale}]]
anthony602ab9b2010-01-05 08:06:50 +0000636% Generate a square shaped kernel of size radius*2+1, and defaulting
637% to a 3x3 (radius 1).
638%
anthonyc1061722010-05-14 06:23:49 +0000639% Note that using a larger radius for the "Square" or the "Diamond" is
640% also equivelent to iterating the basic morphological method that many
641% times. However iterating with the smaller radius is actually faster
642% than using a larger kernel radius.
643%
644% Rectangle:{geometry}
645% Simply generate a rectangle of 1's with the size given. You can also
646% specify the location of the 'control point', otherwise the closest
647% pixel to the center of the rectangle is selected.
648%
649% Properly centered and odd sized rectangles work the best.
anthony602ab9b2010-01-05 08:06:50 +0000650%
anthony3c10fc82010-05-13 02:40:51 +0000651% Disk:[{radius}[,{scale}]]
anthony602ab9b2010-01-05 08:06:50 +0000652% Generate a binary disk of the radius given, radius may be a float.
653% Kernel size will be ceil(radius)*2+1 square.
654% NOTE: Here are some disk shapes of specific interest
anthonyc1061722010-05-14 06:23:49 +0000655% "Disk:1" => "diamond" or "cross:1"
656% "Disk:1.5" => "square"
657% "Disk:2" => "diamond:2"
658% "Disk:2.5" => a general disk shape of radius 2
659% "Disk:2.9" => "square:2"
660% "Disk:3.5" => default - octagonal/disk shape of radius 3
661% "Disk:4.2" => roughly octagonal shape of radius 4
662% "Disk:4.3" => a general disk shape of radius 4
anthony602ab9b2010-01-05 08:06:50 +0000663% After this all the kernel shape becomes more and more circular.
664%
665% Because a "disk" is more circular when using a larger radius, using a
666% larger radius is preferred over iterating the morphological operation.
667%
anthonyc1061722010-05-14 06:23:49 +0000668% Symbol Dilation Kernels
669%
670% These kernel is not a good general morphological kernel, but is used
671% more for highlighting and marking any single pixels in an image using,
672% a "Dilate" method as appropriate.
673%
674% For the same reasons iterating these kernels does not produce the
675% same result as using a larger radius for the symbol.
676%
anthony3c10fc82010-05-13 02:40:51 +0000677% Plus:[{radius}[,{scale}]]
anthony3dd0f622010-05-13 12:57:32 +0000678% Cross:[{radius}[,{scale}]]
anthonyc1061722010-05-14 06:23:49 +0000679% Generate a kernel in the shape of a 'plus' or a 'cross' with
680% a each arm the length of the given radius (default 2).
anthony3dd0f622010-05-13 12:57:32 +0000681%
682% NOTE: "plus:1" is equivelent to a "Diamond" kernel.
anthony602ab9b2010-01-05 08:06:50 +0000683%
anthonyc1061722010-05-14 06:23:49 +0000684% Ring:{radius1},{radius2}[,{scale}]
685% A ring of the values given that falls between the two radii.
686% Defaults to a ring of approximataly 3 radius in a 7x7 kernel.
687% This is the 'edge' pixels of the default "Disk" kernel,
688% More specifically, "Ring" -> "Ring:2.5,3.5,1.0"
anthony602ab9b2010-01-05 08:06:50 +0000689%
anthony3dd0f622010-05-13 12:57:32 +0000690% Hit and Miss Kernels
691%
692% Peak:radius1,radius2
anthonyc1061722010-05-14 06:23:49 +0000693% Find any peak larger than the pixels the fall between the two radii.
694% The default ring of pixels is as per "Ring".
anthony3dd0f622010-05-13 12:57:32 +0000695% Corners
696% Find corners of a binary shape
697% LineEnds
698% Find end points of lines (for pruning a skeletion)
699% LineJunctions
700% Find three line junctions (in a skeletion)
701% ConvexHull
702% Octagonal thicken kernel, to generate convex hulls of 45 degrees
703% Skeleton
704% Thinning kernel, which leaves behind a skeletion of a shape
anthony602ab9b2010-01-05 08:06:50 +0000705%
706% Distance Measuring Kernels
707%
anthonyc1061722010-05-14 06:23:49 +0000708% Different types of distance measuring methods, which are used with the
709% a 'Distance' morphology method for generating a gradient based on
710% distance from an edge of a binary shape, though there is a technique
711% for handling a anti-aliased shape.
712%
713% See the 'Distance' Morphological Method, for information of how it is
714% applied.
715%
anthony3dd0f622010-05-13 12:57:32 +0000716% Chebyshev:[{radius}][x{scale}[%!]]
anthonyc94cdb02010-01-06 08:15:29 +0000717% Chebyshev Distance (also known as Tchebychev Distance) is a value of
718% one to any neighbour, orthogonal or diagonal. One why of thinking of
719% it is the number of squares a 'King' or 'Queen' in chess needs to
720% traverse reach any other position on a chess board. It results in a
721% 'square' like distance function, but one where diagonals are closer
722% than expected.
anthony602ab9b2010-01-05 08:06:50 +0000723%
anthonyc1061722010-05-14 06:23:49 +0000724% Manhatten:[{radius}][x{scale}[%!]]
anthonyc94cdb02010-01-06 08:15:29 +0000725% Manhatten Distance (also known as Rectilinear Distance, or the Taxi
726% Cab metric), is the distance needed when you can only travel in
727% orthogonal (horizontal or vertical) only. It is the distance a 'Rook'
728% in chess would travel. It results in a diamond like distances, where
729% diagonals are further than expected.
anthony602ab9b2010-01-05 08:06:50 +0000730%
anthonyc1061722010-05-14 06:23:49 +0000731% Euclidean:[{radius}][x{scale}[%!]]
anthonyc94cdb02010-01-06 08:15:29 +0000732% Euclidean Distance is the 'direct' or 'as the crow flys distance.
733% However by default the kernel size only has a radius of 1, which
734% limits the distance to 'Knight' like moves, with only orthogonal and
735% diagonal measurements being correct. As such for the default kernel
736% you will get octagonal like distance function, which is reasonally
737% accurate.
738%
739% However if you use a larger radius such as "Euclidean:4" you will
740% get a much smoother distance gradient from the edge of the shape.
741% Of course a larger kernel is slower to use, and generally not needed.
742%
743% To allow the use of fractional distances that you get with diagonals
744% the actual distance is scaled by a fixed value which the user can
745% provide. This is not actually nessary for either ""Chebyshev" or
746% "Manhatten" distance kernels, but is done for all three distance
747% kernels. If no scale is provided it is set to a value of 100,
748% allowing for a maximum distance measurement of 655 pixels using a Q16
749% version of IM, from any edge. However for small images this can
750% result in quite a dark gradient.
751%
anthony602ab9b2010-01-05 08:06:50 +0000752*/
753
cristy2be15382010-01-21 02:38:03 +0000754MagickExport KernelInfo *AcquireKernelBuiltIn(const KernelInfoType type,
anthony602ab9b2010-01-05 08:06:50 +0000755 const GeometryInfo *args)
756{
cristy2be15382010-01-21 02:38:03 +0000757 KernelInfo
anthony602ab9b2010-01-05 08:06:50 +0000758 *kernel;
759
cristy150989e2010-02-01 14:59:39 +0000760 register long
anthony602ab9b2010-01-05 08:06:50 +0000761 i;
762
763 register long
764 u,
765 v;
766
767 double
768 nan = sqrt((double)-1.0); /* Special Value : Not A Number */
769
anthonyc1061722010-05-14 06:23:49 +0000770 /* Generate a new empty kernel if needed */
771 switch(type) {
772 case GaussianKernel:
773 case DOGKernel:
774 case BlurKernel:
775 case DOBKernel:
776 case CometKernel:
777 case DiamondKernel:
778 case SquareKernel:
779 case RectangleKernel:
780 case DiskKernel:
781 case PlusKernel:
782 case CrossKernel:
783 case RingKernel:
784 case PeaksKernel:
785 case ChebyshevKernel:
786 case ManhattenKernel:
787 case EuclideanKernel:
788 kernel=(KernelInfo *) AcquireMagickMemory(sizeof(*kernel));
789 if (kernel == (KernelInfo *) NULL)
790 return(kernel);
791 (void) ResetMagickMemory(kernel,0,sizeof(*kernel));
792 kernel->minimum = kernel->maximum = 0.0;
793 kernel->negative_range = kernel->positive_range = 0.0;
794 kernel->type = type;
795 kernel->next = (KernelInfo *) NULL;
796 kernel->signature = MagickSignature;
797 default:
798 break;
799 }
anthony602ab9b2010-01-05 08:06:50 +0000800
801 switch(type) {
802 /* Convolution Kernels */
803 case GaussianKernel:
anthonyc1061722010-05-14 06:23:49 +0000804 case DOGKernel:
anthony602ab9b2010-01-05 08:06:50 +0000805 { double
anthonyc1061722010-05-14 06:23:49 +0000806 sigma = fabs(args->sigma),
807 sigma2 = fabs(args->xi),
808 gamma;
anthony602ab9b2010-01-05 08:06:50 +0000809
anthonyc1061722010-05-14 06:23:49 +0000810 if ( args->rho >= 1.0 )
811 kernel->width = (unsigned long)args->rho*2+1;
812 else if ( (type == GaussianKernel) || (sigma >= sigma2) )
813 kernel->width = GetOptimalKernelWidth2D(args->rho,sigma);
814 else
815 kernel->width = GetOptimalKernelWidth2D(args->rho,sigma2);
816 kernel->height = kernel->width;
cristyc99304f2010-02-01 15:26:27 +0000817 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony602ab9b2010-01-05 08:06:50 +0000818 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
819 kernel->height*sizeof(double));
820 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +0000821 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000822
anthonyc1061722010-05-14 06:23:49 +0000823 /* Calculate a Positive Gaussian */
824 if ( sigma > MagickEpsilon )
825 { gamma = 1.0/(2.0*sigma*sigma); /* simplify loop expressions */
826 sigma = 1.0/(MagickPI*sigma);
827 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
828 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
829 kernel->values[i] = sigma*exp(-((double)(u*u+v*v))*gamma);
830 }
831 else /* special case - generate a unity kernel */
832 { (void) ResetMagickMemory(kernel->values,0, (size_t)
833 kernel->width*kernel->height*sizeof(double));
834 kernel->values[kernel->x+kernel->y*kernel->width] = 1.0;
835 }
836 if ( type == DOGKernel )
837 { /* Subtract a Negative Gaussian for "Difference of Gaussian" */
838 if ( sigma2 > MagickEpsilon )
839 { sigma = sigma2; /* simplify loop expressions */
840 gamma = 1.0/(2.0*sigma*sigma);
841 sigma = 1.0/(MagickPI*sigma);
842 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
843 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
844 kernel->values[i] -= sigma*exp(-((double)(u*u+v*v))*gamma);
845 }
846 else /* special case - subtract the unity kernel */
847 kernel->values[kernel->x+kernel->y*kernel->width] -= 1.0;
848 }
849 /* Note the above kernel may have been 'clipped' by a user defined
850 ** radius, producing a smaller (darker) kernel. Also for very small
851 ** sigma's (> 0.1) the central value becomes larger than one, and thus
852 ** producing a very bright kernel.
853 **
854 ** Normalization will still be needed.
855 */
anthony602ab9b2010-01-05 08:06:50 +0000856
anthonyc1061722010-05-14 06:23:49 +0000857 /* Work out the meta-data about kernel */
858 kernel->minimum = kernel->maximum = 0.0;
859 kernel->negative_range = kernel->positive_range = 0.0;
860 u=(long) kernel->width*kernel->height;
861 for ( i=0; i < u; i++)
862 {
863 if ( fabs(kernel->values[i]) < MagickEpsilon )
864 kernel->values[i] = 0.0;
865 ( kernel->values[i] < 0)
866 ? ( kernel->negative_range += kernel->values[i] )
867 : ( kernel->positive_range += kernel->values[i] );
868 Minimize(kernel->minimum, kernel->values[i]);
869 Maximize(kernel->maximum, kernel->values[i]);
870 }
anthony3dd0f622010-05-13 12:57:32 +0000871 /* Normalize the 2D Gaussian Kernel
872 **
anthonyc1061722010-05-14 06:23:49 +0000873 ** NB: a CorrelateNormalize performs a normal Normalize if
874 ** there are no negative values.
anthony3dd0f622010-05-13 12:57:32 +0000875 */
anthonyc1061722010-05-14 06:23:49 +0000876 ScaleKernelInfo(kernel, 1.0, CorrelateNormalizeValue);
anthony602ab9b2010-01-05 08:06:50 +0000877
878 break;
879 }
880 case BlurKernel:
anthonyc1061722010-05-14 06:23:49 +0000881 case DOBKernel:
anthony602ab9b2010-01-05 08:06:50 +0000882 { double
anthonyc1061722010-05-14 06:23:49 +0000883 sigma = fabs(args->sigma),
884 sigma2 = fabs(args->xi),
885 gamma;
anthony602ab9b2010-01-05 08:06:50 +0000886
anthonyc1061722010-05-14 06:23:49 +0000887 if ( args->rho >= 1.0 )
888 kernel->width = (unsigned long)args->rho*2+1;
889 else if ( (type == BlurKernel) || (sigma >= sigma2) )
890 kernel->width = GetOptimalKernelWidth1D(args->rho,sigma);
891 else
892 kernel->width = GetOptimalKernelWidth1D(args->rho,sigma2);
anthony602ab9b2010-01-05 08:06:50 +0000893 kernel->height = 1;
anthonyc1061722010-05-14 06:23:49 +0000894 kernel->x = (long) (kernel->width-1)/2;
cristyc99304f2010-02-01 15:26:27 +0000895 kernel->y = 0;
896 kernel->negative_range = kernel->positive_range = 0.0;
anthony602ab9b2010-01-05 08:06:50 +0000897 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
898 kernel->height*sizeof(double));
899 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +0000900 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000901
902#if 1
903#define KernelRank 3
904 /* Formula derived from GetBlurKernel() in "effect.c" (plus bug fix).
905 ** It generates a gaussian 3 times the width, and compresses it into
906 ** the expected range. This produces a closer normalization of the
907 ** resulting kernel, especially for very low sigma values.
908 ** As such while wierd it is prefered.
909 **
910 ** I am told this method originally came from Photoshop.
911 */
anthonyc1061722010-05-14 06:23:49 +0000912
913 /* initialize */
cristy150989e2010-02-01 14:59:39 +0000914 v = (long) (kernel->width*KernelRank-1)/2; /* start/end points to fit range */
anthonyc1061722010-05-14 06:23:49 +0000915 /* Calculate a Positive 1D Gaussian */
916 if ( sigma > MagickEpsilon )
917 { sigma *= KernelRank; /* simplify loop expressions */
918 gamma = 1.0/(2.0*sigma*sigma);
919 sigma = 1.0/(MagickSQ2PI*sigma );
920 for ( u=-v; u <= v; u++) {
921 kernel->values[(u+v)/KernelRank] +=
922 sigma*exp(-((double)(u*u))*gamma);
923 }
924 }
925 else /* special case - generate a unity kernel */
926 kernel->values[kernel->x+kernel->y*kernel->width] = 1.0;
927 if ( type == DOBKernel )
928 { /* Subtract a Second 1D Gaussian for "Difference of Blur" */
929 if ( sigma2 > MagickEpsilon )
930 { sigma = sigma2*KernelRank; /* simplify loop expressions */
931 gamma = 1.0/(2.0*sigma*sigma);
932 sigma = 1.0/(MagickSQ2PI*sigma );
933 for ( u=-v; u <= v; u++)
934 kernel->values[(u+v)/KernelRank] -=
935 sigma*exp(-((double)(u*u))*gamma);
936 }
937 else /* special case - subtract a unity kernel */
938 kernel->values[kernel->x+kernel->y*kernel->width] -= 1.0;
939 }
anthony602ab9b2010-01-05 08:06:50 +0000940#else
anthonyc1061722010-05-14 06:23:49 +0000941 /* Direct calculation without curve averaging */
942
943 /* Calculate a Positive Gaussian */
944 if ( sigma > MagickEpsilon )
945 { gamma = 1.0/(2.0*sigma*sigma); /* simplify loop expressions */
946 sigma = 1.0/(MagickSQ2PI*sigma);
947 for ( i=0, u=-kernel->x; u <= (long)kernel->x; u++, i++)
948 kernel->values[i] = sigma*exp(-((double)(u*u))*gamma);
949 }
950 else /* special case - generate a unity kernel */
951 { (void) ResetMagickMemory(kernel->values,0, (size_t)
952 kernel->width*kernel->height*sizeof(double));
953 kernel->values[kernel->x+kernel->y*kernel->width] = 1.0;
954 }
955 if ( type == DOBKernel )
956 { /* Subtract a Second 1D Gaussian for "Difference of Blur" */
957 if ( sigma2 > MagickEpsilon )
958 { sigma = sigma2; /* simplify loop expressions */
959 gamma = 1.0/(2.0*sigma*sigma);
960 sigma = 1.0/(MagickSQ2PI*sigma);
961 for ( i=0, u=-kernel->x; u <= (long)kernel->x; u++, i++)
962 kernel->values[i] -= sigma*exp(-((double)(u*u))*gamma);
963 }
964 else /* special case - subtract a unity kernel */
965 kernel->values[kernel->x+kernel->y*kernel->width] -= 1.0;
966 }
anthony602ab9b2010-01-05 08:06:50 +0000967#endif
anthonyc1061722010-05-14 06:23:49 +0000968 /* Note the above kernel may have been 'clipped' by a user defined
anthonycc6c8362010-01-25 04:14:01 +0000969 ** radius, producing a smaller (darker) kernel. Also for very small
970 ** sigma's (> 0.1) the central value becomes larger than one, and thus
971 ** producing a very bright kernel.
anthonyc1061722010-05-14 06:23:49 +0000972 **
973 ** Normalization will still be needed.
anthony602ab9b2010-01-05 08:06:50 +0000974 */
anthonycc6c8362010-01-25 04:14:01 +0000975
anthonyc1061722010-05-14 06:23:49 +0000976 /* Work out the meta-data about kernel */
977 for ( i=0; i < (long) kernel->width; i++)
978 {
979 if ( fabs(kernel->values[i]) < MagickEpsilon )
980 kernel->values[i] = 0.0;
981 ( kernel->values[i] < 0)
982 ? ( kernel->negative_range += kernel->values[i] )
983 : ( kernel->positive_range += kernel->values[i] );
984 Minimize(kernel->minimum, kernel->values[i]);
985 Maximize(kernel->maximum, kernel->values[i]);
986 }
987
anthony602ab9b2010-01-05 08:06:50 +0000988 /* Normalize the 1D Gaussian Kernel
989 **
anthonyc1061722010-05-14 06:23:49 +0000990 ** NB: a CorrelateNormalize performs a normal Normalize if
991 ** there are no negative values.
anthony602ab9b2010-01-05 08:06:50 +0000992 */
anthonyc1061722010-05-14 06:23:49 +0000993 ScaleKernelInfo(kernel, 1.0, CorrelateNormalizeValue);
anthonycc6c8362010-01-25 04:14:01 +0000994
anthonyc1061722010-05-14 06:23:49 +0000995 /* rotate the 1D kernel by given angle */
996 RotateKernelInfo(kernel, (type == BlurKernel) ? args->xi : args->psi );
anthony602ab9b2010-01-05 08:06:50 +0000997 break;
998 }
999 case CometKernel:
1000 { double
1001 sigma = fabs(args->sigma);
1002
1003 sigma = (sigma <= MagickEpsilon) ? 1.0 : sigma;
anthony602ab9b2010-01-05 08:06:50 +00001004 if ( args->rho < 1.0 )
1005 kernel->width = GetOptimalKernelWidth1D(args->rho,sigma);
1006 else
1007 kernel->width = (unsigned long)args->rho;
cristyc99304f2010-02-01 15:26:27 +00001008 kernel->x = kernel->y = 0;
anthony602ab9b2010-01-05 08:06:50 +00001009 kernel->height = 1;
cristyc99304f2010-02-01 15:26:27 +00001010 kernel->negative_range = kernel->positive_range = 0.0;
anthony602ab9b2010-01-05 08:06:50 +00001011 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
1012 kernel->height*sizeof(double));
1013 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +00001014 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +00001015
anthonyc1061722010-05-14 06:23:49 +00001016 /* A comet blur is half a 1D gaussian curve, so that the object is
anthony602ab9b2010-01-05 08:06:50 +00001017 ** blurred in one direction only. This may not be quite the right
anthony3dd0f622010-05-13 12:57:32 +00001018 ** curve to use so may change in the future. The function must be
1019 ** normalised after generation, which also resolves any clipping.
anthonyc1061722010-05-14 06:23:49 +00001020 **
1021 ** As we are normalizing and not subtracting gaussians,
1022 ** there is no need for a divisor in the gaussian formula
1023 **
anthony602ab9b2010-01-05 08:06:50 +00001024 */
1025#if 1
1026#define KernelRank 3
1027 sigma *= KernelRank; /* simplify expanded curve */
cristy150989e2010-02-01 14:59:39 +00001028 v = (long) kernel->width*KernelRank; /* start/end points to fit range */
anthony602ab9b2010-01-05 08:06:50 +00001029 (void) ResetMagickMemory(kernel->values,0, (size_t)
1030 kernel->width*sizeof(double));
1031 for ( u=0; u < v; u++) {
1032 kernel->values[u/KernelRank] +=
1033 exp(-((double)(u*u))/(2.0*sigma*sigma))
1034 /* / (MagickSQ2PI*sigma/KernelRank) */ ;
1035 }
cristy150989e2010-02-01 14:59:39 +00001036 for (i=0; i < (long) kernel->width; i++)
cristyc99304f2010-02-01 15:26:27 +00001037 kernel->positive_range += kernel->values[i];
anthony602ab9b2010-01-05 08:06:50 +00001038#else
cristy150989e2010-02-01 14:59:39 +00001039 for ( i=0; i < (long) kernel->width; i++)
cristyc99304f2010-02-01 15:26:27 +00001040 kernel->positive_range += (
anthony602ab9b2010-01-05 08:06:50 +00001041 kernel->values[i] =
1042 exp(-((double)(i*i))/(2.0*sigma*sigma))
1043 /* / (MagickSQ2PI*sigma) */ );
1044#endif
cristyc99304f2010-02-01 15:26:27 +00001045 kernel->minimum = 0;
1046 kernel->maximum = kernel->values[0];
anthony602ab9b2010-01-05 08:06:50 +00001047
anthony999bb2c2010-02-18 12:38:01 +00001048 ScaleKernelInfo(kernel, 1.0, NormalizeValue); /* Normalize */
1049 RotateKernelInfo(kernel, args->xi); /* Rotate by angle */
anthony602ab9b2010-01-05 08:06:50 +00001050 break;
1051 }
anthonyc1061722010-05-14 06:23:49 +00001052
anthony3c10fc82010-05-13 02:40:51 +00001053 /* Convolution Kernels - Well Known Constants */
anthony3c10fc82010-05-13 02:40:51 +00001054 case LaplacianKernel:
anthonyc1061722010-05-14 06:23:49 +00001055 {
anthony3c10fc82010-05-13 02:40:51 +00001056 switch ( (int) args->rho ) {
anthony3dd0f622010-05-13 12:57:32 +00001057 case 0:
1058 default: /* default laplacian 'edge' filter */
anthonyc1061722010-05-14 06:23:49 +00001059 kernel=ParseKernelArray("3: -1,-1,-1 -1,8,-1 -1,-1,-1");
anthony3dd0f622010-05-13 12:57:32 +00001060 break;
anthony3c10fc82010-05-13 02:40:51 +00001061 case 1:
anthonyc1061722010-05-14 06:23:49 +00001062 kernel=ParseKernelArray("3: 0,-1,0 -1,4,-1 0,-1,0");
anthony3c10fc82010-05-13 02:40:51 +00001063 break;
1064 case 2:
anthonyc1061722010-05-14 06:23:49 +00001065 kernel=ParseKernelArray("3: 1,-2,1 -2,4,-2 1,-2,1");
anthony3c10fc82010-05-13 02:40:51 +00001066 break;
anthony3dd0f622010-05-13 12:57:32 +00001067 case 3: /* a 5x5 laplacian */
anthony3c10fc82010-05-13 02:40:51 +00001068 kernel=ParseKernelArray(
anthonyc1061722010-05-14 06:23:49 +00001069 "5: -4,-1,0,-1,-4 -1,2,3,2,-1 0,3,4,3,0 -1,2,3,2,-1 -4,-1,0,-1,-4");
anthony3c10fc82010-05-13 02:40:51 +00001070 break;
anthony3dd0f622010-05-13 12:57:32 +00001071 case 4: /* a 7x7 laplacian */
anthony3c10fc82010-05-13 02:40:51 +00001072 kernel=ParseKernelArray(
anthonyc1061722010-05-14 06:23:49 +00001073 "7:-10,-5,-2,-1,-2,-5,-10 -5,0,3,4,3,0,-5 -2,3,6,7,6,3,-2 -1,4,7,8,7,4,-1 -2,3,6,7,6,3,-2 -5,0,3,4,3,0,-5 -10,-5,-2,-1,-2,-5,-10" );
anthony3c10fc82010-05-13 02:40:51 +00001074 break;
1075 }
1076 if (kernel == (KernelInfo *) NULL)
1077 return(kernel);
1078 kernel->type = type;
1079 break;
1080 }
anthonyc1061722010-05-14 06:23:49 +00001081 case SobelKernel:
anthony602ab9b2010-01-05 08:06:50 +00001082 {
anthonyc1061722010-05-14 06:23:49 +00001083 kernel=ParseKernelArray("3: -1,0,1 -2,0,2 -1,0,1");
1084 if (kernel == (KernelInfo *) NULL)
1085 return(kernel);
1086 kernel->type = type;
1087 RotateKernelInfo(kernel, args->rho); /* Rotate by angle */
1088 break;
1089 }
1090 case RobertsKernel:
1091 {
1092 kernel=ParseKernelArray("3: 0,0,0 -1,1,0 0,0,0");
1093 if (kernel == (KernelInfo *) NULL)
1094 return(kernel);
1095 kernel->type = type;
1096 RotateKernelInfo(kernel, args->rho); /* Rotate by angle */
1097 break;
1098 }
1099 case PrewittKernel:
1100 {
1101 kernel=ParseKernelArray("3: -1,1,1 0,0,0 -1,1,1");
1102 if (kernel == (KernelInfo *) NULL)
1103 return(kernel);
1104 kernel->type = type;
1105 RotateKernelInfo(kernel, args->rho); /* Rotate by angle */
1106 break;
1107 }
1108 case CompassKernel:
1109 {
1110 kernel=ParseKernelArray("3: -1,1,1 -1,-2,1 -1,1,1");
1111 if (kernel == (KernelInfo *) NULL)
1112 return(kernel);
1113 kernel->type = type;
1114 RotateKernelInfo(kernel, args->rho); /* Rotate by angle */
1115 break;
1116 }
1117 /* Boolean Kernels */
1118 case DiamondKernel:
1119 {
1120 if (args->rho < 1.0)
1121 kernel->width = kernel->height = 3; /* default radius = 1 */
1122 else
1123 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
1124 kernel->x = kernel->y = (long) (kernel->width-1)/2;
1125
1126 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
1127 kernel->height*sizeof(double));
1128 if (kernel->values == (double *) NULL)
1129 return(DestroyKernelInfo(kernel));
1130
1131 /* set all kernel values within diamond area to scale given */
1132 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
1133 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
1134 if ((labs(u)+labs(v)) <= (long)kernel->x)
1135 kernel->positive_range += kernel->values[i] = args->sigma;
1136 else
1137 kernel->values[i] = nan;
1138 kernel->minimum = kernel->maximum = args->sigma; /* a flat shape */
1139 break;
1140 }
1141 case SquareKernel:
1142 case RectangleKernel:
1143 { double
1144 scale;
anthony602ab9b2010-01-05 08:06:50 +00001145 if ( type == SquareKernel )
1146 {
1147 if (args->rho < 1.0)
anthonyc94cdb02010-01-06 08:15:29 +00001148 kernel->width = kernel->height = 3; /* default radius = 1 */
anthony602ab9b2010-01-05 08:06:50 +00001149 else
cristy150989e2010-02-01 14:59:39 +00001150 kernel->width = kernel->height = (unsigned long) (2*args->rho+1);
cristyc99304f2010-02-01 15:26:27 +00001151 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony4fd27e22010-02-07 08:17:18 +00001152 scale = args->sigma;
anthony602ab9b2010-01-05 08:06:50 +00001153 }
1154 else {
cristy2be15382010-01-21 02:38:03 +00001155 /* NOTE: user defaults set in "AcquireKernelInfo()" */
anthony602ab9b2010-01-05 08:06:50 +00001156 if ( args->rho < 1.0 || args->sigma < 1.0 )
anthony83ba99b2010-01-24 08:48:15 +00001157 return(DestroyKernelInfo(kernel)); /* invalid args given */
anthony602ab9b2010-01-05 08:06:50 +00001158 kernel->width = (unsigned long)args->rho;
1159 kernel->height = (unsigned long)args->sigma;
1160 if ( args->xi < 0.0 || args->xi > (double)kernel->width ||
1161 args->psi < 0.0 || args->psi > (double)kernel->height )
anthony83ba99b2010-01-24 08:48:15 +00001162 return(DestroyKernelInfo(kernel)); /* invalid args given */
cristyc99304f2010-02-01 15:26:27 +00001163 kernel->x = (long) args->xi;
1164 kernel->y = (long) args->psi;
anthony4fd27e22010-02-07 08:17:18 +00001165 scale = 1.0;
anthony602ab9b2010-01-05 08:06:50 +00001166 }
1167 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
1168 kernel->height*sizeof(double));
1169 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +00001170 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +00001171
anthony3dd0f622010-05-13 12:57:32 +00001172 /* set all kernel values to scale given */
cristy150989e2010-02-01 14:59:39 +00001173 u=(long) kernel->width*kernel->height;
1174 for ( i=0; i < u; i++)
anthony4fd27e22010-02-07 08:17:18 +00001175 kernel->values[i] = scale;
1176 kernel->minimum = kernel->maximum = scale; /* a flat shape */
1177 kernel->positive_range = scale*u;
anthonycc6c8362010-01-25 04:14:01 +00001178 break;
anthony602ab9b2010-01-05 08:06:50 +00001179 }
anthony602ab9b2010-01-05 08:06:50 +00001180 case DiskKernel:
1181 {
anthonyc1061722010-05-14 06:23:49 +00001182 long limit = (long)(args->rho*args->rho);
anthony83ba99b2010-01-24 08:48:15 +00001183 if (args->rho < 0.1) /* default radius approx 3.5 */
1184 kernel->width = kernel->height = 7L, limit = 10L;
anthony602ab9b2010-01-05 08:06:50 +00001185 else
1186 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
cristyc99304f2010-02-01 15:26:27 +00001187 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony602ab9b2010-01-05 08:06:50 +00001188
1189 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
1190 kernel->height*sizeof(double));
1191 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +00001192 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +00001193
anthony3dd0f622010-05-13 12:57:32 +00001194 /* set all kernel values within disk area to scale given */
1195 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
cristyc99304f2010-02-01 15:26:27 +00001196 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
anthony602ab9b2010-01-05 08:06:50 +00001197 if ((u*u+v*v) <= limit)
anthony4fd27e22010-02-07 08:17:18 +00001198 kernel->positive_range += kernel->values[i] = args->sigma;
anthony602ab9b2010-01-05 08:06:50 +00001199 else
1200 kernel->values[i] = nan;
anthony4fd27e22010-02-07 08:17:18 +00001201 kernel->minimum = kernel->maximum = args->sigma; /* a flat shape */
anthony602ab9b2010-01-05 08:06:50 +00001202 break;
1203 }
1204 case PlusKernel:
1205 {
1206 if (args->rho < 1.0)
anthonyc94cdb02010-01-06 08:15:29 +00001207 kernel->width = kernel->height = 5; /* default radius 2 */
anthony602ab9b2010-01-05 08:06:50 +00001208 else
1209 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
cristyc99304f2010-02-01 15:26:27 +00001210 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony602ab9b2010-01-05 08:06:50 +00001211
1212 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
1213 kernel->height*sizeof(double));
1214 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +00001215 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +00001216
anthony3dd0f622010-05-13 12:57:32 +00001217 /* set all kernel values along axises to given scale */
cristyc99304f2010-02-01 15:26:27 +00001218 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
1219 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
anthony4fd27e22010-02-07 08:17:18 +00001220 kernel->values[i] = (u == 0 || v == 0) ? args->sigma : nan;
1221 kernel->minimum = kernel->maximum = args->sigma; /* a flat shape */
1222 kernel->positive_range = args->sigma*(kernel->width*2.0 - 1.0);
anthony602ab9b2010-01-05 08:06:50 +00001223 break;
1224 }
anthony3dd0f622010-05-13 12:57:32 +00001225 case CrossKernel:
1226 {
1227 if (args->rho < 1.0)
1228 kernel->width = kernel->height = 5; /* default radius 2 */
1229 else
1230 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
1231 kernel->x = kernel->y = (long) (kernel->width-1)/2;
1232
1233 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
1234 kernel->height*sizeof(double));
1235 if (kernel->values == (double *) NULL)
1236 return(DestroyKernelInfo(kernel));
1237
1238 /* set all kernel values along axises to given scale */
1239 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
1240 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
1241 kernel->values[i] = (u == v || u == -v) ? args->sigma : nan;
1242 kernel->minimum = kernel->maximum = args->sigma; /* a flat shape */
1243 kernel->positive_range = args->sigma*(kernel->width*2.0 - 1.0);
1244 break;
1245 }
1246 /* HitAndMiss Kernels */
anthonyc1061722010-05-14 06:23:49 +00001247 case RingKernel:
anthony3dd0f622010-05-13 12:57:32 +00001248 case PeaksKernel:
1249 {
1250 long
1251 limit1,
anthonyc1061722010-05-14 06:23:49 +00001252 limit2,
1253 scale;
anthony3dd0f622010-05-13 12:57:32 +00001254
1255 if (args->rho < args->sigma)
1256 {
1257 kernel->width = ((unsigned long)args->sigma)*2+1;
1258 limit1 = (long)args->rho*args->rho;
1259 limit2 = (long)args->sigma*args->sigma;
1260 }
1261 else
1262 {
1263 kernel->width = ((unsigned long)args->rho)*2+1;
1264 limit1 = (long)args->sigma*args->sigma;
1265 limit2 = (long)args->rho*args->rho;
1266 }
anthonyc1061722010-05-14 06:23:49 +00001267 if ( limit2 <= 0 )
1268 kernel->width = 7L, limit1 = 7L, limit2 = 11L;
1269
anthony3dd0f622010-05-13 12:57:32 +00001270 kernel->height = kernel->width;
1271 kernel->x = kernel->y = (long) (kernel->width-1)/2;
1272 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
1273 kernel->height*sizeof(double));
1274 if (kernel->values == (double *) NULL)
1275 return(DestroyKernelInfo(kernel));
1276
anthonyc1061722010-05-14 06:23:49 +00001277 /* set a ring of points of 'scale' ( 0.0 for PeaksKernel ) */
1278 scale = ( type == PeaksKernel) ? 0.0 : args->xi;
anthony3dd0f622010-05-13 12:57:32 +00001279 for ( i=0, v= -kernel->y; v <= (long)kernel->y; v++)
1280 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
1281 { long radius=u*u+v*v;
anthonyc1061722010-05-14 06:23:49 +00001282 if (limit1 < radius && radius <= limit2)
1283 kernel->positive_range += kernel->values[i] = scale;
anthony3dd0f622010-05-13 12:57:32 +00001284 else
1285 kernel->values[i] = nan;
1286 }
anthonyc1061722010-05-14 06:23:49 +00001287 kernel->minimum = kernel->minimum = scale;
1288 if ( type == PeaksKernel ) {
1289 /* set the central point in the middle */
1290 kernel->values[kernel->x+kernel->y*kernel->width] = 1.0;
1291 kernel->positive_range = 1.0;
1292 kernel->maximum = 1.0;
1293 }
anthony3dd0f622010-05-13 12:57:32 +00001294 break;
1295 }
1296 case CornersKernel:
1297 {
anthony3dd0f622010-05-13 12:57:32 +00001298 kernel=ParseKernelArray("3x3: 0,0,- 0,1,1 -,1,-");
1299 if (kernel == (KernelInfo *) NULL)
1300 return(kernel);
1301 kernel->type = type;
1302 ExpandKernelInfo(kernel, 90.0); /* Create a list of 4 rotated kernels */
1303 break;
1304 }
1305 case LineEndsKernel:
1306 {
anthony3dd0f622010-05-13 12:57:32 +00001307 kernel=ParseKernelArray("3x3: 0,-,- 0,1,0 0,0,0");
1308 if (kernel == (KernelInfo *) NULL)
1309 return(kernel);
1310 kernel->type = type;
1311 ExpandKernelInfo(kernel, 45.0); /* Create a list of 8 rotated kernels */
1312 break;
1313 }
1314 case LineJunctionsKernel:
1315 {
1316 KernelInfo
1317 *new_kernel;
anthony3dd0f622010-05-13 12:57:32 +00001318 /* first set of 4 kernels */
1319 kernel=ParseKernelArray("3x3: -,1,- -,1,- 1,-,1");
1320 if (kernel == (KernelInfo *) NULL)
1321 return(kernel);
1322 kernel->type = type;
1323 ExpandKernelInfo(kernel, 90.0);
1324 /* append second set of 4 kernels */
1325 new_kernel=ParseKernelArray("3x3: 1,-,- -,1,- 1,-,1");
1326 if (new_kernel == (KernelInfo *) NULL)
1327 return(DestroyKernelInfo(kernel));
1328 kernel->type = type;
1329 ExpandKernelInfo(new_kernel, 90.0);
1330 LastKernelInfo(kernel)->next = new_kernel;
1331 /* append Thrid set of 4 kernels */
1332 new_kernel=ParseKernelArray("3x3: -,1,- -,1,1 1,-,-");
1333 if (new_kernel == (KernelInfo *) NULL)
1334 return(DestroyKernelInfo(kernel));
1335 kernel->type = type;
1336 ExpandKernelInfo(new_kernel, 90.0);
1337 LastKernelInfo(kernel)->next = new_kernel;
1338 break;
1339 }
1340 case ConvexHullKernel:
1341 {
1342 KernelInfo
1343 *new_kernel;
anthony3dd0f622010-05-13 12:57:32 +00001344 /* first set of 4 kernels */
1345 kernel=ParseKernelArray("3x3: 1,1,- 1,0,- 1,-,0");
1346 if (kernel == (KernelInfo *) NULL)
1347 return(kernel);
1348 kernel->type = type;
1349 ExpandKernelInfo(kernel, 90.0);
1350 /* append second set of 4 kernels */
1351 new_kernel=ParseKernelArray("3x3: -,1,1 -,0,1 0,-,1");
1352 if (new_kernel == (KernelInfo *) NULL)
1353 return(DestroyKernelInfo(kernel));
1354 kernel->type = type;
1355 ExpandKernelInfo(new_kernel, 90.0);
1356 LastKernelInfo(kernel)->next = new_kernel;
1357 break;
1358 }
1359 case SkeletonKernel:
1360 {
1361 KernelInfo
1362 *new_kernel;
anthony3dd0f622010-05-13 12:57:32 +00001363 /* first set of 4 kernels - corners */
1364 kernel=ParseKernelArray("3x3: 0,0,- 0,1,1 -,1,-");
1365 if (kernel == (KernelInfo *) NULL)
1366 return(kernel);
1367 kernel->type = type;
1368 ExpandKernelInfo(kernel, 90);
1369 /* append second set of 4 kernels - edge middles */
1370 new_kernel=ParseKernelArray("3x3: 0,0,0 -,1,- 1,1,1");
1371 if (new_kernel == (KernelInfo *) NULL)
1372 return(DestroyKernelInfo(kernel));
1373 kernel->type = type;
1374 ExpandKernelInfo(new_kernel, 90);
1375 LastKernelInfo(kernel)->next = new_kernel;
1376 break;
1377 }
anthony602ab9b2010-01-05 08:06:50 +00001378 /* Distance Measuring Kernels */
1379 case ChebyshevKernel:
1380 {
anthony602ab9b2010-01-05 08:06:50 +00001381 if (args->rho < 1.0)
anthonyc94cdb02010-01-06 08:15:29 +00001382 kernel->width = kernel->height = 3; /* default radius = 1 */
anthony602ab9b2010-01-05 08:06:50 +00001383 else
1384 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
cristyc99304f2010-02-01 15:26:27 +00001385 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony602ab9b2010-01-05 08:06:50 +00001386
1387 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
1388 kernel->height*sizeof(double));
1389 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +00001390 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +00001391
cristyc99304f2010-02-01 15:26:27 +00001392 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
1393 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
1394 kernel->positive_range += ( kernel->values[i] =
anthonyc84dce52010-05-07 05:42:23 +00001395 args->sigma*((labs(u)>labs(v)) ? labs(u) : labs(v)) );
cristyc99304f2010-02-01 15:26:27 +00001396 kernel->maximum = kernel->values[0];
anthony602ab9b2010-01-05 08:06:50 +00001397 break;
1398 }
1399 case ManhattenKernel:
1400 {
anthony602ab9b2010-01-05 08:06:50 +00001401 if (args->rho < 1.0)
anthonyc94cdb02010-01-06 08:15:29 +00001402 kernel->width = kernel->height = 3; /* default radius = 1 */
anthony602ab9b2010-01-05 08:06:50 +00001403 else
1404 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
cristyc99304f2010-02-01 15:26:27 +00001405 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony602ab9b2010-01-05 08:06:50 +00001406
1407 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
1408 kernel->height*sizeof(double));
1409 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +00001410 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +00001411
cristyc99304f2010-02-01 15:26:27 +00001412 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
1413 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
1414 kernel->positive_range += ( kernel->values[i] =
anthonyc84dce52010-05-07 05:42:23 +00001415 args->sigma*(labs(u)+labs(v)) );
cristyc99304f2010-02-01 15:26:27 +00001416 kernel->maximum = kernel->values[0];
anthony602ab9b2010-01-05 08:06:50 +00001417 break;
1418 }
1419 case EuclideanKernel:
1420 {
anthony602ab9b2010-01-05 08:06:50 +00001421 if (args->rho < 1.0)
anthonyc94cdb02010-01-06 08:15:29 +00001422 kernel->width = kernel->height = 3; /* default radius = 1 */
anthony602ab9b2010-01-05 08:06:50 +00001423 else
1424 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
cristyc99304f2010-02-01 15:26:27 +00001425 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony602ab9b2010-01-05 08:06:50 +00001426
1427 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
1428 kernel->height*sizeof(double));
1429 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +00001430 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +00001431
cristyc99304f2010-02-01 15:26:27 +00001432 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
1433 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
1434 kernel->positive_range += ( kernel->values[i] =
anthonyc84dce52010-05-07 05:42:23 +00001435 args->sigma*sqrt((double)(u*u+v*v)) );
cristyc99304f2010-02-01 15:26:27 +00001436 kernel->maximum = kernel->values[0];
anthony602ab9b2010-01-05 08:06:50 +00001437 break;
1438 }
anthony602ab9b2010-01-05 08:06:50 +00001439 default:
anthonyc1061722010-05-14 06:23:49 +00001440 {
1441 /* Generate a No-Op minimal kernel - 1x1 pixel */
1442 kernel=ParseKernelArray("1");
1443 if (kernel == (KernelInfo *) NULL)
1444 return(kernel);
1445 kernel->type = UndefinedKernel;
1446 break;
1447 }
anthony602ab9b2010-01-05 08:06:50 +00001448 break;
1449 }
1450
1451 return(kernel);
1452}
anthonyc94cdb02010-01-06 08:15:29 +00001453
anthony602ab9b2010-01-05 08:06:50 +00001454/*
1455%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1456% %
1457% %
1458% %
cristy6771f1e2010-03-05 19:43:39 +00001459% C l o n e K e r n e l I n f o %
anthony4fd27e22010-02-07 08:17:18 +00001460% %
1461% %
1462% %
1463%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1464%
anthony1b2bc0a2010-05-12 05:25:22 +00001465% CloneKernelInfo() creates a new clone of the given Kernel List so that its
1466% can be modified without effecting the original. The cloned kernel should
1467% be destroyed using DestoryKernelInfo() when no longer needed.
anthony7a01dcf2010-05-11 12:25:52 +00001468%
cristye6365592010-04-02 17:31:23 +00001469% The format of the CloneKernelInfo method is:
anthony4fd27e22010-02-07 08:17:18 +00001470%
anthony930be612010-02-08 04:26:15 +00001471% KernelInfo *CloneKernelInfo(const KernelInfo *kernel)
anthony4fd27e22010-02-07 08:17:18 +00001472%
1473% A description of each parameter follows:
1474%
1475% o kernel: the Morphology/Convolution kernel to be cloned
1476%
1477*/
cristyef656912010-03-05 19:54:59 +00001478MagickExport KernelInfo *CloneKernelInfo(const KernelInfo *kernel)
anthony4fd27e22010-02-07 08:17:18 +00001479{
1480 register long
1481 i;
1482
cristy19eb6412010-04-23 14:42:29 +00001483 KernelInfo
anthony7a01dcf2010-05-11 12:25:52 +00001484 *new_kernel;
anthony4fd27e22010-02-07 08:17:18 +00001485
1486 assert(kernel != (KernelInfo *) NULL);
anthony7a01dcf2010-05-11 12:25:52 +00001487 new_kernel=(KernelInfo *) AcquireMagickMemory(sizeof(*kernel));
1488 if (new_kernel == (KernelInfo *) NULL)
1489 return(new_kernel);
1490 *new_kernel=(*kernel); /* copy values in structure */
anthony7a01dcf2010-05-11 12:25:52 +00001491
1492 /* replace the values with a copy of the values */
1493 new_kernel->values=(double *) AcquireQuantumMemory(kernel->width,
cristy19eb6412010-04-23 14:42:29 +00001494 kernel->height*sizeof(double));
anthony7a01dcf2010-05-11 12:25:52 +00001495 if (new_kernel->values == (double *) NULL)
1496 return(DestroyKernelInfo(new_kernel));
anthony4fd27e22010-02-07 08:17:18 +00001497 for (i=0; i < (long) (kernel->width*kernel->height); i++)
anthony7a01dcf2010-05-11 12:25:52 +00001498 new_kernel->values[i]=kernel->values[i];
anthony1b2bc0a2010-05-12 05:25:22 +00001499
1500 /* Also clone the next kernel in the kernel list */
1501 if ( kernel->next != (KernelInfo *) NULL ) {
1502 new_kernel->next = CloneKernelInfo(kernel->next);
1503 if ( new_kernel->next == (KernelInfo *) NULL )
1504 return(DestroyKernelInfo(new_kernel));
1505 }
1506
anthony7a01dcf2010-05-11 12:25:52 +00001507 return(new_kernel);
anthony4fd27e22010-02-07 08:17:18 +00001508}
1509
1510/*
1511%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1512% %
1513% %
1514% %
anthony83ba99b2010-01-24 08:48:15 +00001515% D e s t r o y K e r n e l I n f o %
anthony602ab9b2010-01-05 08:06:50 +00001516% %
1517% %
1518% %
1519%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1520%
anthony83ba99b2010-01-24 08:48:15 +00001521% DestroyKernelInfo() frees the memory used by a Convolution/Morphology
1522% kernel.
anthony602ab9b2010-01-05 08:06:50 +00001523%
anthony83ba99b2010-01-24 08:48:15 +00001524% The format of the DestroyKernelInfo method is:
anthony602ab9b2010-01-05 08:06:50 +00001525%
anthony83ba99b2010-01-24 08:48:15 +00001526% KernelInfo *DestroyKernelInfo(KernelInfo *kernel)
anthony602ab9b2010-01-05 08:06:50 +00001527%
1528% A description of each parameter follows:
1529%
1530% o kernel: the Morphology/Convolution kernel to be destroyed
1531%
1532*/
1533
anthony83ba99b2010-01-24 08:48:15 +00001534MagickExport KernelInfo *DestroyKernelInfo(KernelInfo *kernel)
anthony602ab9b2010-01-05 08:06:50 +00001535{
cristy2be15382010-01-21 02:38:03 +00001536 assert(kernel != (KernelInfo *) NULL);
anthony4fd27e22010-02-07 08:17:18 +00001537
anthony7a01dcf2010-05-11 12:25:52 +00001538 if ( kernel->next != (KernelInfo *) NULL )
1539 kernel->next = DestroyKernelInfo(kernel->next);
1540
1541 kernel->values = (double *)RelinquishMagickMemory(kernel->values);
1542 kernel = (KernelInfo *) RelinquishMagickMemory(kernel);
anthony602ab9b2010-01-05 08:06:50 +00001543 return(kernel);
1544}
anthonyc94cdb02010-01-06 08:15:29 +00001545
1546/*
1547%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1548% %
1549% %
1550% %
anthony3c10fc82010-05-13 02:40:51 +00001551% E x p a n d K e r n e l I n f o %
1552% %
1553% %
1554% %
1555%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1556%
1557% ExpandKernelInfo() takes a single kernel, and expands it into a list
1558% of kernels each incrementally rotated the angle given.
1559%
1560% WARNING: 45 degree rotations only works for 3x3 kernels.
1561% While 90 degree roatations only works for linear and square kernels
1562%
1563% The format of the RotateKernelInfo method is:
1564%
1565% void ExpandKernelInfo(KernelInfo *kernel, double angle)
1566%
1567% A description of each parameter follows:
1568%
1569% o kernel: the Morphology/Convolution kernel
1570%
1571% o angle: angle to rotate in degrees
1572%
1573% This function is only internel to this module, as it is not finalized,
1574% especially with regard to non-orthogonal angles, and rotation of larger
1575% 2D kernels.
1576*/
1577static void ExpandKernelInfo(KernelInfo *kernel, double angle)
1578{
1579 KernelInfo
cristya9a61ad2010-05-13 12:47:41 +00001580 *clone,
anthony3c10fc82010-05-13 02:40:51 +00001581 *last;
cristya9a61ad2010-05-13 12:47:41 +00001582
anthony3c10fc82010-05-13 02:40:51 +00001583 double
1584 a;
1585
1586 last = kernel;
1587 for (a=angle; a<355.0; a+=angle) {
cristya9a61ad2010-05-13 12:47:41 +00001588 clone = CloneKernelInfo(last);
1589 RotateKernelInfo(clone, angle);
1590 last->next = clone;
1591 last = clone;
anthony3c10fc82010-05-13 02:40:51 +00001592 }
1593}
1594
1595/*
1596%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1597% %
1598% %
1599% %
anthony29188a82010-01-22 10:12:34 +00001600% 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 +00001601% %
1602% %
1603% %
1604%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1605%
anthony29188a82010-01-22 10:12:34 +00001606% MorphologyImageChannel() applies a user supplied kernel to the image
1607% according to the given mophology method.
anthony602ab9b2010-01-05 08:06:50 +00001608%
1609% The given kernel is assumed to have been pre-scaled appropriatally, usally
1610% by the kernel generator.
1611%
1612% The format of the MorphologyImage method is:
1613%
cristyef656912010-03-05 19:54:59 +00001614% Image *MorphologyImage(const Image *image,MorphologyMethod method,
1615% const long iterations,KernelInfo *kernel,ExceptionInfo *exception)
anthony29188a82010-01-22 10:12:34 +00001616% Image *MorphologyImageChannel(const Image *image, const ChannelType
cristyef656912010-03-05 19:54:59 +00001617% channel,MorphologyMethod method,const long iterations,
1618% KernelInfo *kernel,ExceptionInfo *exception)
anthony602ab9b2010-01-05 08:06:50 +00001619%
1620% A description of each parameter follows:
1621%
1622% o image: the image.
1623%
1624% o method: the morphology method to be applied.
1625%
1626% o iterations: apply the operation this many times (or no change).
1627% A value of -1 means loop until no change found.
1628% How this is applied may depend on the morphology method.
1629% Typically this is a value of 1.
1630%
1631% o channel: the channel type.
1632%
1633% o kernel: An array of double representing the morphology kernel.
anthony29188a82010-01-22 10:12:34 +00001634% Warning: kernel may be normalized for the Convolve method.
anthony602ab9b2010-01-05 08:06:50 +00001635%
1636% o exception: return any errors or warnings in this structure.
1637%
1638%
1639% TODO: bias and auto-scale handling of the kernel for convolution
1640% The given kernel is assumed to have been pre-scaled appropriatally, usally
1641% by the kernel generator.
1642%
1643*/
1644
anthony930be612010-02-08 04:26:15 +00001645
anthony602ab9b2010-01-05 08:06:50 +00001646/* Internal function
anthony7a01dcf2010-05-11 12:25:52 +00001647 * Apply the low-level Morphology Method Primatives using the given Kernel
anthony930be612010-02-08 04:26:15 +00001648 * Returning the number of pixels that changed.
1649 * Two pre-created images must be provided, no image is created.
anthony602ab9b2010-01-05 08:06:50 +00001650 */
anthony7a01dcf2010-05-11 12:25:52 +00001651static unsigned long MorphologyPrimative(const Image *image, Image
anthony602ab9b2010-01-05 08:06:50 +00001652 *result_image, const MorphologyMethod method, const ChannelType channel,
cristy2be15382010-01-21 02:38:03 +00001653 const KernelInfo *kernel, ExceptionInfo *exception)
anthony602ab9b2010-01-05 08:06:50 +00001654{
cristy2be15382010-01-21 02:38:03 +00001655#define MorphologyTag "Morphology/Image"
anthony602ab9b2010-01-05 08:06:50 +00001656
1657 long
cristy150989e2010-02-01 14:59:39 +00001658 progress,
anthony29188a82010-01-22 10:12:34 +00001659 y, offx, offy,
anthony602ab9b2010-01-05 08:06:50 +00001660 changed;
1661
1662 MagickBooleanType
1663 status;
1664
1665 MagickPixelPacket
1666 bias;
1667
1668 CacheView
1669 *p_view,
1670 *q_view;
1671
anthony4fd27e22010-02-07 08:17:18 +00001672 /* Only the most basic morphology is actually performed by this routine */
anthony4fd27e22010-02-07 08:17:18 +00001673
anthony602ab9b2010-01-05 08:06:50 +00001674 /*
anthony4fd27e22010-02-07 08:17:18 +00001675 Apply Basic Morphology to Image.
anthony602ab9b2010-01-05 08:06:50 +00001676 */
1677 status=MagickTrue;
1678 changed=0;
1679 progress=0;
1680
1681 GetMagickPixelPacket(image,&bias);
1682 SetMagickPixelPacketBias(image,&bias);
anthonycc6c8362010-01-25 04:14:01 +00001683 /* Future: handle auto-bias from user, based on kernel input */
anthony602ab9b2010-01-05 08:06:50 +00001684
1685 p_view=AcquireCacheView(image);
1686 q_view=AcquireCacheView(result_image);
anthony29188a82010-01-22 10:12:34 +00001687
anthonycc6c8362010-01-25 04:14:01 +00001688 /* Some methods (including convolve) needs use a reflected kernel.
1689 * Adjust 'origin' offsets for this reflected kernel.
anthony29188a82010-01-22 10:12:34 +00001690 */
cristyc99304f2010-02-01 15:26:27 +00001691 offx = kernel->x;
1692 offy = kernel->y;
anthony29188a82010-01-22 10:12:34 +00001693 switch(method) {
anthony930be612010-02-08 04:26:15 +00001694 case ConvolveMorphology:
1695 case DilateMorphology:
1696 case DilateIntensityMorphology:
1697 case DistanceMorphology:
anthony5ef8e942010-05-11 06:51:12 +00001698 /* kernel needs to used with reflection about origin */
cristy150989e2010-02-01 14:59:39 +00001699 offx = (long) kernel->width-offx-1;
1700 offy = (long) kernel->height-offy-1;
anthony29188a82010-01-22 10:12:34 +00001701 break;
anthony5ef8e942010-05-11 06:51:12 +00001702 case ErodeMorphology:
1703 case ErodeIntensityMorphology:
1704 case HitAndMissMorphology:
1705 case ThinningMorphology:
1706 case ThickenMorphology:
1707 /* kernel is user as is, without reflection */
1708 break;
anthony930be612010-02-08 04:26:15 +00001709 default:
anthony5ef8e942010-05-11 06:51:12 +00001710 perror("Not a low level Morphology Method");
anthony930be612010-02-08 04:26:15 +00001711 break;
anthony29188a82010-01-22 10:12:34 +00001712 }
1713
anthony602ab9b2010-01-05 08:06:50 +00001714#if defined(MAGICKCORE_OPENMP_SUPPORT)
1715 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
1716#endif
cristy150989e2010-02-01 14:59:39 +00001717 for (y=0; y < (long) image->rows; y++)
anthony602ab9b2010-01-05 08:06:50 +00001718 {
1719 MagickBooleanType
1720 sync;
1721
1722 register const PixelPacket
1723 *restrict p;
1724
1725 register const IndexPacket
1726 *restrict p_indexes;
1727
1728 register PixelPacket
1729 *restrict q;
1730
1731 register IndexPacket
1732 *restrict q_indexes;
1733
cristy150989e2010-02-01 14:59:39 +00001734 register long
anthony602ab9b2010-01-05 08:06:50 +00001735 x;
1736
anthony29188a82010-01-22 10:12:34 +00001737 unsigned long
anthony602ab9b2010-01-05 08:06:50 +00001738 r;
1739
1740 if (status == MagickFalse)
1741 continue;
anthony29188a82010-01-22 10:12:34 +00001742 p=GetCacheViewVirtualPixels(p_view, -offx, y-offy,
1743 image->columns+kernel->width, kernel->height, exception);
anthony602ab9b2010-01-05 08:06:50 +00001744 q=GetCacheViewAuthenticPixels(q_view,0,y,result_image->columns,1,
1745 exception);
1746 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
1747 {
1748 status=MagickFalse;
1749 continue;
1750 }
1751 p_indexes=GetCacheViewVirtualIndexQueue(p_view);
1752 q_indexes=GetCacheViewAuthenticIndexQueue(q_view);
anthony29188a82010-01-22 10:12:34 +00001753 r = (image->columns+kernel->width)*offy+offx; /* constant */
1754
cristy150989e2010-02-01 14:59:39 +00001755 for (x=0; x < (long) image->columns; x++)
anthony602ab9b2010-01-05 08:06:50 +00001756 {
cristy150989e2010-02-01 14:59:39 +00001757 long
anthony602ab9b2010-01-05 08:06:50 +00001758 v;
1759
cristy150989e2010-02-01 14:59:39 +00001760 register long
anthony602ab9b2010-01-05 08:06:50 +00001761 u;
1762
1763 register const double
1764 *restrict k;
1765
1766 register const PixelPacket
1767 *restrict k_pixels;
1768
1769 register const IndexPacket
1770 *restrict k_indexes;
1771
1772 MagickPixelPacket
anthony5ef8e942010-05-11 06:51:12 +00001773 result,
1774 min,
1775 max;
anthony602ab9b2010-01-05 08:06:50 +00001776
anthony29188a82010-01-22 10:12:34 +00001777 /* Copy input to ouput image for unused channels
anthony83ba99b2010-01-24 08:48:15 +00001778 * This removes need for 'cloning' a new image every iteration
anthony29188a82010-01-22 10:12:34 +00001779 */
anthony602ab9b2010-01-05 08:06:50 +00001780 *q = p[r];
1781 if (image->colorspace == CMYKColorspace)
1782 q_indexes[x] = p_indexes[r];
1783
anthony5ef8e942010-05-11 06:51:12 +00001784 /* Defaults */
1785 min.red =
1786 min.green =
1787 min.blue =
1788 min.opacity =
1789 min.index = (MagickRealType) QuantumRange;
1790 max.red =
1791 max.green =
1792 max.blue =
1793 max.opacity =
1794 max.index = (MagickRealType) 0;
1795 /* original pixel value */
1796 result.red = (MagickRealType) p[r].red;
1797 result.green = (MagickRealType) p[r].green;
1798 result.blue = (MagickRealType) p[r].blue;
1799 result.opacity = QuantumRange - (MagickRealType) p[r].opacity;
cristy9b1a5442010-05-13 12:35:40 +00001800 result.index = 0;
anthony5ef8e942010-05-11 06:51:12 +00001801 if ( image->colorspace == CMYKColorspace)
1802 result.index = (MagickRealType) p_indexes[r];
1803
anthony602ab9b2010-01-05 08:06:50 +00001804 switch (method) {
1805 case ConvolveMorphology:
anthony930be612010-02-08 04:26:15 +00001806 /* Set the user defined bias of the weighted average output
1807 **
1808 ** FUTURE: provide some way for internal functions to disable
anthony5ef8e942010-05-11 06:51:12 +00001809 ** user provided bias and scaling effects.
anthony930be612010-02-08 04:26:15 +00001810 */
anthony602ab9b2010-01-05 08:06:50 +00001811 result=bias;
anthony930be612010-02-08 04:26:15 +00001812 break;
anthony4fd27e22010-02-07 08:17:18 +00001813 case DilateIntensityMorphology:
1814 case ErodeIntensityMorphology:
anthony5ef8e942010-05-11 06:51:12 +00001815 result.red = 0.0; /* flag indicating when first match found */
anthony4fd27e22010-02-07 08:17:18 +00001816 break;
anthony602ab9b2010-01-05 08:06:50 +00001817 default:
anthony602ab9b2010-01-05 08:06:50 +00001818 break;
1819 }
1820
1821 switch ( method ) {
1822 case ConvolveMorphology:
anthony930be612010-02-08 04:26:15 +00001823 /* Weighted Average of pixels using reflected kernel
1824 **
1825 ** NOTE for correct working of this operation for asymetrical
1826 ** kernels, the kernel needs to be applied in its reflected form.
1827 ** That is its values needs to be reversed.
1828 **
1829 ** Correlation is actually the same as this but without reflecting
1830 ** the kernel, and thus 'lower-level' that Convolution. However
1831 ** as Convolution is the more common method used, and it does not
1832 ** really cost us much in terms of processing to use a reflected
anthony5ef8e942010-05-11 06:51:12 +00001833 ** kernel, so it is Convolution that is implemented.
anthony930be612010-02-08 04:26:15 +00001834 **
1835 ** Correlation will have its kernel reflected before calling
1836 ** this function to do a Convolve.
1837 **
1838 ** For more details of Correlation vs Convolution see
1839 ** http://www.cs.umd.edu/~djacobs/CMSC426/Convolution.pdf
1840 */
anthony5ef8e942010-05-11 06:51:12 +00001841 if (((channel & SyncChannels) != 0 ) &&
1842 (image->matte == MagickTrue))
1843 { /* Channel has a 'Sync' Flag, and Alpha Channel enabled.
1844 ** Weight the color channels with Alpha Channel so that
1845 ** transparent pixels are not part of the results.
1846 */
anthony602ab9b2010-01-05 08:06:50 +00001847 MagickRealType
anthony5ef8e942010-05-11 06:51:12 +00001848 alpha, /* color channel weighting : kernel*alpha */
1849 gamma; /* divisor, sum of weighting values */
anthony602ab9b2010-01-05 08:06:50 +00001850
1851 gamma=0.0;
anthony29188a82010-01-22 10:12:34 +00001852 k = &kernel->values[ kernel->width*kernel->height-1 ];
anthony602ab9b2010-01-05 08:06:50 +00001853 k_pixels = p;
1854 k_indexes = p_indexes;
cristy150989e2010-02-01 14:59:39 +00001855 for (v=0; v < (long) kernel->height; v++) {
1856 for (u=0; u < (long) kernel->width; u++, k--) {
anthony602ab9b2010-01-05 08:06:50 +00001857 if ( IsNan(*k) ) continue;
1858 alpha=(*k)*(QuantumScale*(QuantumRange-
1859 k_pixels[u].opacity));
1860 gamma += alpha;
1861 result.red += alpha*k_pixels[u].red;
1862 result.green += alpha*k_pixels[u].green;
1863 result.blue += alpha*k_pixels[u].blue;
anthony83ba99b2010-01-24 08:48:15 +00001864 result.opacity += (*k)*(QuantumRange-k_pixels[u].opacity);
anthony602ab9b2010-01-05 08:06:50 +00001865 if ( image->colorspace == CMYKColorspace)
1866 result.index += alpha*k_indexes[u];
1867 }
1868 k_pixels += image->columns+kernel->width;
1869 k_indexes += image->columns+kernel->width;
1870 }
1871 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
anthony83ba99b2010-01-24 08:48:15 +00001872 result.red *= gamma;
1873 result.green *= gamma;
1874 result.blue *= gamma;
1875 result.opacity *= gamma;
1876 result.index *= gamma;
anthony602ab9b2010-01-05 08:06:50 +00001877 }
anthony5ef8e942010-05-11 06:51:12 +00001878 else
1879 {
1880 /* No 'Sync' flag, or no Alpha involved.
1881 ** Convolution is simple individual channel weigthed sum.
1882 */
1883 k = &kernel->values[ kernel->width*kernel->height-1 ];
1884 k_pixels = p;
1885 k_indexes = p_indexes;
1886 for (v=0; v < (long) kernel->height; v++) {
1887 for (u=0; u < (long) kernel->width; u++, k--) {
1888 if ( IsNan(*k) ) continue;
1889 result.red += (*k)*k_pixels[u].red;
1890 result.green += (*k)*k_pixels[u].green;
1891 result.blue += (*k)*k_pixels[u].blue;
1892 result.opacity += (*k)*(QuantumRange-k_pixels[u].opacity);
1893 if ( image->colorspace == CMYKColorspace)
1894 result.index += (*k)*k_indexes[u];
1895 }
1896 k_pixels += image->columns+kernel->width;
1897 k_indexes += image->columns+kernel->width;
1898 }
1899 }
anthony602ab9b2010-01-05 08:06:50 +00001900 break;
1901
anthony4fd27e22010-02-07 08:17:18 +00001902 case ErodeMorphology:
anthony5ef8e942010-05-11 06:51:12 +00001903 /* Minimum Value within kernel neighbourhood
anthony930be612010-02-08 04:26:15 +00001904 **
1905 ** NOTE that the kernel is not reflected for this operation!
1906 **
1907 ** NOTE: in normal Greyscale Morphology, the kernel value should
1908 ** be added to the real value, this is currently not done, due to
1909 ** the nature of the boolean kernels being used.
1910 */
anthony4fd27e22010-02-07 08:17:18 +00001911 k = kernel->values;
1912 k_pixels = p;
1913 k_indexes = p_indexes;
1914 for (v=0; v < (long) kernel->height; v++) {
1915 for (u=0; u < (long) kernel->width; u++, k++) {
1916 if ( IsNan(*k) || (*k) < 0.5 ) continue;
anthony5ef8e942010-05-11 06:51:12 +00001917 Minimize(min.red, (double) k_pixels[u].red);
1918 Minimize(min.green, (double) k_pixels[u].green);
1919 Minimize(min.blue, (double) k_pixels[u].blue);
1920 Minimize(min.opacity,
anthonyd37a5cb2010-05-07 06:37:03 +00001921 QuantumRange-(double) k_pixels[u].opacity);
anthony4fd27e22010-02-07 08:17:18 +00001922 if ( image->colorspace == CMYKColorspace)
anthony5ef8e942010-05-11 06:51:12 +00001923 Minimize(min.index, (double) k_indexes[u]);
anthony4fd27e22010-02-07 08:17:18 +00001924 }
1925 k_pixels += image->columns+kernel->width;
1926 k_indexes += image->columns+kernel->width;
1927 }
1928 break;
1929
anthony5ef8e942010-05-11 06:51:12 +00001930
anthony83ba99b2010-01-24 08:48:15 +00001931 case DilateMorphology:
anthony5ef8e942010-05-11 06:51:12 +00001932 /* Maximum Value within kernel neighbourhood
anthony930be612010-02-08 04:26:15 +00001933 **
1934 ** NOTE for correct working of this operation for asymetrical
1935 ** kernels, the kernel needs to be applied in its reflected form.
1936 ** That is its values needs to be reversed.
1937 **
1938 ** NOTE: in normal Greyscale Morphology, the kernel value should
1939 ** be added to the real value, this is currently not done, due to
1940 ** the nature of the boolean kernels being used.
1941 **
1942 */
anthony29188a82010-01-22 10:12:34 +00001943 k = &kernel->values[ kernel->width*kernel->height-1 ];
anthony602ab9b2010-01-05 08:06:50 +00001944 k_pixels = p;
1945 k_indexes = p_indexes;
cristy150989e2010-02-01 14:59:39 +00001946 for (v=0; v < (long) kernel->height; v++) {
1947 for (u=0; u < (long) kernel->width; u++, k--) {
anthony602ab9b2010-01-05 08:06:50 +00001948 if ( IsNan(*k) || (*k) < 0.5 ) continue;
anthony5ef8e942010-05-11 06:51:12 +00001949 Maximize(max.red, (double) k_pixels[u].red);
1950 Maximize(max.green, (double) k_pixels[u].green);
1951 Maximize(max.blue, (double) k_pixels[u].blue);
1952 Maximize(max.opacity,
anthonyd37a5cb2010-05-07 06:37:03 +00001953 QuantumRange-(double) k_pixels[u].opacity);
anthony602ab9b2010-01-05 08:06:50 +00001954 if ( image->colorspace == CMYKColorspace)
anthony5ef8e942010-05-11 06:51:12 +00001955 Maximize(max.index, (double) k_indexes[u]);
anthony602ab9b2010-01-05 08:06:50 +00001956 }
1957 k_pixels += image->columns+kernel->width;
1958 k_indexes += image->columns+kernel->width;
1959 }
anthony602ab9b2010-01-05 08:06:50 +00001960 break;
1961
anthony5ef8e942010-05-11 06:51:12 +00001962 case HitAndMissMorphology:
1963 case ThinningMorphology:
1964 case ThickenMorphology:
1965 /* Minimum of Foreground Pixel minus Maxumum of Background Pixels
1966 **
1967 ** NOTE that the kernel is not reflected for this operation,
1968 ** and consists of both foreground and background pixel
1969 ** neighbourhoods, 0.0 for background, and 1.0 for foreground
1970 ** with either Nan or 0.5 values for don't care.
1971 **
1972 ** Note that this can produce negative results, though really
1973 ** only a positive match has any real value.
1974 */
1975 k = kernel->values;
1976 k_pixels = p;
1977 k_indexes = p_indexes;
1978 for (v=0; v < (long) kernel->height; v++) {
1979 for (u=0; u < (long) kernel->width; u++, k++) {
1980 if ( IsNan(*k) ) continue;
1981 if ( (*k) > 0.7 )
1982 { /* minimim of foreground pixels */
1983 Minimize(min.red, (double) k_pixels[u].red);
1984 Minimize(min.green, (double) k_pixels[u].green);
1985 Minimize(min.blue, (double) k_pixels[u].blue);
1986 Minimize(min.opacity,
1987 QuantumRange-(double) k_pixels[u].opacity);
1988 if ( image->colorspace == CMYKColorspace)
1989 Minimize(min.index, (double) k_indexes[u]);
1990 }
1991 else if ( (*k) < 0.3 )
1992 { /* maximum of background pixels */
1993 Maximize(max.red, (double) k_pixels[u].red);
1994 Maximize(max.green, (double) k_pixels[u].green);
1995 Maximize(max.blue, (double) k_pixels[u].blue);
1996 Maximize(max.opacity,
1997 QuantumRange-(double) k_pixels[u].opacity);
1998 if ( image->colorspace == CMYKColorspace)
1999 Maximize(max.index, (double) k_indexes[u]);
2000 }
2001 }
2002 k_pixels += image->columns+kernel->width;
2003 k_indexes += image->columns+kernel->width;
2004 }
2005 /* Pattern Match only if min fg larger than min bg pixels */
2006 min.red -= max.red; Maximize( min.red, 0.0 );
2007 min.green -= max.green; Maximize( min.green, 0.0 );
2008 min.blue -= max.blue; Maximize( min.blue, 0.0 );
2009 min.opacity -= max.opacity; Maximize( min.opacity, 0.0 );
2010 min.index -= max.index; Maximize( min.index, 0.0 );
2011 break;
2012
anthony4fd27e22010-02-07 08:17:18 +00002013 case ErodeIntensityMorphology:
anthony930be612010-02-08 04:26:15 +00002014 /* Select Pixel with Minimum Intensity within kernel neighbourhood
2015 **
2016 ** WARNING: the intensity test fails for CMYK and does not
2017 ** take into account the moderating effect of teh alpha channel
2018 ** on the intensity.
2019 **
2020 ** NOTE that the kernel is not reflected for this operation!
2021 */
anthony602ab9b2010-01-05 08:06:50 +00002022 k = kernel->values;
2023 k_pixels = p;
2024 k_indexes = p_indexes;
cristy150989e2010-02-01 14:59:39 +00002025 for (v=0; v < (long) kernel->height; v++) {
2026 for (u=0; u < (long) kernel->width; u++, k++) {
anthony602ab9b2010-01-05 08:06:50 +00002027 if ( IsNan(*k) || (*k) < 0.5 ) continue;
anthony4fd27e22010-02-07 08:17:18 +00002028 if ( result.red == 0.0 ||
2029 PixelIntensity(&(k_pixels[u])) < PixelIntensity(q) ) {
2030 /* copy the whole pixel - no channel selection */
2031 *q = k_pixels[u];
2032 if ( result.red > 0.0 ) changed++;
2033 result.red = 1.0;
2034 }
anthony602ab9b2010-01-05 08:06:50 +00002035 }
2036 k_pixels += image->columns+kernel->width;
2037 k_indexes += image->columns+kernel->width;
2038 }
anthony602ab9b2010-01-05 08:06:50 +00002039 break;
2040
anthony83ba99b2010-01-24 08:48:15 +00002041 case DilateIntensityMorphology:
anthony930be612010-02-08 04:26:15 +00002042 /* Select Pixel with Maximum Intensity within kernel neighbourhood
2043 **
2044 ** WARNING: the intensity test fails for CMYK and does not
2045 ** take into account the moderating effect of teh alpha channel
2046 ** on the intensity.
2047 **
2048 ** NOTE for correct working of this operation for asymetrical
2049 ** kernels, the kernel needs to be applied in its reflected form.
2050 ** That is its values needs to be reversed.
2051 */
anthony29188a82010-01-22 10:12:34 +00002052 k = &kernel->values[ kernel->width*kernel->height-1 ];
anthony602ab9b2010-01-05 08:06:50 +00002053 k_pixels = p;
2054 k_indexes = p_indexes;
cristy150989e2010-02-01 14:59:39 +00002055 for (v=0; v < (long) kernel->height; v++) {
2056 for (u=0; u < (long) kernel->width; u++, k--) {
anthony29188a82010-01-22 10:12:34 +00002057 if ( IsNan(*k) || (*k) < 0.5 ) continue; /* boolean kernel */
2058 if ( result.red == 0.0 ||
2059 PixelIntensity(&(k_pixels[u])) > PixelIntensity(q) ) {
2060 /* copy the whole pixel - no channel selection */
2061 *q = k_pixels[u];
2062 if ( result.red > 0.0 ) changed++;
2063 result.red = 1.0;
2064 }
anthony602ab9b2010-01-05 08:06:50 +00002065 }
2066 k_pixels += image->columns+kernel->width;
2067 k_indexes += image->columns+kernel->width;
2068 }
anthony602ab9b2010-01-05 08:06:50 +00002069 break;
2070
anthony5ef8e942010-05-11 06:51:12 +00002071
anthony602ab9b2010-01-05 08:06:50 +00002072 case DistanceMorphology:
anthony930be612010-02-08 04:26:15 +00002073 /* Add kernel Value and select the minimum value found.
2074 ** The result is a iterative distance from edge of image shape.
2075 **
2076 ** All Distance Kernels are symetrical, but that may not always
2077 ** be the case. For example how about a distance from left edges?
2078 ** To work correctly with asymetrical kernels the reflected kernel
2079 ** needs to be applied.
anthony5ef8e942010-05-11 06:51:12 +00002080 **
2081 ** Actually this is really a GreyErode with a negative kernel!
2082 **
anthony930be612010-02-08 04:26:15 +00002083 */
anthony29188a82010-01-22 10:12:34 +00002084 k = &kernel->values[ kernel->width*kernel->height-1 ];
anthony602ab9b2010-01-05 08:06:50 +00002085 k_pixels = p;
2086 k_indexes = p_indexes;
cristy150989e2010-02-01 14:59:39 +00002087 for (v=0; v < (long) kernel->height; v++) {
2088 for (u=0; u < (long) kernel->width; u++, k--) {
anthony602ab9b2010-01-05 08:06:50 +00002089 if ( IsNan(*k) ) continue;
2090 Minimize(result.red, (*k)+k_pixels[u].red);
2091 Minimize(result.green, (*k)+k_pixels[u].green);
2092 Minimize(result.blue, (*k)+k_pixels[u].blue);
2093 Minimize(result.opacity, (*k)+QuantumRange-k_pixels[u].opacity);
2094 if ( image->colorspace == CMYKColorspace)
2095 Minimize(result.index, (*k)+k_indexes[u]);
2096 }
2097 k_pixels += image->columns+kernel->width;
2098 k_indexes += image->columns+kernel->width;
2099 }
anthony602ab9b2010-01-05 08:06:50 +00002100 break;
2101
2102 case UndefinedMorphology:
2103 default:
2104 break; /* Do nothing */
anthony83ba99b2010-01-24 08:48:15 +00002105 }
anthony5ef8e942010-05-11 06:51:12 +00002106 /* Final mathematics of results (combine with original image?)
2107 **
2108 ** NOTE: Difference Morphology operators Edge* and *Hat could also
2109 ** be done here but works better with iteration as a image difference
2110 ** in the controling function (below). Thicken and Thinning however
2111 ** should be done here so thay can be iterated correctly.
2112 */
2113 switch ( method ) {
2114 case HitAndMissMorphology:
2115 case ErodeMorphology:
2116 result = min; /* minimum of neighbourhood */
2117 break;
2118 case DilateMorphology:
2119 result = max; /* maximum of neighbourhood */
2120 break;
2121 case ThinningMorphology:
2122 /* subtract pattern match from original */
2123 result.red -= min.red;
2124 result.green -= min.green;
2125 result.blue -= min.blue;
2126 result.opacity -= min.opacity;
2127 result.index -= min.index;
2128 break;
2129 case ThickenMorphology:
2130 /* Union with original image (maximize) - or should this be + */
2131 Maximize( result.red, min.red );
2132 Maximize( result.green, min.green );
2133 Maximize( result.blue, min.blue );
2134 Maximize( result.opacity, min.opacity );
2135 Maximize( result.index, min.index );
2136 break;
2137 default:
2138 /* result directly calculated or assigned */
2139 break;
2140 }
2141 /* Assign the resulting pixel values - Clamping Result */
anthony83ba99b2010-01-24 08:48:15 +00002142 switch ( method ) {
2143 case UndefinedMorphology:
2144 case DilateIntensityMorphology:
2145 case ErodeIntensityMorphology:
anthony930be612010-02-08 04:26:15 +00002146 break; /* full pixel was directly assigned - not a channel method */
anthony83ba99b2010-01-24 08:48:15 +00002147 default:
anthony83ba99b2010-01-24 08:48:15 +00002148 if ((channel & RedChannel) != 0)
2149 q->red = ClampToQuantum(result.red);
2150 if ((channel & GreenChannel) != 0)
2151 q->green = ClampToQuantum(result.green);
2152 if ((channel & BlueChannel) != 0)
2153 q->blue = ClampToQuantum(result.blue);
2154 if ((channel & OpacityChannel) != 0
2155 && image->matte == MagickTrue )
2156 q->opacity = ClampToQuantum(QuantumRange-result.opacity);
2157 if ((channel & IndexChannel) != 0
2158 && image->colorspace == CMYKColorspace)
2159 q_indexes[x] = ClampToQuantum(result.index);
2160 break;
2161 }
anthony5ef8e942010-05-11 06:51:12 +00002162 /* Count up changed pixels */
anthony83ba99b2010-01-24 08:48:15 +00002163 if ( ( p[r].red != q->red )
2164 || ( p[r].green != q->green )
2165 || ( p[r].blue != q->blue )
2166 || ( p[r].opacity != q->opacity )
2167 || ( image->colorspace == CMYKColorspace &&
2168 p_indexes[r] != q_indexes[x] ) )
2169 changed++; /* The pixel had some value changed! */
anthony602ab9b2010-01-05 08:06:50 +00002170 p++;
2171 q++;
anthony83ba99b2010-01-24 08:48:15 +00002172 } /* x */
anthony602ab9b2010-01-05 08:06:50 +00002173 sync=SyncCacheViewAuthenticPixels(q_view,exception);
2174 if (sync == MagickFalse)
2175 status=MagickFalse;
2176 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2177 {
2178 MagickBooleanType
2179 proceed;
2180
2181#if defined(MAGICKCORE_OPENMP_SUPPORT)
2182 #pragma omp critical (MagickCore_MorphologyImage)
2183#endif
2184 proceed=SetImageProgress(image,MorphologyTag,progress++,image->rows);
2185 if (proceed == MagickFalse)
2186 status=MagickFalse;
2187 }
anthony83ba99b2010-01-24 08:48:15 +00002188 } /* y */
anthony602ab9b2010-01-05 08:06:50 +00002189 result_image->type=image->type;
2190 q_view=DestroyCacheView(q_view);
2191 p_view=DestroyCacheView(p_view);
cristy150989e2010-02-01 14:59:39 +00002192 return(status ? (unsigned long) changed : 0);
anthony602ab9b2010-01-05 08:06:50 +00002193}
2194
anthony4fd27e22010-02-07 08:17:18 +00002195
2196MagickExport Image *MorphologyImage(const Image *image, const MorphologyMethod
anthony930be612010-02-08 04:26:15 +00002197 method, const long iterations,const KernelInfo *kernel, ExceptionInfo
2198 *exception)
cristy2be15382010-01-21 02:38:03 +00002199{
2200 Image
2201 *morphology_image;
2202
2203 morphology_image=MorphologyImageChannel(image,DefaultChannels,method,
2204 iterations,kernel,exception);
2205 return(morphology_image);
2206}
2207
anthony4fd27e22010-02-07 08:17:18 +00002208
cristyef656912010-03-05 19:54:59 +00002209MagickExport Image *MorphologyImageChannel(const Image *image,
2210 const ChannelType channel,const MorphologyMethod method,
2211 const long iterations,const KernelInfo *kernel,ExceptionInfo *exception)
anthony602ab9b2010-01-05 08:06:50 +00002212{
anthony602ab9b2010-01-05 08:06:50 +00002213 Image
2214 *new_image,
anthony1b2bc0a2010-05-12 05:25:22 +00002215 *old_image,
anthony4fd27e22010-02-07 08:17:18 +00002216 *grad_image;
anthony602ab9b2010-01-05 08:06:50 +00002217
anthony4fd27e22010-02-07 08:17:18 +00002218 KernelInfo
anthony1b2bc0a2010-05-12 05:25:22 +00002219 *curr_kernel,
2220 *this_kernel;
anthony4fd27e22010-02-07 08:17:18 +00002221
2222 MorphologyMethod
2223 curr_method;
2224
anthony1b2bc0a2010-05-12 05:25:22 +00002225 unsigned long
2226 count,
2227 limit,
2228 changed,
2229 total_changed,
2230 kernel_number;
2231
anthony602ab9b2010-01-05 08:06:50 +00002232 assert(image != (Image *) NULL);
2233 assert(image->signature == MagickSignature);
anthony4fd27e22010-02-07 08:17:18 +00002234 assert(kernel != (KernelInfo *) NULL);
2235 assert(kernel->signature == MagickSignature);
anthony602ab9b2010-01-05 08:06:50 +00002236 assert(exception != (ExceptionInfo *) NULL);
2237 assert(exception->signature == MagickSignature);
2238
anthony602ab9b2010-01-05 08:06:50 +00002239 if ( iterations == 0 )
2240 return((Image *)NULL); /* null operation - nothing to do! */
2241
2242 /* kernel must be valid at this point
2243 * (except maybe for posible future morphology methods like "Prune"
2244 */
cristy2be15382010-01-21 02:38:03 +00002245 assert(kernel != (KernelInfo *)NULL);
anthony602ab9b2010-01-05 08:06:50 +00002246
anthony1b2bc0a2010-05-12 05:25:22 +00002247 new_image = (Image *) NULL; /* for cleanup */
2248 old_image = (Image *) NULL;
2249 grad_image = (Image *) NULL;
2250 curr_kernel = (KernelInfo *) NULL;
2251 count = 0; /* interation count */
2252 changed = 1; /* was last run succesfull */
anthony930be612010-02-08 04:26:15 +00002253 curr_kernel = (KernelInfo *)kernel; /* allow kernel and method */
2254 curr_method = method; /* to be changed as nessary */
anthony4fd27e22010-02-07 08:17:18 +00002255
cristy150989e2010-02-01 14:59:39 +00002256 limit = (unsigned long) iterations;
anthony602ab9b2010-01-05 08:06:50 +00002257 if ( iterations < 0 )
2258 limit = image->columns > image->rows ? image->columns : image->rows;
2259
anthony4fd27e22010-02-07 08:17:18 +00002260 /* Third-level morphology methods */
2261 switch( curr_method ) {
2262 case EdgeMorphology:
2263 grad_image = MorphologyImageChannel(image, channel,
2264 DilateMorphology, iterations, curr_kernel, exception);
anthony1b2bc0a2010-05-12 05:25:22 +00002265 if ( grad_image == (Image *) NULL )
2266 goto error_cleanup;
anthony4fd27e22010-02-07 08:17:18 +00002267 /* FALL-THRU */
2268 case EdgeInMorphology:
2269 curr_method = ErodeMorphology;
anthony602ab9b2010-01-05 08:06:50 +00002270 break;
anthony4fd27e22010-02-07 08:17:18 +00002271 case EdgeOutMorphology:
2272 curr_method = DilateMorphology;
anthony602ab9b2010-01-05 08:06:50 +00002273 break;
anthony4fd27e22010-02-07 08:17:18 +00002274 case TopHatMorphology:
2275 curr_method = OpenMorphology;
2276 break;
2277 case BottomHatMorphology:
2278 curr_method = CloseMorphology;
2279 break;
2280 default:
anthony930be612010-02-08 04:26:15 +00002281 break; /* not a third-level method */
anthony4fd27e22010-02-07 08:17:18 +00002282 }
2283
2284 /* Second-level morphology methods */
2285 switch( curr_method ) {
anthony930be612010-02-08 04:26:15 +00002286 case OpenMorphology:
2287 /* Open is a Erode then a Dilate without reflection */
anthony4fd27e22010-02-07 08:17:18 +00002288 new_image = MorphologyImageChannel(image, channel,
2289 ErodeMorphology, iterations, curr_kernel, exception);
anthony602ab9b2010-01-05 08:06:50 +00002290 if (new_image == (Image *) NULL)
anthony1b2bc0a2010-05-12 05:25:22 +00002291 goto error_cleanup;
anthony4fd27e22010-02-07 08:17:18 +00002292 curr_method = DilateMorphology;
2293 break;
anthony602ab9b2010-01-05 08:06:50 +00002294 case OpenIntensityMorphology:
anthony4fd27e22010-02-07 08:17:18 +00002295 new_image = MorphologyImageChannel(image, channel,
2296 ErodeIntensityMorphology, iterations, curr_kernel, exception);
anthony602ab9b2010-01-05 08:06:50 +00002297 if (new_image == (Image *) NULL)
anthony1b2bc0a2010-05-12 05:25:22 +00002298 goto error_cleanup;
anthony4fd27e22010-02-07 08:17:18 +00002299 curr_method = DilateIntensityMorphology;
2300 break;
anthony930be612010-02-08 04:26:15 +00002301
2302 case CloseMorphology:
2303 /* Close is a Dilate then Erode using reflected kernel */
2304 /* A reflected kernel is needed for a Close */
2305 if ( curr_kernel == kernel )
2306 curr_kernel = CloneKernelInfo(kernel);
anthony1b2bc0a2010-05-12 05:25:22 +00002307 if (curr_kernel == (KernelInfo *) NULL)
2308 goto error_cleanup;
anthony930be612010-02-08 04:26:15 +00002309 RotateKernelInfo(curr_kernel,180);
2310 new_image = MorphologyImageChannel(image, channel,
2311 DilateMorphology, iterations, curr_kernel, exception);
2312 if (new_image == (Image *) NULL)
anthony1b2bc0a2010-05-12 05:25:22 +00002313 goto error_cleanup;
anthony930be612010-02-08 04:26:15 +00002314 curr_method = ErodeMorphology;
2315 break;
anthony4fd27e22010-02-07 08:17:18 +00002316 case CloseIntensityMorphology:
anthony930be612010-02-08 04:26:15 +00002317 /* A reflected kernel is needed for a Close */
2318 if ( curr_kernel == kernel )
2319 curr_kernel = CloneKernelInfo(kernel);
anthony1b2bc0a2010-05-12 05:25:22 +00002320 if (curr_kernel == (KernelInfo *) NULL)
2321 goto error_cleanup;
anthony4fd27e22010-02-07 08:17:18 +00002322 RotateKernelInfo(curr_kernel,180);
2323 new_image = MorphologyImageChannel(image, channel,
2324 DilateIntensityMorphology, iterations, curr_kernel, exception);
2325 if (new_image == (Image *) NULL)
anthony1b2bc0a2010-05-12 05:25:22 +00002326 goto error_cleanup;
anthony4fd27e22010-02-07 08:17:18 +00002327 curr_method = ErodeIntensityMorphology;
anthony602ab9b2010-01-05 08:06:50 +00002328 break;
2329
anthony930be612010-02-08 04:26:15 +00002330 case CorrelateMorphology:
2331 /* A Correlation is actually a Convolution with a reflected kernel.
2332 ** However a Convolution is a weighted sum with a reflected kernel.
2333 ** It may seem stange to convert a Correlation into a Convolution
2334 ** as the Correleation is the simplier method, but Convolution is
2335 ** much more commonly used, and it makes sense to implement it directly
2336 ** so as to avoid the need to duplicate the kernel when it is not
2337 ** required (which is typically the default).
2338 */
2339 if ( curr_kernel == kernel )
2340 curr_kernel = CloneKernelInfo(kernel);
anthony1b2bc0a2010-05-12 05:25:22 +00002341 if (curr_kernel == (KernelInfo *) NULL)
2342 goto error_cleanup;
anthony930be612010-02-08 04:26:15 +00002343 RotateKernelInfo(curr_kernel,180);
2344 curr_method = ConvolveMorphology;
2345 /* FALL-THRU into Correlate (weigthed sum without reflection) */
2346
anthonyc94cdb02010-01-06 08:15:29 +00002347 case ConvolveMorphology:
anthony1b2bc0a2010-05-12 05:25:22 +00002348 { /* Scale or Normalize kernel, according to user wishes
2349 ** before using it for the Convolve/Correlate method.
2350 **
2351 ** FUTURE: provide some way for internal functions to disable
2352 ** user bias and scaling effects.
2353 */
2354 const char
2355 *artifact = GetImageArtifact(image,"convolve:scale");
2356 if ( artifact != (char *)NULL ) {
2357 GeometryFlags
2358 flags;
2359 GeometryInfo
2360 args;
anthony999bb2c2010-02-18 12:38:01 +00002361
anthony1b2bc0a2010-05-12 05:25:22 +00002362 if ( curr_kernel == kernel )
2363 curr_kernel = CloneKernelInfo(kernel);
2364 if (curr_kernel == (KernelInfo *) NULL)
2365 goto error_cleanup;
2366 args.rho = 1.0;
2367 flags = (GeometryFlags) ParseGeometry(artifact, &args);
2368 ScaleKernelInfo(curr_kernel, args.rho, flags);
2369 }
anthony4fd27e22010-02-07 08:17:18 +00002370 }
anthony930be612010-02-08 04:26:15 +00002371 /* FALL-THRU to do the first, and typically the only iteration */
anthony4fd27e22010-02-07 08:17:18 +00002372
anthony602ab9b2010-01-05 08:06:50 +00002373 default:
anthony930be612010-02-08 04:26:15 +00002374 /* Do a single iteration using the Low-Level Morphology method!
2375 ** This ensures a "new_image" has been generated, but allows us to skip
2376 ** the creation of 'old_image' if no more iterations are needed.
2377 **
2378 ** The "curr_method" should also be set to a low-level method that is
anthony7a01dcf2010-05-11 12:25:52 +00002379 ** understood by the MorphologyPrimative() internal function.
anthony602ab9b2010-01-05 08:06:50 +00002380 */
2381 new_image=CloneImage(image,0,0,MagickTrue,exception);
2382 if (new_image == (Image *) NULL)
anthony1b2bc0a2010-05-12 05:25:22 +00002383 goto error_cleanup;
anthony602ab9b2010-01-05 08:06:50 +00002384 if (SetImageStorageClass(new_image,DirectClass) == MagickFalse)
2385 {
2386 InheritException(exception,&new_image->exception);
anthony1b2bc0a2010-05-12 05:25:22 +00002387 goto exit_cleanup;
anthony602ab9b2010-01-05 08:06:50 +00002388 }
anthony1b2bc0a2010-05-12 05:25:22 +00002389 count++; /* iteration count */
anthony7a01dcf2010-05-11 12:25:52 +00002390 changed = MorphologyPrimative(image,new_image,curr_method,channel,
2391 curr_kernel, exception);
anthony602ab9b2010-01-05 08:06:50 +00002392 if ( GetImageArtifact(image,"verbose") != (const char *) NULL )
anthony1b2bc0a2010-05-12 05:25:22 +00002393 fprintf(stderr, "Morphology %s:%lu.%lu => Changed %lu\n",
anthony4fd27e22010-02-07 08:17:18 +00002394 MagickOptionToMnemonic(MagickMorphologyOptions, curr_method),
anthony1b2bc0a2010-05-12 05:25:22 +00002395 count, 0L, changed);
anthony930be612010-02-08 04:26:15 +00002396 break;
anthony602ab9b2010-01-05 08:06:50 +00002397 }
anthony1b2bc0a2010-05-12 05:25:22 +00002398 /* At this point
2399 * + "curr_method" should be set to a low-level morphology method
2400 * + "count=1" if the first iteration of the first kernel has been done.
2401 * + "new_image" is defined and contains the current resulting image
2402 */
anthony602ab9b2010-01-05 08:06:50 +00002403
anthony1b2bc0a2010-05-12 05:25:22 +00002404 /* The Hit-And-Miss Morphology is not 'iterated' against the resulting
2405 ** image from the previous morphology step. It must always be applied
2406 ** to the original image, with the results collected into a union (maximimum
2407 ** or lighten) of all the results. Multiple kernels may be applied but
2408 ** an iteration of the morphology does nothing, so is ignored.
anthony7a01dcf2010-05-11 12:25:52 +00002409 **
anthony1b2bc0a2010-05-12 05:25:22 +00002410 ** The first kernel is guranteed to have been done, so lets continue the
2411 ** process, with the other kernels in the kernel list.
anthony7a01dcf2010-05-11 12:25:52 +00002412 */
anthony1b2bc0a2010-05-12 05:25:22 +00002413 if ( method == HitAndMissMorphology )
anthony7a01dcf2010-05-11 12:25:52 +00002414 {
anthony1b2bc0a2010-05-12 05:25:22 +00002415 if ( curr_kernel->next != (KernelInfo *) NULL ) {
2416 /* create a second working image */
2417 old_image = CloneImage(image,0,0,MagickTrue,exception);
2418 if (old_image == (Image *) NULL)
2419 goto error_cleanup;
2420 if (SetImageStorageClass(old_image,DirectClass) == MagickFalse)
2421 {
2422 InheritException(exception,&old_image->exception);
2423 goto exit_cleanup;
2424 }
anthony7a01dcf2010-05-11 12:25:52 +00002425
anthony1b2bc0a2010-05-12 05:25:22 +00002426 /* loop through rest of the kernels */
2427 this_kernel=curr_kernel->next;
2428 kernel_number=1;
2429 while( this_kernel != (KernelInfo *) NULL )
2430 {
2431 changed = MorphologyPrimative(image,old_image,curr_method,channel,
2432 this_kernel,exception);
2433 (void) CompositeImageChannel(new_image,
2434 (ChannelType) (channel & ~SyncChannels), LightenCompositeOp,
2435 old_image, 0, 0);
2436 if ( GetImageArtifact(image,"verbose") != (const char *) NULL )
2437 fprintf(stderr, "Morphology %s:%lu.%lu => Changed %lu\n",
2438 MagickOptionToMnemonic(MagickMorphologyOptions, curr_method),
2439 count, kernel_number, changed);
2440 this_kernel = this_kernel->next;
2441 kernel_number++;
2442 }
2443 old_image=DestroyImage(old_image);
2444 }
2445 goto exit_cleanup;
anthony7a01dcf2010-05-11 12:25:52 +00002446 }
2447
anthony1b2bc0a2010-05-12 05:25:22 +00002448 /* Repeat the low-level morphology over all kernels
2449 until iteration count limit or no change from any kernel is found */
2450 if ( ( count < limit && changed > 0 ) ||
2451 curr_kernel->next != (KernelInfo *) NULL ) {
anthony930be612010-02-08 04:26:15 +00002452
anthony1b2bc0a2010-05-12 05:25:22 +00002453 /* create a second working image */
2454 old_image = CloneImage(image,0,0,MagickTrue,exception);
anthony602ab9b2010-01-05 08:06:50 +00002455 if (old_image == (Image *) NULL)
anthony1b2bc0a2010-05-12 05:25:22 +00002456 goto error_cleanup;
anthony602ab9b2010-01-05 08:06:50 +00002457 if (SetImageStorageClass(old_image,DirectClass) == MagickFalse)
2458 {
2459 InheritException(exception,&old_image->exception);
anthony1b2bc0a2010-05-12 05:25:22 +00002460 goto exit_cleanup;
anthony602ab9b2010-01-05 08:06:50 +00002461 }
anthony1b2bc0a2010-05-12 05:25:22 +00002462
2463 /* reset variables for the first/next iteration, or next kernel) */
2464 kernel_number = 0;
2465 this_kernel = curr_kernel;
2466 total_changed = count != 0 ? changed : 0;
2467 if ( count != 0 && this_kernel != (KernelInfo *) NULL ) {
2468 count = 0; /* first iteration is not yet finished! */
2469 this_kernel = curr_kernel->next;
2470 kernel_number = 1;
2471 total_changed = changed;
2472 }
2473
2474 while ( count < limit ) {
2475 count++;
2476 while ( this_kernel != (KernelInfo *) NULL ) {
anthony602ab9b2010-01-05 08:06:50 +00002477 Image *tmp = old_image;
2478 old_image = new_image;
2479 new_image = tmp;
anthony7a01dcf2010-05-11 12:25:52 +00002480 changed = MorphologyPrimative(old_image,new_image,curr_method,channel,
anthony1b2bc0a2010-05-12 05:25:22 +00002481 this_kernel,exception);
anthony602ab9b2010-01-05 08:06:50 +00002482 if ( GetImageArtifact(image,"verbose") != (const char *) NULL )
anthony1b2bc0a2010-05-12 05:25:22 +00002483 fprintf(stderr, "Morphology %s:%lu.%lu => Changed %lu\n",
anthony4fd27e22010-02-07 08:17:18 +00002484 MagickOptionToMnemonic(MagickMorphologyOptions, curr_method),
anthony1b2bc0a2010-05-12 05:25:22 +00002485 count, kernel_number, changed);
2486 total_changed += changed;
2487 this_kernel = this_kernel->next;
2488 kernel_number++;
anthony602ab9b2010-01-05 08:06:50 +00002489 }
anthony3dd0f622010-05-13 12:57:32 +00002490 if ( kernel_number > 1 )
2491 if ( GetImageArtifact(image,"verbose") != (const char *) NULL )
2492 fprintf(stderr, "Morphology %s:%lu ===> Total Changed %lu\n",
2493 MagickOptionToMnemonic(MagickMorphologyOptions, curr_method),
2494 count, total_changed);
anthony1b2bc0a2010-05-12 05:25:22 +00002495 if ( total_changed == 0 )
2496 break; /* no changes after processing all kernels - ABORT */
2497 /* prepare for next loop */
2498 total_changed = 0;
2499 kernel_number = 0;
2500 this_kernel = curr_kernel;
2501 }
cristy150989e2010-02-01 14:59:39 +00002502 old_image=DestroyImage(old_image);
anthony602ab9b2010-01-05 08:06:50 +00002503 }
anthony930be612010-02-08 04:26:15 +00002504
anthony3dd0f622010-05-13 12:57:32 +00002505 /* finished with kernel - destroy any copy that was made */
anthony4fd27e22010-02-07 08:17:18 +00002506 if ( curr_kernel != kernel )
2507 curr_kernel=DestroyKernelInfo(curr_kernel);
2508
anthony7d10d742010-05-06 07:05:29 +00002509 /* Third-level Subtractive methods post-processing
2510 **
2511 ** The removal of any 'Sync' channel flag in the Image Compositon below
2512 ** ensures the compose method is applied in a purely mathematical way, only
2513 ** the selected channels, without any normal 'alpha blending' normally
2514 ** associated with the compose method.
2515 **
2516 ** Note "method" here is the 'original' morphological method, and not the
2517 ** 'current' morphological method used above to generate "new_image".
2518 */
anthony4fd27e22010-02-07 08:17:18 +00002519 switch( method ) {
2520 case EdgeOutMorphology:
2521 case EdgeInMorphology:
2522 case TopHatMorphology:
2523 case BottomHatMorphology:
anthony930be612010-02-08 04:26:15 +00002524 /* Get Difference relative to the original image */
cristyaeb2cbc2010-05-07 13:28:58 +00002525 (void) CompositeImageChannel(new_image, (ChannelType) (channel & ~SyncChannels),
anthony7d10d742010-05-06 07:05:29 +00002526 DifferenceCompositeOp, image, 0, 0);
anthony4fd27e22010-02-07 08:17:18 +00002527 break;
anthony7d10d742010-05-06 07:05:29 +00002528 case EdgeMorphology:
2529 /* Difference the Eroded image from the saved Dilated image */
cristyaeb2cbc2010-05-07 13:28:58 +00002530 (void) CompositeImageChannel(new_image, (ChannelType) (channel & ~SyncChannels),
anthony7d10d742010-05-06 07:05:29 +00002531 DifferenceCompositeOp, grad_image, 0, 0);
anthony4fd27e22010-02-07 08:17:18 +00002532 grad_image=DestroyImage(grad_image);
2533 break;
2534 default:
2535 break;
2536 }
anthony602ab9b2010-01-05 08:06:50 +00002537
2538 return(new_image);
anthony1b2bc0a2010-05-12 05:25:22 +00002539
2540 /* Yes goto's are bad, but in this case it makes cleanup lot more efficient */
2541error_cleanup:
2542 if ( new_image != (Image *) NULL )
2543 DestroyImage(new_image);
2544exit_cleanup:
2545 if ( old_image != (Image *) NULL )
2546 DestroyImage(old_image);
2547 if ( curr_kernel != (KernelInfo *) NULL && curr_kernel != kernel )
2548 curr_kernel=DestroyKernelInfo(curr_kernel);
2549 return(new_image);
anthony602ab9b2010-01-05 08:06:50 +00002550}
anthony83ba99b2010-01-24 08:48:15 +00002551
2552/*
2553%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2554% %
2555% %
2556% %
anthony4fd27e22010-02-07 08:17:18 +00002557+ R o t a t e K e r n e l I n f o %
anthony83ba99b2010-01-24 08:48:15 +00002558% %
2559% %
2560% %
2561%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2562%
anthony4fd27e22010-02-07 08:17:18 +00002563% RotateKernelInfo() rotates the kernel by the angle given. Currently it is
anthony83ba99b2010-01-24 08:48:15 +00002564% restricted to 90 degree angles, but this may be improved in the future.
2565%
anthony4fd27e22010-02-07 08:17:18 +00002566% The format of the RotateKernelInfo method is:
anthony83ba99b2010-01-24 08:48:15 +00002567%
anthony4fd27e22010-02-07 08:17:18 +00002568% void RotateKernelInfo(KernelInfo *kernel, double angle)
anthony83ba99b2010-01-24 08:48:15 +00002569%
2570% A description of each parameter follows:
2571%
2572% o kernel: the Morphology/Convolution kernel
2573%
2574% o angle: angle to rotate in degrees
2575%
anthonyc4c86e02010-01-27 09:30:32 +00002576% This function is only internel to this module, as it is not finalized,
2577% especially with regard to non-orthogonal angles, and rotation of larger
2578% 2D kernels.
anthony83ba99b2010-01-24 08:48:15 +00002579*/
anthony4fd27e22010-02-07 08:17:18 +00002580static void RotateKernelInfo(KernelInfo *kernel, double angle)
anthony83ba99b2010-01-24 08:48:15 +00002581{
anthony1b2bc0a2010-05-12 05:25:22 +00002582 /* angle the lower kernels first */
2583 if ( kernel->next != (KernelInfo *) NULL)
2584 RotateKernelInfo(kernel->next, angle);
2585
anthony83ba99b2010-01-24 08:48:15 +00002586 /* WARNING: Currently assumes the kernel (rightly) is horizontally symetrical
2587 **
2588 ** TODO: expand beyond simple 90 degree rotates, flips and flops
2589 */
2590
2591 /* Modulus the angle */
2592 angle = fmod(angle, 360.0);
2593 if ( angle < 0 )
2594 angle += 360.0;
2595
anthony3c10fc82010-05-13 02:40:51 +00002596 if ( 337.5 < angle || angle <= 22.5 )
anthony83ba99b2010-01-24 08:48:15 +00002597 return; /* no change! - At least at this time */
2598
anthony3dd0f622010-05-13 12:57:32 +00002599 /* Handle special cases */
anthony83ba99b2010-01-24 08:48:15 +00002600 switch (kernel->type) {
2601 /* These built-in kernels are cylindrical kernels, rotating is useless */
2602 case GaussianKernel:
anthony83ba99b2010-01-24 08:48:15 +00002603 case DOGKernel:
2604 case DiskKernel:
anthony3dd0f622010-05-13 12:57:32 +00002605 case PeaksKernel:
2606 case LaplacianKernel:
anthony83ba99b2010-01-24 08:48:15 +00002607 case ChebyshevKernel:
2608 case ManhattenKernel:
2609 case EuclideanKernel:
2610 return;
2611
2612 /* These may be rotatable at non-90 angles in the future */
2613 /* but simply rotating them in multiples of 90 degrees is useless */
2614 case SquareKernel:
2615 case DiamondKernel:
2616 case PlusKernel:
anthony3dd0f622010-05-13 12:57:32 +00002617 case CrossKernel:
anthony83ba99b2010-01-24 08:48:15 +00002618 return;
2619
2620 /* These only allows a +/-90 degree rotation (by transpose) */
2621 /* A 180 degree rotation is useless */
2622 case BlurKernel:
2623 case RectangleKernel:
2624 if ( 135.0 < angle && angle <= 225.0 )
2625 return;
2626 if ( 225.0 < angle && angle <= 315.0 )
2627 angle -= 180;
2628 break;
2629
anthony3dd0f622010-05-13 12:57:32 +00002630 default:
anthony83ba99b2010-01-24 08:48:15 +00002631 break;
2632 }
anthony3c10fc82010-05-13 02:40:51 +00002633 /* Attempt rotations by 45 degrees */
2634 if ( 22.5 < fmod(angle,90.0) && fmod(angle,90.0) <= 67.5 )
2635 {
2636 if ( kernel->width == 3 && kernel->height == 3 )
2637 { /* Rotate a 3x3 square by 45 degree angle */
2638 MagickRealType t = kernel->values[0];
2639 kernel->values[0] = kernel->values[1];
2640 kernel->values[1] = kernel->values[2];
2641 kernel->values[2] = kernel->values[5];
2642 kernel->values[5] = kernel->values[8];
2643 kernel->values[8] = kernel->values[7];
2644 kernel->values[7] = kernel->values[6];
2645 kernel->values[6] = kernel->values[3];
2646 kernel->values[3] = t;
2647 angle = fmod(angle+45.0, 360.0);
2648 }
2649 else
2650 perror("Unable to rotate non-3x3 kernel by 45 degrees");
2651 }
2652 if ( 45.0 < fmod(angle, 180.0) && fmod(angle,180.0) <= 135.0 )
2653 {
2654 if ( kernel->width == 1 || kernel->height == 1 )
2655 { /* Do a transpose of the image, which results in a 90
2656 ** degree rotation of a 1 dimentional kernel
2657 */
2658 long
2659 t;
2660 t = (long) kernel->width;
2661 kernel->width = kernel->height;
2662 kernel->height = (unsigned long) t;
2663 t = kernel->x;
2664 kernel->x = kernel->y;
2665 kernel->y = t;
2666 if ( kernel->width == 1 )
2667 angle = fmod(angle+270.0, 360.0); /* angle -90 degrees */
2668 else
2669 angle = fmod(angle+90.0, 360.0); /* angle +90 degrees */
2670 }
2671 else if ( kernel->width == kernel->height )
2672 { /* Rotate a square array of values by 90 degrees */
2673 register unsigned long
2674 i,j,x,y;
2675 register MagickRealType
2676 *k,t;
2677 k=kernel->values;
2678 for( i=0, x=kernel->width-1; i<=x; i++, x--)
2679 for( j=0, y=kernel->height-1; j<y; j++, y--)
2680 { t = k[i+j*kernel->width];
2681 k[i+j*kernel->width] = k[y+i*kernel->width];
2682 k[y+i*kernel->width] = k[x+y*kernel->width];
2683 k[x+y*kernel->width] = k[j+x*kernel->width];
2684 k[j+x*kernel->width] = t;
2685 }
2686 angle = fmod(angle+90.0, 360.0); /* angle +90 degrees */
2687 }
2688 else
2689 perror("Unable to rotate a non-square, non-linear kernel 90 degrees");
2690 }
anthony83ba99b2010-01-24 08:48:15 +00002691 if ( 135.0 < angle && angle <= 225.0 )
2692 {
2693 /* For a 180 degree rotation - also know as a reflection */
2694 /* This is actually a very very common operation! */
2695 /* Basically all that is needed is a reversal of the kernel data! */
2696 unsigned long
2697 i,j;
2698 register double
2699 *k,t;
2700
2701 k=kernel->values;
2702 for ( i=0, j=kernel->width*kernel->height-1; i<j; i++, j--)
2703 t=k[i], k[i]=k[j], k[j]=t;
2704
anthony930be612010-02-08 04:26:15 +00002705 kernel->x = (long) kernel->width - kernel->x - 1;
2706 kernel->y = (long) kernel->height - kernel->y - 1;
anthony3c10fc82010-05-13 02:40:51 +00002707 angle = fmod(angle+180.0, 360.0); /* angle+180 degrees */
anthony83ba99b2010-01-24 08:48:15 +00002708 }
anthony3c10fc82010-05-13 02:40:51 +00002709 /* At this point angle should at least between -45 (315) and +45 degrees
anthony83ba99b2010-01-24 08:48:15 +00002710 * In the future some form of non-orthogonal angled rotates could be
2711 * performed here, posibily with a linear kernel restriction.
2712 */
2713
2714#if 0
anthony3c10fc82010-05-13 02:40:51 +00002715 { /* Do a Flop by reversing each row.
anthony83ba99b2010-01-24 08:48:15 +00002716 */
2717 unsigned long
2718 y;
cristy150989e2010-02-01 14:59:39 +00002719 register long
anthony83ba99b2010-01-24 08:48:15 +00002720 x,r;
2721 register double
2722 *k,t;
2723
2724 for ( y=0, k=kernel->values; y < kernel->height; y++, k+=kernel->width)
2725 for ( x=0, r=kernel->width-1; x<kernel->width/2; x++, r--)
2726 t=k[x], k[x]=k[r], k[r]=t;
2727
cristyc99304f2010-02-01 15:26:27 +00002728 kernel->x = kernel->width - kernel->x - 1;
anthony83ba99b2010-01-24 08:48:15 +00002729 angle = fmod(angle+180.0, 360.0);
2730 }
2731#endif
2732 return;
2733}
2734
2735/*
2736%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2737% %
2738% %
2739% %
cristy6771f1e2010-03-05 19:43:39 +00002740% S c a l e K e r n e l I n f o %
anthonycc6c8362010-01-25 04:14:01 +00002741% %
2742% %
2743% %
2744%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2745%
anthony1b2bc0a2010-05-12 05:25:22 +00002746% ScaleKernelInfo() scales the given kernel list by the given amount, with or
2747% without normalization of the sum of the kernel values (as per given flags).
anthonycc6c8362010-01-25 04:14:01 +00002748%
anthony999bb2c2010-02-18 12:38:01 +00002749% By default (no flags given) the values within the kernel is scaled
anthony1b2bc0a2010-05-12 05:25:22 +00002750% directly using given scaling factor without change.
anthonycc6c8362010-01-25 04:14:01 +00002751%
anthony1b2bc0a2010-05-12 05:25:22 +00002752% If any 'normalize_flags' are given the kernel will first be normalized and
2753% then further scaled by the scaling factor value given. A 'PercentValue'
2754% flag will cause the given scaling factor to be divided by one hundred
2755% percent.
anthony999bb2c2010-02-18 12:38:01 +00002756%
2757% Kernel normalization ('normalize_flags' given) is designed to ensure that
2758% any use of the kernel scaling factor with 'Convolve' or 'Correlate'
anthony1b2bc0a2010-05-12 05:25:22 +00002759% morphology methods will fall into -1.0 to +1.0 range. Note that for
2760% non-HDRI versions of IM this may cause images to have any negative results
2761% clipped, unless some 'bias' is used.
anthony999bb2c2010-02-18 12:38:01 +00002762%
2763% More specifically. Kernels which only contain positive values (such as a
2764% 'Gaussian' kernel) will be scaled so that those values sum to +1.0,
anthony1b2bc0a2010-05-12 05:25:22 +00002765% ensuring a 0.0 to +1.0 output range for non-HDRI images.
anthony999bb2c2010-02-18 12:38:01 +00002766%
2767% For Kernels that contain some negative values, (such as 'Sharpen' kernels)
2768% the kernel will be scaled by the absolute of the sum of kernel values, so
2769% that it will generally fall within the +/- 1.0 range.
2770%
2771% For kernels whose values sum to zero, (such as 'Laplician' kernels) kernel
2772% will be scaled by just the sum of the postive values, so that its output
2773% range will again fall into the +/- 1.0 range.
2774%
2775% For special kernels designed for locating shapes using 'Correlate', (often
2776% only containing +1 and -1 values, representing foreground/brackground
2777% matching) a special normalization method is provided to scale the positive
2778% values seperatally to those of the negative values, so the kernel will be
2779% forced to become a zero-sum kernel better suited to such searches.
2780%
anthony1b2bc0a2010-05-12 05:25:22 +00002781% WARNING: Correct normalization of the kernel assumes that the '*_range'
anthony999bb2c2010-02-18 12:38:01 +00002782% attributes within the kernel structure have been correctly set during the
2783% kernels creation.
2784%
2785% NOTE: The values used for 'normalize_flags' have been selected specifically
anthony1b2bc0a2010-05-12 05:25:22 +00002786% to match the use of geometry options, so that '!' means NormalizeValue,
2787% '^' means CorrelateNormalizeValue, and '%' means PercentValue. All other
anthony999bb2c2010-02-18 12:38:01 +00002788% GeometryFlags values are ignored.
anthonycc6c8362010-01-25 04:14:01 +00002789%
anthony4fd27e22010-02-07 08:17:18 +00002790% The format of the ScaleKernelInfo method is:
anthonycc6c8362010-01-25 04:14:01 +00002791%
anthony999bb2c2010-02-18 12:38:01 +00002792% void ScaleKernelInfo(KernelInfo *kernel, const double scaling_factor,
2793% const MagickStatusType normalize_flags )
anthonycc6c8362010-01-25 04:14:01 +00002794%
2795% A description of each parameter follows:
2796%
2797% o kernel: the Morphology/Convolution kernel
2798%
anthony999bb2c2010-02-18 12:38:01 +00002799% o scaling_factor:
2800% multiply all values (after normalization) by this factor if not
2801% zero. If the kernel is normalized regardless of any flags.
2802%
2803% o normalize_flags:
2804% GeometryFlags defining normalization method to use.
2805% specifically: NormalizeValue, CorrelateNormalizeValue,
2806% and/or PercentValue
anthonycc6c8362010-01-25 04:14:01 +00002807%
anthonyc4c86e02010-01-27 09:30:32 +00002808% This function is internal to this module only at this time, but can be
2809% exported to other modules if needed.
anthonycc6c8362010-01-25 04:14:01 +00002810*/
cristy6771f1e2010-03-05 19:43:39 +00002811MagickExport void ScaleKernelInfo(KernelInfo *kernel,
2812 const double scaling_factor,const GeometryFlags normalize_flags)
anthonycc6c8362010-01-25 04:14:01 +00002813{
cristy150989e2010-02-01 14:59:39 +00002814 register long
anthonycc6c8362010-01-25 04:14:01 +00002815 i;
2816
anthony999bb2c2010-02-18 12:38:01 +00002817 register double
2818 pos_scale,
2819 neg_scale;
2820
anthony1b2bc0a2010-05-12 05:25:22 +00002821 /* scale the lower kernels first */
2822 if ( kernel->next != (KernelInfo *) NULL)
2823 ScaleKernelInfo(kernel->next, scaling_factor, normalize_flags);
2824
anthony999bb2c2010-02-18 12:38:01 +00002825 pos_scale = 1.0;
2826 if ( (normalize_flags&NormalizeValue) != 0 ) {
2827 /* normalize kernel appropriately */
2828 if ( fabs(kernel->positive_range + kernel->negative_range) > MagickEpsilon )
2829 pos_scale = fabs(kernel->positive_range + kernel->negative_range);
anthonycc6c8362010-01-25 04:14:01 +00002830 else
anthony999bb2c2010-02-18 12:38:01 +00002831 pos_scale = kernel->positive_range; /* special zero-summing kernel */
2832 }
2833 /* force kernel into being a normalized zero-summing kernel */
2834 if ( (normalize_flags&CorrelateNormalizeValue) != 0 ) {
2835 pos_scale = ( fabs(kernel->positive_range) > MagickEpsilon )
2836 ? kernel->positive_range : 1.0;
2837 neg_scale = ( fabs(kernel->negative_range) > MagickEpsilon )
2838 ? -kernel->negative_range : 1.0;
2839 }
2840 else
2841 neg_scale = pos_scale;
2842
2843 /* finialize scaling_factor for positive and negative components */
2844 pos_scale = scaling_factor/pos_scale;
2845 neg_scale = scaling_factor/neg_scale;
2846 if ( (normalize_flags&PercentValue) != 0 ) {
2847 pos_scale /= 100.0;
2848 neg_scale /= 100.0;
anthonycc6c8362010-01-25 04:14:01 +00002849 }
2850
cristy150989e2010-02-01 14:59:39 +00002851 for (i=0; i < (long) (kernel->width*kernel->height); i++)
anthonycc6c8362010-01-25 04:14:01 +00002852 if ( ! IsNan(kernel->values[i]) )
anthony999bb2c2010-02-18 12:38:01 +00002853 kernel->values[i] *= (kernel->values[i] >= 0) ? pos_scale : neg_scale;
anthonycc6c8362010-01-25 04:14:01 +00002854
anthony999bb2c2010-02-18 12:38:01 +00002855 /* convolution output range */
2856 kernel->positive_range *= pos_scale;
2857 kernel->negative_range *= neg_scale;
2858 /* maximum and minimum values in kernel */
2859 kernel->maximum *= (kernel->maximum >= 0.0) ? pos_scale : neg_scale;
2860 kernel->minimum *= (kernel->minimum >= 0.0) ? pos_scale : neg_scale;
2861
2862 /* swap kernel settings if user scaling factor is negative */
2863 if ( scaling_factor < MagickEpsilon ) {
2864 double t;
2865 t = kernel->positive_range;
2866 kernel->positive_range = kernel->negative_range;
2867 kernel->negative_range = t;
2868 t = kernel->maximum;
2869 kernel->maximum = kernel->minimum;
2870 kernel->minimum = 1;
2871 }
anthonycc6c8362010-01-25 04:14:01 +00002872
2873 return;
2874}
2875
2876/*
2877%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2878% %
2879% %
2880% %
anthony4fd27e22010-02-07 08:17:18 +00002881+ S h o w K e r n e l I n f o %
anthony83ba99b2010-01-24 08:48:15 +00002882% %
2883% %
2884% %
2885%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2886%
anthony4fd27e22010-02-07 08:17:18 +00002887% ShowKernelInfo() outputs the details of the given kernel defination to
2888% standard error, generally due to a users 'showkernel' option request.
anthony83ba99b2010-01-24 08:48:15 +00002889%
2890% The format of the ShowKernel method is:
2891%
anthony4fd27e22010-02-07 08:17:18 +00002892% void ShowKernelInfo(KernelInfo *kernel)
anthony83ba99b2010-01-24 08:48:15 +00002893%
2894% A description of each parameter follows:
2895%
2896% o kernel: the Morphology/Convolution kernel
2897%
anthonyc4c86e02010-01-27 09:30:32 +00002898% This function is internal to this module only at this time. That may change
2899% in the future.
anthony83ba99b2010-01-24 08:48:15 +00002900*/
anthony4fd27e22010-02-07 08:17:18 +00002901MagickExport void ShowKernelInfo(KernelInfo *kernel)
anthony83ba99b2010-01-24 08:48:15 +00002902{
anthony7a01dcf2010-05-11 12:25:52 +00002903 KernelInfo
2904 *k;
anthony83ba99b2010-01-24 08:48:15 +00002905
anthony7a01dcf2010-05-11 12:25:52 +00002906 long
2907 c, i, u, v;
2908
2909 for (c=0, k=kernel; k != (KernelInfo *) NULL; c++, k=k->next ) {
2910
2911 fprintf(stderr, "Kernel ");
2912 if ( kernel->next != (KernelInfo *) NULL )
2913 fprintf(stderr, " #%ld", c );
2914 fprintf(stderr, " \"%s\" of size %lux%lu%+ld%+ld ",
2915 MagickOptionToMnemonic(MagickKernelOptions, k->type),
2916 kernel->width, k->height,
2917 kernel->x, kernel->y );
2918 fprintf(stderr,
2919 " with values from %.*lg to %.*lg\n",
2920 GetMagickPrecision(), k->minimum,
2921 GetMagickPrecision(), k->maximum);
2922 fprintf(stderr, "Forming convolution output range from %.*lg to %.*lg%s\n",
2923 GetMagickPrecision(), k->negative_range,
2924 GetMagickPrecision(), k->positive_range,
2925 /*kernel->normalized == MagickTrue ? " (normalized)" : */ "" );
2926 for (i=v=0; v < (long) k->height; v++) {
2927 fprintf(stderr,"%2ld:",v);
2928 for (u=0; u < (long) k->width; u++, i++)
2929 if ( IsNan(k->values[i]) )
2930 fprintf(stderr," %*s", GetMagickPrecision()+2, "nan");
2931 else
2932 fprintf(stderr," %*.*lg", GetMagickPrecision()+2,
2933 GetMagickPrecision(), k->values[i]);
2934 fprintf(stderr,"\n");
2935 }
anthony83ba99b2010-01-24 08:48:15 +00002936 }
2937}
anthonycc6c8362010-01-25 04:14:01 +00002938
2939/*
2940%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2941% %
2942% %
2943% %
anthony4fd27e22010-02-07 08:17:18 +00002944+ Z e r o K e r n e l N a n s %
anthonycc6c8362010-01-25 04:14:01 +00002945% %
2946% %
2947% %
2948%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2949%
2950% ZeroKernelNans() replaces any special 'nan' value that may be present in
2951% the kernel with a zero value. This is typically done when the kernel will
2952% be used in special hardware (GPU) convolution processors, to simply
2953% matters.
2954%
2955% The format of the ZeroKernelNans method is:
2956%
2957% voidZeroKernelNans (KernelInfo *kernel)
2958%
2959% A description of each parameter follows:
2960%
2961% o kernel: the Morphology/Convolution kernel
2962%
2963% FUTURE: return the information in a string for API usage.
2964*/
anthonyc4c86e02010-01-27 09:30:32 +00002965MagickExport void ZeroKernelNans(KernelInfo *kernel)
anthonycc6c8362010-01-25 04:14:01 +00002966{
cristy150989e2010-02-01 14:59:39 +00002967 register long
anthonycc6c8362010-01-25 04:14:01 +00002968 i;
2969
anthony1b2bc0a2010-05-12 05:25:22 +00002970 /* scale the lower kernels first */
2971 if ( kernel->next != (KernelInfo *) NULL)
2972 ZeroKernelNans(kernel->next);
2973
cristy150989e2010-02-01 14:59:39 +00002974 for (i=0; i < (long) (kernel->width*kernel->height); i++)
anthonycc6c8362010-01-25 04:14:01 +00002975 if ( IsNan(kernel->values[i]) )
2976 kernel->values[i] = 0.0;
2977
2978 return;
2979}